diff --git a/packages/subprocess/subprocess-local/src/index.ts b/packages/subprocess/subprocess-local/src/index.ts index aa0f3bb11b..5aa19f1e0b 100644 --- a/packages/subprocess/subprocess-local/src/index.ts +++ b/packages/subprocess/subprocess-local/src/index.ts @@ -35,7 +35,7 @@ import { probeLinuxNative, } from './linux-scope.ts' import { launchWindowsJob, probeWindowsJob } from './windows-job.ts' -import { targetEnvironment, validateTerminalTarget } from './runner-launch.ts' +import { targetEnvironment } from './runner-launch.ts' import { createProcessInspector } from './process-inspector.ts' import type { ProcessInspector } from './process-inspector.ts' import { LocalTerminalHandle } from './terminal.ts' @@ -230,7 +230,7 @@ export class LocalSubprocessRuntime extends SubprocessRuntime { throw new Error('subprocess-local: terminal argv must contain a program') } spec.signal?.throwIfAborted() - const env = validateTerminalTarget(spec) + const env = targetEnvironment(spec) const options: IPtyForkOptions = { name: 'dumb', rows: spec.rows, diff --git a/packages/subprocess/subprocess-local/src/runner-launch.ts b/packages/subprocess/subprocess-local/src/runner-launch.ts index acfa48d984..f25929d48c 100644 --- a/packages/subprocess/subprocess-local/src/runner-launch.ts +++ b/packages/subprocess/subprocess-local/src/runner-launch.ts @@ -5,7 +5,7 @@ import { accessSync, constants as fsConstants, statSync } from 'node:fs' import { extname, isAbsolute } from 'node:path' import { inspect } from 'node:util' import { fileURLToPath } from 'node:url' -import type { SubprocessSpawnSpec, SubprocessTerminalSpawnSpec } from '@deepseek-ai/dsh-subprocess' +import type { SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess' import { childEnv } from './spawn.ts' /** The one private environment variable consumed before target state is restored. */ @@ -289,12 +289,3 @@ export function targetEnvironment( } return env } - -/** - * Validate Linux PTY target strings before creating its request or terminal. - * @param spec - terminal subprocess request to validate. - * @returns complete validated target environment. - */ -export function validateTerminalTarget(spec: SubprocessTerminalSpawnSpec): Record { - return targetEnvironment(spec) -} diff --git a/packages/subprocess/subprocess-local/src/spawn-runner.ts b/packages/subprocess/subprocess-local/src/spawn-runner.ts index e710f8130a..4e5d3e8e60 100644 --- a/packages/subprocess/subprocess-local/src/spawn-runner.ts +++ b/packages/subprocess/subprocess-local/src/spawn-runner.ts @@ -78,7 +78,7 @@ function nodeSpawnError( name: 'Error', message, ...source.stack === undefined ? {} : { - stack: source.stack.replace(/^[^\n]*/, `Error: ${message}`), + stack: source.stack.replace(/^[^\n]*/, () => `Error: ${message}`), }, code, ...errno === undefined ? {} : { errno }, diff --git a/packages/subprocess/subprocess-local/tests/spawn-runner.spec.ts b/packages/subprocess/subprocess-local/tests/spawn-runner.spec.ts index f63fc2c7ae..8ca545503a 100644 --- a/packages/subprocess/subprocess-local/tests/spawn-runner.spec.ts +++ b/packages/subprocess/subprocess-local/tests/spawn-runner.spec.ts @@ -42,7 +42,6 @@ import { spawnRunnerInvocation, SUBPROCESS_RUNNER_ENV, targetEnvironment, - validateTerminalTarget, WINDOWS_RUNNER_SELECTION, } from '../src/runner-launch.ts' import { @@ -114,10 +113,11 @@ async function runWindows( host: FakeRunnerHost, native: SpawnRunnerInternals, start: unknown = { type: 'start', cwd: 'C:\\target', env: { TARGET: 'yes', dsh_subprocess_runner: 'restored' } }, + targetArgv: string[] = ['tool.exe', 'literal arg'], ): Promise { const running = runSpawnRunner( WINDOWS_RUNNER_SELECTION, - ['--', 'tool.exe', 'literal arg'], + ['--', ...targetArgv], hostArgument(host), native, ) @@ -276,7 +276,6 @@ describe('runner launch inputs', () => { expect(targetEnvironment(spec)).toMatchObject({ EXPLICIT: 'yes' }) expect(targetEnvironment({ ...spec, env: { '=C:': 'C:\\target' } })) .toMatchObject({ '=C:': 'C:\\target' }) - expect(validateTerminalTarget({ ...spec, rows: 24, cols: 80 })).toMatchObject({ EXPLICIT: 'yes' }) for (const invalid of [ { ...spec, argv: ['node\0'] }, { ...spec, argv: ['node', 'a\0'] }, @@ -602,20 +601,20 @@ describe('Windows Job runner protocol owner', () => { }) it('maps the bounded Win32 process-creation error classes', async () => { - for (const [win32Code, code, errno, enriched] of [ - [2, 'ENOENT', -4058, true], - [3, 'ENOENT', -4058, true], - [267, 'ENOENT', -4058, true], - [740, 'EACCES', -4092, true], - [5, 'EPERM', -4048, false], - [193, 'EFTYPE', -4028, false], - [999, 'UNKNOWN', -4094, false], + for (const [win32Code, code, errno, enriched, program] of [ + [2, 'ENOENT', -4058, true, 'tool.exe'], + [3, 'ENOENT', -4058, true, 'tool.exe'], + [267, 'ENOENT', -4058, true, 'tool.exe'], + [740, 'EACCES', -4092, true, '$&.exe'], + [5, 'EPERM', -4048, false, 'tool.exe'], + [193, 'EFTYPE', -4028, false, 'tool.exe'], + [999, 'UNKNOWN', -4094, false, 'tool.exe'], ] as const) { const host = new FakeRunnerHost() await runWindows(host, internals({ spawnCurrentTokenJobProcess: vi.fn(() => { throw new Win32Error('CreateProcessW', win32Code) }), - })) - const syscall = enriched ? 'spawn tool.exe' : 'spawn' + }), undefined, [program, 'literal arg']) + const syscall = enriched ? `spawn ${program}` : 'spawn' expect(host.sent).toMatchObject([{ type: 'error', error: { @@ -630,7 +629,7 @@ describe('Windows Job runner protocol owner', () => { if (result.type !== 'error') throw new Error('expected runner error') expect(result.error.stack?.split('\n')[0]).toBe(`Error: ${syscall} ${code}`) if (enriched) { - expect(result.error).toMatchObject({ path: 'tool.exe', spawnargs: ['literal arg'] }) + expect(result.error).toMatchObject({ path: program, spawnargs: ['literal arg'] }) } else { expect(result.error).not.toHaveProperty('path') expect(result.error).not.toHaveProperty('spawnargs')