diff options
| author | Adam Malczewski <[email protected]> | 2026-06-10 16:01:33 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-10 16:01:33 +0900 |
| commit | bf862168f0fd7b10d02ae04a9d82f7c37b9d85e5 (patch) | |
| tree | 073048a5775c605d8c28862d0f8c83e63327a17e /packages/tool-shell/src/spawn.ts | |
| parent | 9e7554cde98f45df30dad1f9d356b6954138685b (diff) | |
| download | dispatch-bf862168f0fd7b10d02ae04a9d82f7c37b9d85e5.tar.gz dispatch-bf862168f0fd7b10d02ae04a9d82f7c37b9d85e5.zip | |
feat(tools): add run_shell, edit_file, write_file + read_file directory listing
Four standard-tier tool extensions (one tool per extension, zero ABI change):
- tool-read-file: read_file now lists directory contents (sorted, /-suffixed subdirs)
- tool-shell: run_shell (foreground, streamed, cancellable, cwd, timeout + output cap)
- tool-edit-file: edit_file (oldString/newString/replaceAll; errors on absent/non-unique)
- tool-write-file: write_file (explicit overwrite flag)
Registered in host-bin CORE_EXTENSIONS. Live boot clean (shell capability accepted).
686 vitest + 89 bun = 775 tests; tsc -b EXIT 0; biome clean.
Diffstat (limited to 'packages/tool-shell/src/spawn.ts')
| -rw-r--r-- | packages/tool-shell/src/spawn.ts | 46 |
1 files changed, 46 insertions, 0 deletions
diff --git a/packages/tool-shell/src/spawn.ts b/packages/tool-shell/src/spawn.ts new file mode 100644 index 0000000..9025c26 --- /dev/null +++ b/packages/tool-shell/src/spawn.ts @@ -0,0 +1,46 @@ +import { spawn as nodeSpawn } from "node:child_process"; +import type { SpawnResult, SpawnShell } from "./shell.js"; + +export const realSpawn: SpawnShell = (params): Promise<SpawnResult> => { + return new Promise<SpawnResult>((resolve) => { + const child = nodeSpawn("sh", ["-c", params.command], { + cwd: params.cwd, + stdio: ["ignore", "pipe", "pipe"], + }); + + let timedOut = false; + let killed = false; + const timer = setTimeout(() => { + timedOut = true; + child.kill("SIGKILL"); + }, params.timeout); + + const onAbort = () => { + killed = true; + child.kill("SIGKILL"); + }; + params.signal.addEventListener("abort", onAbort, { once: true }); + + child.stdout.on("data", (chunk: Buffer) => { + params.onOutput(chunk.toString(), "stdout"); + }); + + child.stderr.on("data", (chunk: Buffer) => { + params.onOutput(chunk.toString(), "stderr"); + }); + + child.on("close", (code) => { + clearTimeout(timer); + params.signal.removeEventListener("abort", onAbort); + resolve({ exitCode: code, timedOut }); + }); + + child.on("error", () => { + clearTimeout(timer); + params.signal.removeEventListener("abort", onAbort); + if (!killed && !timedOut) { + resolve({ exitCode: 1, timedOut: false }); + } + }); + }); +}; |
