/** Caller-owned stream closure and bounded observations across remote allocation. */ import { duplexPair, type Duplex } from 'node:stream' import { getEventListeners } from 'node:events' import { Context } from '@deepseek-ai/cordis' import { describe, expect, it, onTestFinished } from 'vitest' import { z } from 'zod' import type { SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess' import { SshSubprocessRuntime } from '../src/index.ts' type Stage = 'prepare' | 'connect' | 'start' | 'wait' | 'terminate' const id = '8fbfb59c-fbce-44f7-bd87-5ab5a0ea02aa' const spec: SubprocessSpawnSpec = { argv: ['node'], cwd: '/workspace', graceMs: 5, stdio: { stdin: 'ignore', stdout: 'pipe', stderr: 'pipe' }, } const finishedValue = { outcome: { exitCode: 0, signal: null }, spills: {}, collected: {} } async function setup(options: { pause?: Stage; failStart?: Error; failWait?: Error; failTerminate?: Error } = {}) { const ctx = new Context() const gate = Promise.withResolvers() const entered = Promise.withResolvers() const started = Promise.withResolvers() const finished = Promise.withResolvers() const peers = new Map() const calls: string[] = [] const errors: unknown[] = [] ctx.logger.error = ((error: unknown) => { errors.push(error) }) as typeof ctx.logger.error const stage = async (name: Stage, signal?: AbortSignal) => { if (options.pause !== name) { signal?.throwIfAborted(); return } entered.resolve(undefined) signal?.throwIfAborted() const cancelled = Promise.withResolvers() const abort = (): void => { cancelled.reject(new Error('SSH operation cancelled')) } signal?.addEventListener('abort', abort, { once: true }) try { await Promise.race([gate.promise, cancelled.promise]) } finally { signal?.removeEventListener('abort', abort) } } const connection = { request: async (method: string, _params: unknown, schema: z.ZodType, signal?: AbortSignal): Promise => { calls.push(method) let value: unknown if (method === 'process.prepare') { await stage('prepare', signal) value = { id, streams: { stdout: { path: '/tmp/stdout', capability: 'a'.repeat(64) }, stderr: { path: '/tmp/stderr', capability: 'b'.repeat(64) }, } } } else if (method === 'process.start') { await stage('start', signal) if (options.failStart !== undefined) throw options.failStart started.resolve(undefined) value = {} } else if (method === 'process.done') value = await finished.promise else if (method === 'process.wait') { await stage('wait', signal) if (options.failWait !== undefined) throw options.failWait value = true } else if (method === 'process.terminate') { await stage('terminate', signal) if (options.failTerminate !== undefined) throw options.failTerminate value = null } else throw new Error(`Unexpected request ${method}`) return schema.parse(value) }, connectStream: async (endpoint: { path: string }, signal?: AbortSignal) => { await stage('connect', signal) const [host, remote] = duplexPair({ allowHalfOpen: true }) host.on('error', () => {}) remote.on('error', () => {}) peers.set(endpoint.path, { host, remote }) return host }, dispose: () => Promise.resolve(), } ctx.provide('ssh', connection as never) const fiber = await ctx.plugin(SshSubprocessRuntime) onTestFinished(async () => { gate.resolve(undefined) finished.resolve(finishedValue) for (const peer of peers.values()) { peer.host.destroy(); peer.remote.destroy() } await fiber.dispose() if (options.failTerminate === undefined) expect(errors).toEqual([]) }) const peer = (name: string) => { const entry = peers.get(`/tmp/${name}`) if (entry === undefined) throw new Error('transport was not allocated') return entry } return { runtime: ctx.subprocess, calls, peer, entered: entered.promise, started: started.promise, release: () => { gate.resolve(undefined) }, finished } } describe('SSH public output streams', () => { it.each(['stdout', 'stderr'] as const)('closes only the matching transport when %s is destroyed', async (name) => { const test = await setup() const handle = test.runtime.spawn(spec) await test.started handle[name]!.destroy() await new Promise(resolve => setImmediate(resolve)) expect(test.peer(name).host.destroyed).toBe(true) const other = name === 'stdout' ? 'stderr' : 'stdout' expect(test.peer(other).host.destroyed).toBe(false) expect(test.calls).not.toContain('process.terminate') }) it.each(['stdout', 'stderr'] as const)('remembers %s closure before remote allocation finishes', async (name) => { const test = await setup({ pause: 'prepare' }) const handle = test.runtime.spawn(spec) await test.entered handle[name]!.destroy() test.release() await test.started expect(test.peer(name).host.destroyed).toBe(true) expect(test.peer(name === 'stdout' ? 'stderr' : 'stdout').host.destroyed).toBe(false) }) }) describe('SSH range-observation cancellation', () => { it.each(['prepare', 'connect', 'start', 'wait', 'terminate'] as const)('bounds the whole observation while %s is pending', async (pause) => { const test = await setup({ pause }) const handle = test.runtime.spawn(spec) if (pause === 'terminate') { await test.started; handle.terminate() } const controller = new AbortController() let settled = false const observed = handle.waitForExit(controller.signal).then( value => ({ value }), (error: unknown) => ({ error }), ).finally(() => { settled = true }) await test.entered controller.abort('wait deadline') await new Promise(resolve => setImmediate(resolve)) expect(settled).toBe(true) expect(await observed).toEqual({ value: false }) expect(getEventListeners(controller.signal, 'abort')).toHaveLength(0) if (pause !== 'terminate') expect(test.calls).not.toContain('process.terminate') test.release() expect(await handle.waitForExit()).toBe(true) }) it('bounds termination after failed startup while retaining real termination failure', async () => { const cleanup = new Error('range owner unavailable') const test = await setup({ pause: 'terminate', failStart: new Error('start refused'), failTerminate: cleanup }) const handle = test.runtime.spawn(spec) const controller = new AbortController() let settled = false const observed = handle.waitForExit(controller.signal).then( value => ({ value }), (error: unknown) => ({ error }), ).finally(() => { settled = true }) await test.entered controller.abort() await new Promise(resolve => setImmediate(resolve)) expect(settled).toBe(true) expect(await observed).toEqual({ value: false }) test.release() await expect(handle.waitForExit()).rejects.toBe(cleanup) }) it('returns false for a pre-aborted live observation and true once quiescence is known', async () => { const test = await setup({ pause: 'prepare' }) const handle = test.runtime.spawn(spec) const signal = AbortSignal.abort() let settled = false const observed = handle.waitForExit(signal).then( value => ({ value }), (error: unknown) => ({ error }), ).finally(() => { settled = true }) await new Promise(resolve => setImmediate(resolve)) expect(settled).toBe(true) expect(await observed).toEqual({ value: false }) test.release() expect(await handle.waitForExit()).toBe(true) expect(await handle.waitForExit(signal)).toBe(true) }) it('preserves real observation failure and detaches the caller cancellation listener', async () => { const failure = new Error('remote range observation failed') const test = await setup({ failWait: failure }) const handle = test.runtime.spawn(spec) await test.started const controller = new AbortController() await expect(handle.waitForExit(controller.signal)).rejects.toBe(failure) expect(getEventListeners(controller.signal, 'abort')).toHaveLength(0) }) it('returns true when an uncancelled bounded observation completes', async () => { const test = await setup() const handle = test.runtime.spawn(spec) const controller = new AbortController() expect(await handle.waitForExit(controller.signal)).toBe(true) expect(getEventListeners(controller.signal, 'abort')).toHaveLength(0) }) })