import { mkdirSync, writeFileSync } from 'node:fs' import { mkdtemp, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { dirname, join } from 'node:path' import { execa } from 'execa' import { resolveExampleLaunch, type ExampleLaunch } from '@deepseek-ai/dsh-loader-smoke' const POSIX_PTY_DRIVER = String.raw` import errno, json, os, pty, select, signal, sys, time node, launch_args_json, launch_env_json, cwd, actions_json, expected_exit, timeout_seconds = sys.argv[1:] env = os.environ.copy() env.update(json.loads(launch_env_json)) env.update({"COLUMNS": "100", "LINES": "30"}) # Deterministic banner: a developer shell's COLORTERM=truecolor would switch the # banner to the per-letter gradient (one SGR per letter), breaking literal # DEEPSEEK assertions. The gradient path has its own unit and snapshot coverage. env.pop("COLORTERM", None) actions = json.loads(actions_json) pid, fd = pty.fork() if pid == 0: os.chdir(cwd) os.execvpe(node, [node, *json.loads(launch_args_json)], env) output = bytearray() action_index = 0 deadline = time.monotonic() + float(timeout_seconds) status = None while time.monotonic() < deadline: ready, _, _ = select.select([fd], [], [], 0.05) if ready: try: chunk = os.read(fd, 65536) except OSError as error: if error.errno != errno.EIO: raise chunk = b"" if chunk: output.extend(chunk) while action_index < len(actions) and actions[action_index]["waitFor"].encode() in output: action = actions[action_index] if "writeFile" in action: target = os.path.join(cwd, action["writeFile"]["path"]) os.makedirs(os.path.dirname(target), exist_ok=True) with open(target, "w", encoding="utf-8") as handle: handle.write(action["writeFile"]["content"]) if "send" in action: os.write(fd, action["send"].encode()) else: os.write(fd, action["send"].encode()) action_index += 1 waited, candidate = os.waitpid(pid, os.WNOHANG) if waited == pid: status = candidate break if status is None: os.kill(pid, signal.SIGKILL) _, status = os.waitpid(pid, 0) sys.stdout.buffer.write(output) if action_index != len(actions): sys.stderr.write(f"completed {action_index}/{len(actions)} PTY actions before timeout\n") sys.exit(124) actual_exit = os.waitstatus_to_exitcode(status) if actual_exit != int(expected_exit): sys.stderr.write(f"expected exit {expected_exit}, got {actual_exit}\n") sys.exit(125) ` /** One terminal input or workspace mutation performed after its marker renders. */ type TuiPtyAction = | { readonly waitFor: string; readonly send: string } | { readonly waitFor: string readonly writeFile: { readonly path: string; readonly content: string } readonly send?: string } /** Inputs for a keyless real-Loader TUI process smoke. */ export interface TuiPtySmokeOptions { readonly label: string readonly tempDirPrefix: string readonly binScript: string /** Config argument; ignored when {@link configArgs} is set. */ readonly configPath?: string /** Full argument vector for the bin (e.g. `[]` for a bin with a built-in default config). */ readonly configArgs?: readonly string[] readonly tsconfigPath: string readonly actions?: readonly TuiPtyAction[] readonly env?: Readonly readonly expectedExitCode?: number readonly timeoutMs?: number /** Seed the isolated workspace (`cwd`, with `$DSH_HOME` at `.dsh` and the agents home at `.agents`) before launch. */ readonly prepare?: (cwd: string) => Promise /** Inspect the workspace after a passing run, before the temp dir is removed. */ readonly inspect?: (cwd: string) => Promise } function definedEnv(env: NodeJS.ProcessEnv): Record { return Object.fromEntries( Object.entries(env).filter((entry): entry is [string, string] => entry[1] !== undefined), ) } async function runPosixPtySmoke( launch: ExampleLaunch, cwd: string, options: TuiPtySmokeOptions, timeoutMs: number, ): Promise { // The driver owns the PTY deadline (`timeoutMs`); the outer execa deadline // only backstops a wedged python3 process itself. const result = await execa('python3', [ '-c', POSIX_PTY_DRIVER, launch.command, JSON.stringify(launch.args), JSON.stringify(launch.env), cwd, JSON.stringify(options.actions ?? []), String(options.expectedExitCode ?? 0), String(timeoutMs / 1_000), ], { stdin: 'ignore', timeout: timeoutMs + 5_000, killSignal: 'SIGKILL', reject: false, stripFinalNewline: false, }) if (result.timedOut) { throw new Error(`${options.label} PTY driver did not exit. stdout:\n${result.stdout}\nstderr:\n${result.stderr}`) } if (result.failed) { throw new Error(`${options.label} PTY driver exited ${String(result.exitCode)}. stdout:\n${result.stdout}\nstderr:\n${result.stderr}`) } return result.stdout } async function runWindowsPtySmoke( launch: ExampleLaunch, cwd: string, options: TuiPtySmokeOptions, timeoutMs: number, ): Promise { const pty = await import('node-pty') return await new Promise((resolve, reject) => { const actions = options.actions ?? [] const expectedExitCode = options.expectedExitCode ?? 0 let output = '' let actionIndex = 0 let timedOut = false const terminal = pty.spawn(launch.command, launch.args, { name: 'xterm-256color', cols: 100, rows: 30, cwd, env: definedEnv({ ...process.env, ...launch.env, // Match the POSIX driver: no COLORTERM, so the banner never takes the // truecolor gradient path under a developer's shell. COLORTERM: undefined, COLUMNS: '100', LINES: '30', }), }) const timer = setTimeout(() => { timedOut = true terminal.kill() }, timeoutMs) terminal.onData((chunk) => { output += chunk while (actionIndex < actions.length && output.includes(actions[actionIndex]!.waitFor)) { const action = actions[actionIndex]! if ('writeFile' in action) { const target = join(cwd, action.writeFile.path) mkdirSync(dirname(target), { recursive: true }) writeFileSync(target, action.writeFile.content) const input = action.send if (input !== undefined) terminal.write(input) } else { terminal.write(action.send) } actionIndex += 1 } }) terminal.onExit(({ exitCode, signal }) => { clearTimeout(timer) if (timedOut) { reject(new Error(`${options.label} PTY process did not exit before ${String(timeoutMs)}ms. output:\n${output}`)) } else if (actionIndex !== actions.length) { reject(new Error(`${options.label} completed ${String(actionIndex)}/${String(actions.length)} PTY actions. output:\n${output}`)) } else if (exitCode !== expectedExitCode) { reject(new Error(`${options.label} expected exit ${String(expectedExitCode)}, got ${String(exitCode)} (signal ${String(signal)}). output:\n${output}`)) } else { resolve(output) } }) }) } /** * Boot an example in a real pseudo-terminal (ConPTY on Windows), drive * marker-gated input, and return captured bytes after the expected process exit. * @param options - launch paths, environment, actions, and expected exit code. * @returns complete pseudo-terminal output. */ export async function runTuiPtySmoke(options: TuiPtySmokeOptions): Promise { const cwd = await mkdtemp(join(tmpdir(), options.tempDirPrefix)) const timeoutMs = options.timeoutMs ?? 25_000 try { await options.prepare?.(cwd) const launch = resolveExampleLaunch({ srcBin: options.binScript, // `configPath` is the dsh `--config ` tree override; `configArgs` // is the raw-args escape (e.g. `['--resume', ]`) for other flags. configArgs: options.configArgs !== undefined ? [...options.configArgs] /* v8 ignore next -- every caller passes configPath or configArgs; the fallback keeps the type total */ : options.configPath !== undefined ? ['--config', options.configPath] : [], tsconfigPath: options.tsconfigPath, env: { DSH_HOME: join(cwd, '.dsh'), DSH_AGENTS_HOME: join(cwd, '.agents'), ...options.env, }, }) const output = process.platform === 'win32' ? await runWindowsPtySmoke(launch, cwd, options, timeoutMs) : await runPosixPtySmoke(launch, cwd, options, timeoutMs) // Inspect the workspace before `finally` removes it (e.g. the session log). await options.inspect?.(cwd) return output } finally { await rm(cwd, { recursive: true, force: true }) } }