@ai-agent-forge/plugin-process-exec 0.86.0 → 0.86.1

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/agent-forge.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "packageFormatVersion": 1,
3
3
  "id": "agent-forge.plugin.process-exec",
4
- "pluginVersion": "0.86.0",
4
+ "pluginVersion": "0.86.1",
5
5
  "apiVersion": "1",
6
6
  "minHostVersion": "0.84.4",
7
7
  "capabilityRefs": [
@@ -16,6 +16,15 @@ export interface NodeProcessExecAdapterOptionsV1 {
16
16
  /** Deterministic host seam; production defaults to Node's shell-free spawn. */
17
17
  readonly spawn?: typeof spawn;
18
18
  }
19
+ /**
20
+ * Builds a bounded preview from the retained capture. `captured` holds the
21
+ * complete output when `totalBytes <= previewBytes`; otherwise it holds the
22
+ * first `head` bytes followed by the last `tail` bytes of the stream, and the
23
+ * two segments are joined with an omission marker. The marker rides on top of
24
+ * the segment budgets but the assembled preview never exceeds
25
+ * `PROCESS_EXEC_MAX_PREVIEW_BYTES_V1`.
26
+ */
27
+ export declare function buildHeadTailPreviewV1(captured: Uint8Array, previewBytes: number, totalBytes: number): string;
19
28
  /** Redacts credentials and host paths before a preview can reach the model. */
20
29
  export declare function sanitizeProcessTextV1(value: string, maximumChars?: number, cwd?: string): string;
21
30
  /** Node host adapter with shell-free argv execution and bounded output previews. */
@@ -1 +1 @@
1
- {"version":3,"file":"node-adapter.d.ts","sourceRoot":"","sources":["../src/node-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAG3C,OAAO,EAGN,KAAK,oBAAoB,EAGzB,MAAM,YAAY,CAAC;AAKpB,eAAO,MAAM,gCAAgC,QAAc,CAAC;AAE5D,eAAO,MAAM,iCAAiC,OAAO,CAAC;AAItD,eAAO,MAAM,sCAAsC,MAAM,CAAC;AAE1D,MAAM,WAAW,+BAA+B;IAC/C,iEAAiE;IACjE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,4DAA4D;IAC5D,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CAC9B;AAQD,+EAA+E;AAC/E,wBAAgB,qBAAqB,CACpC,KAAK,EAAE,MAAM,EACb,YAAY,SAAoC,EAChD,GAAG,CAAC,EAAE,MAAM,GACV,MAAM,CAUR;AAwID,oFAAoF;AACpF,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,+BAA+B,GAAG,oBAAoB,CAwL7G","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { isAbsolute } from \"node:path\";\nimport { killProcessTree, sanitizeBinaryOutput } from \"./shell-utils.ts\";\nimport {\n\tPROCESS_EXEC_HOST_ADAPTER_ID_V1,\n\tPROCESS_EXEC_HOST_ADAPTER_VERSION_V1,\n\ttype ProcessExecAdapterV1,\n\ttype ProcessExecRequestV1,\n\ttype ProcessExecResultV1,\n} from \"./types.ts\";\n\nconst DEFAULT_TIMEOUT_MS_V1 = 30_000;\nconst MAX_TIMEOUT_MS_V1 = 120_000;\nconst DEFAULT_OUTPUT_BYTES_V1 = 64 * 1024;\nexport const PROCESS_EXEC_MAX_OUTPUT_BYTES_V1 = 1024 * 1024;\nconst DEFAULT_PREVIEW_BYTES_V1 = 4096;\nexport const PROCESS_EXEC_MAX_PREVIEW_BYTES_V1 = 8192;\nconst MAX_ARGS_V1 = 256;\nconst MAX_COMMAND_CHARS_V1 = 512;\nconst MAX_ARG_CHARS_V1 = 4096;\nexport const PROCESS_EXEC_MAX_OPERATION_ID_CHARS_V1 = 256;\n\nexport interface NodeProcessExecAdapterOptionsV1 {\n\t/** Host-owned working directory; requests cannot override it. */\n\treadonly cwd: string;\n\treadonly env?: Readonly<Record<string, string>>;\n\treadonly defaultTimeoutMs?: number;\n\treadonly maxStdoutBytes?: number;\n\treadonly maxStderrBytes?: number;\n\treadonly previewBytes?: number;\n\t/** Test seam for the host-owned process-tree terminator. */\n\treadonly killProcessTree?: (pid: number) => void;\n\t/** Deterministic host seam; production defaults to Node's shell-free spawn. */\n\treadonly spawn?: typeof spawn;\n}\n\nconst SAFE_ENVIRONMENT_KEYS_V1 = new Set(\n\t[\"PATH\", \"PATHEXT\", \"SystemRoot\", \"SYSTEMROOT\", \"WINDIR\", \"TEMP\", \"TMP\", \"TMPDIR\", \"LANG\", \"LC_ALL\"].map((key) =>\n\t\tkey.toLowerCase(),\n\t),\n);\n\n/** Redacts credentials and host paths before a preview can reach the model. */\nexport function sanitizeProcessTextV1(\n\tvalue: string,\n\tmaximumChars = PROCESS_EXEC_MAX_PREVIEW_BYTES_V1,\n\tcwd?: string,\n): string {\n\tlet text = sanitizeBinaryOutput(value);\n\tif (cwd !== undefined && cwd !== \"\") text = text.replaceAll(cwd, \"[redacted-path]\");\n\ttext = text\n\t\t.replace(\n\t\t\t/((?:api[-_ ]?key|access[-_ ]?token|auth(?:orization)?|bearer|password|passwd|secret|token)\\s*[:=]\\s*)([^\\s,;]+)/giu,\n\t\t\t\"$1[redacted-secret]\",\n\t\t)\n\t\t.replace(/(?:[A-Za-z]:[\\\\/]|\\\\\\\\|\\/(?:home|Users|private|tmp|var|workspace)\\/)[^\\s\"'<>]*/gu, \"[redacted-path]\");\n\treturn text.length <= maximumChars ? text : text.slice(0, maximumChars);\n}\n\nfunction hostEnvironment(explicit: Readonly<Record<string, string>> | undefined): NodeJS.ProcessEnv {\n\tconst inherited: NodeJS.ProcessEnv = {};\n\tfor (const [key, value] of Object.entries(process.env)) {\n\t\tif (value !== undefined && SAFE_ENVIRONMENT_KEYS_V1.has(key.toLowerCase())) inherited[key] = value;\n\t}\n\treturn { ...inherited, ...(explicit ?? {}) };\n}\n\nfunction positiveBound(value: number | undefined, fallback: number, maximum: number, label: string): number {\n\tconst result = value ?? fallback;\n\tif (!Number.isSafeInteger(result) || result < 1 || result > maximum)\n\t\tthrow new Error(`${label} must be a positive integer at most ${maximum}`);\n\treturn result;\n}\n\nfunction inputError(operationId: string, message: string): ProcessExecResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"failed\",\n\t\tsummary: \"Process request was invalid\",\n\t\tstdoutBytes: 0,\n\t\tstderrBytes: 0,\n\t\tstdoutPreview: \"\",\n\t\tstderrPreview: \"\",\n\t\tstdoutTruncated: false,\n\t\tstderrTruncated: false,\n\t\terror: { version: 1, code: \"invalid_input\", message },\n\t};\n}\n\nfunction cancelled(operationId: string): ProcessExecResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"cancelled\",\n\t\tsummary: \"Process execution was cancelled\",\n\t\tstdoutBytes: 0,\n\t\tstderrBytes: 0,\n\t\tstdoutPreview: \"\",\n\t\tstderrPreview: \"\",\n\t\tstdoutTruncated: false,\n\t\tstderrTruncated: false,\n\t\terror: { version: 1, code: \"cancelled\", message: \"Process execution was cancelled\" },\n\t};\n}\n\nfunction validateRequest(request: ProcessExecRequestV1): string | undefined {\n\tif (request.version !== 1) return \"version must be 1\";\n\tif (\n\t\ttypeof request.operationId !== \"string\" ||\n\t\trequest.operationId.trim() === \"\" ||\n\t\trequest.operationId.length > PROCESS_EXEC_MAX_OPERATION_ID_CHARS_V1\n\t)\n\t\treturn \"operationId is invalid\";\n\tif (\n\t\ttypeof request.command !== \"string\" ||\n\t\trequest.command.trim() === \"\" ||\n\t\trequest.command.length > MAX_COMMAND_CHARS_V1 ||\n\t\t/[\\u0000\\r\\n]/u.test(request.command)\n\t)\n\t\treturn \"command is invalid\";\n\tif (!Array.isArray(request.args) || request.args.length > MAX_ARGS_V1) return \"args are invalid\";\n\tif (\n\t\trequest.args.some((arg) => typeof arg !== \"string\" || arg.length > MAX_ARG_CHARS_V1 || /[\\u0000\\r\\n]/u.test(arg))\n\t)\n\t\treturn \"args are invalid\";\n\tif (\n\t\trequest.timeoutMs !== undefined &&\n\t\t(!Number.isSafeInteger(request.timeoutMs) || request.timeoutMs < 1 || request.timeoutMs > MAX_TIMEOUT_MS_V1)\n\t)\n\t\treturn `timeoutMs must be a positive integer at most ${MAX_TIMEOUT_MS_V1}`;\n\treturn undefined;\n}\n\nfunction previewText(bytes: Uint8Array): string {\n\treturn new TextDecoder().decode(bytes);\n}\n\nfunction result(\n\trequest: ProcessExecRequestV1,\n\tstatus: ProcessExecResultV1[\"status\"],\n\tstdout: Uint8Array,\n\tstderr: Uint8Array,\n\tstdoutBytes: number,\n\tstderrBytes: number,\n\tpreviewBytes: number,\n\ttermination?: \"timeout\" | \"output_budget_exceeded\" | \"spawn_failed\" | \"exit_nonzero\" | \"cancelled\",\n\tcode?: number,\n\tcwd?: string,\n): ProcessExecResultV1 {\n\tconst stdoutPreview = sanitizeProcessTextV1(previewText(stdout.subarray(0, previewBytes)), previewBytes, cwd);\n\tconst stderrPreview = sanitizeProcessTextV1(previewText(stderr.subarray(0, previewBytes)), previewBytes, cwd);\n\tconst errorCode = termination;\n\tconst error =\n\t\terrorCode === undefined\n\t\t\t? undefined\n\t\t\t: {\n\t\t\t\t\tversion: 1 as const,\n\t\t\t\t\tcode: errorCode,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\terrorCode === \"exit_nonzero\"\n\t\t\t\t\t\t\t? \"Process exited with a non-zero status\"\n\t\t\t\t\t\t\t: errorCode === \"cancelled\"\n\t\t\t\t\t\t\t\t? \"Process execution was cancelled\"\n\t\t\t\t\t\t\t\t: \"Process execution failed\",\n\t\t\t\t};\n\treturn {\n\t\tversion: 1,\n\t\toperationId: request.operationId,\n\t\tstatus,\n\t\tsummary:\n\t\t\tstatus === \"succeeded\"\n\t\t\t\t? \"Process completed successfully\"\n\t\t\t\t: termination === \"cancelled\"\n\t\t\t\t\t? \"Process execution was cancelled\"\n\t\t\t\t\t: termination === \"timeout\"\n\t\t\t\t\t\t? \"Process execution timed out\"\n\t\t\t\t\t\t: termination === \"output_budget_exceeded\"\n\t\t\t\t\t\t\t? \"Process output exceeded the host budget\"\n\t\t\t\t\t\t\t: termination === \"spawn_failed\"\n\t\t\t\t\t\t\t\t? \"Process could not be started\"\n\t\t\t\t\t\t\t\t: \"Process exited with a non-zero status\",\n\t\t...(code === undefined ? {} : { exitCode: code }),\n\t\tstdoutBytes,\n\t\tstderrBytes,\n\t\tstdoutPreview,\n\t\tstderrPreview,\n\t\tstdoutTruncated: stdoutBytes > previewBytes,\n\t\tstderrTruncated: stderrBytes > previewBytes,\n\t\t...(error === undefined ? {} : { error }),\n\t};\n}\n\n/** Node host adapter with shell-free argv execution and bounded output previews. */\nexport function createNodeProcessExecAdapterV1(options: NodeProcessExecAdapterOptionsV1): ProcessExecAdapterV1 {\n\tif (typeof options.cwd !== \"string\" || !isAbsolute(options.cwd)) throw new Error(\"cwd must be an absolute path\");\n\tconst defaultTimeoutMs = positiveBound(\n\t\toptions.defaultTimeoutMs,\n\t\tDEFAULT_TIMEOUT_MS_V1,\n\t\tMAX_TIMEOUT_MS_V1,\n\t\t\"defaultTimeoutMs\",\n\t);\n\tconst maxStdoutBytes = positiveBound(\n\t\toptions.maxStdoutBytes,\n\t\tDEFAULT_OUTPUT_BYTES_V1,\n\t\tPROCESS_EXEC_MAX_OUTPUT_BYTES_V1,\n\t\t\"maxStdoutBytes\",\n\t);\n\tconst maxStderrBytes = positiveBound(\n\t\toptions.maxStderrBytes,\n\t\tDEFAULT_OUTPUT_BYTES_V1,\n\t\tPROCESS_EXEC_MAX_OUTPUT_BYTES_V1,\n\t\t\"maxStderrBytes\",\n\t);\n\tconst previewBytes = positiveBound(\n\t\toptions.previewBytes,\n\t\tDEFAULT_PREVIEW_BYTES_V1,\n\t\tPROCESS_EXEC_MAX_PREVIEW_BYTES_V1,\n\t\t\"previewBytes\",\n\t);\n\tconst env = hostEnvironment(options.env);\n\tconst spawnProcess = options.spawn ?? spawn;\n\tconst terminateTree = options.killProcessTree ?? killProcessTree;\n\n\treturn {\n\t\tadapterId: PROCESS_EXEC_HOST_ADAPTER_ID_V1,\n\t\tadapterVersion: PROCESS_EXEC_HOST_ADAPTER_VERSION_V1,\n\t\trun: (request, signal) => {\n\t\t\tconst operationId =\n\t\t\t\ttypeof request.operationId === \"string\" && request.operationId.trim() !== \"\"\n\t\t\t\t\t? request.operationId\n\t\t\t\t\t: \"invalid-process-operation\";\n\t\t\tconst invalid = validateRequest(request);\n\t\t\tif (invalid !== undefined) return Promise.resolve(inputError(operationId, invalid));\n\t\t\tif (signal?.aborted) return Promise.resolve(cancelled(operationId));\n\t\t\tconst timeoutMs = request.timeoutMs ?? defaultTimeoutMs;\n\t\t\treturn new Promise<ProcessExecResultV1>((resolve) => {\n\t\t\t\tlet child: ReturnType<typeof spawn>;\n\t\t\t\ttry {\n\t\t\t\t\tchild = spawnProcess(request.command, [...request.args], {\n\t\t\t\t\t\tcwd: options.cwd,\n\t\t\t\t\t\t...(env === undefined ? {} : { env }),\n\t\t\t\t\t\tshell: false,\n\t\t\t\t\t\twindowsHide: true,\n\t\t\t\t\t\tdetached: process.platform !== \"win32\",\n\t\t\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\t\t});\n\t\t\t\t} catch {\n\t\t\t\t\tresolve(\n\t\t\t\t\t\tresult(\n\t\t\t\t\t\t\trequest,\n\t\t\t\t\t\t\t\"failed\",\n\t\t\t\t\t\t\tnew Uint8Array(),\n\t\t\t\t\t\t\tnew Uint8Array(),\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\tpreviewBytes,\n\t\t\t\t\t\t\t\"spawn_failed\",\n\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\toptions.cwd,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet stdoutBytes = 0;\n\t\t\t\tlet stderrBytes = 0;\n\t\t\t\tconst stdoutPreview = new Uint8Array(previewBytes);\n\t\t\t\tconst stderrPreview = new Uint8Array(previewBytes);\n\t\t\t\tlet stdoutPreviewLength = 0;\n\t\t\t\tlet stderrPreviewLength = 0;\n\t\t\t\tlet termination:\n\t\t\t\t\t| \"timeout\"\n\t\t\t\t\t| \"output_budget_exceeded\"\n\t\t\t\t\t| \"spawn_failed\"\n\t\t\t\t\t| \"exit_nonzero\"\n\t\t\t\t\t| \"cancelled\"\n\t\t\t\t\t| undefined;\n\t\t\t\tlet settled = false;\n\t\t\t\tlet exited = false;\n\t\t\t\tlet terminationRequested = false;\n\t\t\t\tlet forceKillTimer: ReturnType<typeof setTimeout> | undefined;\n\t\t\t\tlet hardFinishTimer: ReturnType<typeof setTimeout> | undefined;\n\t\t\t\tconst append = (target: Uint8Array, length: number, chunk: Uint8Array): number => {\n\t\t\t\t\tconst copy = Math.min(chunk.byteLength, target.byteLength - length);\n\t\t\t\t\ttarget.set(chunk.subarray(0, copy), length);\n\t\t\t\t\treturn length + copy;\n\t\t\t\t};\n\t\t\t\tconst forceTerminate = (): void => {\n\t\t\t\t\tif (exited) return;\n\t\t\t\t\tif (typeof child.pid === \"number\" && child.pid > 0) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tterminateTree(child.pid);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// The host cleanup boundary is best-effort; close/error still settles the operation.\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (!child.killed) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tchild.kill(\"SIGKILL\");\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// The close/error event remains authoritative.\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tconst armForceTermination = (): void => {\n\t\t\t\t\tif (forceKillTimer !== undefined) return;\n\t\t\t\t\tforceKillTimer = setTimeout(() => {\n\t\t\t\t\t\tforceTerminate();\n\t\t\t\t\t\thardFinishTimer = setTimeout(() => finalize(null), 500);\n\t\t\t\t\t}, 2000);\n\t\t\t\t};\n\t\t\t\tconst terminate = (reason: \"timeout\" | \"output_budget_exceeded\" | \"spawn_failed\" | \"cancelled\"): void => {\n\t\t\t\t\tif (termination === undefined) termination = reason;\n\t\t\t\t\tif (!terminationRequested) {\n\t\t\t\t\t\tterminationRequested = true;\n\t\t\t\t\t\tif (!child.killed) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tchild.kill(\"SIGTERM\");\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t// The force cleanup timer still runs.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tarmForceTermination();\n\t\t\t\t};\n\t\t\t\tconst onAbort = () => {\n\t\t\t\t\tterminate(\"cancelled\");\n\t\t\t\t};\n\t\t\t\tconst finalize = (code: number | null): void => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\texited = true;\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\tif (forceKillTimer !== undefined) clearTimeout(forceKillTimer);\n\t\t\t\t\tif (hardFinishTimer !== undefined) clearTimeout(hardFinishTimer);\n\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\tif (signal?.aborted && termination === undefined) termination = \"cancelled\";\n\t\t\t\t\tif (termination === undefined && code !== 0) termination = \"exit_nonzero\";\n\t\t\t\t\tconst status =\n\t\t\t\t\t\ttermination === undefined ? \"succeeded\" : termination === \"cancelled\" ? \"cancelled\" : \"failed\";\n\t\t\t\t\tresolve(\n\t\t\t\t\t\tresult(\n\t\t\t\t\t\t\trequest,\n\t\t\t\t\t\t\tstatus,\n\t\t\t\t\t\t\tstdoutPreview.subarray(0, stdoutPreviewLength),\n\t\t\t\t\t\t\tstderrPreview.subarray(0, stderrPreviewLength),\n\t\t\t\t\t\t\tstdoutBytes,\n\t\t\t\t\t\t\tstderrBytes,\n\t\t\t\t\t\t\tpreviewBytes,\n\t\t\t\t\t\t\ttermination,\n\t\t\t\t\t\t\tcode ?? undefined,\n\t\t\t\t\t\t\toptions.cwd,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t};\n\t\t\t\tconst timeout = setTimeout(() => terminate(\"timeout\"), timeoutMs);\n\t\t\t\tif (signal !== undefined) signal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\tchild.stdout?.on(\"data\", (chunk: Buffer | Uint8Array) => {\n\t\t\t\t\tconst bytes = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);\n\t\t\t\t\tstdoutBytes += bytes.byteLength;\n\t\t\t\t\tstdoutPreviewLength = append(stdoutPreview, stdoutPreviewLength, bytes);\n\t\t\t\t\tif (stdoutBytes > maxStdoutBytes) terminate(\"output_budget_exceeded\");\n\t\t\t\t});\n\t\t\t\tchild.stderr?.on(\"data\", (chunk: Buffer | Uint8Array) => {\n\t\t\t\t\tconst bytes = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);\n\t\t\t\t\tstderrBytes += bytes.byteLength;\n\t\t\t\t\tstderrPreviewLength = append(stderrPreview, stderrPreviewLength, bytes);\n\t\t\t\t\tif (stderrBytes > maxStderrBytes) terminate(\"output_budget_exceeded\");\n\t\t\t\t});\n\t\t\t\tchild.once(\"error\", () => {\n\t\t\t\t\tterminate(\"spawn_failed\");\n\t\t\t\t});\n\t\t\t\tchild.once(\"close\", (code) => {\n\t\t\t\t\texited = true;\n\t\t\t\t\tfinalize(code);\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"node-adapter.d.ts","sourceRoot":"","sources":["../src/node-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAG3C,OAAO,EAGN,KAAK,oBAAoB,EAGzB,MAAM,YAAY,CAAC;AAKpB,eAAO,MAAM,gCAAgC,QAAc,CAAC;AAE5D,eAAO,MAAM,iCAAiC,OAAO,CAAC;AAItD,eAAO,MAAM,sCAAsC,MAAM,CAAC;AAE1D,MAAM,WAAW,+BAA+B;IAC/C,iEAAiE;IACjE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,4DAA4D;IAC5D,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CAC9B;AAkBD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAc7G;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CACpC,KAAK,EAAE,MAAM,EACb,YAAY,SAAoC,EAChD,GAAG,CAAC,EAAE,MAAM,GACV,MAAM,CAkBR;AAgMD,oFAAoF;AACpF,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,+BAA+B,GAAG,oBAAoB,CAiL7G","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { isAbsolute } from \"node:path\";\nimport { killProcessTree, sanitizeBinaryOutput } from \"./shell-utils.ts\";\nimport {\n\tPROCESS_EXEC_HOST_ADAPTER_ID_V1,\n\tPROCESS_EXEC_HOST_ADAPTER_VERSION_V1,\n\ttype ProcessExecAdapterV1,\n\ttype ProcessExecRequestV1,\n\ttype ProcessExecResultV1,\n} from \"./types.ts\";\n\nconst DEFAULT_TIMEOUT_MS_V1 = 30_000;\nconst MAX_TIMEOUT_MS_V1 = 120_000;\nconst DEFAULT_OUTPUT_BYTES_V1 = 64 * 1024;\nexport const PROCESS_EXEC_MAX_OUTPUT_BYTES_V1 = 1024 * 1024;\nconst DEFAULT_PREVIEW_BYTES_V1 = 4096;\nexport const PROCESS_EXEC_MAX_PREVIEW_BYTES_V1 = 8192;\nconst MAX_ARGS_V1 = 256;\nconst MAX_COMMAND_CHARS_V1 = 512;\nconst MAX_ARG_CHARS_V1 = 4096;\nexport const PROCESS_EXEC_MAX_OPERATION_ID_CHARS_V1 = 256;\n\nexport interface NodeProcessExecAdapterOptionsV1 {\n\t/** Host-owned working directory; requests cannot override it. */\n\treadonly cwd: string;\n\treadonly env?: Readonly<Record<string, string>>;\n\treadonly defaultTimeoutMs?: number;\n\treadonly maxStdoutBytes?: number;\n\treadonly maxStderrBytes?: number;\n\treadonly previewBytes?: number;\n\t/** Test seam for the host-owned process-tree terminator. */\n\treadonly killProcessTree?: (pid: number) => void;\n\t/** Deterministic host seam; production defaults to Node's shell-free spawn. */\n\treadonly spawn?: typeof spawn;\n}\n\nconst SAFE_ENVIRONMENT_KEYS_V1 = new Set(\n\t[\"PATH\", \"PATHEXT\", \"SystemRoot\", \"SYSTEMROOT\", \"WINDIR\", \"TEMP\", \"TMP\", \"TMPDIR\", \"LANG\", \"LC_ALL\"].map((key) =>\n\t\tkey.toLowerCase(),\n\t),\n);\n\n/** Splits a preview budget into head/tail segments (75% head, rest tail; head wins on tiny budgets). */\nfunction splitPreviewBudgetV1(budget: number): { head: number; tail: number } {\n\tconst head = Math.ceil((budget * 3) / 4);\n\treturn { head, tail: Math.max(0, budget - head) };\n}\n\nfunction omissionMarkerV1(omitted: number, unit: \"bytes\" | \"chars\"): string {\n\treturn `\\n... [${omitted} ${unit} omitted] ...\\n`;\n}\n\n/**\n * Builds a bounded preview from the retained capture. `captured` holds the\n * complete output when `totalBytes <= previewBytes`; otherwise it holds the\n * first `head` bytes followed by the last `tail` bytes of the stream, and the\n * two segments are joined with an omission marker. The marker rides on top of\n * the segment budgets but the assembled preview never exceeds\n * `PROCESS_EXEC_MAX_PREVIEW_BYTES_V1`.\n */\nexport function buildHeadTailPreviewV1(captured: Uint8Array, previewBytes: number, totalBytes: number): string {\n\tif (totalBytes <= previewBytes) return previewText(captured);\n\tconst budget = splitPreviewBudgetV1(previewBytes);\n\tconst headPart = captured.subarray(0, Math.min(budget.head, captured.byteLength));\n\tlet tailBytes = Math.min(budget.tail, Math.max(0, captured.byteLength - headPart.byteLength));\n\tlet marker = omissionMarkerV1(Math.max(0, totalBytes - headPart.byteLength - tailBytes), \"bytes\");\n\twhile (headPart.byteLength + tailBytes + marker.length > PROCESS_EXEC_MAX_PREVIEW_BYTES_V1 && tailBytes > 0) {\n\t\ttailBytes = Math.max(\n\t\t\t0,\n\t\t\tMath.min(tailBytes, PROCESS_EXEC_MAX_PREVIEW_BYTES_V1 - headPart.byteLength - marker.length),\n\t\t);\n\t\tmarker = omissionMarkerV1(Math.max(0, totalBytes - headPart.byteLength - tailBytes), \"bytes\");\n\t}\n\treturn previewText(headPart) + marker + previewText(captured.subarray(captured.byteLength - tailBytes));\n}\n\n/** Redacts credentials and host paths before a preview can reach the model. */\nexport function sanitizeProcessTextV1(\n\tvalue: string,\n\tmaximumChars = PROCESS_EXEC_MAX_PREVIEW_BYTES_V1,\n\tcwd?: string,\n): string {\n\tlet text = sanitizeBinaryOutput(value);\n\tif (cwd !== undefined && cwd !== \"\") text = text.replaceAll(cwd, \"[redacted-path]\");\n\ttext = text\n\t\t.replace(\n\t\t\t/((?:api[-_ ]?key|access[-_ ]?token|auth(?:orization)?|bearer|password|passwd|secret|token)\\s*[:=]\\s*)([^\\s,;]+)/giu,\n\t\t\t\"$1[redacted-secret]\",\n\t\t)\n\t\t.replace(/(?:[A-Za-z]:[\\\\/]|\\\\\\\\|\\/(?:home|Users|private|tmp|var|workspace)\\/)[^\\s\"'<>]*/gu, \"[redacted-path]\");\n\tif (text.length <= maximumChars) return text;\n\tconst head = Math.ceil((maximumChars * 3) / 4);\n\tlet tail = maximumChars - head;\n\tlet marker = omissionMarkerV1(text.length - head - tail, \"chars\");\n\twhile (head + tail + marker.length > maximumChars && tail > 0) {\n\t\ttail = Math.max(0, Math.min(tail, maximumChars - head - marker.length));\n\t\tmarker = omissionMarkerV1(text.length - head - tail, \"chars\");\n\t}\n\treturn text.slice(0, head) + marker + (tail > 0 ? text.slice(text.length - tail) : \"\");\n}\n\nfunction hostEnvironment(explicit: Readonly<Record<string, string>> | undefined): NodeJS.ProcessEnv {\n\tconst inherited: NodeJS.ProcessEnv = {};\n\tfor (const [key, value] of Object.entries(process.env)) {\n\t\tif (value !== undefined && SAFE_ENVIRONMENT_KEYS_V1.has(key.toLowerCase())) inherited[key] = value;\n\t}\n\treturn { ...inherited, ...(explicit ?? {}) };\n}\n\nfunction positiveBound(value: number | undefined, fallback: number, maximum: number, label: string): number {\n\tconst result = value ?? fallback;\n\tif (!Number.isSafeInteger(result) || result < 1 || result > maximum)\n\t\tthrow new Error(`${label} must be a positive integer at most ${maximum}`);\n\treturn result;\n}\n\nfunction inputError(operationId: string, message: string): ProcessExecResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"failed\",\n\t\tsummary: \"Process request was invalid\",\n\t\tstdoutBytes: 0,\n\t\tstderrBytes: 0,\n\t\tstdoutPreview: \"\",\n\t\tstderrPreview: \"\",\n\t\tstdoutTruncated: false,\n\t\tstderrTruncated: false,\n\t\terror: { version: 1, code: \"invalid_input\", message },\n\t};\n}\n\nfunction cancelled(operationId: string): ProcessExecResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"cancelled\",\n\t\tsummary: \"Process execution was cancelled\",\n\t\tstdoutBytes: 0,\n\t\tstderrBytes: 0,\n\t\tstdoutPreview: \"\",\n\t\tstderrPreview: \"\",\n\t\tstdoutTruncated: false,\n\t\tstderrTruncated: false,\n\t\terror: { version: 1, code: \"cancelled\", message: \"Process execution was cancelled\" },\n\t};\n}\n\nfunction validateRequest(request: ProcessExecRequestV1): string | undefined {\n\tif (request.version !== 1) return \"version must be 1\";\n\tif (\n\t\ttypeof request.operationId !== \"string\" ||\n\t\trequest.operationId.trim() === \"\" ||\n\t\trequest.operationId.length > PROCESS_EXEC_MAX_OPERATION_ID_CHARS_V1\n\t)\n\t\treturn \"operationId is invalid\";\n\tif (\n\t\ttypeof request.command !== \"string\" ||\n\t\trequest.command.trim() === \"\" ||\n\t\trequest.command.length > MAX_COMMAND_CHARS_V1 ||\n\t\t/[\\u0000\\r\\n]/u.test(request.command)\n\t)\n\t\treturn \"command is invalid\";\n\tif (!Array.isArray(request.args) || request.args.length > MAX_ARGS_V1) return \"args are invalid\";\n\tif (\n\t\trequest.args.some((arg) => typeof arg !== \"string\" || arg.length > MAX_ARG_CHARS_V1 || /[\\u0000\\r\\n]/u.test(arg))\n\t)\n\t\treturn \"args are invalid\";\n\tif (\n\t\trequest.timeoutMs !== undefined &&\n\t\t(!Number.isSafeInteger(request.timeoutMs) || request.timeoutMs < 1 || request.timeoutMs > MAX_TIMEOUT_MS_V1)\n\t)\n\t\treturn `timeoutMs must be a positive integer at most ${MAX_TIMEOUT_MS_V1}`;\n\treturn undefined;\n}\n\nfunction previewText(bytes: Uint8Array): string {\n\treturn new TextDecoder().decode(bytes);\n}\n\ninterface PreviewCaptureV1 {\n\treadonly head: Uint8Array;\n\treadonly tail: Uint8Array;\n\theadLength: number;\n\ttailLength: number;\n}\n\n/** Retains the first head-budget and last tail-budget bytes of a stream for head+tail previews. */\nfunction createPreviewCaptureV1(previewBytes: number): PreviewCaptureV1 {\n\tconst budget = splitPreviewBudgetV1(previewBytes);\n\treturn { head: new Uint8Array(budget.head), tail: new Uint8Array(budget.tail), headLength: 0, tailLength: 0 };\n}\n\nfunction capturePreviewChunkV1(capture: PreviewCaptureV1, chunk: Uint8Array): void {\n\tconst headCopy = Math.min(chunk.byteLength, capture.head.byteLength - capture.headLength);\n\tcapture.head.set(chunk.subarray(0, headCopy), capture.headLength);\n\tcapture.headLength += headCopy;\n\tconst tailSize = capture.tail.byteLength;\n\tif (tailSize === 0) return;\n\tif (chunk.byteLength >= tailSize) {\n\t\tcapture.tail.set(chunk.subarray(chunk.byteLength - tailSize));\n\t\tcapture.tailLength = tailSize;\n\t\treturn;\n\t}\n\tcapture.tail.copyWithin(0, chunk.byteLength);\n\tcapture.tail.set(chunk, tailSize - chunk.byteLength);\n\tcapture.tailLength = Math.min(tailSize, capture.tailLength + chunk.byteLength);\n}\n\n/** Materializes the retained bytes: the full output when it fit the budget, else head followed by tail. */\nfunction previewCaptureBytesV1(capture: PreviewCaptureV1, totalBytes: number): Uint8Array {\n\tconst capacity = capture.head.byteLength + capture.tail.byteLength;\n\tif (totalBytes <= capture.head.byteLength) return capture.head.subarray(0, totalBytes);\n\tif (totalBytes <= capacity) {\n\t\tconst tailFrom = totalBytes - capture.head.byteLength;\n\t\tconst captured = new Uint8Array(totalBytes);\n\t\tcaptured.set(capture.head);\n\t\tcaptured.set(capture.tail.subarray(capture.tailLength - tailFrom, capture.tailLength), capture.headLength);\n\t\treturn captured;\n\t}\n\tconst captured = new Uint8Array(capacity);\n\tcaptured.set(capture.head.subarray(0, capture.headLength));\n\tcaptured.set(capture.tail.subarray(0, capture.tailLength), capture.headLength);\n\treturn captured;\n}\n\nfunction result(\n\trequest: ProcessExecRequestV1,\n\tstatus: ProcessExecResultV1[\"status\"],\n\tstdout: Uint8Array,\n\tstderr: Uint8Array,\n\tstdoutBytes: number,\n\tstderrBytes: number,\n\tpreviewBytes: number,\n\ttermination?: \"timeout\" | \"output_budget_exceeded\" | \"spawn_failed\" | \"exit_nonzero\" | \"cancelled\",\n\tcode?: number,\n\tcwd?: string,\n): ProcessExecResultV1 {\n\t// The byte-level head/tail split is authoritative; sanitize only redacts and\n\t// acts as the absolute-size circuit breaker at the contract cap.\n\tconst stdoutPreview = sanitizeProcessTextV1(\n\t\tbuildHeadTailPreviewV1(stdout, previewBytes, stdoutBytes),\n\t\tPROCESS_EXEC_MAX_PREVIEW_BYTES_V1,\n\t\tcwd,\n\t);\n\tconst stderrPreview = sanitizeProcessTextV1(\n\t\tbuildHeadTailPreviewV1(stderr, previewBytes, stderrBytes),\n\t\tPROCESS_EXEC_MAX_PREVIEW_BYTES_V1,\n\t\tcwd,\n\t);\n\tconst errorCode = termination;\n\tconst error =\n\t\terrorCode === undefined\n\t\t\t? undefined\n\t\t\t: {\n\t\t\t\t\tversion: 1 as const,\n\t\t\t\t\tcode: errorCode,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\terrorCode === \"exit_nonzero\"\n\t\t\t\t\t\t\t? \"Process exited with a non-zero status\"\n\t\t\t\t\t\t\t: errorCode === \"cancelled\"\n\t\t\t\t\t\t\t\t? \"Process execution was cancelled\"\n\t\t\t\t\t\t\t\t: \"Process execution failed\",\n\t\t\t\t};\n\treturn {\n\t\tversion: 1,\n\t\toperationId: request.operationId,\n\t\tstatus,\n\t\tsummary:\n\t\t\tstatus === \"succeeded\"\n\t\t\t\t? \"Process completed successfully\"\n\t\t\t\t: termination === \"cancelled\"\n\t\t\t\t\t? \"Process execution was cancelled\"\n\t\t\t\t\t: termination === \"timeout\"\n\t\t\t\t\t\t? \"Process execution timed out\"\n\t\t\t\t\t\t: termination === \"output_budget_exceeded\"\n\t\t\t\t\t\t\t? \"Process output exceeded the host budget\"\n\t\t\t\t\t\t\t: termination === \"spawn_failed\"\n\t\t\t\t\t\t\t\t? \"Process could not be started\"\n\t\t\t\t\t\t\t\t: \"Process exited with a non-zero status\",\n\t\t...(code === undefined ? {} : { exitCode: code }),\n\t\tstdoutBytes,\n\t\tstderrBytes,\n\t\tstdoutPreview,\n\t\tstderrPreview,\n\t\tstdoutTruncated: stdoutBytes > previewBytes,\n\t\tstderrTruncated: stderrBytes > previewBytes,\n\t\t...(error === undefined ? {} : { error }),\n\t};\n}\n\n/** Node host adapter with shell-free argv execution and bounded output previews. */\nexport function createNodeProcessExecAdapterV1(options: NodeProcessExecAdapterOptionsV1): ProcessExecAdapterV1 {\n\tif (typeof options.cwd !== \"string\" || !isAbsolute(options.cwd)) throw new Error(\"cwd must be an absolute path\");\n\tconst defaultTimeoutMs = positiveBound(\n\t\toptions.defaultTimeoutMs,\n\t\tDEFAULT_TIMEOUT_MS_V1,\n\t\tMAX_TIMEOUT_MS_V1,\n\t\t\"defaultTimeoutMs\",\n\t);\n\tconst maxStdoutBytes = positiveBound(\n\t\toptions.maxStdoutBytes,\n\t\tDEFAULT_OUTPUT_BYTES_V1,\n\t\tPROCESS_EXEC_MAX_OUTPUT_BYTES_V1,\n\t\t\"maxStdoutBytes\",\n\t);\n\tconst maxStderrBytes = positiveBound(\n\t\toptions.maxStderrBytes,\n\t\tDEFAULT_OUTPUT_BYTES_V1,\n\t\tPROCESS_EXEC_MAX_OUTPUT_BYTES_V1,\n\t\t\"maxStderrBytes\",\n\t);\n\tconst previewBytes = positiveBound(\n\t\toptions.previewBytes,\n\t\tDEFAULT_PREVIEW_BYTES_V1,\n\t\tPROCESS_EXEC_MAX_PREVIEW_BYTES_V1,\n\t\t\"previewBytes\",\n\t);\n\tconst env = hostEnvironment(options.env);\n\tconst spawnProcess = options.spawn ?? spawn;\n\tconst terminateTree = options.killProcessTree ?? killProcessTree;\n\n\treturn {\n\t\tadapterId: PROCESS_EXEC_HOST_ADAPTER_ID_V1,\n\t\tadapterVersion: PROCESS_EXEC_HOST_ADAPTER_VERSION_V1,\n\t\trun: (request, signal) => {\n\t\t\tconst operationId =\n\t\t\t\ttypeof request.operationId === \"string\" && request.operationId.trim() !== \"\"\n\t\t\t\t\t? request.operationId\n\t\t\t\t\t: \"invalid-process-operation\";\n\t\t\tconst invalid = validateRequest(request);\n\t\t\tif (invalid !== undefined) return Promise.resolve(inputError(operationId, invalid));\n\t\t\tif (signal?.aborted) return Promise.resolve(cancelled(operationId));\n\t\t\tconst timeoutMs = request.timeoutMs ?? defaultTimeoutMs;\n\t\t\treturn new Promise<ProcessExecResultV1>((resolve) => {\n\t\t\t\tlet child: ReturnType<typeof spawn>;\n\t\t\t\ttry {\n\t\t\t\t\tchild = spawnProcess(request.command, [...request.args], {\n\t\t\t\t\t\tcwd: options.cwd,\n\t\t\t\t\t\t...(env === undefined ? {} : { env }),\n\t\t\t\t\t\tshell: false,\n\t\t\t\t\t\twindowsHide: true,\n\t\t\t\t\t\tdetached: process.platform !== \"win32\",\n\t\t\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\t\t});\n\t\t\t\t} catch {\n\t\t\t\t\tresolve(\n\t\t\t\t\t\tresult(\n\t\t\t\t\t\t\trequest,\n\t\t\t\t\t\t\t\"failed\",\n\t\t\t\t\t\t\tnew Uint8Array(),\n\t\t\t\t\t\t\tnew Uint8Array(),\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\tpreviewBytes,\n\t\t\t\t\t\t\t\"spawn_failed\",\n\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\toptions.cwd,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet stdoutBytes = 0;\n\t\t\t\tlet stderrBytes = 0;\n\t\t\t\tconst stdoutCapture = createPreviewCaptureV1(previewBytes);\n\t\t\t\tconst stderrCapture = createPreviewCaptureV1(previewBytes);\n\t\t\t\tlet termination:\n\t\t\t\t\t| \"timeout\"\n\t\t\t\t\t| \"output_budget_exceeded\"\n\t\t\t\t\t| \"spawn_failed\"\n\t\t\t\t\t| \"exit_nonzero\"\n\t\t\t\t\t| \"cancelled\"\n\t\t\t\t\t| undefined;\n\t\t\t\tlet settled = false;\n\t\t\t\tlet exited = false;\n\t\t\t\tlet terminationRequested = false;\n\t\t\t\tlet forceKillTimer: ReturnType<typeof setTimeout> | undefined;\n\t\t\t\tlet hardFinishTimer: ReturnType<typeof setTimeout> | undefined;\n\t\t\t\tconst forceTerminate = (): void => {\n\t\t\t\t\tif (exited) return;\n\t\t\t\t\tif (typeof child.pid === \"number\" && child.pid > 0) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tterminateTree(child.pid);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// The host cleanup boundary is best-effort; close/error still settles the operation.\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (!child.killed) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tchild.kill(\"SIGKILL\");\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// The close/error event remains authoritative.\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tconst armForceTermination = (): void => {\n\t\t\t\t\tif (forceKillTimer !== undefined) return;\n\t\t\t\t\tforceKillTimer = setTimeout(() => {\n\t\t\t\t\t\tforceTerminate();\n\t\t\t\t\t\thardFinishTimer = setTimeout(() => finalize(null), 500);\n\t\t\t\t\t}, 2000);\n\t\t\t\t};\n\t\t\t\tconst terminate = (reason: \"timeout\" | \"output_budget_exceeded\" | \"spawn_failed\" | \"cancelled\"): void => {\n\t\t\t\t\tif (termination === undefined) termination = reason;\n\t\t\t\t\tif (!terminationRequested) {\n\t\t\t\t\t\tterminationRequested = true;\n\t\t\t\t\t\tif (!child.killed) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tchild.kill(\"SIGTERM\");\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t// The force cleanup timer still runs.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tarmForceTermination();\n\t\t\t\t};\n\t\t\t\tconst onAbort = () => {\n\t\t\t\t\tterminate(\"cancelled\");\n\t\t\t\t};\n\t\t\t\tconst finalize = (code: number | null): void => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\texited = true;\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\tif (forceKillTimer !== undefined) clearTimeout(forceKillTimer);\n\t\t\t\t\tif (hardFinishTimer !== undefined) clearTimeout(hardFinishTimer);\n\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\tif (signal?.aborted && termination === undefined) termination = \"cancelled\";\n\t\t\t\t\tif (termination === undefined && code !== 0) termination = \"exit_nonzero\";\n\t\t\t\t\tconst status =\n\t\t\t\t\t\ttermination === undefined ? \"succeeded\" : termination === \"cancelled\" ? \"cancelled\" : \"failed\";\n\t\t\t\t\tresolve(\n\t\t\t\t\t\tresult(\n\t\t\t\t\t\t\trequest,\n\t\t\t\t\t\t\tstatus,\n\t\t\t\t\t\t\tpreviewCaptureBytesV1(stdoutCapture, stdoutBytes),\n\t\t\t\t\t\t\tpreviewCaptureBytesV1(stderrCapture, stderrBytes),\n\t\t\t\t\t\t\tstdoutBytes,\n\t\t\t\t\t\t\tstderrBytes,\n\t\t\t\t\t\t\tpreviewBytes,\n\t\t\t\t\t\t\ttermination,\n\t\t\t\t\t\t\tcode ?? undefined,\n\t\t\t\t\t\t\toptions.cwd,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t};\n\t\t\t\tconst timeout = setTimeout(() => terminate(\"timeout\"), timeoutMs);\n\t\t\t\tif (signal !== undefined) signal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\tchild.stdout?.on(\"data\", (chunk: Buffer | Uint8Array) => {\n\t\t\t\t\tconst bytes = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);\n\t\t\t\t\tstdoutBytes += bytes.byteLength;\n\t\t\t\t\tcapturePreviewChunkV1(stdoutCapture, bytes);\n\t\t\t\t\tif (stdoutBytes > maxStdoutBytes) terminate(\"output_budget_exceeded\");\n\t\t\t\t});\n\t\t\t\tchild.stderr?.on(\"data\", (chunk: Buffer | Uint8Array) => {\n\t\t\t\t\tconst bytes = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);\n\t\t\t\t\tstderrBytes += bytes.byteLength;\n\t\t\t\t\tcapturePreviewChunkV1(stderrCapture, bytes);\n\t\t\t\t\tif (stderrBytes > maxStderrBytes) terminate(\"output_budget_exceeded\");\n\t\t\t\t});\n\t\t\t\tchild.once(\"error\", () => {\n\t\t\t\t\tterminate(\"spawn_failed\");\n\t\t\t\t});\n\t\t\t\tchild.once(\"close\", (code) => {\n\t\t\t\t\texited = true;\n\t\t\t\t\tfinalize(code);\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t};\n}\n"]}
@@ -13,6 +13,35 @@ const MAX_COMMAND_CHARS_V1 = 512;
13
13
  const MAX_ARG_CHARS_V1 = 4096;
14
14
  export const PROCESS_EXEC_MAX_OPERATION_ID_CHARS_V1 = 256;
15
15
  const SAFE_ENVIRONMENT_KEYS_V1 = new Set(["PATH", "PATHEXT", "SystemRoot", "SYSTEMROOT", "WINDIR", "TEMP", "TMP", "TMPDIR", "LANG", "LC_ALL"].map((key) => key.toLowerCase()));
16
+ /** Splits a preview budget into head/tail segments (75% head, rest tail; head wins on tiny budgets). */
17
+ function splitPreviewBudgetV1(budget) {
18
+ const head = Math.ceil((budget * 3) / 4);
19
+ return { head, tail: Math.max(0, budget - head) };
20
+ }
21
+ function omissionMarkerV1(omitted, unit) {
22
+ return `\n... [${omitted} ${unit} omitted] ...\n`;
23
+ }
24
+ /**
25
+ * Builds a bounded preview from the retained capture. `captured` holds the
26
+ * complete output when `totalBytes <= previewBytes`; otherwise it holds the
27
+ * first `head` bytes followed by the last `tail` bytes of the stream, and the
28
+ * two segments are joined with an omission marker. The marker rides on top of
29
+ * the segment budgets but the assembled preview never exceeds
30
+ * `PROCESS_EXEC_MAX_PREVIEW_BYTES_V1`.
31
+ */
32
+ export function buildHeadTailPreviewV1(captured, previewBytes, totalBytes) {
33
+ if (totalBytes <= previewBytes)
34
+ return previewText(captured);
35
+ const budget = splitPreviewBudgetV1(previewBytes);
36
+ const headPart = captured.subarray(0, Math.min(budget.head, captured.byteLength));
37
+ let tailBytes = Math.min(budget.tail, Math.max(0, captured.byteLength - headPart.byteLength));
38
+ let marker = omissionMarkerV1(Math.max(0, totalBytes - headPart.byteLength - tailBytes), "bytes");
39
+ while (headPart.byteLength + tailBytes + marker.length > PROCESS_EXEC_MAX_PREVIEW_BYTES_V1 && tailBytes > 0) {
40
+ tailBytes = Math.max(0, Math.min(tailBytes, PROCESS_EXEC_MAX_PREVIEW_BYTES_V1 - headPart.byteLength - marker.length));
41
+ marker = omissionMarkerV1(Math.max(0, totalBytes - headPart.byteLength - tailBytes), "bytes");
42
+ }
43
+ return previewText(headPart) + marker + previewText(captured.subarray(captured.byteLength - tailBytes));
44
+ }
16
45
  /** Redacts credentials and host paths before a preview can reach the model. */
17
46
  export function sanitizeProcessTextV1(value, maximumChars = PROCESS_EXEC_MAX_PREVIEW_BYTES_V1, cwd) {
18
47
  let text = sanitizeBinaryOutput(value);
@@ -21,7 +50,16 @@ export function sanitizeProcessTextV1(value, maximumChars = PROCESS_EXEC_MAX_PRE
21
50
  text = text
22
51
  .replace(/((?:api[-_ ]?key|access[-_ ]?token|auth(?:orization)?|bearer|password|passwd|secret|token)\s*[:=]\s*)([^\s,;]+)/giu, "$1[redacted-secret]")
23
52
  .replace(/(?:[A-Za-z]:[\\/]|\\\\|\/(?:home|Users|private|tmp|var|workspace)\/)[^\s"'<>]*/gu, "[redacted-path]");
24
- return text.length <= maximumChars ? text : text.slice(0, maximumChars);
53
+ if (text.length <= maximumChars)
54
+ return text;
55
+ const head = Math.ceil((maximumChars * 3) / 4);
56
+ let tail = maximumChars - head;
57
+ let marker = omissionMarkerV1(text.length - head - tail, "chars");
58
+ while (head + tail + marker.length > maximumChars && tail > 0) {
59
+ tail = Math.max(0, Math.min(tail, maximumChars - head - marker.length));
60
+ marker = omissionMarkerV1(text.length - head - tail, "chars");
61
+ }
62
+ return text.slice(0, head) + marker + (tail > 0 ? text.slice(text.length - tail) : "");
25
63
  }
26
64
  function hostEnvironment(explicit) {
27
65
  const inherited = {};
@@ -91,9 +129,49 @@ function validateRequest(request) {
91
129
  function previewText(bytes) {
92
130
  return new TextDecoder().decode(bytes);
93
131
  }
132
+ /** Retains the first head-budget and last tail-budget bytes of a stream for head+tail previews. */
133
+ function createPreviewCaptureV1(previewBytes) {
134
+ const budget = splitPreviewBudgetV1(previewBytes);
135
+ return { head: new Uint8Array(budget.head), tail: new Uint8Array(budget.tail), headLength: 0, tailLength: 0 };
136
+ }
137
+ function capturePreviewChunkV1(capture, chunk) {
138
+ const headCopy = Math.min(chunk.byteLength, capture.head.byteLength - capture.headLength);
139
+ capture.head.set(chunk.subarray(0, headCopy), capture.headLength);
140
+ capture.headLength += headCopy;
141
+ const tailSize = capture.tail.byteLength;
142
+ if (tailSize === 0)
143
+ return;
144
+ if (chunk.byteLength >= tailSize) {
145
+ capture.tail.set(chunk.subarray(chunk.byteLength - tailSize));
146
+ capture.tailLength = tailSize;
147
+ return;
148
+ }
149
+ capture.tail.copyWithin(0, chunk.byteLength);
150
+ capture.tail.set(chunk, tailSize - chunk.byteLength);
151
+ capture.tailLength = Math.min(tailSize, capture.tailLength + chunk.byteLength);
152
+ }
153
+ /** Materializes the retained bytes: the full output when it fit the budget, else head followed by tail. */
154
+ function previewCaptureBytesV1(capture, totalBytes) {
155
+ const capacity = capture.head.byteLength + capture.tail.byteLength;
156
+ if (totalBytes <= capture.head.byteLength)
157
+ return capture.head.subarray(0, totalBytes);
158
+ if (totalBytes <= capacity) {
159
+ const tailFrom = totalBytes - capture.head.byteLength;
160
+ const captured = new Uint8Array(totalBytes);
161
+ captured.set(capture.head);
162
+ captured.set(capture.tail.subarray(capture.tailLength - tailFrom, capture.tailLength), capture.headLength);
163
+ return captured;
164
+ }
165
+ const captured = new Uint8Array(capacity);
166
+ captured.set(capture.head.subarray(0, capture.headLength));
167
+ captured.set(capture.tail.subarray(0, capture.tailLength), capture.headLength);
168
+ return captured;
169
+ }
94
170
  function result(request, status, stdout, stderr, stdoutBytes, stderrBytes, previewBytes, termination, code, cwd) {
95
- const stdoutPreview = sanitizeProcessTextV1(previewText(stdout.subarray(0, previewBytes)), previewBytes, cwd);
96
- const stderrPreview = sanitizeProcessTextV1(previewText(stderr.subarray(0, previewBytes)), previewBytes, cwd);
171
+ // The byte-level head/tail split is authoritative; sanitize only redacts and
172
+ // acts as the absolute-size circuit breaker at the contract cap.
173
+ const stdoutPreview = sanitizeProcessTextV1(buildHeadTailPreviewV1(stdout, previewBytes, stdoutBytes), PROCESS_EXEC_MAX_PREVIEW_BYTES_V1, cwd);
174
+ const stderrPreview = sanitizeProcessTextV1(buildHeadTailPreviewV1(stderr, previewBytes, stderrBytes), PROCESS_EXEC_MAX_PREVIEW_BYTES_V1, cwd);
97
175
  const errorCode = termination;
98
176
  const error = errorCode === undefined
99
177
  ? undefined
@@ -173,21 +251,14 @@ export function createNodeProcessExecAdapterV1(options) {
173
251
  }
174
252
  let stdoutBytes = 0;
175
253
  let stderrBytes = 0;
176
- const stdoutPreview = new Uint8Array(previewBytes);
177
- const stderrPreview = new Uint8Array(previewBytes);
178
- let stdoutPreviewLength = 0;
179
- let stderrPreviewLength = 0;
254
+ const stdoutCapture = createPreviewCaptureV1(previewBytes);
255
+ const stderrCapture = createPreviewCaptureV1(previewBytes);
180
256
  let termination;
181
257
  let settled = false;
182
258
  let exited = false;
183
259
  let terminationRequested = false;
184
260
  let forceKillTimer;
185
261
  let hardFinishTimer;
186
- const append = (target, length, chunk) => {
187
- const copy = Math.min(chunk.byteLength, target.byteLength - length);
188
- target.set(chunk.subarray(0, copy), length);
189
- return length + copy;
190
- };
191
262
  const forceTerminate = () => {
192
263
  if (exited)
193
264
  return;
@@ -251,7 +322,7 @@ export function createNodeProcessExecAdapterV1(options) {
251
322
  if (termination === undefined && code !== 0)
252
323
  termination = "exit_nonzero";
253
324
  const status = termination === undefined ? "succeeded" : termination === "cancelled" ? "cancelled" : "failed";
254
- resolve(result(request, status, stdoutPreview.subarray(0, stdoutPreviewLength), stderrPreview.subarray(0, stderrPreviewLength), stdoutBytes, stderrBytes, previewBytes, termination, code ?? undefined, options.cwd));
325
+ resolve(result(request, status, previewCaptureBytesV1(stdoutCapture, stdoutBytes), previewCaptureBytesV1(stderrCapture, stderrBytes), stdoutBytes, stderrBytes, previewBytes, termination, code ?? undefined, options.cwd));
255
326
  };
256
327
  const timeout = setTimeout(() => terminate("timeout"), timeoutMs);
257
328
  if (signal !== undefined)
@@ -259,14 +330,14 @@ export function createNodeProcessExecAdapterV1(options) {
259
330
  child.stdout?.on("data", (chunk) => {
260
331
  const bytes = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);
261
332
  stdoutBytes += bytes.byteLength;
262
- stdoutPreviewLength = append(stdoutPreview, stdoutPreviewLength, bytes);
333
+ capturePreviewChunkV1(stdoutCapture, bytes);
263
334
  if (stdoutBytes > maxStdoutBytes)
264
335
  terminate("output_budget_exceeded");
265
336
  });
266
337
  child.stderr?.on("data", (chunk) => {
267
338
  const bytes = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);
268
339
  stderrBytes += bytes.byteLength;
269
- stderrPreviewLength = append(stderrPreview, stderrPreviewLength, bytes);
340
+ capturePreviewChunkV1(stderrCapture, bytes);
270
341
  if (stderrBytes > maxStderrBytes)
271
342
  terminate("output_budget_exceeded");
272
343
  });
@@ -1 +1 @@
1
- {"version":3,"file":"node-adapter.js","sourceRoot":"","sources":["../src/node-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { isAbsolute } from \"node:path\";\nimport { killProcessTree, sanitizeBinaryOutput } from \"./shell-utils.ts\";\nimport {\n\tPROCESS_EXEC_HOST_ADAPTER_ID_V1,\n\tPROCESS_EXEC_HOST_ADAPTER_VERSION_V1,\n\ttype ProcessExecAdapterV1,\n\ttype ProcessExecRequestV1,\n\ttype ProcessExecResultV1,\n} from \"./types.ts\";\n\nconst DEFAULT_TIMEOUT_MS_V1 = 30_000;\nconst MAX_TIMEOUT_MS_V1 = 120_000;\nconst DEFAULT_OUTPUT_BYTES_V1 = 64 * 1024;\nexport const PROCESS_EXEC_MAX_OUTPUT_BYTES_V1 = 1024 * 1024;\nconst DEFAULT_PREVIEW_BYTES_V1 = 4096;\nexport const PROCESS_EXEC_MAX_PREVIEW_BYTES_V1 = 8192;\nconst MAX_ARGS_V1 = 256;\nconst MAX_COMMAND_CHARS_V1 = 512;\nconst MAX_ARG_CHARS_V1 = 4096;\nexport const PROCESS_EXEC_MAX_OPERATION_ID_CHARS_V1 = 256;\n\nexport interface NodeProcessExecAdapterOptionsV1 {\n\t/** Host-owned working directory; requests cannot override it. */\n\treadonly cwd: string;\n\treadonly env?: Readonly<Record<string, string>>;\n\treadonly defaultTimeoutMs?: number;\n\treadonly maxStdoutBytes?: number;\n\treadonly maxStderrBytes?: number;\n\treadonly previewBytes?: number;\n\t/** Test seam for the host-owned process-tree terminator. */\n\treadonly killProcessTree?: (pid: number) => void;\n\t/** Deterministic host seam; production defaults to Node's shell-free spawn. */\n\treadonly spawn?: typeof spawn;\n}\n\nconst SAFE_ENVIRONMENT_KEYS_V1 = new Set(\n\t[\"PATH\", \"PATHEXT\", \"SystemRoot\", \"SYSTEMROOT\", \"WINDIR\", \"TEMP\", \"TMP\", \"TMPDIR\", \"LANG\", \"LC_ALL\"].map((key) =>\n\t\tkey.toLowerCase(),\n\t),\n);\n\n/** Redacts credentials and host paths before a preview can reach the model. */\nexport function sanitizeProcessTextV1(\n\tvalue: string,\n\tmaximumChars = PROCESS_EXEC_MAX_PREVIEW_BYTES_V1,\n\tcwd?: string,\n): string {\n\tlet text = sanitizeBinaryOutput(value);\n\tif (cwd !== undefined && cwd !== \"\") text = text.replaceAll(cwd, \"[redacted-path]\");\n\ttext = text\n\t\t.replace(\n\t\t\t/((?:api[-_ ]?key|access[-_ ]?token|auth(?:orization)?|bearer|password|passwd|secret|token)\\s*[:=]\\s*)([^\\s,;]+)/giu,\n\t\t\t\"$1[redacted-secret]\",\n\t\t)\n\t\t.replace(/(?:[A-Za-z]:[\\\\/]|\\\\\\\\|\\/(?:home|Users|private|tmp|var|workspace)\\/)[^\\s\"'<>]*/gu, \"[redacted-path]\");\n\treturn text.length <= maximumChars ? text : text.slice(0, maximumChars);\n}\n\nfunction hostEnvironment(explicit: Readonly<Record<string, string>> | undefined): NodeJS.ProcessEnv {\n\tconst inherited: NodeJS.ProcessEnv = {};\n\tfor (const [key, value] of Object.entries(process.env)) {\n\t\tif (value !== undefined && SAFE_ENVIRONMENT_KEYS_V1.has(key.toLowerCase())) inherited[key] = value;\n\t}\n\treturn { ...inherited, ...(explicit ?? {}) };\n}\n\nfunction positiveBound(value: number | undefined, fallback: number, maximum: number, label: string): number {\n\tconst result = value ?? fallback;\n\tif (!Number.isSafeInteger(result) || result < 1 || result > maximum)\n\t\tthrow new Error(`${label} must be a positive integer at most ${maximum}`);\n\treturn result;\n}\n\nfunction inputError(operationId: string, message: string): ProcessExecResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"failed\",\n\t\tsummary: \"Process request was invalid\",\n\t\tstdoutBytes: 0,\n\t\tstderrBytes: 0,\n\t\tstdoutPreview: \"\",\n\t\tstderrPreview: \"\",\n\t\tstdoutTruncated: false,\n\t\tstderrTruncated: false,\n\t\terror: { version: 1, code: \"invalid_input\", message },\n\t};\n}\n\nfunction cancelled(operationId: string): ProcessExecResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"cancelled\",\n\t\tsummary: \"Process execution was cancelled\",\n\t\tstdoutBytes: 0,\n\t\tstderrBytes: 0,\n\t\tstdoutPreview: \"\",\n\t\tstderrPreview: \"\",\n\t\tstdoutTruncated: false,\n\t\tstderrTruncated: false,\n\t\terror: { version: 1, code: \"cancelled\", message: \"Process execution was cancelled\" },\n\t};\n}\n\nfunction validateRequest(request: ProcessExecRequestV1): string | undefined {\n\tif (request.version !== 1) return \"version must be 1\";\n\tif (\n\t\ttypeof request.operationId !== \"string\" ||\n\t\trequest.operationId.trim() === \"\" ||\n\t\trequest.operationId.length > PROCESS_EXEC_MAX_OPERATION_ID_CHARS_V1\n\t)\n\t\treturn \"operationId is invalid\";\n\tif (\n\t\ttypeof request.command !== \"string\" ||\n\t\trequest.command.trim() === \"\" ||\n\t\trequest.command.length > MAX_COMMAND_CHARS_V1 ||\n\t\t/[\\u0000\\r\\n]/u.test(request.command)\n\t)\n\t\treturn \"command is invalid\";\n\tif (!Array.isArray(request.args) || request.args.length > MAX_ARGS_V1) return \"args are invalid\";\n\tif (\n\t\trequest.args.some((arg) => typeof arg !== \"string\" || arg.length > MAX_ARG_CHARS_V1 || /[\\u0000\\r\\n]/u.test(arg))\n\t)\n\t\treturn \"args are invalid\";\n\tif (\n\t\trequest.timeoutMs !== undefined &&\n\t\t(!Number.isSafeInteger(request.timeoutMs) || request.timeoutMs < 1 || request.timeoutMs > MAX_TIMEOUT_MS_V1)\n\t)\n\t\treturn `timeoutMs must be a positive integer at most ${MAX_TIMEOUT_MS_V1}`;\n\treturn undefined;\n}\n\nfunction previewText(bytes: Uint8Array): string {\n\treturn new TextDecoder().decode(bytes);\n}\n\nfunction result(\n\trequest: ProcessExecRequestV1,\n\tstatus: ProcessExecResultV1[\"status\"],\n\tstdout: Uint8Array,\n\tstderr: Uint8Array,\n\tstdoutBytes: number,\n\tstderrBytes: number,\n\tpreviewBytes: number,\n\ttermination?: \"timeout\" | \"output_budget_exceeded\" | \"spawn_failed\" | \"exit_nonzero\" | \"cancelled\",\n\tcode?: number,\n\tcwd?: string,\n): ProcessExecResultV1 {\n\tconst stdoutPreview = sanitizeProcessTextV1(previewText(stdout.subarray(0, previewBytes)), previewBytes, cwd);\n\tconst stderrPreview = sanitizeProcessTextV1(previewText(stderr.subarray(0, previewBytes)), previewBytes, cwd);\n\tconst errorCode = termination;\n\tconst error =\n\t\terrorCode === undefined\n\t\t\t? undefined\n\t\t\t: {\n\t\t\t\t\tversion: 1 as const,\n\t\t\t\t\tcode: errorCode,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\terrorCode === \"exit_nonzero\"\n\t\t\t\t\t\t\t? \"Process exited with a non-zero status\"\n\t\t\t\t\t\t\t: errorCode === \"cancelled\"\n\t\t\t\t\t\t\t\t? \"Process execution was cancelled\"\n\t\t\t\t\t\t\t\t: \"Process execution failed\",\n\t\t\t\t};\n\treturn {\n\t\tversion: 1,\n\t\toperationId: request.operationId,\n\t\tstatus,\n\t\tsummary:\n\t\t\tstatus === \"succeeded\"\n\t\t\t\t? \"Process completed successfully\"\n\t\t\t\t: termination === \"cancelled\"\n\t\t\t\t\t? \"Process execution was cancelled\"\n\t\t\t\t\t: termination === \"timeout\"\n\t\t\t\t\t\t? \"Process execution timed out\"\n\t\t\t\t\t\t: termination === \"output_budget_exceeded\"\n\t\t\t\t\t\t\t? \"Process output exceeded the host budget\"\n\t\t\t\t\t\t\t: termination === \"spawn_failed\"\n\t\t\t\t\t\t\t\t? \"Process could not be started\"\n\t\t\t\t\t\t\t\t: \"Process exited with a non-zero status\",\n\t\t...(code === undefined ? {} : { exitCode: code }),\n\t\tstdoutBytes,\n\t\tstderrBytes,\n\t\tstdoutPreview,\n\t\tstderrPreview,\n\t\tstdoutTruncated: stdoutBytes > previewBytes,\n\t\tstderrTruncated: stderrBytes > previewBytes,\n\t\t...(error === undefined ? {} : { error }),\n\t};\n}\n\n/** Node host adapter with shell-free argv execution and bounded output previews. */\nexport function createNodeProcessExecAdapterV1(options: NodeProcessExecAdapterOptionsV1): ProcessExecAdapterV1 {\n\tif (typeof options.cwd !== \"string\" || !isAbsolute(options.cwd)) throw new Error(\"cwd must be an absolute path\");\n\tconst defaultTimeoutMs = positiveBound(\n\t\toptions.defaultTimeoutMs,\n\t\tDEFAULT_TIMEOUT_MS_V1,\n\t\tMAX_TIMEOUT_MS_V1,\n\t\t\"defaultTimeoutMs\",\n\t);\n\tconst maxStdoutBytes = positiveBound(\n\t\toptions.maxStdoutBytes,\n\t\tDEFAULT_OUTPUT_BYTES_V1,\n\t\tPROCESS_EXEC_MAX_OUTPUT_BYTES_V1,\n\t\t\"maxStdoutBytes\",\n\t);\n\tconst maxStderrBytes = positiveBound(\n\t\toptions.maxStderrBytes,\n\t\tDEFAULT_OUTPUT_BYTES_V1,\n\t\tPROCESS_EXEC_MAX_OUTPUT_BYTES_V1,\n\t\t\"maxStderrBytes\",\n\t);\n\tconst previewBytes = positiveBound(\n\t\toptions.previewBytes,\n\t\tDEFAULT_PREVIEW_BYTES_V1,\n\t\tPROCESS_EXEC_MAX_PREVIEW_BYTES_V1,\n\t\t\"previewBytes\",\n\t);\n\tconst env = hostEnvironment(options.env);\n\tconst spawnProcess = options.spawn ?? spawn;\n\tconst terminateTree = options.killProcessTree ?? killProcessTree;\n\n\treturn {\n\t\tadapterId: PROCESS_EXEC_HOST_ADAPTER_ID_V1,\n\t\tadapterVersion: PROCESS_EXEC_HOST_ADAPTER_VERSION_V1,\n\t\trun: (request, signal) => {\n\t\t\tconst operationId =\n\t\t\t\ttypeof request.operationId === \"string\" && request.operationId.trim() !== \"\"\n\t\t\t\t\t? request.operationId\n\t\t\t\t\t: \"invalid-process-operation\";\n\t\t\tconst invalid = validateRequest(request);\n\t\t\tif (invalid !== undefined) return Promise.resolve(inputError(operationId, invalid));\n\t\t\tif (signal?.aborted) return Promise.resolve(cancelled(operationId));\n\t\t\tconst timeoutMs = request.timeoutMs ?? defaultTimeoutMs;\n\t\t\treturn new Promise<ProcessExecResultV1>((resolve) => {\n\t\t\t\tlet child: ReturnType<typeof spawn>;\n\t\t\t\ttry {\n\t\t\t\t\tchild = spawnProcess(request.command, [...request.args], {\n\t\t\t\t\t\tcwd: options.cwd,\n\t\t\t\t\t\t...(env === undefined ? {} : { env }),\n\t\t\t\t\t\tshell: false,\n\t\t\t\t\t\twindowsHide: true,\n\t\t\t\t\t\tdetached: process.platform !== \"win32\",\n\t\t\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\t\t});\n\t\t\t\t} catch {\n\t\t\t\t\tresolve(\n\t\t\t\t\t\tresult(\n\t\t\t\t\t\t\trequest,\n\t\t\t\t\t\t\t\"failed\",\n\t\t\t\t\t\t\tnew Uint8Array(),\n\t\t\t\t\t\t\tnew Uint8Array(),\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\tpreviewBytes,\n\t\t\t\t\t\t\t\"spawn_failed\",\n\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\toptions.cwd,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet stdoutBytes = 0;\n\t\t\t\tlet stderrBytes = 0;\n\t\t\t\tconst stdoutPreview = new Uint8Array(previewBytes);\n\t\t\t\tconst stderrPreview = new Uint8Array(previewBytes);\n\t\t\t\tlet stdoutPreviewLength = 0;\n\t\t\t\tlet stderrPreviewLength = 0;\n\t\t\t\tlet termination:\n\t\t\t\t\t| \"timeout\"\n\t\t\t\t\t| \"output_budget_exceeded\"\n\t\t\t\t\t| \"spawn_failed\"\n\t\t\t\t\t| \"exit_nonzero\"\n\t\t\t\t\t| \"cancelled\"\n\t\t\t\t\t| undefined;\n\t\t\t\tlet settled = false;\n\t\t\t\tlet exited = false;\n\t\t\t\tlet terminationRequested = false;\n\t\t\t\tlet forceKillTimer: ReturnType<typeof setTimeout> | undefined;\n\t\t\t\tlet hardFinishTimer: ReturnType<typeof setTimeout> | undefined;\n\t\t\t\tconst append = (target: Uint8Array, length: number, chunk: Uint8Array): number => {\n\t\t\t\t\tconst copy = Math.min(chunk.byteLength, target.byteLength - length);\n\t\t\t\t\ttarget.set(chunk.subarray(0, copy), length);\n\t\t\t\t\treturn length + copy;\n\t\t\t\t};\n\t\t\t\tconst forceTerminate = (): void => {\n\t\t\t\t\tif (exited) return;\n\t\t\t\t\tif (typeof child.pid === \"number\" && child.pid > 0) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tterminateTree(child.pid);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// The host cleanup boundary is best-effort; close/error still settles the operation.\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (!child.killed) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tchild.kill(\"SIGKILL\");\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// The close/error event remains authoritative.\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tconst armForceTermination = (): void => {\n\t\t\t\t\tif (forceKillTimer !== undefined) return;\n\t\t\t\t\tforceKillTimer = setTimeout(() => {\n\t\t\t\t\t\tforceTerminate();\n\t\t\t\t\t\thardFinishTimer = setTimeout(() => finalize(null), 500);\n\t\t\t\t\t}, 2000);\n\t\t\t\t};\n\t\t\t\tconst terminate = (reason: \"timeout\" | \"output_budget_exceeded\" | \"spawn_failed\" | \"cancelled\"): void => {\n\t\t\t\t\tif (termination === undefined) termination = reason;\n\t\t\t\t\tif (!terminationRequested) {\n\t\t\t\t\t\tterminationRequested = true;\n\t\t\t\t\t\tif (!child.killed) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tchild.kill(\"SIGTERM\");\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t// The force cleanup timer still runs.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tarmForceTermination();\n\t\t\t\t};\n\t\t\t\tconst onAbort = () => {\n\t\t\t\t\tterminate(\"cancelled\");\n\t\t\t\t};\n\t\t\t\tconst finalize = (code: number | null): void => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\texited = true;\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\tif (forceKillTimer !== undefined) clearTimeout(forceKillTimer);\n\t\t\t\t\tif (hardFinishTimer !== undefined) clearTimeout(hardFinishTimer);\n\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\tif (signal?.aborted && termination === undefined) termination = \"cancelled\";\n\t\t\t\t\tif (termination === undefined && code !== 0) termination = \"exit_nonzero\";\n\t\t\t\t\tconst status =\n\t\t\t\t\t\ttermination === undefined ? \"succeeded\" : termination === \"cancelled\" ? \"cancelled\" : \"failed\";\n\t\t\t\t\tresolve(\n\t\t\t\t\t\tresult(\n\t\t\t\t\t\t\trequest,\n\t\t\t\t\t\t\tstatus,\n\t\t\t\t\t\t\tstdoutPreview.subarray(0, stdoutPreviewLength),\n\t\t\t\t\t\t\tstderrPreview.subarray(0, stderrPreviewLength),\n\t\t\t\t\t\t\tstdoutBytes,\n\t\t\t\t\t\t\tstderrBytes,\n\t\t\t\t\t\t\tpreviewBytes,\n\t\t\t\t\t\t\ttermination,\n\t\t\t\t\t\t\tcode ?? undefined,\n\t\t\t\t\t\t\toptions.cwd,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t};\n\t\t\t\tconst timeout = setTimeout(() => terminate(\"timeout\"), timeoutMs);\n\t\t\t\tif (signal !== undefined) signal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\tchild.stdout?.on(\"data\", (chunk: Buffer | Uint8Array) => {\n\t\t\t\t\tconst bytes = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);\n\t\t\t\t\tstdoutBytes += bytes.byteLength;\n\t\t\t\t\tstdoutPreviewLength = append(stdoutPreview, stdoutPreviewLength, bytes);\n\t\t\t\t\tif (stdoutBytes > maxStdoutBytes) terminate(\"output_budget_exceeded\");\n\t\t\t\t});\n\t\t\t\tchild.stderr?.on(\"data\", (chunk: Buffer | Uint8Array) => {\n\t\t\t\t\tconst bytes = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);\n\t\t\t\t\tstderrBytes += bytes.byteLength;\n\t\t\t\t\tstderrPreviewLength = append(stderrPreview, stderrPreviewLength, bytes);\n\t\t\t\t\tif (stderrBytes > maxStderrBytes) terminate(\"output_budget_exceeded\");\n\t\t\t\t});\n\t\t\t\tchild.once(\"error\", () => {\n\t\t\t\t\tterminate(\"spawn_failed\");\n\t\t\t\t});\n\t\t\t\tchild.once(\"close\", (code) => {\n\t\t\t\t\texited = true;\n\t\t\t\t\tfinalize(code);\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"node-adapter.js","sourceRoot":"","sources":["../src/node-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { isAbsolute } from \"node:path\";\nimport { killProcessTree, sanitizeBinaryOutput } from \"./shell-utils.ts\";\nimport {\n\tPROCESS_EXEC_HOST_ADAPTER_ID_V1,\n\tPROCESS_EXEC_HOST_ADAPTER_VERSION_V1,\n\ttype ProcessExecAdapterV1,\n\ttype ProcessExecRequestV1,\n\ttype ProcessExecResultV1,\n} from \"./types.ts\";\n\nconst DEFAULT_TIMEOUT_MS_V1 = 30_000;\nconst MAX_TIMEOUT_MS_V1 = 120_000;\nconst DEFAULT_OUTPUT_BYTES_V1 = 64 * 1024;\nexport const PROCESS_EXEC_MAX_OUTPUT_BYTES_V1 = 1024 * 1024;\nconst DEFAULT_PREVIEW_BYTES_V1 = 4096;\nexport const PROCESS_EXEC_MAX_PREVIEW_BYTES_V1 = 8192;\nconst MAX_ARGS_V1 = 256;\nconst MAX_COMMAND_CHARS_V1 = 512;\nconst MAX_ARG_CHARS_V1 = 4096;\nexport const PROCESS_EXEC_MAX_OPERATION_ID_CHARS_V1 = 256;\n\nexport interface NodeProcessExecAdapterOptionsV1 {\n\t/** Host-owned working directory; requests cannot override it. */\n\treadonly cwd: string;\n\treadonly env?: Readonly<Record<string, string>>;\n\treadonly defaultTimeoutMs?: number;\n\treadonly maxStdoutBytes?: number;\n\treadonly maxStderrBytes?: number;\n\treadonly previewBytes?: number;\n\t/** Test seam for the host-owned process-tree terminator. */\n\treadonly killProcessTree?: (pid: number) => void;\n\t/** Deterministic host seam; production defaults to Node's shell-free spawn. */\n\treadonly spawn?: typeof spawn;\n}\n\nconst SAFE_ENVIRONMENT_KEYS_V1 = new Set(\n\t[\"PATH\", \"PATHEXT\", \"SystemRoot\", \"SYSTEMROOT\", \"WINDIR\", \"TEMP\", \"TMP\", \"TMPDIR\", \"LANG\", \"LC_ALL\"].map((key) =>\n\t\tkey.toLowerCase(),\n\t),\n);\n\n/** Splits a preview budget into head/tail segments (75% head, rest tail; head wins on tiny budgets). */\nfunction splitPreviewBudgetV1(budget: number): { head: number; tail: number } {\n\tconst head = Math.ceil((budget * 3) / 4);\n\treturn { head, tail: Math.max(0, budget - head) };\n}\n\nfunction omissionMarkerV1(omitted: number, unit: \"bytes\" | \"chars\"): string {\n\treturn `\\n... [${omitted} ${unit} omitted] ...\\n`;\n}\n\n/**\n * Builds a bounded preview from the retained capture. `captured` holds the\n * complete output when `totalBytes <= previewBytes`; otherwise it holds the\n * first `head` bytes followed by the last `tail` bytes of the stream, and the\n * two segments are joined with an omission marker. The marker rides on top of\n * the segment budgets but the assembled preview never exceeds\n * `PROCESS_EXEC_MAX_PREVIEW_BYTES_V1`.\n */\nexport function buildHeadTailPreviewV1(captured: Uint8Array, previewBytes: number, totalBytes: number): string {\n\tif (totalBytes <= previewBytes) return previewText(captured);\n\tconst budget = splitPreviewBudgetV1(previewBytes);\n\tconst headPart = captured.subarray(0, Math.min(budget.head, captured.byteLength));\n\tlet tailBytes = Math.min(budget.tail, Math.max(0, captured.byteLength - headPart.byteLength));\n\tlet marker = omissionMarkerV1(Math.max(0, totalBytes - headPart.byteLength - tailBytes), \"bytes\");\n\twhile (headPart.byteLength + tailBytes + marker.length > PROCESS_EXEC_MAX_PREVIEW_BYTES_V1 && tailBytes > 0) {\n\t\ttailBytes = Math.max(\n\t\t\t0,\n\t\t\tMath.min(tailBytes, PROCESS_EXEC_MAX_PREVIEW_BYTES_V1 - headPart.byteLength - marker.length),\n\t\t);\n\t\tmarker = omissionMarkerV1(Math.max(0, totalBytes - headPart.byteLength - tailBytes), \"bytes\");\n\t}\n\treturn previewText(headPart) + marker + previewText(captured.subarray(captured.byteLength - tailBytes));\n}\n\n/** Redacts credentials and host paths before a preview can reach the model. */\nexport function sanitizeProcessTextV1(\n\tvalue: string,\n\tmaximumChars = PROCESS_EXEC_MAX_PREVIEW_BYTES_V1,\n\tcwd?: string,\n): string {\n\tlet text = sanitizeBinaryOutput(value);\n\tif (cwd !== undefined && cwd !== \"\") text = text.replaceAll(cwd, \"[redacted-path]\");\n\ttext = text\n\t\t.replace(\n\t\t\t/((?:api[-_ ]?key|access[-_ ]?token|auth(?:orization)?|bearer|password|passwd|secret|token)\\s*[:=]\\s*)([^\\s,;]+)/giu,\n\t\t\t\"$1[redacted-secret]\",\n\t\t)\n\t\t.replace(/(?:[A-Za-z]:[\\\\/]|\\\\\\\\|\\/(?:home|Users|private|tmp|var|workspace)\\/)[^\\s\"'<>]*/gu, \"[redacted-path]\");\n\tif (text.length <= maximumChars) return text;\n\tconst head = Math.ceil((maximumChars * 3) / 4);\n\tlet tail = maximumChars - head;\n\tlet marker = omissionMarkerV1(text.length - head - tail, \"chars\");\n\twhile (head + tail + marker.length > maximumChars && tail > 0) {\n\t\ttail = Math.max(0, Math.min(tail, maximumChars - head - marker.length));\n\t\tmarker = omissionMarkerV1(text.length - head - tail, \"chars\");\n\t}\n\treturn text.slice(0, head) + marker + (tail > 0 ? text.slice(text.length - tail) : \"\");\n}\n\nfunction hostEnvironment(explicit: Readonly<Record<string, string>> | undefined): NodeJS.ProcessEnv {\n\tconst inherited: NodeJS.ProcessEnv = {};\n\tfor (const [key, value] of Object.entries(process.env)) {\n\t\tif (value !== undefined && SAFE_ENVIRONMENT_KEYS_V1.has(key.toLowerCase())) inherited[key] = value;\n\t}\n\treturn { ...inherited, ...(explicit ?? {}) };\n}\n\nfunction positiveBound(value: number | undefined, fallback: number, maximum: number, label: string): number {\n\tconst result = value ?? fallback;\n\tif (!Number.isSafeInteger(result) || result < 1 || result > maximum)\n\t\tthrow new Error(`${label} must be a positive integer at most ${maximum}`);\n\treturn result;\n}\n\nfunction inputError(operationId: string, message: string): ProcessExecResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"failed\",\n\t\tsummary: \"Process request was invalid\",\n\t\tstdoutBytes: 0,\n\t\tstderrBytes: 0,\n\t\tstdoutPreview: \"\",\n\t\tstderrPreview: \"\",\n\t\tstdoutTruncated: false,\n\t\tstderrTruncated: false,\n\t\terror: { version: 1, code: \"invalid_input\", message },\n\t};\n}\n\nfunction cancelled(operationId: string): ProcessExecResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"cancelled\",\n\t\tsummary: \"Process execution was cancelled\",\n\t\tstdoutBytes: 0,\n\t\tstderrBytes: 0,\n\t\tstdoutPreview: \"\",\n\t\tstderrPreview: \"\",\n\t\tstdoutTruncated: false,\n\t\tstderrTruncated: false,\n\t\terror: { version: 1, code: \"cancelled\", message: \"Process execution was cancelled\" },\n\t};\n}\n\nfunction validateRequest(request: ProcessExecRequestV1): string | undefined {\n\tif (request.version !== 1) return \"version must be 1\";\n\tif (\n\t\ttypeof request.operationId !== \"string\" ||\n\t\trequest.operationId.trim() === \"\" ||\n\t\trequest.operationId.length > PROCESS_EXEC_MAX_OPERATION_ID_CHARS_V1\n\t)\n\t\treturn \"operationId is invalid\";\n\tif (\n\t\ttypeof request.command !== \"string\" ||\n\t\trequest.command.trim() === \"\" ||\n\t\trequest.command.length > MAX_COMMAND_CHARS_V1 ||\n\t\t/[\\u0000\\r\\n]/u.test(request.command)\n\t)\n\t\treturn \"command is invalid\";\n\tif (!Array.isArray(request.args) || request.args.length > MAX_ARGS_V1) return \"args are invalid\";\n\tif (\n\t\trequest.args.some((arg) => typeof arg !== \"string\" || arg.length > MAX_ARG_CHARS_V1 || /[\\u0000\\r\\n]/u.test(arg))\n\t)\n\t\treturn \"args are invalid\";\n\tif (\n\t\trequest.timeoutMs !== undefined &&\n\t\t(!Number.isSafeInteger(request.timeoutMs) || request.timeoutMs < 1 || request.timeoutMs > MAX_TIMEOUT_MS_V1)\n\t)\n\t\treturn `timeoutMs must be a positive integer at most ${MAX_TIMEOUT_MS_V1}`;\n\treturn undefined;\n}\n\nfunction previewText(bytes: Uint8Array): string {\n\treturn new TextDecoder().decode(bytes);\n}\n\ninterface PreviewCaptureV1 {\n\treadonly head: Uint8Array;\n\treadonly tail: Uint8Array;\n\theadLength: number;\n\ttailLength: number;\n}\n\n/** Retains the first head-budget and last tail-budget bytes of a stream for head+tail previews. */\nfunction createPreviewCaptureV1(previewBytes: number): PreviewCaptureV1 {\n\tconst budget = splitPreviewBudgetV1(previewBytes);\n\treturn { head: new Uint8Array(budget.head), tail: new Uint8Array(budget.tail), headLength: 0, tailLength: 0 };\n}\n\nfunction capturePreviewChunkV1(capture: PreviewCaptureV1, chunk: Uint8Array): void {\n\tconst headCopy = Math.min(chunk.byteLength, capture.head.byteLength - capture.headLength);\n\tcapture.head.set(chunk.subarray(0, headCopy), capture.headLength);\n\tcapture.headLength += headCopy;\n\tconst tailSize = capture.tail.byteLength;\n\tif (tailSize === 0) return;\n\tif (chunk.byteLength >= tailSize) {\n\t\tcapture.tail.set(chunk.subarray(chunk.byteLength - tailSize));\n\t\tcapture.tailLength = tailSize;\n\t\treturn;\n\t}\n\tcapture.tail.copyWithin(0, chunk.byteLength);\n\tcapture.tail.set(chunk, tailSize - chunk.byteLength);\n\tcapture.tailLength = Math.min(tailSize, capture.tailLength + chunk.byteLength);\n}\n\n/** Materializes the retained bytes: the full output when it fit the budget, else head followed by tail. */\nfunction previewCaptureBytesV1(capture: PreviewCaptureV1, totalBytes: number): Uint8Array {\n\tconst capacity = capture.head.byteLength + capture.tail.byteLength;\n\tif (totalBytes <= capture.head.byteLength) return capture.head.subarray(0, totalBytes);\n\tif (totalBytes <= capacity) {\n\t\tconst tailFrom = totalBytes - capture.head.byteLength;\n\t\tconst captured = new Uint8Array(totalBytes);\n\t\tcaptured.set(capture.head);\n\t\tcaptured.set(capture.tail.subarray(capture.tailLength - tailFrom, capture.tailLength), capture.headLength);\n\t\treturn captured;\n\t}\n\tconst captured = new Uint8Array(capacity);\n\tcaptured.set(capture.head.subarray(0, capture.headLength));\n\tcaptured.set(capture.tail.subarray(0, capture.tailLength), capture.headLength);\n\treturn captured;\n}\n\nfunction result(\n\trequest: ProcessExecRequestV1,\n\tstatus: ProcessExecResultV1[\"status\"],\n\tstdout: Uint8Array,\n\tstderr: Uint8Array,\n\tstdoutBytes: number,\n\tstderrBytes: number,\n\tpreviewBytes: number,\n\ttermination?: \"timeout\" | \"output_budget_exceeded\" | \"spawn_failed\" | \"exit_nonzero\" | \"cancelled\",\n\tcode?: number,\n\tcwd?: string,\n): ProcessExecResultV1 {\n\t// The byte-level head/tail split is authoritative; sanitize only redacts and\n\t// acts as the absolute-size circuit breaker at the contract cap.\n\tconst stdoutPreview = sanitizeProcessTextV1(\n\t\tbuildHeadTailPreviewV1(stdout, previewBytes, stdoutBytes),\n\t\tPROCESS_EXEC_MAX_PREVIEW_BYTES_V1,\n\t\tcwd,\n\t);\n\tconst stderrPreview = sanitizeProcessTextV1(\n\t\tbuildHeadTailPreviewV1(stderr, previewBytes, stderrBytes),\n\t\tPROCESS_EXEC_MAX_PREVIEW_BYTES_V1,\n\t\tcwd,\n\t);\n\tconst errorCode = termination;\n\tconst error =\n\t\terrorCode === undefined\n\t\t\t? undefined\n\t\t\t: {\n\t\t\t\t\tversion: 1 as const,\n\t\t\t\t\tcode: errorCode,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\terrorCode === \"exit_nonzero\"\n\t\t\t\t\t\t\t? \"Process exited with a non-zero status\"\n\t\t\t\t\t\t\t: errorCode === \"cancelled\"\n\t\t\t\t\t\t\t\t? \"Process execution was cancelled\"\n\t\t\t\t\t\t\t\t: \"Process execution failed\",\n\t\t\t\t};\n\treturn {\n\t\tversion: 1,\n\t\toperationId: request.operationId,\n\t\tstatus,\n\t\tsummary:\n\t\t\tstatus === \"succeeded\"\n\t\t\t\t? \"Process completed successfully\"\n\t\t\t\t: termination === \"cancelled\"\n\t\t\t\t\t? \"Process execution was cancelled\"\n\t\t\t\t\t: termination === \"timeout\"\n\t\t\t\t\t\t? \"Process execution timed out\"\n\t\t\t\t\t\t: termination === \"output_budget_exceeded\"\n\t\t\t\t\t\t\t? \"Process output exceeded the host budget\"\n\t\t\t\t\t\t\t: termination === \"spawn_failed\"\n\t\t\t\t\t\t\t\t? \"Process could not be started\"\n\t\t\t\t\t\t\t\t: \"Process exited with a non-zero status\",\n\t\t...(code === undefined ? {} : { exitCode: code }),\n\t\tstdoutBytes,\n\t\tstderrBytes,\n\t\tstdoutPreview,\n\t\tstderrPreview,\n\t\tstdoutTruncated: stdoutBytes > previewBytes,\n\t\tstderrTruncated: stderrBytes > previewBytes,\n\t\t...(error === undefined ? {} : { error }),\n\t};\n}\n\n/** Node host adapter with shell-free argv execution and bounded output previews. */\nexport function createNodeProcessExecAdapterV1(options: NodeProcessExecAdapterOptionsV1): ProcessExecAdapterV1 {\n\tif (typeof options.cwd !== \"string\" || !isAbsolute(options.cwd)) throw new Error(\"cwd must be an absolute path\");\n\tconst defaultTimeoutMs = positiveBound(\n\t\toptions.defaultTimeoutMs,\n\t\tDEFAULT_TIMEOUT_MS_V1,\n\t\tMAX_TIMEOUT_MS_V1,\n\t\t\"defaultTimeoutMs\",\n\t);\n\tconst maxStdoutBytes = positiveBound(\n\t\toptions.maxStdoutBytes,\n\t\tDEFAULT_OUTPUT_BYTES_V1,\n\t\tPROCESS_EXEC_MAX_OUTPUT_BYTES_V1,\n\t\t\"maxStdoutBytes\",\n\t);\n\tconst maxStderrBytes = positiveBound(\n\t\toptions.maxStderrBytes,\n\t\tDEFAULT_OUTPUT_BYTES_V1,\n\t\tPROCESS_EXEC_MAX_OUTPUT_BYTES_V1,\n\t\t\"maxStderrBytes\",\n\t);\n\tconst previewBytes = positiveBound(\n\t\toptions.previewBytes,\n\t\tDEFAULT_PREVIEW_BYTES_V1,\n\t\tPROCESS_EXEC_MAX_PREVIEW_BYTES_V1,\n\t\t\"previewBytes\",\n\t);\n\tconst env = hostEnvironment(options.env);\n\tconst spawnProcess = options.spawn ?? spawn;\n\tconst terminateTree = options.killProcessTree ?? killProcessTree;\n\n\treturn {\n\t\tadapterId: PROCESS_EXEC_HOST_ADAPTER_ID_V1,\n\t\tadapterVersion: PROCESS_EXEC_HOST_ADAPTER_VERSION_V1,\n\t\trun: (request, signal) => {\n\t\t\tconst operationId =\n\t\t\t\ttypeof request.operationId === \"string\" && request.operationId.trim() !== \"\"\n\t\t\t\t\t? request.operationId\n\t\t\t\t\t: \"invalid-process-operation\";\n\t\t\tconst invalid = validateRequest(request);\n\t\t\tif (invalid !== undefined) return Promise.resolve(inputError(operationId, invalid));\n\t\t\tif (signal?.aborted) return Promise.resolve(cancelled(operationId));\n\t\t\tconst timeoutMs = request.timeoutMs ?? defaultTimeoutMs;\n\t\t\treturn new Promise<ProcessExecResultV1>((resolve) => {\n\t\t\t\tlet child: ReturnType<typeof spawn>;\n\t\t\t\ttry {\n\t\t\t\t\tchild = spawnProcess(request.command, [...request.args], {\n\t\t\t\t\t\tcwd: options.cwd,\n\t\t\t\t\t\t...(env === undefined ? {} : { env }),\n\t\t\t\t\t\tshell: false,\n\t\t\t\t\t\twindowsHide: true,\n\t\t\t\t\t\tdetached: process.platform !== \"win32\",\n\t\t\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\t\t});\n\t\t\t\t} catch {\n\t\t\t\t\tresolve(\n\t\t\t\t\t\tresult(\n\t\t\t\t\t\t\trequest,\n\t\t\t\t\t\t\t\"failed\",\n\t\t\t\t\t\t\tnew Uint8Array(),\n\t\t\t\t\t\t\tnew Uint8Array(),\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\tpreviewBytes,\n\t\t\t\t\t\t\t\"spawn_failed\",\n\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\toptions.cwd,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet stdoutBytes = 0;\n\t\t\t\tlet stderrBytes = 0;\n\t\t\t\tconst stdoutCapture = createPreviewCaptureV1(previewBytes);\n\t\t\t\tconst stderrCapture = createPreviewCaptureV1(previewBytes);\n\t\t\t\tlet termination:\n\t\t\t\t\t| \"timeout\"\n\t\t\t\t\t| \"output_budget_exceeded\"\n\t\t\t\t\t| \"spawn_failed\"\n\t\t\t\t\t| \"exit_nonzero\"\n\t\t\t\t\t| \"cancelled\"\n\t\t\t\t\t| undefined;\n\t\t\t\tlet settled = false;\n\t\t\t\tlet exited = false;\n\t\t\t\tlet terminationRequested = false;\n\t\t\t\tlet forceKillTimer: ReturnType<typeof setTimeout> | undefined;\n\t\t\t\tlet hardFinishTimer: ReturnType<typeof setTimeout> | undefined;\n\t\t\t\tconst forceTerminate = (): void => {\n\t\t\t\t\tif (exited) return;\n\t\t\t\t\tif (typeof child.pid === \"number\" && child.pid > 0) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tterminateTree(child.pid);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// The host cleanup boundary is best-effort; close/error still settles the operation.\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (!child.killed) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tchild.kill(\"SIGKILL\");\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// The close/error event remains authoritative.\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tconst armForceTermination = (): void => {\n\t\t\t\t\tif (forceKillTimer !== undefined) return;\n\t\t\t\t\tforceKillTimer = setTimeout(() => {\n\t\t\t\t\t\tforceTerminate();\n\t\t\t\t\t\thardFinishTimer = setTimeout(() => finalize(null), 500);\n\t\t\t\t\t}, 2000);\n\t\t\t\t};\n\t\t\t\tconst terminate = (reason: \"timeout\" | \"output_budget_exceeded\" | \"spawn_failed\" | \"cancelled\"): void => {\n\t\t\t\t\tif (termination === undefined) termination = reason;\n\t\t\t\t\tif (!terminationRequested) {\n\t\t\t\t\t\tterminationRequested = true;\n\t\t\t\t\t\tif (!child.killed) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tchild.kill(\"SIGTERM\");\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t// The force cleanup timer still runs.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tarmForceTermination();\n\t\t\t\t};\n\t\t\t\tconst onAbort = () => {\n\t\t\t\t\tterminate(\"cancelled\");\n\t\t\t\t};\n\t\t\t\tconst finalize = (code: number | null): void => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\texited = true;\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\tif (forceKillTimer !== undefined) clearTimeout(forceKillTimer);\n\t\t\t\t\tif (hardFinishTimer !== undefined) clearTimeout(hardFinishTimer);\n\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\tif (signal?.aborted && termination === undefined) termination = \"cancelled\";\n\t\t\t\t\tif (termination === undefined && code !== 0) termination = \"exit_nonzero\";\n\t\t\t\t\tconst status =\n\t\t\t\t\t\ttermination === undefined ? \"succeeded\" : termination === \"cancelled\" ? \"cancelled\" : \"failed\";\n\t\t\t\t\tresolve(\n\t\t\t\t\t\tresult(\n\t\t\t\t\t\t\trequest,\n\t\t\t\t\t\t\tstatus,\n\t\t\t\t\t\t\tpreviewCaptureBytesV1(stdoutCapture, stdoutBytes),\n\t\t\t\t\t\t\tpreviewCaptureBytesV1(stderrCapture, stderrBytes),\n\t\t\t\t\t\t\tstdoutBytes,\n\t\t\t\t\t\t\tstderrBytes,\n\t\t\t\t\t\t\tpreviewBytes,\n\t\t\t\t\t\t\ttermination,\n\t\t\t\t\t\t\tcode ?? undefined,\n\t\t\t\t\t\t\toptions.cwd,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t};\n\t\t\t\tconst timeout = setTimeout(() => terminate(\"timeout\"), timeoutMs);\n\t\t\t\tif (signal !== undefined) signal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\tchild.stdout?.on(\"data\", (chunk: Buffer | Uint8Array) => {\n\t\t\t\t\tconst bytes = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);\n\t\t\t\t\tstdoutBytes += bytes.byteLength;\n\t\t\t\t\tcapturePreviewChunkV1(stdoutCapture, bytes);\n\t\t\t\t\tif (stdoutBytes > maxStdoutBytes) terminate(\"output_budget_exceeded\");\n\t\t\t\t});\n\t\t\t\tchild.stderr?.on(\"data\", (chunk: Buffer | Uint8Array) => {\n\t\t\t\t\tconst bytes = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);\n\t\t\t\t\tstderrBytes += bytes.byteLength;\n\t\t\t\t\tcapturePreviewChunkV1(stderrCapture, bytes);\n\t\t\t\t\tif (stderrBytes > maxStderrBytes) terminate(\"output_budget_exceeded\");\n\t\t\t\t});\n\t\t\t\tchild.once(\"error\", () => {\n\t\t\t\t\tterminate(\"spawn_failed\");\n\t\t\t\t});\n\t\t\t\tchild.once(\"close\", (code) => {\n\t\t\t\t\texited = true;\n\t\t\t\t\tfinalize(code);\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t};\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-agent-forge/plugin-process-exec",
3
- "version": "0.86.0",
3
+ "version": "0.86.1",
4
4
  "description": "Process execution capability plugin for Agent Forge (full-control process_exec tool)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -45,11 +45,11 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "typebox": "1.3.27",
48
- "@ai-agent-forge/plugin-sdk": "^0.86.0"
48
+ "@ai-agent-forge/plugin-sdk": "^0.86.1"
49
49
  },
50
50
  "devDependencies": {
51
51
  "shx": "0.4.0",
52
52
  "vitest": "4.1.9",
53
- "@ai-agent-forge/agent-forge": "^0.86.0"
53
+ "@ai-agent-forge/agent-forge": "^0.86.1"
54
54
  }
55
55
  }
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "agent-forge.plugin.process-exec",
3
- "version": "0.86.0",
3
+ "version": "0.86.1",
4
4
  "apiVersion": "1-draft",
5
5
  "entry": "./dist/entry.js",
6
6
  "hostVersion": ">=0.84.4",