@leanandmean/coding-agent 0.74.1-scramjet.32 → 0.74.1-scramjet.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -12,6 +12,7 @@
12
12
 
13
13
  ### Added
14
14
 
15
+ - Support exact stdin transport and tool-attached approval context with durable transcript ordering for guarded forge publication. ([#486](https://github.com/LeanAndMean/scramjet/pull/486))
15
16
  - Expose detached startup version checks and package-manager installation ownership for product update notifications. ([#434](https://github.com/LeanAndMean/scramjet/pull/434))
16
17
  - Append-only finalized transcript history with a bounded live canvas prevents routine response completion, tool finalization, height resize, and overlay updates from replaying or jumping terminal scrollback. ([#396](https://github.com/LeanAndMean/scramjet/pull/396))
17
18
 
@@ -11,6 +11,12 @@ export interface ExecOptions {
11
11
  timeout?: number;
12
12
  /** Working directory */
13
13
  cwd?: string;
14
+ /** Exact UTF-8 input to write before closing stdin */
15
+ stdin?: string;
16
+ }
17
+ export interface ExecError {
18
+ message: string;
19
+ code?: string;
14
20
  }
15
21
  /**
16
22
  * Result of executing a shell command.
@@ -20,6 +26,10 @@ export interface ExecResult {
20
26
  stderr: string;
21
27
  code: number;
22
28
  killed: boolean;
29
+ spawnError?: ExecError;
30
+ stdinError?: ExecError;
31
+ stdoutError?: ExecError;
32
+ stderrError?: ExecError;
23
33
  }
24
34
  /**
25
35
  * Execute a shell command and return stdout/stderr/code.
@@ -1 +1 @@
1
- {"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../../src/core/exec.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,wCAAwC;IACxC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wBAAwB;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,CAAC,CAgErB","sourcesContent":["/**\n * Shared command execution utilities for extensions and custom tools.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { waitForChildProcess } from \"../utils/child-process.js\";\n\n/**\n * Options for executing shell commands.\n */\nexport interface ExecOptions {\n\t/** AbortSignal to cancel the command */\n\tsignal?: AbortSignal;\n\t/** Timeout in milliseconds */\n\ttimeout?: number;\n\t/** Working directory */\n\tcwd?: string;\n}\n\n/**\n * Result of executing a shell command.\n */\nexport interface ExecResult {\n\tstdout: string;\n\tstderr: string;\n\tcode: number;\n\tkilled: boolean;\n}\n\n/**\n * Execute a shell command and return stdout/stderr/code.\n * Supports timeout and abort signal.\n */\nexport async function execCommand(\n\tcommand: string,\n\targs: string[],\n\tcwd: string,\n\toptions?: ExecOptions,\n): Promise<ExecResult> {\n\treturn new Promise((resolve) => {\n\t\tconst proc = spawn(command, args, {\n\t\t\tcwd,\n\t\t\tshell: false,\n\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t});\n\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\tlet killed = false;\n\t\tlet settled = false;\n\t\tlet timeoutId: NodeJS.Timeout | undefined;\n\t\tlet killTimeoutId: NodeJS.Timeout | undefined;\n\n\t\t// SCRAMJET-DIVERGENCE: timeout escalation tracks settlement rather than signal delivery (#432).\n\t\tconst killProcess = () => {\n\t\t\tif (!killed) {\n\t\t\t\tkilled = true;\n\t\t\t\tproc.kill(\"SIGTERM\");\n\t\t\t\tkillTimeoutId = setTimeout(() => {\n\t\t\t\t\tif (!settled) proc.kill(\"SIGKILL\");\n\t\t\t\t}, 5000);\n\t\t\t}\n\t\t};\n\n\t\t// Handle abort signal\n\t\tif (options?.signal) {\n\t\t\tif (options.signal.aborted) {\n\t\t\t\tkillProcess();\n\t\t\t} else {\n\t\t\t\toptions.signal.addEventListener(\"abort\", killProcess, { once: true });\n\t\t\t}\n\t\t}\n\n\t\t// Handle timeout\n\t\tif (options?.timeout && options.timeout > 0) {\n\t\t\ttimeoutId = setTimeout(() => {\n\t\t\t\tkillProcess();\n\t\t\t}, options.timeout);\n\t\t}\n\n\t\tproc.stdout?.on(\"data\", (data) => {\n\t\t\tstdout += data.toString();\n\t\t});\n\n\t\tproc.stderr?.on(\"data\", (data) => {\n\t\t\tstderr += data.toString();\n\t\t});\n\n\t\t// Wait for process termination without hanging on inherited stdio handles\n\t\t// held open by detached descendants.\n\t\tconst finish = (code: number) => {\n\t\t\tsettled = true;\n\t\t\tif (timeoutId) clearTimeout(timeoutId);\n\t\t\tif (killTimeoutId) clearTimeout(killTimeoutId);\n\t\t\tif (options?.signal) options.signal.removeEventListener(\"abort\", killProcess);\n\t\t\tresolve({ stdout, stderr, code, killed });\n\t\t};\n\n\t\twaitForChildProcess(proc)\n\t\t\t.then((code) => finish(code ?? 0))\n\t\t\t.catch(() => finish(1));\n\t});\n}\n"]}
1
+ {"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../../src/core/exec.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,wCAAwC;IACxC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wBAAwB;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,SAAS;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,WAAW,CAAC,EAAE,SAAS,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,CAAC,CAkHrB","sourcesContent":["/**\n * Shared command execution utilities for extensions and custom tools.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { StringDecoder } from \"node:string_decoder\";\nimport { waitForChildProcess } from \"../utils/child-process.js\";\n\n/**\n * Options for executing shell commands.\n */\nexport interface ExecOptions {\n\t/** AbortSignal to cancel the command */\n\tsignal?: AbortSignal;\n\t/** Timeout in milliseconds */\n\ttimeout?: number;\n\t/** Working directory */\n\tcwd?: string;\n\t/** Exact UTF-8 input to write before closing stdin */\n\tstdin?: string;\n}\n\nexport interface ExecError {\n\tmessage: string;\n\tcode?: string;\n}\n\n/**\n * Result of executing a shell command.\n */\nexport interface ExecResult {\n\tstdout: string;\n\tstderr: string;\n\tcode: number;\n\tkilled: boolean;\n\tspawnError?: ExecError;\n\tstdinError?: ExecError;\n\tstdoutError?: ExecError;\n\tstderrError?: ExecError;\n}\n\n/**\n * Execute a shell command and return stdout/stderr/code.\n * Supports timeout and abort signal.\n */\nexport async function execCommand(\n\tcommand: string,\n\targs: string[],\n\tcwd: string,\n\toptions?: ExecOptions,\n): Promise<ExecResult> {\n\treturn new Promise((resolve) => {\n\t\tconst proc = spawn(command, args, {\n\t\t\tcwd,\n\t\t\tshell: false,\n\t\t\tstdio: [options?.stdin === undefined ? \"ignore\" : \"pipe\", \"pipe\", \"pipe\"],\n\t\t});\n\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\t// SCRAMJET-DIVERGENCE: decode process output across chunk boundaries for exact publication verification (#479).\n\t\tconst stdoutDecoder = new StringDecoder(\"utf8\");\n\t\tconst stderrDecoder = new StringDecoder(\"utf8\");\n\t\tlet killed = false;\n\t\tlet settled = false;\n\t\tlet spawned = false;\n\t\tlet terminationRequested = false;\n\t\tlet spawnError: ExecError | undefined;\n\t\tlet stdinError: ExecError | undefined;\n\t\tlet stdoutError: ExecError | undefined;\n\t\tlet stderrError: ExecError | undefined;\n\t\tlet timeoutId: NodeJS.Timeout | undefined;\n\t\tlet killTimeoutId: NodeJS.Timeout | undefined;\n\n\t\t// SCRAMJET-DIVERGENCE: timeout escalation tracks settlement rather than signal delivery (#432).\n\t\tconst terminateSpawnedProcess = () => {\n\t\t\tif (killed || !spawned || !proc.kill(\"SIGTERM\")) return;\n\t\t\tkilled = true;\n\t\t\tkillTimeoutId = setTimeout(() => {\n\t\t\t\tif (!settled) proc.kill(\"SIGKILL\");\n\t\t\t}, 5000);\n\t\t};\n\t\tconst killProcess = () => {\n\t\t\tterminationRequested = true;\n\t\t\tterminateSpawnedProcess();\n\t\t};\n\n\t\t// Handle abort signal\n\t\tif (options?.signal) {\n\t\t\tif (options.signal.aborted) {\n\t\t\t\tkillProcess();\n\t\t\t} else {\n\t\t\t\toptions.signal.addEventListener(\"abort\", killProcess, { once: true });\n\t\t\t}\n\t\t}\n\n\t\t// Handle timeout\n\t\tif (options?.timeout && options.timeout > 0) {\n\t\t\ttimeoutId = setTimeout(() => {\n\t\t\t\tkillProcess();\n\t\t\t}, options.timeout);\n\t\t}\n\n\t\tproc.once(\"spawn\", () => {\n\t\t\tspawned = true;\n\t\t\tif (terminationRequested) terminateSpawnedProcess();\n\t\t});\n\n\t\tproc.stdout?.on(\"data\", (data) => {\n\t\t\tstdout += stdoutDecoder.write(data);\n\t\t});\n\n\t\tproc.stderr?.on(\"data\", (data) => {\n\t\t\tstderr += stderrDecoder.write(data);\n\t\t});\n\n\t\tconst errorDetails = (error: Error): ExecError => {\n\t\t\tconst code = (error as NodeJS.ErrnoException).code;\n\t\t\treturn code === undefined ? { message: error.message } : { message: error.message, code };\n\t\t};\n\n\t\tproc.stdout?.once(\"error\", (error: Error) => {\n\t\t\tstdoutError ??= errorDetails(error);\n\t\t\tkillProcess();\n\t\t});\n\t\tproc.stderr?.once(\"error\", (error: Error) => {\n\t\t\tstderrError ??= errorDetails(error);\n\t\t\tkillProcess();\n\t\t});\n\n\t\tconst stdinSettled = new Promise<void>((resolveStdin) => {\n\t\t\tif (options?.stdin === undefined || proc.stdin === null) {\n\t\t\t\tresolveStdin();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst onStdinError = (error: Error) => {\n\t\t\t\tstdinError ??= errorDetails(error);\n\t\t\t};\n\t\t\tproc.stdin.on(\"error\", onStdinError);\n\t\t\tproc.stdin.once(\"close\", () => {\n\t\t\t\tproc.stdin?.removeListener(\"error\", onStdinError);\n\t\t\t\tresolveStdin();\n\t\t\t});\n\t\t\tproc.stdin.end(options.stdin, \"utf8\");\n\t\t});\n\n\t\tconst processSettled = waitForChildProcess(proc)\n\t\t\t.then((code) => code ?? 0)\n\t\t\t.catch((error: Error) => {\n\t\t\t\tif (!spawned) spawnError = errorDetails(error);\n\t\t\t\treturn 1;\n\t\t\t});\n\n\t\tPromise.all([processSettled, stdinSettled]).then(([code]) => {\n\t\t\tstdout += stdoutDecoder.end();\n\t\t\tstderr += stderrDecoder.end();\n\t\t\tsettled = true;\n\t\t\tif (timeoutId) clearTimeout(timeoutId);\n\t\t\tif (killTimeoutId) clearTimeout(killTimeoutId);\n\t\t\tif (options?.signal) options.signal.removeEventListener(\"abort\", killProcess);\n\t\t\tresolve({ stdout, stderr, code, killed, spawnError, stdinError, stdoutError, stderrError });\n\t\t});\n\t});\n}\n"]}
package/dist/core/exec.js CHANGED
@@ -2,6 +2,7 @@
2
2
  * Shared command execution utilities for extensions and custom tools.
3
3
  */
4
4
  import { spawn } from "node:child_process";
5
+ import { StringDecoder } from "node:string_decoder";
5
6
  import { waitForChildProcess } from "../utils/child-process.js";
6
7
  /**
7
8
  * Execute a shell command and return stdout/stderr/code.
@@ -12,24 +13,36 @@ export async function execCommand(command, args, cwd, options) {
12
13
  const proc = spawn(command, args, {
13
14
  cwd,
14
15
  shell: false,
15
- stdio: ["ignore", "pipe", "pipe"],
16
+ stdio: [options?.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"],
16
17
  });
17
18
  let stdout = "";
18
19
  let stderr = "";
20
+ // SCRAMJET-DIVERGENCE: decode process output across chunk boundaries for exact publication verification (#479).
21
+ const stdoutDecoder = new StringDecoder("utf8");
22
+ const stderrDecoder = new StringDecoder("utf8");
19
23
  let killed = false;
20
24
  let settled = false;
25
+ let spawned = false;
26
+ let terminationRequested = false;
27
+ let spawnError;
28
+ let stdinError;
29
+ let stdoutError;
30
+ let stderrError;
21
31
  let timeoutId;
22
32
  let killTimeoutId;
23
33
  // SCRAMJET-DIVERGENCE: timeout escalation tracks settlement rather than signal delivery (#432).
34
+ const terminateSpawnedProcess = () => {
35
+ if (killed || !spawned || !proc.kill("SIGTERM"))
36
+ return;
37
+ killed = true;
38
+ killTimeoutId = setTimeout(() => {
39
+ if (!settled)
40
+ proc.kill("SIGKILL");
41
+ }, 5000);
42
+ };
24
43
  const killProcess = () => {
25
- if (!killed) {
26
- killed = true;
27
- proc.kill("SIGTERM");
28
- killTimeoutId = setTimeout(() => {
29
- if (!settled)
30
- proc.kill("SIGKILL");
31
- }, 5000);
32
- }
44
+ terminationRequested = true;
45
+ terminateSpawnedProcess();
33
46
  };
34
47
  // Handle abort signal
35
48
  if (options?.signal) {
@@ -46,15 +59,54 @@ export async function execCommand(command, args, cwd, options) {
46
59
  killProcess();
47
60
  }, options.timeout);
48
61
  }
62
+ proc.once("spawn", () => {
63
+ spawned = true;
64
+ if (terminationRequested)
65
+ terminateSpawnedProcess();
66
+ });
49
67
  proc.stdout?.on("data", (data) => {
50
- stdout += data.toString();
68
+ stdout += stdoutDecoder.write(data);
51
69
  });
52
70
  proc.stderr?.on("data", (data) => {
53
- stderr += data.toString();
71
+ stderr += stderrDecoder.write(data);
72
+ });
73
+ const errorDetails = (error) => {
74
+ const code = error.code;
75
+ return code === undefined ? { message: error.message } : { message: error.message, code };
76
+ };
77
+ proc.stdout?.once("error", (error) => {
78
+ stdoutError ??= errorDetails(error);
79
+ killProcess();
80
+ });
81
+ proc.stderr?.once("error", (error) => {
82
+ stderrError ??= errorDetails(error);
83
+ killProcess();
54
84
  });
55
- // Wait for process termination without hanging on inherited stdio handles
56
- // held open by detached descendants.
57
- const finish = (code) => {
85
+ const stdinSettled = new Promise((resolveStdin) => {
86
+ if (options?.stdin === undefined || proc.stdin === null) {
87
+ resolveStdin();
88
+ return;
89
+ }
90
+ const onStdinError = (error) => {
91
+ stdinError ??= errorDetails(error);
92
+ };
93
+ proc.stdin.on("error", onStdinError);
94
+ proc.stdin.once("close", () => {
95
+ proc.stdin?.removeListener("error", onStdinError);
96
+ resolveStdin();
97
+ });
98
+ proc.stdin.end(options.stdin, "utf8");
99
+ });
100
+ const processSettled = waitForChildProcess(proc)
101
+ .then((code) => code ?? 0)
102
+ .catch((error) => {
103
+ if (!spawned)
104
+ spawnError = errorDetails(error);
105
+ return 1;
106
+ });
107
+ Promise.all([processSettled, stdinSettled]).then(([code]) => {
108
+ stdout += stdoutDecoder.end();
109
+ stderr += stderrDecoder.end();
58
110
  settled = true;
59
111
  if (timeoutId)
60
112
  clearTimeout(timeoutId);
@@ -62,11 +114,8 @@ export async function execCommand(command, args, cwd, options) {
62
114
  clearTimeout(killTimeoutId);
63
115
  if (options?.signal)
64
116
  options.signal.removeEventListener("abort", killProcess);
65
- resolve({ stdout, stderr, code, killed });
66
- };
67
- waitForChildProcess(proc)
68
- .then((code) => finish(code ?? 0))
69
- .catch(() => finish(1));
117
+ resolve({ stdout, stderr, code, killed, spawnError, stdinError, stdoutError, stderrError });
118
+ });
70
119
  });
71
120
  }
72
121
  //# sourceMappingURL=exec.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/core/exec.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAwBhE;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAChC,OAAe,EACf,IAAc,EACd,GAAW,EACX,OAAqB,EACC;IACtB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE;YACjC,GAAG;YACH,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SACjC,CAAC,CAAC;QAEH,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,MAAM,GAAG,KAAK,CAAC;QACnB,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,IAAI,SAAqC,CAAC;QAC1C,IAAI,aAAyC,CAAC;QAE9C,gGAAgG;QAChG,MAAM,WAAW,GAAG,GAAG,EAAE,CAAC;YACzB,IAAI,CAAC,MAAM,EAAE,CAAC;gBACb,MAAM,GAAG,IAAI,CAAC;gBACd,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBACrB,aAAa,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC;oBAChC,IAAI,CAAC,OAAO;wBAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAAA,CACnC,EAAE,IAAI,CAAC,CAAC;YACV,CAAC;QAAA,CACD,CAAC;QAEF,sBAAsB;QACtB,IAAI,OAAO,EAAE,MAAM,EAAE,CAAC;YACrB,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBAC5B,WAAW,EAAE,CAAC;YACf,CAAC;iBAAM,CAAC;gBACP,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YACvE,CAAC;QACF,CAAC;QAED,iBAAiB;QACjB,IAAI,OAAO,EAAE,OAAO,IAAI,OAAO,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;YAC7C,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC;gBAC5B,WAAW,EAAE,CAAC;YAAA,CACd,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;QACrB,CAAC;QAED,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;YACjC,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAAA,CAC1B,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;YACjC,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAAA,CAC1B,CAAC,CAAC;QAEH,0EAA0E;QAC1E,qCAAqC;QACrC,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC;YAChC,OAAO,GAAG,IAAI,CAAC;YACf,IAAI,SAAS;gBAAE,YAAY,CAAC,SAAS,CAAC,CAAC;YACvC,IAAI,aAAa;gBAAE,YAAY,CAAC,aAAa,CAAC,CAAC;YAC/C,IAAI,OAAO,EAAE,MAAM;gBAAE,OAAO,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;YAC9E,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QAAA,CAC1C,CAAC;QAEF,mBAAmB,CAAC,IAAI,CAAC;aACvB,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;aACjC,KAAK,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAAA,CACzB,CAAC,CAAC;AAAA,CACH","sourcesContent":["/**\n * Shared command execution utilities for extensions and custom tools.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { waitForChildProcess } from \"../utils/child-process.js\";\n\n/**\n * Options for executing shell commands.\n */\nexport interface ExecOptions {\n\t/** AbortSignal to cancel the command */\n\tsignal?: AbortSignal;\n\t/** Timeout in milliseconds */\n\ttimeout?: number;\n\t/** Working directory */\n\tcwd?: string;\n}\n\n/**\n * Result of executing a shell command.\n */\nexport interface ExecResult {\n\tstdout: string;\n\tstderr: string;\n\tcode: number;\n\tkilled: boolean;\n}\n\n/**\n * Execute a shell command and return stdout/stderr/code.\n * Supports timeout and abort signal.\n */\nexport async function execCommand(\n\tcommand: string,\n\targs: string[],\n\tcwd: string,\n\toptions?: ExecOptions,\n): Promise<ExecResult> {\n\treturn new Promise((resolve) => {\n\t\tconst proc = spawn(command, args, {\n\t\t\tcwd,\n\t\t\tshell: false,\n\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t});\n\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\tlet killed = false;\n\t\tlet settled = false;\n\t\tlet timeoutId: NodeJS.Timeout | undefined;\n\t\tlet killTimeoutId: NodeJS.Timeout | undefined;\n\n\t\t// SCRAMJET-DIVERGENCE: timeout escalation tracks settlement rather than signal delivery (#432).\n\t\tconst killProcess = () => {\n\t\t\tif (!killed) {\n\t\t\t\tkilled = true;\n\t\t\t\tproc.kill(\"SIGTERM\");\n\t\t\t\tkillTimeoutId = setTimeout(() => {\n\t\t\t\t\tif (!settled) proc.kill(\"SIGKILL\");\n\t\t\t\t}, 5000);\n\t\t\t}\n\t\t};\n\n\t\t// Handle abort signal\n\t\tif (options?.signal) {\n\t\t\tif (options.signal.aborted) {\n\t\t\t\tkillProcess();\n\t\t\t} else {\n\t\t\t\toptions.signal.addEventListener(\"abort\", killProcess, { once: true });\n\t\t\t}\n\t\t}\n\n\t\t// Handle timeout\n\t\tif (options?.timeout && options.timeout > 0) {\n\t\t\ttimeoutId = setTimeout(() => {\n\t\t\t\tkillProcess();\n\t\t\t}, options.timeout);\n\t\t}\n\n\t\tproc.stdout?.on(\"data\", (data) => {\n\t\t\tstdout += data.toString();\n\t\t});\n\n\t\tproc.stderr?.on(\"data\", (data) => {\n\t\t\tstderr += data.toString();\n\t\t});\n\n\t\t// Wait for process termination without hanging on inherited stdio handles\n\t\t// held open by detached descendants.\n\t\tconst finish = (code: number) => {\n\t\t\tsettled = true;\n\t\t\tif (timeoutId) clearTimeout(timeoutId);\n\t\t\tif (killTimeoutId) clearTimeout(killTimeoutId);\n\t\t\tif (options?.signal) options.signal.removeEventListener(\"abort\", killProcess);\n\t\t\tresolve({ stdout, stderr, code, killed });\n\t\t};\n\n\t\twaitForChildProcess(proc)\n\t\t\t.then((code) => finish(code ?? 0))\n\t\t\t.catch(() => finish(1));\n\t});\n}\n"]}
1
+ {"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/core/exec.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared command execution utilities for extensions and custom tools.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { StringDecoder } from \"node:string_decoder\";\nimport { waitForChildProcess } from \"../utils/child-process.js\";\n\n/**\n * Options for executing shell commands.\n */\nexport interface ExecOptions {\n\t/** AbortSignal to cancel the command */\n\tsignal?: AbortSignal;\n\t/** Timeout in milliseconds */\n\ttimeout?: number;\n\t/** Working directory */\n\tcwd?: string;\n\t/** Exact UTF-8 input to write before closing stdin */\n\tstdin?: string;\n}\n\nexport interface ExecError {\n\tmessage: string;\n\tcode?: string;\n}\n\n/**\n * Result of executing a shell command.\n */\nexport interface ExecResult {\n\tstdout: string;\n\tstderr: string;\n\tcode: number;\n\tkilled: boolean;\n\tspawnError?: ExecError;\n\tstdinError?: ExecError;\n\tstdoutError?: ExecError;\n\tstderrError?: ExecError;\n}\n\n/**\n * Execute a shell command and return stdout/stderr/code.\n * Supports timeout and abort signal.\n */\nexport async function execCommand(\n\tcommand: string,\n\targs: string[],\n\tcwd: string,\n\toptions?: ExecOptions,\n): Promise<ExecResult> {\n\treturn new Promise((resolve) => {\n\t\tconst proc = spawn(command, args, {\n\t\t\tcwd,\n\t\t\tshell: false,\n\t\t\tstdio: [options?.stdin === undefined ? \"ignore\" : \"pipe\", \"pipe\", \"pipe\"],\n\t\t});\n\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\t// SCRAMJET-DIVERGENCE: decode process output across chunk boundaries for exact publication verification (#479).\n\t\tconst stdoutDecoder = new StringDecoder(\"utf8\");\n\t\tconst stderrDecoder = new StringDecoder(\"utf8\");\n\t\tlet killed = false;\n\t\tlet settled = false;\n\t\tlet spawned = false;\n\t\tlet terminationRequested = false;\n\t\tlet spawnError: ExecError | undefined;\n\t\tlet stdinError: ExecError | undefined;\n\t\tlet stdoutError: ExecError | undefined;\n\t\tlet stderrError: ExecError | undefined;\n\t\tlet timeoutId: NodeJS.Timeout | undefined;\n\t\tlet killTimeoutId: NodeJS.Timeout | undefined;\n\n\t\t// SCRAMJET-DIVERGENCE: timeout escalation tracks settlement rather than signal delivery (#432).\n\t\tconst terminateSpawnedProcess = () => {\n\t\t\tif (killed || !spawned || !proc.kill(\"SIGTERM\")) return;\n\t\t\tkilled = true;\n\t\t\tkillTimeoutId = setTimeout(() => {\n\t\t\t\tif (!settled) proc.kill(\"SIGKILL\");\n\t\t\t}, 5000);\n\t\t};\n\t\tconst killProcess = () => {\n\t\t\tterminationRequested = true;\n\t\t\tterminateSpawnedProcess();\n\t\t};\n\n\t\t// Handle abort signal\n\t\tif (options?.signal) {\n\t\t\tif (options.signal.aborted) {\n\t\t\t\tkillProcess();\n\t\t\t} else {\n\t\t\t\toptions.signal.addEventListener(\"abort\", killProcess, { once: true });\n\t\t\t}\n\t\t}\n\n\t\t// Handle timeout\n\t\tif (options?.timeout && options.timeout > 0) {\n\t\t\ttimeoutId = setTimeout(() => {\n\t\t\t\tkillProcess();\n\t\t\t}, options.timeout);\n\t\t}\n\n\t\tproc.once(\"spawn\", () => {\n\t\t\tspawned = true;\n\t\t\tif (terminationRequested) terminateSpawnedProcess();\n\t\t});\n\n\t\tproc.stdout?.on(\"data\", (data) => {\n\t\t\tstdout += stdoutDecoder.write(data);\n\t\t});\n\n\t\tproc.stderr?.on(\"data\", (data) => {\n\t\t\tstderr += stderrDecoder.write(data);\n\t\t});\n\n\t\tconst errorDetails = (error: Error): ExecError => {\n\t\t\tconst code = (error as NodeJS.ErrnoException).code;\n\t\t\treturn code === undefined ? { message: error.message } : { message: error.message, code };\n\t\t};\n\n\t\tproc.stdout?.once(\"error\", (error: Error) => {\n\t\t\tstdoutError ??= errorDetails(error);\n\t\t\tkillProcess();\n\t\t});\n\t\tproc.stderr?.once(\"error\", (error: Error) => {\n\t\t\tstderrError ??= errorDetails(error);\n\t\t\tkillProcess();\n\t\t});\n\n\t\tconst stdinSettled = new Promise<void>((resolveStdin) => {\n\t\t\tif (options?.stdin === undefined || proc.stdin === null) {\n\t\t\t\tresolveStdin();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst onStdinError = (error: Error) => {\n\t\t\t\tstdinError ??= errorDetails(error);\n\t\t\t};\n\t\t\tproc.stdin.on(\"error\", onStdinError);\n\t\t\tproc.stdin.once(\"close\", () => {\n\t\t\t\tproc.stdin?.removeListener(\"error\", onStdinError);\n\t\t\t\tresolveStdin();\n\t\t\t});\n\t\t\tproc.stdin.end(options.stdin, \"utf8\");\n\t\t});\n\n\t\tconst processSettled = waitForChildProcess(proc)\n\t\t\t.then((code) => code ?? 0)\n\t\t\t.catch((error: Error) => {\n\t\t\t\tif (!spawned) spawnError = errorDetails(error);\n\t\t\t\treturn 1;\n\t\t\t});\n\n\t\tPromise.all([processSettled, stdinSettled]).then(([code]) => {\n\t\t\tstdout += stdoutDecoder.end();\n\t\t\tstderr += stderrDecoder.end();\n\t\t\tsettled = true;\n\t\t\tif (timeoutId) clearTimeout(timeoutId);\n\t\t\tif (killTimeoutId) clearTimeout(killTimeoutId);\n\t\t\tif (options?.signal) options.signal.removeEventListener(\"abort\", killProcess);\n\t\t\tresolve({ stdout, stderr, code, killed, spawnError, stdinError, stdoutError, stderrError });\n\t\t});\n\t});\n}\n"]}
@@ -121,6 +121,11 @@ export interface ExtensionUIContext {
121
121
  dispose?(): void;
122
122
  }>, options?: {
123
123
  overlay?: boolean;
124
+ /** Commit immutable visual context at a pending tool's transcript position before focusing controls. Not persisted. */
125
+ toolAttachedContext?: {
126
+ toolCallId: string;
127
+ render: (tui: TUI, theme: Theme) => Component;
128
+ };
124
129
  /** Overlay positioning/sizing options. Can be static or a function for dynamic updates. */
125
130
  overlayOptions?: OverlayOptions | (() => OverlayOptions);
126
131
  /** Called with the overlay handle after the overlay is shown. Use to control visibility. */
@@ -226,7 +231,7 @@ export interface DispatchUserInputOptions {
226
231
  export interface ExtensionContext {
227
232
  /** UI methods for user interaction */
228
233
  ui: ExtensionUIContext;
229
- /** Whether UI is available (false in print/RPC mode) */
234
+ /** Whether some UI transport is available; individual methods may still be unsupported (for example custom() in RPC). */
230
235
  hasUI: boolean;
231
236
  /** Current working directory */
232
237
  cwd: string;