@ai-agent-forge/plugin-process-exec 0.85.0
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/README.md +41 -0
- package/agent-forge.json +12 -0
- package/dist/capability.d.ts +5 -0
- package/dist/capability.d.ts.map +1 -0
- package/dist/capability.js +210 -0
- package/dist/capability.js.map +1 -0
- package/dist/entry.d.ts +3 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +40 -0
- package/dist/entry.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/node-adapter.d.ts +23 -0
- package/dist/node-adapter.d.ts.map +1 -0
- package/dist/node-adapter.js +284 -0
- package/dist/node-adapter.js.map +1 -0
- package/dist/shell-utils.d.ts +14 -0
- package/dist/shell-utils.d.ts.map +1 -0
- package/dist/shell-utils.js +80 -0
- package/dist/shell-utils.js.map +1 -0
- package/dist/types.d.ts +45 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -0
- package/plugin.json +9 -0
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @agent-forge/plugin-process-exec
|
|
2
|
+
|
|
3
|
+
Process execution capability plugin for [Agent Forge](https://github.com/agent-forge/agent-forge), extracted from the
|
|
4
|
+
host's `src/plugins/process-exec` (D-075 S3). It registers one full-control tool, `process_exec`, which runs an argv
|
|
5
|
+
command through a host-selected process adapter and returns bounded, sanitized stdout/stderr previews.
|
|
6
|
+
|
|
7
|
+
The plugin is deliberately inert at load time. The real Node adapter requires a host-owned absolute `cwd` (the
|
|
8
|
+
workspace directory), which no load-time seam can supply, so the standalone entry reports an explicit `unsupported`
|
|
9
|
+
result until a host binds `createNodeProcessExecAdapterV1({ cwd, ... })` through its own profile machinery. Readonly
|
|
10
|
+
hosts get no tool registration at all.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
# from a local checkout
|
|
16
|
+
agent-forge plugin install ./plugins/process-exec
|
|
17
|
+
|
|
18
|
+
# from npm
|
|
19
|
+
agent-forge plugin install @agent-forge/plugin-process-exec
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The package ships two manifests: `agent-forge.json` (ecosystem manifest for the deterministic plugin resolver) and
|
|
23
|
+
`plugin.json` (host-load manifest consumed by `loadCapabilityPluginFromManifest`, entry `./dist/entry.js`).
|
|
24
|
+
|
|
25
|
+
## Library surface
|
|
26
|
+
|
|
27
|
+
`import { createNodeProcessExecAdapterV1, createProcessExecCapabilityFactoryV1 } from "@agent-forge/plugin-process-exec";`
|
|
28
|
+
|
|
29
|
+
- `createNodeProcessExecAdapterV1({ cwd, ... })` — shell-free argv execution with output budgets, previews, timeouts,
|
|
30
|
+
and cross-platform process-tree termination; `cwd` is host-owned and requests cannot override it.
|
|
31
|
+
- `sanitizeProcessTextV1(value, maximum?, cwd?)` — redacts credentials and host paths from previews.
|
|
32
|
+
- `createProcessExecCapabilityFactoryV1(adapter)` — registers `process_exec` on a full-control `CapabilityAPI`.
|
|
33
|
+
- Default export of `dist/entry.js` — the precomposed unsupported-adapter factory used by external plugin loading.
|
|
34
|
+
|
|
35
|
+
## Development
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
npm install --ignore-scripts # repo root, creates the workspace symlink
|
|
39
|
+
npm run build # plugins/process-exec (tsgo)
|
|
40
|
+
npm test # vitest; the entry fidelity test requires `npm run build` first
|
|
41
|
+
```
|
package/agent-forge.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"packageFormatVersion": 1,
|
|
3
|
+
"id": "agent-forge.plugin.process-exec",
|
|
4
|
+
"pluginVersion": "0.84.4",
|
|
5
|
+
"apiVersion": "1",
|
|
6
|
+
"minHostVersion": "0.84.4",
|
|
7
|
+
"capabilityRefs": [{ "id": "process.exec", "version": 1 }],
|
|
8
|
+
"platform": {
|
|
9
|
+
"os": ["windows", "macos", "linux"],
|
|
10
|
+
"runtime": [{ "name": "node" }]
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CapabilityPluginSyncFactory } from "@agent-forge/plugin-sdk";
|
|
2
|
+
import type { ProcessExecAdapterV1 } from "./types.ts";
|
|
3
|
+
/** Registers process execution only for a full-control host. */
|
|
4
|
+
export declare function createProcessExecCapabilityFactoryV1(runtime: ProcessExecAdapterV1): CapabilityPluginSyncFactory;
|
|
5
|
+
//# sourceMappingURL=capability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAU3E,OAAO,KAAK,EAAE,oBAAoB,EAA6C,MAAM,YAAY,CAAC;AA6KlG,gEAAgE;AAChE,wBAAgB,oCAAoC,CAAC,OAAO,EAAE,oBAAoB,GAAG,2BAA2B,CAmD/G","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { CapabilityPluginSyncFactory } from \"@agent-forge/plugin-sdk\";\nimport { validatePluginErrorV1 } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { Value } from \"typebox/value\";\nimport {\n\tPROCESS_EXEC_MAX_OPERATION_ID_CHARS_V1,\n\tPROCESS_EXEC_MAX_OUTPUT_BYTES_V1,\n\tPROCESS_EXEC_MAX_PREVIEW_BYTES_V1,\n\tsanitizeProcessTextV1,\n} from \"./node-adapter.ts\";\nimport type { ProcessExecAdapterV1, ProcessExecRequestV1, ProcessExecResultV1 } from \"./types.ts\";\n\nconst requestSchema = Type.Object(\n\t{\n\t\tcommand: Type.String({ minLength: 1, maxLength: 512, pattern: \"^[^\\\\u0000\\\\r\\\\n]+$\" }),\n\t\targs: Type.Array(Type.String({ maxLength: 4096, pattern: \"^[^\\\\u0000\\\\r\\\\n]*$\" }), { maxItems: 256 }),\n\t\ttimeoutMs: Type.Optional(Type.Integer({ minimum: 1, maximum: 120_000 })),\n\t},\n\t{ additionalProperties: false },\n);\nconst inputSchema = Type.Object({ request: requestSchema }, { additionalProperties: false });\n\nconst INVALID_ERROR = Symbol(\"process-exec-invalid-error\");\n\nfunction generatedOperationId(): string {\n\treturn `profile-runtime-tool-call-${randomUUID()}`;\n}\n\nfunction objectInput(first: unknown, second: unknown): Record<string, unknown> {\n\tconst value = second === undefined ? first : second;\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value))\n\t\tthrow new Error(\"Process input must be an object\");\n\treturn value as Record<string, unknown>;\n}\n\nfunction operationIdFor(first: unknown, second: unknown): string {\n\tconst explicitOperationId = second === undefined ? undefined : first;\n\treturn typeof explicitOperationId === \"string\" &&\n\t\texplicitOperationId.trim() !== \"\" &&\n\t\texplicitOperationId.length <= PROCESS_EXEC_MAX_OPERATION_ID_CHARS_V1 &&\n\t\t![...explicitOperationId].some((character) => /[\\u0000-\\u001f\\u007f]/u.test(character))\n\t\t? explicitOperationId\n\t\t: generatedOperationId();\n}\n\nfunction invocation(\n\tfirst: unknown,\n\tsecond: unknown,\n): { readonly input: Record<string, unknown>; readonly operationId: string } {\n\treturn {\n\t\tinput: objectInput(first, second),\n\t\toperationId: operationIdFor(first, second),\n\t};\n}\n\nfunction signalFrom(value: unknown): AbortSignal | undefined {\n\treturn value instanceof AbortSignal ? value : undefined;\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 malformed(operationId: string): ProcessExecResultV1 {\n\treturn {\n\t\t...cancelled(operationId),\n\t\tstatus: \"failed\",\n\t\tsummary: \"Process adapter returned an invalid result\",\n\t\terror: { version: 1, code: \"adapter_failed\", message: \"Process adapter returned an invalid result\" },\n\t};\n}\n\nfunction invalidInput(operationId: string): ProcessExecResultV1 {\n\treturn {\n\t\t...cancelled(operationId),\n\t\tstatus: \"failed\",\n\t\tsummary: \"Process request was invalid\",\n\t\terror: { version: 1, code: \"invalid_input\", message: \"Process request was invalid\" },\n\t};\n}\n\nfunction boundedError(value: unknown): ProcessExecResultV1[\"error\"] | typeof INVALID_ERROR | undefined {\n\tif (value === undefined) return undefined;\n\ttry {\n\t\tconst error = validatePluginErrorV1(value, \"Process adapter error\");\n\t\tif (\n\t\t\terror.code.length > 128 ||\n\t\t\terror.message.length > 512 ||\n\t\t\t(error.causeCode !== undefined && error.causeCode.length > 128)\n\t\t)\n\t\t\treturn INVALID_ERROR;\n\t\treturn {\n\t\t\tversion: 1,\n\t\t\tcode: error.code,\n\t\t\tmessage: sanitizeProcessTextV1(error.message, 512),\n\t\t\t...(error.causeCode === undefined ? {} : { causeCode: error.causeCode }),\n\t\t};\n\t} catch {\n\t\treturn INVALID_ERROR;\n\t}\n}\n\nfunction boundedResult(value: unknown, operationId: string): ProcessExecResultV1 {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) return malformed(operationId);\n\tconst result = value as Record<string, unknown>;\n\tconst exitCode = result.exitCode;\n\tconst stdoutBytes = typeof result.stdoutBytes === \"number\" ? result.stdoutBytes : undefined;\n\tconst stderrBytes = typeof result.stderrBytes === \"number\" ? result.stderrBytes : undefined;\n\tif (\n\t\tresult.version !== 1 ||\n\t\tresult.operationId !== operationId ||\n\t\ttypeof result.status !== \"string\" ||\n\t\t![\"succeeded\", \"failed\", \"cancelled\", \"needs_user\", \"unsupported\"].includes(result.status) ||\n\t\ttypeof result.summary !== \"string\" ||\n\t\tresult.summary.length > 2048 ||\n\t\tstdoutBytes === undefined ||\n\t\tstderrBytes === undefined ||\n\t\t!Number.isSafeInteger(stdoutBytes) ||\n\t\t!Number.isSafeInteger(stderrBytes) ||\n\t\tstdoutBytes < 0 ||\n\t\tstderrBytes < 0 ||\n\t\tstdoutBytes > PROCESS_EXEC_MAX_OUTPUT_BYTES_V1 ||\n\t\tstderrBytes > PROCESS_EXEC_MAX_OUTPUT_BYTES_V1 ||\n\t\ttypeof result.stdoutPreview !== \"string\" ||\n\t\ttypeof result.stderrPreview !== \"string\" ||\n\t\tresult.stdoutPreview.length > PROCESS_EXEC_MAX_PREVIEW_BYTES_V1 ||\n\t\tresult.stderrPreview.length > PROCESS_EXEC_MAX_PREVIEW_BYTES_V1 ||\n\t\t(stdoutBytes === 0 && result.stdoutPreview.length !== 0) ||\n\t\t(stderrBytes === 0 && result.stderrPreview.length !== 0) ||\n\t\ttypeof result.stdoutTruncated !== \"boolean\" ||\n\t\ttypeof result.stderrTruncated !== \"boolean\"\n\t)\n\t\treturn malformed(operationId);\n\tconst status = result.status as ProcessExecResultV1[\"status\"];\n\tif (\n\t\t(exitCode !== undefined && (typeof exitCode !== \"number\" || !Number.isSafeInteger(exitCode) || exitCode < 0)) ||\n\t\t(status === \"succeeded\" && result.error !== undefined)\n\t)\n\t\treturn malformed(operationId);\n\tconst error = boundedError(result.error);\n\tif (error === INVALID_ERROR || (status !== \"succeeded\" && error === undefined)) return malformed(operationId);\n\tconst projected: ProcessExecResultV1 = {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus,\n\t\tsummary: sanitizeProcessTextV1(result.summary as string, 2048),\n\t\t...(exitCode === undefined ? {} : { exitCode: exitCode as number }),\n\t\tstdoutBytes,\n\t\tstderrBytes,\n\t\tstdoutPreview: sanitizeProcessTextV1(result.stdoutPreview as string),\n\t\tstderrPreview: sanitizeProcessTextV1(result.stderrPreview as string),\n\t\tstdoutTruncated: result.stdoutTruncated as boolean,\n\t\tstderrTruncated: result.stderrTruncated as boolean,\n\t\t...(error === undefined ? {} : { error }),\n\t};\n\tif (new TextEncoder().encode(JSON.stringify(projected)).byteLength > 64 * 1024) return malformed(operationId);\n\treturn projected;\n}\n\nasync function runWithCancellation(\n\trun: () => Promise<ProcessExecResultV1>,\n\tsignal: AbortSignal | undefined,\n\toperationId: string,\n): Promise<ProcessExecResultV1> {\n\t// The adapter owns cancellation and must settle after it has drained its\n\t// child/process resources. Do not race the adapter with a local cancellation\n\t// promise: that would return before the host has finished cleanup.\n\tconst output = await run();\n\treturn signal?.aborted && output.status !== \"cancelled\" ? cancelled(operationId) : output;\n}\n\n/** Registers process execution only for a full-control host. */\nexport function createProcessExecCapabilityFactoryV1(runtime: ProcessExecAdapterV1): CapabilityPluginSyncFactory {\n\treturn (api) => {\n\t\tif (api.host.mode === \"readonly\") return;\n\t\tapi.registerTool({\n\t\t\tname: \"process_exec\",\n\t\t\tlabel: \"Process exec\",\n\t\t\tdescription: \"Run an argv command through the selected host process adapter with bounded output\",\n\t\t\tparameters: inputSchema,\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tlet invocationResult: { readonly input: Record<string, unknown>; readonly operationId: string };\n\t\t\t\ttry {\n\t\t\t\t\tinvocationResult = invocation(first, second);\n\t\t\t\t} catch {\n\t\t\t\t\treturn invalidInput(operationIdFor(first, second));\n\t\t\t\t}\n\t\t\t\tconst { input, operationId } = invocationResult;\n\t\t\t\tconst signal = signalFrom(third);\n\t\t\t\tif (signal?.aborted) return cancelled(operationId);\n\t\t\t\tlet request: ProcessExecRequestV1;\n\t\t\t\ttry {\n\t\t\t\t\tif (!Value.Check(inputSchema, input)) return invalidInput(operationId);\n\t\t\t\t\tconst requestInput = objectInput(input.request, undefined);\n\t\t\t\t\tif (\n\t\t\t\t\t\ttypeof requestInput.command !== \"string\" ||\n\t\t\t\t\t\trequestInput.command.trim() === \"\" ||\n\t\t\t\t\t\t!Array.isArray(requestInput.args) ||\n\t\t\t\t\t\trequestInput.args.some((arg) => typeof arg !== \"string\")\n\t\t\t\t\t)\n\t\t\t\t\t\treturn invalidInput(operationId);\n\t\t\t\t\tconst command = requestInput.command as string;\n\t\t\t\t\tconst args = requestInput.args as string[];\n\t\t\t\t\tconst timeoutMs = requestInput.timeoutMs as number | undefined;\n\t\t\t\t\trequest = {\n\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\toperationId,\n\t\t\t\t\t\tcommand,\n\t\t\t\t\t\targs,\n\t\t\t\t\t\t...(timeoutMs === undefined ? {} : { timeoutMs }),\n\t\t\t\t\t};\n\t\t\t\t} catch {\n\t\t\t\t\treturn invalidInput(operationId);\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tconst output = await runWithCancellation(() => runtime.run(request, signal), signal, operationId);\n\t\t\t\t\treturn boundedResult(output, operationId);\n\t\t\t\t} catch {\n\t\t\t\t\treturn signal?.aborted ? cancelled(operationId) : malformed(operationId);\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t};\n}\n"]}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { validatePluginErrorV1 } from "@agent-forge/plugin-sdk";
|
|
3
|
+
import { Type } from "typebox";
|
|
4
|
+
import { Value } from "typebox/value";
|
|
5
|
+
import { PROCESS_EXEC_MAX_OPERATION_ID_CHARS_V1, PROCESS_EXEC_MAX_OUTPUT_BYTES_V1, PROCESS_EXEC_MAX_PREVIEW_BYTES_V1, sanitizeProcessTextV1, } from "./node-adapter.js";
|
|
6
|
+
const requestSchema = Type.Object({
|
|
7
|
+
command: Type.String({ minLength: 1, maxLength: 512, pattern: "^[^\\u0000\\r\\n]+$" }),
|
|
8
|
+
args: Type.Array(Type.String({ maxLength: 4096, pattern: "^[^\\u0000\\r\\n]*$" }), { maxItems: 256 }),
|
|
9
|
+
timeoutMs: Type.Optional(Type.Integer({ minimum: 1, maximum: 120_000 })),
|
|
10
|
+
}, { additionalProperties: false });
|
|
11
|
+
const inputSchema = Type.Object({ request: requestSchema }, { additionalProperties: false });
|
|
12
|
+
const INVALID_ERROR = Symbol("process-exec-invalid-error");
|
|
13
|
+
function generatedOperationId() {
|
|
14
|
+
return `profile-runtime-tool-call-${randomUUID()}`;
|
|
15
|
+
}
|
|
16
|
+
function objectInput(first, second) {
|
|
17
|
+
const value = second === undefined ? first : second;
|
|
18
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
19
|
+
throw new Error("Process input must be an object");
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
function operationIdFor(first, second) {
|
|
23
|
+
const explicitOperationId = second === undefined ? undefined : first;
|
|
24
|
+
return typeof explicitOperationId === "string" &&
|
|
25
|
+
explicitOperationId.trim() !== "" &&
|
|
26
|
+
explicitOperationId.length <= PROCESS_EXEC_MAX_OPERATION_ID_CHARS_V1 &&
|
|
27
|
+
![...explicitOperationId].some((character) => /[\u0000-\u001f\u007f]/u.test(character))
|
|
28
|
+
? explicitOperationId
|
|
29
|
+
: generatedOperationId();
|
|
30
|
+
}
|
|
31
|
+
function invocation(first, second) {
|
|
32
|
+
return {
|
|
33
|
+
input: objectInput(first, second),
|
|
34
|
+
operationId: operationIdFor(first, second),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function signalFrom(value) {
|
|
38
|
+
return value instanceof AbortSignal ? value : undefined;
|
|
39
|
+
}
|
|
40
|
+
function cancelled(operationId) {
|
|
41
|
+
return {
|
|
42
|
+
version: 1,
|
|
43
|
+
operationId,
|
|
44
|
+
status: "cancelled",
|
|
45
|
+
summary: "Process execution was cancelled",
|
|
46
|
+
stdoutBytes: 0,
|
|
47
|
+
stderrBytes: 0,
|
|
48
|
+
stdoutPreview: "",
|
|
49
|
+
stderrPreview: "",
|
|
50
|
+
stdoutTruncated: false,
|
|
51
|
+
stderrTruncated: false,
|
|
52
|
+
error: { version: 1, code: "cancelled", message: "Process execution was cancelled" },
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function malformed(operationId) {
|
|
56
|
+
return {
|
|
57
|
+
...cancelled(operationId),
|
|
58
|
+
status: "failed",
|
|
59
|
+
summary: "Process adapter returned an invalid result",
|
|
60
|
+
error: { version: 1, code: "adapter_failed", message: "Process adapter returned an invalid result" },
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function invalidInput(operationId) {
|
|
64
|
+
return {
|
|
65
|
+
...cancelled(operationId),
|
|
66
|
+
status: "failed",
|
|
67
|
+
summary: "Process request was invalid",
|
|
68
|
+
error: { version: 1, code: "invalid_input", message: "Process request was invalid" },
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function boundedError(value) {
|
|
72
|
+
if (value === undefined)
|
|
73
|
+
return undefined;
|
|
74
|
+
try {
|
|
75
|
+
const error = validatePluginErrorV1(value, "Process adapter error");
|
|
76
|
+
if (error.code.length > 128 ||
|
|
77
|
+
error.message.length > 512 ||
|
|
78
|
+
(error.causeCode !== undefined && error.causeCode.length > 128))
|
|
79
|
+
return INVALID_ERROR;
|
|
80
|
+
return {
|
|
81
|
+
version: 1,
|
|
82
|
+
code: error.code,
|
|
83
|
+
message: sanitizeProcessTextV1(error.message, 512),
|
|
84
|
+
...(error.causeCode === undefined ? {} : { causeCode: error.causeCode }),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return INVALID_ERROR;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function boundedResult(value, operationId) {
|
|
92
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
93
|
+
return malformed(operationId);
|
|
94
|
+
const result = value;
|
|
95
|
+
const exitCode = result.exitCode;
|
|
96
|
+
const stdoutBytes = typeof result.stdoutBytes === "number" ? result.stdoutBytes : undefined;
|
|
97
|
+
const stderrBytes = typeof result.stderrBytes === "number" ? result.stderrBytes : undefined;
|
|
98
|
+
if (result.version !== 1 ||
|
|
99
|
+
result.operationId !== operationId ||
|
|
100
|
+
typeof result.status !== "string" ||
|
|
101
|
+
!["succeeded", "failed", "cancelled", "needs_user", "unsupported"].includes(result.status) ||
|
|
102
|
+
typeof result.summary !== "string" ||
|
|
103
|
+
result.summary.length > 2048 ||
|
|
104
|
+
stdoutBytes === undefined ||
|
|
105
|
+
stderrBytes === undefined ||
|
|
106
|
+
!Number.isSafeInteger(stdoutBytes) ||
|
|
107
|
+
!Number.isSafeInteger(stderrBytes) ||
|
|
108
|
+
stdoutBytes < 0 ||
|
|
109
|
+
stderrBytes < 0 ||
|
|
110
|
+
stdoutBytes > PROCESS_EXEC_MAX_OUTPUT_BYTES_V1 ||
|
|
111
|
+
stderrBytes > PROCESS_EXEC_MAX_OUTPUT_BYTES_V1 ||
|
|
112
|
+
typeof result.stdoutPreview !== "string" ||
|
|
113
|
+
typeof result.stderrPreview !== "string" ||
|
|
114
|
+
result.stdoutPreview.length > PROCESS_EXEC_MAX_PREVIEW_BYTES_V1 ||
|
|
115
|
+
result.stderrPreview.length > PROCESS_EXEC_MAX_PREVIEW_BYTES_V1 ||
|
|
116
|
+
(stdoutBytes === 0 && result.stdoutPreview.length !== 0) ||
|
|
117
|
+
(stderrBytes === 0 && result.stderrPreview.length !== 0) ||
|
|
118
|
+
typeof result.stdoutTruncated !== "boolean" ||
|
|
119
|
+
typeof result.stderrTruncated !== "boolean")
|
|
120
|
+
return malformed(operationId);
|
|
121
|
+
const status = result.status;
|
|
122
|
+
if ((exitCode !== undefined && (typeof exitCode !== "number" || !Number.isSafeInteger(exitCode) || exitCode < 0)) ||
|
|
123
|
+
(status === "succeeded" && result.error !== undefined))
|
|
124
|
+
return malformed(operationId);
|
|
125
|
+
const error = boundedError(result.error);
|
|
126
|
+
if (error === INVALID_ERROR || (status !== "succeeded" && error === undefined))
|
|
127
|
+
return malformed(operationId);
|
|
128
|
+
const projected = {
|
|
129
|
+
version: 1,
|
|
130
|
+
operationId,
|
|
131
|
+
status,
|
|
132
|
+
summary: sanitizeProcessTextV1(result.summary, 2048),
|
|
133
|
+
...(exitCode === undefined ? {} : { exitCode: exitCode }),
|
|
134
|
+
stdoutBytes,
|
|
135
|
+
stderrBytes,
|
|
136
|
+
stdoutPreview: sanitizeProcessTextV1(result.stdoutPreview),
|
|
137
|
+
stderrPreview: sanitizeProcessTextV1(result.stderrPreview),
|
|
138
|
+
stdoutTruncated: result.stdoutTruncated,
|
|
139
|
+
stderrTruncated: result.stderrTruncated,
|
|
140
|
+
...(error === undefined ? {} : { error }),
|
|
141
|
+
};
|
|
142
|
+
if (new TextEncoder().encode(JSON.stringify(projected)).byteLength > 64 * 1024)
|
|
143
|
+
return malformed(operationId);
|
|
144
|
+
return projected;
|
|
145
|
+
}
|
|
146
|
+
async function runWithCancellation(run, signal, operationId) {
|
|
147
|
+
// The adapter owns cancellation and must settle after it has drained its
|
|
148
|
+
// child/process resources. Do not race the adapter with a local cancellation
|
|
149
|
+
// promise: that would return before the host has finished cleanup.
|
|
150
|
+
const output = await run();
|
|
151
|
+
return signal?.aborted && output.status !== "cancelled" ? cancelled(operationId) : output;
|
|
152
|
+
}
|
|
153
|
+
/** Registers process execution only for a full-control host. */
|
|
154
|
+
export function createProcessExecCapabilityFactoryV1(runtime) {
|
|
155
|
+
return (api) => {
|
|
156
|
+
if (api.host.mode === "readonly")
|
|
157
|
+
return;
|
|
158
|
+
api.registerTool({
|
|
159
|
+
name: "process_exec",
|
|
160
|
+
label: "Process exec",
|
|
161
|
+
description: "Run an argv command through the selected host process adapter with bounded output",
|
|
162
|
+
parameters: inputSchema,
|
|
163
|
+
execute: async (first, second, third) => {
|
|
164
|
+
let invocationResult;
|
|
165
|
+
try {
|
|
166
|
+
invocationResult = invocation(first, second);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return invalidInput(operationIdFor(first, second));
|
|
170
|
+
}
|
|
171
|
+
const { input, operationId } = invocationResult;
|
|
172
|
+
const signal = signalFrom(third);
|
|
173
|
+
if (signal?.aborted)
|
|
174
|
+
return cancelled(operationId);
|
|
175
|
+
let request;
|
|
176
|
+
try {
|
|
177
|
+
if (!Value.Check(inputSchema, input))
|
|
178
|
+
return invalidInput(operationId);
|
|
179
|
+
const requestInput = objectInput(input.request, undefined);
|
|
180
|
+
if (typeof requestInput.command !== "string" ||
|
|
181
|
+
requestInput.command.trim() === "" ||
|
|
182
|
+
!Array.isArray(requestInput.args) ||
|
|
183
|
+
requestInput.args.some((arg) => typeof arg !== "string"))
|
|
184
|
+
return invalidInput(operationId);
|
|
185
|
+
const command = requestInput.command;
|
|
186
|
+
const args = requestInput.args;
|
|
187
|
+
const timeoutMs = requestInput.timeoutMs;
|
|
188
|
+
request = {
|
|
189
|
+
version: 1,
|
|
190
|
+
operationId,
|
|
191
|
+
command,
|
|
192
|
+
args,
|
|
193
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return invalidInput(operationId);
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
const output = await runWithCancellation(() => runtime.run(request, signal), signal, operationId);
|
|
201
|
+
return boundedResult(output, operationId);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return signal?.aborted ? cancelled(operationId) : malformed(operationId);
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=capability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability.js","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { CapabilityPluginSyncFactory } from \"@agent-forge/plugin-sdk\";\nimport { validatePluginErrorV1 } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { Value } from \"typebox/value\";\nimport {\n\tPROCESS_EXEC_MAX_OPERATION_ID_CHARS_V1,\n\tPROCESS_EXEC_MAX_OUTPUT_BYTES_V1,\n\tPROCESS_EXEC_MAX_PREVIEW_BYTES_V1,\n\tsanitizeProcessTextV1,\n} from \"./node-adapter.ts\";\nimport type { ProcessExecAdapterV1, ProcessExecRequestV1, ProcessExecResultV1 } from \"./types.ts\";\n\nconst requestSchema = Type.Object(\n\t{\n\t\tcommand: Type.String({ minLength: 1, maxLength: 512, pattern: \"^[^\\\\u0000\\\\r\\\\n]+$\" }),\n\t\targs: Type.Array(Type.String({ maxLength: 4096, pattern: \"^[^\\\\u0000\\\\r\\\\n]*$\" }), { maxItems: 256 }),\n\t\ttimeoutMs: Type.Optional(Type.Integer({ minimum: 1, maximum: 120_000 })),\n\t},\n\t{ additionalProperties: false },\n);\nconst inputSchema = Type.Object({ request: requestSchema }, { additionalProperties: false });\n\nconst INVALID_ERROR = Symbol(\"process-exec-invalid-error\");\n\nfunction generatedOperationId(): string {\n\treturn `profile-runtime-tool-call-${randomUUID()}`;\n}\n\nfunction objectInput(first: unknown, second: unknown): Record<string, unknown> {\n\tconst value = second === undefined ? first : second;\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value))\n\t\tthrow new Error(\"Process input must be an object\");\n\treturn value as Record<string, unknown>;\n}\n\nfunction operationIdFor(first: unknown, second: unknown): string {\n\tconst explicitOperationId = second === undefined ? undefined : first;\n\treturn typeof explicitOperationId === \"string\" &&\n\t\texplicitOperationId.trim() !== \"\" &&\n\t\texplicitOperationId.length <= PROCESS_EXEC_MAX_OPERATION_ID_CHARS_V1 &&\n\t\t![...explicitOperationId].some((character) => /[\\u0000-\\u001f\\u007f]/u.test(character))\n\t\t? explicitOperationId\n\t\t: generatedOperationId();\n}\n\nfunction invocation(\n\tfirst: unknown,\n\tsecond: unknown,\n): { readonly input: Record<string, unknown>; readonly operationId: string } {\n\treturn {\n\t\tinput: objectInput(first, second),\n\t\toperationId: operationIdFor(first, second),\n\t};\n}\n\nfunction signalFrom(value: unknown): AbortSignal | undefined {\n\treturn value instanceof AbortSignal ? value : undefined;\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 malformed(operationId: string): ProcessExecResultV1 {\n\treturn {\n\t\t...cancelled(operationId),\n\t\tstatus: \"failed\",\n\t\tsummary: \"Process adapter returned an invalid result\",\n\t\terror: { version: 1, code: \"adapter_failed\", message: \"Process adapter returned an invalid result\" },\n\t};\n}\n\nfunction invalidInput(operationId: string): ProcessExecResultV1 {\n\treturn {\n\t\t...cancelled(operationId),\n\t\tstatus: \"failed\",\n\t\tsummary: \"Process request was invalid\",\n\t\terror: { version: 1, code: \"invalid_input\", message: \"Process request was invalid\" },\n\t};\n}\n\nfunction boundedError(value: unknown): ProcessExecResultV1[\"error\"] | typeof INVALID_ERROR | undefined {\n\tif (value === undefined) return undefined;\n\ttry {\n\t\tconst error = validatePluginErrorV1(value, \"Process adapter error\");\n\t\tif (\n\t\t\terror.code.length > 128 ||\n\t\t\terror.message.length > 512 ||\n\t\t\t(error.causeCode !== undefined && error.causeCode.length > 128)\n\t\t)\n\t\t\treturn INVALID_ERROR;\n\t\treturn {\n\t\t\tversion: 1,\n\t\t\tcode: error.code,\n\t\t\tmessage: sanitizeProcessTextV1(error.message, 512),\n\t\t\t...(error.causeCode === undefined ? {} : { causeCode: error.causeCode }),\n\t\t};\n\t} catch {\n\t\treturn INVALID_ERROR;\n\t}\n}\n\nfunction boundedResult(value: unknown, operationId: string): ProcessExecResultV1 {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) return malformed(operationId);\n\tconst result = value as Record<string, unknown>;\n\tconst exitCode = result.exitCode;\n\tconst stdoutBytes = typeof result.stdoutBytes === \"number\" ? result.stdoutBytes : undefined;\n\tconst stderrBytes = typeof result.stderrBytes === \"number\" ? result.stderrBytes : undefined;\n\tif (\n\t\tresult.version !== 1 ||\n\t\tresult.operationId !== operationId ||\n\t\ttypeof result.status !== \"string\" ||\n\t\t![\"succeeded\", \"failed\", \"cancelled\", \"needs_user\", \"unsupported\"].includes(result.status) ||\n\t\ttypeof result.summary !== \"string\" ||\n\t\tresult.summary.length > 2048 ||\n\t\tstdoutBytes === undefined ||\n\t\tstderrBytes === undefined ||\n\t\t!Number.isSafeInteger(stdoutBytes) ||\n\t\t!Number.isSafeInteger(stderrBytes) ||\n\t\tstdoutBytes < 0 ||\n\t\tstderrBytes < 0 ||\n\t\tstdoutBytes > PROCESS_EXEC_MAX_OUTPUT_BYTES_V1 ||\n\t\tstderrBytes > PROCESS_EXEC_MAX_OUTPUT_BYTES_V1 ||\n\t\ttypeof result.stdoutPreview !== \"string\" ||\n\t\ttypeof result.stderrPreview !== \"string\" ||\n\t\tresult.stdoutPreview.length > PROCESS_EXEC_MAX_PREVIEW_BYTES_V1 ||\n\t\tresult.stderrPreview.length > PROCESS_EXEC_MAX_PREVIEW_BYTES_V1 ||\n\t\t(stdoutBytes === 0 && result.stdoutPreview.length !== 0) ||\n\t\t(stderrBytes === 0 && result.stderrPreview.length !== 0) ||\n\t\ttypeof result.stdoutTruncated !== \"boolean\" ||\n\t\ttypeof result.stderrTruncated !== \"boolean\"\n\t)\n\t\treturn malformed(operationId);\n\tconst status = result.status as ProcessExecResultV1[\"status\"];\n\tif (\n\t\t(exitCode !== undefined && (typeof exitCode !== \"number\" || !Number.isSafeInteger(exitCode) || exitCode < 0)) ||\n\t\t(status === \"succeeded\" && result.error !== undefined)\n\t)\n\t\treturn malformed(operationId);\n\tconst error = boundedError(result.error);\n\tif (error === INVALID_ERROR || (status !== \"succeeded\" && error === undefined)) return malformed(operationId);\n\tconst projected: ProcessExecResultV1 = {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus,\n\t\tsummary: sanitizeProcessTextV1(result.summary as string, 2048),\n\t\t...(exitCode === undefined ? {} : { exitCode: exitCode as number }),\n\t\tstdoutBytes,\n\t\tstderrBytes,\n\t\tstdoutPreview: sanitizeProcessTextV1(result.stdoutPreview as string),\n\t\tstderrPreview: sanitizeProcessTextV1(result.stderrPreview as string),\n\t\tstdoutTruncated: result.stdoutTruncated as boolean,\n\t\tstderrTruncated: result.stderrTruncated as boolean,\n\t\t...(error === undefined ? {} : { error }),\n\t};\n\tif (new TextEncoder().encode(JSON.stringify(projected)).byteLength > 64 * 1024) return malformed(operationId);\n\treturn projected;\n}\n\nasync function runWithCancellation(\n\trun: () => Promise<ProcessExecResultV1>,\n\tsignal: AbortSignal | undefined,\n\toperationId: string,\n): Promise<ProcessExecResultV1> {\n\t// The adapter owns cancellation and must settle after it has drained its\n\t// child/process resources. Do not race the adapter with a local cancellation\n\t// promise: that would return before the host has finished cleanup.\n\tconst output = await run();\n\treturn signal?.aborted && output.status !== \"cancelled\" ? cancelled(operationId) : output;\n}\n\n/** Registers process execution only for a full-control host. */\nexport function createProcessExecCapabilityFactoryV1(runtime: ProcessExecAdapterV1): CapabilityPluginSyncFactory {\n\treturn (api) => {\n\t\tif (api.host.mode === \"readonly\") return;\n\t\tapi.registerTool({\n\t\t\tname: \"process_exec\",\n\t\t\tlabel: \"Process exec\",\n\t\t\tdescription: \"Run an argv command through the selected host process adapter with bounded output\",\n\t\t\tparameters: inputSchema,\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tlet invocationResult: { readonly input: Record<string, unknown>; readonly operationId: string };\n\t\t\t\ttry {\n\t\t\t\t\tinvocationResult = invocation(first, second);\n\t\t\t\t} catch {\n\t\t\t\t\treturn invalidInput(operationIdFor(first, second));\n\t\t\t\t}\n\t\t\t\tconst { input, operationId } = invocationResult;\n\t\t\t\tconst signal = signalFrom(third);\n\t\t\t\tif (signal?.aborted) return cancelled(operationId);\n\t\t\t\tlet request: ProcessExecRequestV1;\n\t\t\t\ttry {\n\t\t\t\t\tif (!Value.Check(inputSchema, input)) return invalidInput(operationId);\n\t\t\t\t\tconst requestInput = objectInput(input.request, undefined);\n\t\t\t\t\tif (\n\t\t\t\t\t\ttypeof requestInput.command !== \"string\" ||\n\t\t\t\t\t\trequestInput.command.trim() === \"\" ||\n\t\t\t\t\t\t!Array.isArray(requestInput.args) ||\n\t\t\t\t\t\trequestInput.args.some((arg) => typeof arg !== \"string\")\n\t\t\t\t\t)\n\t\t\t\t\t\treturn invalidInput(operationId);\n\t\t\t\t\tconst command = requestInput.command as string;\n\t\t\t\t\tconst args = requestInput.args as string[];\n\t\t\t\t\tconst timeoutMs = requestInput.timeoutMs as number | undefined;\n\t\t\t\t\trequest = {\n\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\toperationId,\n\t\t\t\t\t\tcommand,\n\t\t\t\t\t\targs,\n\t\t\t\t\t\t...(timeoutMs === undefined ? {} : { timeoutMs }),\n\t\t\t\t\t};\n\t\t\t\t} catch {\n\t\t\t\t\treturn invalidInput(operationId);\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tconst output = await runWithCancellation(() => runtime.run(request, signal), signal, operationId);\n\t\t\t\t\treturn boundedResult(output, operationId);\n\t\t\t\t} catch {\n\t\t\t\t\treturn signal?.aborted ? cancelled(operationId) : malformed(operationId);\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t};\n}\n"]}
|
package/dist/entry.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":";AAyCA,wBAA6F","sourcesContent":["import { createProcessExecCapabilityFactoryV1 } from \"./capability.ts\";\nimport type { ProcessExecAdapterV1, ProcessExecRequestV1, ProcessExecResultV1 } from \"./types.ts\";\nimport { PROCESS_EXEC_HOST_ADAPTER_ID_V1, PROCESS_EXEC_HOST_ADAPTER_VERSION_V1 } from \"./types.ts\";\n\n/**\n * Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.\n *\n * The real Node adapter is host-bound: `createNodeProcessExecAdapterV1` requires\n * a host-owned absolute `cwd` (the workspace directory the host selected), which\n * no load-time seam can supply. Constructing it here would either throw at\n * import time or silently anchor execution to an arbitrary process cwd, so the\n * entry ships an explicit unsupported adapter instead: the `process_exec` tool\n * registers on full-control hosts and reports an `unsupported` result until a\n * host binds a real adapter through its own profile machinery (fail-closed by\n * design, same contract as the embedded build).\n */\nfunction createUnsupportedProcessExecAdapterV1(): ProcessExecAdapterV1 {\n\tconst unsupported = (request: ProcessExecRequestV1): ProcessExecResultV1 => ({\n\t\tversion: 1,\n\t\toperationId: request.operationId,\n\t\tstatus: \"unsupported\",\n\t\tsummary: \"No process executor is bound by this host\",\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: {\n\t\t\tversion: 1,\n\t\t\tcode: \"process_exec_unavailable\",\n\t\t\tmessage: \"No host process executor is bound\",\n\t\t},\n\t});\n\treturn {\n\t\tadapterId: PROCESS_EXEC_HOST_ADAPTER_ID_V1,\n\t\tadapterVersion: PROCESS_EXEC_HOST_ADAPTER_VERSION_V1,\n\t\trun: async (request) => unsupported(request),\n\t};\n}\n\nexport default createProcessExecCapabilityFactoryV1(createUnsupportedProcessExecAdapterV1());\n"]}
|
package/dist/entry.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createProcessExecCapabilityFactoryV1 } from "./capability.js";
|
|
2
|
+
import { PROCESS_EXEC_HOST_ADAPTER_ID_V1, PROCESS_EXEC_HOST_ADAPTER_VERSION_V1 } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.
|
|
5
|
+
*
|
|
6
|
+
* The real Node adapter is host-bound: `createNodeProcessExecAdapterV1` requires
|
|
7
|
+
* a host-owned absolute `cwd` (the workspace directory the host selected), which
|
|
8
|
+
* no load-time seam can supply. Constructing it here would either throw at
|
|
9
|
+
* import time or silently anchor execution to an arbitrary process cwd, so the
|
|
10
|
+
* entry ships an explicit unsupported adapter instead: the `process_exec` tool
|
|
11
|
+
* registers on full-control hosts and reports an `unsupported` result until a
|
|
12
|
+
* host binds a real adapter through its own profile machinery (fail-closed by
|
|
13
|
+
* design, same contract as the embedded build).
|
|
14
|
+
*/
|
|
15
|
+
function createUnsupportedProcessExecAdapterV1() {
|
|
16
|
+
const unsupported = (request) => ({
|
|
17
|
+
version: 1,
|
|
18
|
+
operationId: request.operationId,
|
|
19
|
+
status: "unsupported",
|
|
20
|
+
summary: "No process executor is bound by this host",
|
|
21
|
+
stdoutBytes: 0,
|
|
22
|
+
stderrBytes: 0,
|
|
23
|
+
stdoutPreview: "",
|
|
24
|
+
stderrPreview: "",
|
|
25
|
+
stdoutTruncated: false,
|
|
26
|
+
stderrTruncated: false,
|
|
27
|
+
error: {
|
|
28
|
+
version: 1,
|
|
29
|
+
code: "process_exec_unavailable",
|
|
30
|
+
message: "No host process executor is bound",
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
adapterId: PROCESS_EXEC_HOST_ADAPTER_ID_V1,
|
|
35
|
+
adapterVersion: PROCESS_EXEC_HOST_ADAPTER_VERSION_V1,
|
|
36
|
+
run: async (request) => unsupported(request),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export default createProcessExecCapabilityFactoryV1(createUnsupportedProcessExecAdapterV1());
|
|
40
|
+
//# sourceMappingURL=entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oCAAoC,EAAE,MAAM,iBAAiB,CAAC;AAEvE,OAAO,EAAE,+BAA+B,EAAE,oCAAoC,EAAE,MAAM,YAAY,CAAC;AAEnG;;;;;;;;;;;GAWG;AACH,SAAS,qCAAqC,GAAyB;IACtE,MAAM,WAAW,GAAG,CAAC,OAA6B,EAAuB,EAAE,CAAC,CAAC;QAC5E,OAAO,EAAE,CAAC;QACV,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,MAAM,EAAE,aAAa;QACrB,OAAO,EAAE,2CAA2C;QACpD,WAAW,EAAE,CAAC;QACd,WAAW,EAAE,CAAC;QACd,aAAa,EAAE,EAAE;QACjB,aAAa,EAAE,EAAE;QACjB,eAAe,EAAE,KAAK;QACtB,eAAe,EAAE,KAAK;QACtB,KAAK,EAAE;YACN,OAAO,EAAE,CAAC;YACV,IAAI,EAAE,0BAA0B;YAChC,OAAO,EAAE,mCAAmC;SAC5C;KACD,CAAC,CAAC;IACH,OAAO;QACN,SAAS,EAAE,+BAA+B;QAC1C,cAAc,EAAE,oCAAoC;QACpD,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC;KAC5C,CAAC;AAAA,CACF;AAED,eAAe,oCAAoC,CAAC,qCAAqC,EAAE,CAAC,CAAC","sourcesContent":["import { createProcessExecCapabilityFactoryV1 } from \"./capability.ts\";\nimport type { ProcessExecAdapterV1, ProcessExecRequestV1, ProcessExecResultV1 } from \"./types.ts\";\nimport { PROCESS_EXEC_HOST_ADAPTER_ID_V1, PROCESS_EXEC_HOST_ADAPTER_VERSION_V1 } from \"./types.ts\";\n\n/**\n * Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.\n *\n * The real Node adapter is host-bound: `createNodeProcessExecAdapterV1` requires\n * a host-owned absolute `cwd` (the workspace directory the host selected), which\n * no load-time seam can supply. Constructing it here would either throw at\n * import time or silently anchor execution to an arbitrary process cwd, so the\n * entry ships an explicit unsupported adapter instead: the `process_exec` tool\n * registers on full-control hosts and reports an `unsupported` result until a\n * host binds a real adapter through its own profile machinery (fail-closed by\n * design, same contract as the embedded build).\n */\nfunction createUnsupportedProcessExecAdapterV1(): ProcessExecAdapterV1 {\n\tconst unsupported = (request: ProcessExecRequestV1): ProcessExecResultV1 => ({\n\t\tversion: 1,\n\t\toperationId: request.operationId,\n\t\tstatus: \"unsupported\",\n\t\tsummary: \"No process executor is bound by this host\",\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: {\n\t\t\tversion: 1,\n\t\t\tcode: \"process_exec_unavailable\",\n\t\t\tmessage: \"No host process executor is bound\",\n\t\t},\n\t});\n\treturn {\n\t\tadapterId: PROCESS_EXEC_HOST_ADAPTER_ID_V1,\n\t\tadapterVersion: PROCESS_EXEC_HOST_ADAPTER_VERSION_V1,\n\t\trun: async (request) => unsupported(request),\n\t};\n}\n\nexport default createProcessExecCapabilityFactoryV1(createUnsupportedProcessExecAdapterV1());\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { createProcessExecCapabilityFactoryV1 } from "./capability.ts";
|
|
2
|
+
export type { NodeProcessExecAdapterOptionsV1 } from "./node-adapter.ts";
|
|
3
|
+
export { createNodeProcessExecAdapterV1, PROCESS_EXEC_MAX_OPERATION_ID_CHARS_V1, PROCESS_EXEC_MAX_OUTPUT_BYTES_V1, PROCESS_EXEC_MAX_PREVIEW_BYTES_V1, sanitizeProcessTextV1, } from "./node-adapter.ts";
|
|
4
|
+
export { killProcessTree, sanitizeBinaryOutput } from "./shell-utils.ts";
|
|
5
|
+
export type * from "./types.ts";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oCAAoC,EAAE,MAAM,iBAAiB,CAAC;AACvE,YAAY,EAAE,+BAA+B,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EACN,8BAA8B,EAC9B,sCAAsC,EACtC,gCAAgC,EAChC,iCAAiC,EACjC,qBAAqB,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACzE,mBAAmB,YAAY,CAAC","sourcesContent":["export { createProcessExecCapabilityFactoryV1 } from \"./capability.ts\";\nexport type { NodeProcessExecAdapterOptionsV1 } from \"./node-adapter.ts\";\nexport {\n\tcreateNodeProcessExecAdapterV1,\n\tPROCESS_EXEC_MAX_OPERATION_ID_CHARS_V1,\n\tPROCESS_EXEC_MAX_OUTPUT_BYTES_V1,\n\tPROCESS_EXEC_MAX_PREVIEW_BYTES_V1,\n\tsanitizeProcessTextV1,\n} from \"./node-adapter.ts\";\nexport { killProcessTree, sanitizeBinaryOutput } from \"./shell-utils.ts\";\nexport type * from \"./types.ts\";\n"]}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createProcessExecCapabilityFactoryV1 } from "./capability.js";
|
|
2
|
+
export { createNodeProcessExecAdapterV1, PROCESS_EXEC_MAX_OPERATION_ID_CHARS_V1, PROCESS_EXEC_MAX_OUTPUT_BYTES_V1, PROCESS_EXEC_MAX_PREVIEW_BYTES_V1, sanitizeProcessTextV1, } from "./node-adapter.js";
|
|
3
|
+
export { killProcessTree, sanitizeBinaryOutput } from "./shell-utils.js";
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oCAAoC,EAAE,MAAM,iBAAiB,CAAC;AAEvE,OAAO,EACN,8BAA8B,EAC9B,sCAAsC,EACtC,gCAAgC,EAChC,iCAAiC,EACjC,qBAAqB,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC","sourcesContent":["export { createProcessExecCapabilityFactoryV1 } from \"./capability.ts\";\nexport type { NodeProcessExecAdapterOptionsV1 } from \"./node-adapter.ts\";\nexport {\n\tcreateNodeProcessExecAdapterV1,\n\tPROCESS_EXEC_MAX_OPERATION_ID_CHARS_V1,\n\tPROCESS_EXEC_MAX_OUTPUT_BYTES_V1,\n\tPROCESS_EXEC_MAX_PREVIEW_BYTES_V1,\n\tsanitizeProcessTextV1,\n} from \"./node-adapter.ts\";\nexport { killProcessTree, sanitizeBinaryOutput } from \"./shell-utils.ts\";\nexport type * from \"./types.ts\";\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { type ProcessExecAdapterV1 } from "./types.ts";
|
|
3
|
+
export declare const PROCESS_EXEC_MAX_OUTPUT_BYTES_V1: number;
|
|
4
|
+
export declare const PROCESS_EXEC_MAX_PREVIEW_BYTES_V1 = 8192;
|
|
5
|
+
export declare const PROCESS_EXEC_MAX_OPERATION_ID_CHARS_V1 = 256;
|
|
6
|
+
export interface NodeProcessExecAdapterOptionsV1 {
|
|
7
|
+
/** Host-owned working directory; requests cannot override it. */
|
|
8
|
+
readonly cwd: string;
|
|
9
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
10
|
+
readonly defaultTimeoutMs?: number;
|
|
11
|
+
readonly maxStdoutBytes?: number;
|
|
12
|
+
readonly maxStderrBytes?: number;
|
|
13
|
+
readonly previewBytes?: number;
|
|
14
|
+
/** Test seam for the host-owned process-tree terminator. */
|
|
15
|
+
readonly killProcessTree?: (pid: number) => void;
|
|
16
|
+
/** Deterministic host seam; production defaults to Node's shell-free spawn. */
|
|
17
|
+
readonly spawn?: typeof spawn;
|
|
18
|
+
}
|
|
19
|
+
/** Redacts credentials and host paths before a preview can reach the model. */
|
|
20
|
+
export declare function sanitizeProcessTextV1(value: string, maximumChars?: number, cwd?: string): string;
|
|
21
|
+
/** Node host adapter with shell-free argv execution and bounded output previews. */
|
|
22
|
+
export declare function createNodeProcessExecAdapterV1(options: NodeProcessExecAdapterOptionsV1): ProcessExecAdapterV1;
|
|
23
|
+
//# sourceMappingURL=node-adapter.d.ts.map
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { isAbsolute } from "node:path";
|
|
3
|
+
import { killProcessTree, sanitizeBinaryOutput } from "./shell-utils.js";
|
|
4
|
+
import { PROCESS_EXEC_HOST_ADAPTER_ID_V1, PROCESS_EXEC_HOST_ADAPTER_VERSION_V1, } from "./types.js";
|
|
5
|
+
const DEFAULT_TIMEOUT_MS_V1 = 30_000;
|
|
6
|
+
const MAX_TIMEOUT_MS_V1 = 120_000;
|
|
7
|
+
const DEFAULT_OUTPUT_BYTES_V1 = 64 * 1024;
|
|
8
|
+
export const PROCESS_EXEC_MAX_OUTPUT_BYTES_V1 = 1024 * 1024;
|
|
9
|
+
const DEFAULT_PREVIEW_BYTES_V1 = 4096;
|
|
10
|
+
export const PROCESS_EXEC_MAX_PREVIEW_BYTES_V1 = 8192;
|
|
11
|
+
const MAX_ARGS_V1 = 256;
|
|
12
|
+
const MAX_COMMAND_CHARS_V1 = 512;
|
|
13
|
+
const MAX_ARG_CHARS_V1 = 4096;
|
|
14
|
+
export const PROCESS_EXEC_MAX_OPERATION_ID_CHARS_V1 = 256;
|
|
15
|
+
const SAFE_ENVIRONMENT_KEYS_V1 = new Set(["PATH", "PATHEXT", "SystemRoot", "SYSTEMROOT", "WINDIR", "TEMP", "TMP", "TMPDIR", "LANG", "LC_ALL"].map((key) => key.toLowerCase()));
|
|
16
|
+
/** Redacts credentials and host paths before a preview can reach the model. */
|
|
17
|
+
export function sanitizeProcessTextV1(value, maximumChars = PROCESS_EXEC_MAX_PREVIEW_BYTES_V1, cwd) {
|
|
18
|
+
let text = sanitizeBinaryOutput(value);
|
|
19
|
+
if (cwd !== undefined && cwd !== "")
|
|
20
|
+
text = text.replaceAll(cwd, "[redacted-path]");
|
|
21
|
+
text = text
|
|
22
|
+
.replace(/((?:api[-_ ]?key|access[-_ ]?token|auth(?:orization)?|bearer|password|passwd|secret|token)\s*[:=]\s*)([^\s,;]+)/giu, "$1[redacted-secret]")
|
|
23
|
+
.replace(/(?:[A-Za-z]:[\\/]|\\\\|\/(?:home|Users|private|tmp|var|workspace)\/)[^\s"'<>]*/gu, "[redacted-path]");
|
|
24
|
+
return text.length <= maximumChars ? text : text.slice(0, maximumChars);
|
|
25
|
+
}
|
|
26
|
+
function hostEnvironment(explicit) {
|
|
27
|
+
const inherited = {};
|
|
28
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
29
|
+
if (value !== undefined && SAFE_ENVIRONMENT_KEYS_V1.has(key.toLowerCase()))
|
|
30
|
+
inherited[key] = value;
|
|
31
|
+
}
|
|
32
|
+
return { ...inherited, ...(explicit ?? {}) };
|
|
33
|
+
}
|
|
34
|
+
function positiveBound(value, fallback, maximum, label) {
|
|
35
|
+
const result = value ?? fallback;
|
|
36
|
+
if (!Number.isSafeInteger(result) || result < 1 || result > maximum)
|
|
37
|
+
throw new Error(`${label} must be a positive integer at most ${maximum}`);
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
function inputError(operationId, message) {
|
|
41
|
+
return {
|
|
42
|
+
version: 1,
|
|
43
|
+
operationId,
|
|
44
|
+
status: "failed",
|
|
45
|
+
summary: "Process request was invalid",
|
|
46
|
+
stdoutBytes: 0,
|
|
47
|
+
stderrBytes: 0,
|
|
48
|
+
stdoutPreview: "",
|
|
49
|
+
stderrPreview: "",
|
|
50
|
+
stdoutTruncated: false,
|
|
51
|
+
stderrTruncated: false,
|
|
52
|
+
error: { version: 1, code: "invalid_input", message },
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function cancelled(operationId) {
|
|
56
|
+
return {
|
|
57
|
+
version: 1,
|
|
58
|
+
operationId,
|
|
59
|
+
status: "cancelled",
|
|
60
|
+
summary: "Process execution was cancelled",
|
|
61
|
+
stdoutBytes: 0,
|
|
62
|
+
stderrBytes: 0,
|
|
63
|
+
stdoutPreview: "",
|
|
64
|
+
stderrPreview: "",
|
|
65
|
+
stdoutTruncated: false,
|
|
66
|
+
stderrTruncated: false,
|
|
67
|
+
error: { version: 1, code: "cancelled", message: "Process execution was cancelled" },
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function validateRequest(request) {
|
|
71
|
+
if (request.version !== 1)
|
|
72
|
+
return "version must be 1";
|
|
73
|
+
if (typeof request.operationId !== "string" ||
|
|
74
|
+
request.operationId.trim() === "" ||
|
|
75
|
+
request.operationId.length > PROCESS_EXEC_MAX_OPERATION_ID_CHARS_V1)
|
|
76
|
+
return "operationId is invalid";
|
|
77
|
+
if (typeof request.command !== "string" ||
|
|
78
|
+
request.command.trim() === "" ||
|
|
79
|
+
request.command.length > MAX_COMMAND_CHARS_V1 ||
|
|
80
|
+
/[\u0000\r\n]/u.test(request.command))
|
|
81
|
+
return "command is invalid";
|
|
82
|
+
if (!Array.isArray(request.args) || request.args.length > MAX_ARGS_V1)
|
|
83
|
+
return "args are invalid";
|
|
84
|
+
if (request.args.some((arg) => typeof arg !== "string" || arg.length > MAX_ARG_CHARS_V1 || /[\u0000\r\n]/u.test(arg)))
|
|
85
|
+
return "args are invalid";
|
|
86
|
+
if (request.timeoutMs !== undefined &&
|
|
87
|
+
(!Number.isSafeInteger(request.timeoutMs) || request.timeoutMs < 1 || request.timeoutMs > MAX_TIMEOUT_MS_V1))
|
|
88
|
+
return `timeoutMs must be a positive integer at most ${MAX_TIMEOUT_MS_V1}`;
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
function previewText(bytes) {
|
|
92
|
+
return new TextDecoder().decode(bytes);
|
|
93
|
+
}
|
|
94
|
+
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);
|
|
97
|
+
const errorCode = termination;
|
|
98
|
+
const error = errorCode === undefined
|
|
99
|
+
? undefined
|
|
100
|
+
: {
|
|
101
|
+
version: 1,
|
|
102
|
+
code: errorCode,
|
|
103
|
+
message: errorCode === "exit_nonzero"
|
|
104
|
+
? "Process exited with a non-zero status"
|
|
105
|
+
: errorCode === "cancelled"
|
|
106
|
+
? "Process execution was cancelled"
|
|
107
|
+
: "Process execution failed",
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
version: 1,
|
|
111
|
+
operationId: request.operationId,
|
|
112
|
+
status,
|
|
113
|
+
summary: status === "succeeded"
|
|
114
|
+
? "Process completed successfully"
|
|
115
|
+
: termination === "cancelled"
|
|
116
|
+
? "Process execution was cancelled"
|
|
117
|
+
: termination === "timeout"
|
|
118
|
+
? "Process execution timed out"
|
|
119
|
+
: termination === "output_budget_exceeded"
|
|
120
|
+
? "Process output exceeded the host budget"
|
|
121
|
+
: termination === "spawn_failed"
|
|
122
|
+
? "Process could not be started"
|
|
123
|
+
: "Process exited with a non-zero status",
|
|
124
|
+
...(code === undefined ? {} : { exitCode: code }),
|
|
125
|
+
stdoutBytes,
|
|
126
|
+
stderrBytes,
|
|
127
|
+
stdoutPreview,
|
|
128
|
+
stderrPreview,
|
|
129
|
+
stdoutTruncated: stdoutBytes > previewBytes,
|
|
130
|
+
stderrTruncated: stderrBytes > previewBytes,
|
|
131
|
+
...(error === undefined ? {} : { error }),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** Node host adapter with shell-free argv execution and bounded output previews. */
|
|
135
|
+
export function createNodeProcessExecAdapterV1(options) {
|
|
136
|
+
if (typeof options.cwd !== "string" || !isAbsolute(options.cwd))
|
|
137
|
+
throw new Error("cwd must be an absolute path");
|
|
138
|
+
const defaultTimeoutMs = positiveBound(options.defaultTimeoutMs, DEFAULT_TIMEOUT_MS_V1, MAX_TIMEOUT_MS_V1, "defaultTimeoutMs");
|
|
139
|
+
const maxStdoutBytes = positiveBound(options.maxStdoutBytes, DEFAULT_OUTPUT_BYTES_V1, PROCESS_EXEC_MAX_OUTPUT_BYTES_V1, "maxStdoutBytes");
|
|
140
|
+
const maxStderrBytes = positiveBound(options.maxStderrBytes, DEFAULT_OUTPUT_BYTES_V1, PROCESS_EXEC_MAX_OUTPUT_BYTES_V1, "maxStderrBytes");
|
|
141
|
+
const previewBytes = positiveBound(options.previewBytes, DEFAULT_PREVIEW_BYTES_V1, PROCESS_EXEC_MAX_PREVIEW_BYTES_V1, "previewBytes");
|
|
142
|
+
const env = hostEnvironment(options.env);
|
|
143
|
+
const spawnProcess = options.spawn ?? spawn;
|
|
144
|
+
const terminateTree = options.killProcessTree ?? killProcessTree;
|
|
145
|
+
return {
|
|
146
|
+
adapterId: PROCESS_EXEC_HOST_ADAPTER_ID_V1,
|
|
147
|
+
adapterVersion: PROCESS_EXEC_HOST_ADAPTER_VERSION_V1,
|
|
148
|
+
run: (request, signal) => {
|
|
149
|
+
const operationId = typeof request.operationId === "string" && request.operationId.trim() !== ""
|
|
150
|
+
? request.operationId
|
|
151
|
+
: "invalid-process-operation";
|
|
152
|
+
const invalid = validateRequest(request);
|
|
153
|
+
if (invalid !== undefined)
|
|
154
|
+
return Promise.resolve(inputError(operationId, invalid));
|
|
155
|
+
if (signal?.aborted)
|
|
156
|
+
return Promise.resolve(cancelled(operationId));
|
|
157
|
+
const timeoutMs = request.timeoutMs ?? defaultTimeoutMs;
|
|
158
|
+
return new Promise((resolve) => {
|
|
159
|
+
let child;
|
|
160
|
+
try {
|
|
161
|
+
child = spawnProcess(request.command, [...request.args], {
|
|
162
|
+
cwd: options.cwd,
|
|
163
|
+
...(env === undefined ? {} : { env }),
|
|
164
|
+
shell: false,
|
|
165
|
+
windowsHide: true,
|
|
166
|
+
detached: process.platform !== "win32",
|
|
167
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
resolve(result(request, "failed", new Uint8Array(), new Uint8Array(), 0, 0, previewBytes, "spawn_failed", undefined, options.cwd));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
let stdoutBytes = 0;
|
|
175
|
+
let stderrBytes = 0;
|
|
176
|
+
const stdoutPreview = new Uint8Array(previewBytes);
|
|
177
|
+
const stderrPreview = new Uint8Array(previewBytes);
|
|
178
|
+
let stdoutPreviewLength = 0;
|
|
179
|
+
let stderrPreviewLength = 0;
|
|
180
|
+
let termination;
|
|
181
|
+
let settled = false;
|
|
182
|
+
let exited = false;
|
|
183
|
+
let terminationRequested = false;
|
|
184
|
+
let forceKillTimer;
|
|
185
|
+
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
|
+
const forceTerminate = () => {
|
|
192
|
+
if (exited)
|
|
193
|
+
return;
|
|
194
|
+
if (typeof child.pid === "number" && child.pid > 0) {
|
|
195
|
+
try {
|
|
196
|
+
terminateTree(child.pid);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// The host cleanup boundary is best-effort; close/error still settles the operation.
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
else if (!child.killed) {
|
|
203
|
+
try {
|
|
204
|
+
child.kill("SIGKILL");
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
// The close/error event remains authoritative.
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
const armForceTermination = () => {
|
|
212
|
+
if (forceKillTimer !== undefined)
|
|
213
|
+
return;
|
|
214
|
+
forceKillTimer = setTimeout(() => {
|
|
215
|
+
forceTerminate();
|
|
216
|
+
hardFinishTimer = setTimeout(() => finalize(null), 500);
|
|
217
|
+
}, 2000);
|
|
218
|
+
};
|
|
219
|
+
const terminate = (reason) => {
|
|
220
|
+
if (termination === undefined)
|
|
221
|
+
termination = reason;
|
|
222
|
+
if (!terminationRequested) {
|
|
223
|
+
terminationRequested = true;
|
|
224
|
+
if (!child.killed) {
|
|
225
|
+
try {
|
|
226
|
+
child.kill("SIGTERM");
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
// The force cleanup timer still runs.
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
armForceTermination();
|
|
234
|
+
};
|
|
235
|
+
const onAbort = () => {
|
|
236
|
+
terminate("cancelled");
|
|
237
|
+
};
|
|
238
|
+
const finalize = (code) => {
|
|
239
|
+
if (settled)
|
|
240
|
+
return;
|
|
241
|
+
settled = true;
|
|
242
|
+
exited = true;
|
|
243
|
+
clearTimeout(timeout);
|
|
244
|
+
if (forceKillTimer !== undefined)
|
|
245
|
+
clearTimeout(forceKillTimer);
|
|
246
|
+
if (hardFinishTimer !== undefined)
|
|
247
|
+
clearTimeout(hardFinishTimer);
|
|
248
|
+
signal?.removeEventListener("abort", onAbort);
|
|
249
|
+
if (signal?.aborted && termination === undefined)
|
|
250
|
+
termination = "cancelled";
|
|
251
|
+
if (termination === undefined && code !== 0)
|
|
252
|
+
termination = "exit_nonzero";
|
|
253
|
+
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));
|
|
255
|
+
};
|
|
256
|
+
const timeout = setTimeout(() => terminate("timeout"), timeoutMs);
|
|
257
|
+
if (signal !== undefined)
|
|
258
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
259
|
+
child.stdout?.on("data", (chunk) => {
|
|
260
|
+
const bytes = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);
|
|
261
|
+
stdoutBytes += bytes.byteLength;
|
|
262
|
+
stdoutPreviewLength = append(stdoutPreview, stdoutPreviewLength, bytes);
|
|
263
|
+
if (stdoutBytes > maxStdoutBytes)
|
|
264
|
+
terminate("output_budget_exceeded");
|
|
265
|
+
});
|
|
266
|
+
child.stderr?.on("data", (chunk) => {
|
|
267
|
+
const bytes = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);
|
|
268
|
+
stderrBytes += bytes.byteLength;
|
|
269
|
+
stderrPreviewLength = append(stderrPreview, stderrPreviewLength, bytes);
|
|
270
|
+
if (stderrBytes > maxStderrBytes)
|
|
271
|
+
terminate("output_budget_exceeded");
|
|
272
|
+
});
|
|
273
|
+
child.once("error", () => {
|
|
274
|
+
terminate("spawn_failed");
|
|
275
|
+
});
|
|
276
|
+
child.once("close", (code) => {
|
|
277
|
+
exited = true;
|
|
278
|
+
finalize(code);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
//# sourceMappingURL=node-adapter.js.map
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitize binary output for display/storage.
|
|
3
|
+
* Removes characters that crash string-width or cause display issues:
|
|
4
|
+
* - Control characters (except tab, newline, carriage return)
|
|
5
|
+
* - Lone surrogates
|
|
6
|
+
* - Unicode Format characters (crash string-width due to a bug)
|
|
7
|
+
* - Characters with undefined code points
|
|
8
|
+
*/
|
|
9
|
+
export declare function sanitizeBinaryOutput(str: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Kill a process and all its children (cross-platform)
|
|
12
|
+
*/
|
|
13
|
+
export declare function killProcessTree(pid: number): void;
|
|
14
|
+
//# sourceMappingURL=shell-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-utils.d.ts","sourceRoot":"","sources":["../src/shell-utils.ts"],"names":[],"mappings":"AAQA;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA8BxD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CA+BjD","sourcesContent":["/**\n * Inlined from agent-forge `src/utils/shell.ts` (D-075 S3 extraction) so this\n * package keeps its dependency closure limited to the plugin SDK and typebox.\n * Semantics are unchanged.\n */\nimport { spawn } from \"node:child_process\";\nimport { join } from \"node:path\";\n\n/**\n * Sanitize binary output for display/storage.\n * Removes characters that crash string-width or cause display issues:\n * - Control characters (except tab, newline, carriage return)\n * - Lone surrogates\n * - Unicode Format characters (crash string-width due to a bug)\n * - Characters with undefined code points\n */\nexport function sanitizeBinaryOutput(str: string): string {\n\t// Use Array.from to properly iterate over code points (not code units)\n\t// This handles surrogate pairs correctly and catches edge cases where\n\t// codePointAt() might return undefined\n\treturn Array.from(str)\n\t\t.filter((char) => {\n\t\t\t// Filter out characters that cause string-width to crash\n\t\t\t// This includes:\n\t\t\t// - Unicode format characters\n\t\t\t// - Lone surrogates (already filtered by Array.from)\n\t\t\t// - Control chars except \\t \\n \\r\n\t\t\t// - Characters with undefined code points\n\n\t\t\tconst code = char.codePointAt(0);\n\n\t\t\t// v8 ignore next -- Array.from iterating over a legal string, codePointAt will not return undefined\n\t\t\tif (code === undefined) return false;\n\n\t\t\t// Allow tab, newline, carriage return\n\t\t\tif (code === 0x09 || code === 0x0a || code === 0x0d) return true;\n\n\t\t\t// Filter out control characters (0x00-0x1F, except 0x09, 0x0a, 0x0d)\n\t\t\tif (code <= 0x1f) return false;\n\n\t\t\t// Filter out Unicode format characters\n\t\t\tif (code >= 0xfff9 && code <= 0xfffb) return false;\n\n\t\t\treturn true;\n\t\t})\n\t\t.join(\"\");\n}\n\n/**\n * Kill a process and all its children (cross-platform)\n */\nexport function killProcessTree(pid: number): void {\n\tif (process.platform === \"win32\") {\n\t\t// Use the trusted System32 executable so cleanup does not depend on PATH.\n\t\ttry {\n\t\t\tconst child = spawn(\n\t\t\t\tjoin(process.env.SystemRoot ?? \"C:\\\\Windows\", \"System32\", \"taskkill.exe\"),\n\t\t\t\t[\"/F\", \"/T\", \"/PID\", String(pid)],\n\t\t\t\t{\n\t\t\t\t\tstdio: \"ignore\",\n\t\t\t\t\tdetached: true,\n\t\t\t\t\twindowsHide: true,\n\t\t\t\t},\n\t\t\t);\n\t\t\t// A failed spawn emits \"error\" asynchronously; consume it to avoid crashing Node.\n\t\t\tchild.once(\"error\", () => {});\n\t\t} catch {\n\t\t\t// Ignore errors if taskkill fails.\n\t\t}\n\t} else {\n\t\t// Use SIGKILL on Unix/Linux/Mac\n\t\ttry {\n\t\t\tprocess.kill(-pid, \"SIGKILL\");\n\t\t} catch {\n\t\t\t// Fallback to killing just the child if process group kill fails\n\t\t\ttry {\n\t\t\t\tprocess.kill(pid, \"SIGKILL\");\n\t\t\t} catch {\n\t\t\t\t// Process already dead\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inlined from agent-forge `src/utils/shell.ts` (D-075 S3 extraction) so this
|
|
3
|
+
* package keeps its dependency closure limited to the plugin SDK and typebox.
|
|
4
|
+
* Semantics are unchanged.
|
|
5
|
+
*/
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
/**
|
|
9
|
+
* Sanitize binary output for display/storage.
|
|
10
|
+
* Removes characters that crash string-width or cause display issues:
|
|
11
|
+
* - Control characters (except tab, newline, carriage return)
|
|
12
|
+
* - Lone surrogates
|
|
13
|
+
* - Unicode Format characters (crash string-width due to a bug)
|
|
14
|
+
* - Characters with undefined code points
|
|
15
|
+
*/
|
|
16
|
+
export function sanitizeBinaryOutput(str) {
|
|
17
|
+
// Use Array.from to properly iterate over code points (not code units)
|
|
18
|
+
// This handles surrogate pairs correctly and catches edge cases where
|
|
19
|
+
// codePointAt() might return undefined
|
|
20
|
+
return Array.from(str)
|
|
21
|
+
.filter((char) => {
|
|
22
|
+
// Filter out characters that cause string-width to crash
|
|
23
|
+
// This includes:
|
|
24
|
+
// - Unicode format characters
|
|
25
|
+
// - Lone surrogates (already filtered by Array.from)
|
|
26
|
+
// - Control chars except \t \n \r
|
|
27
|
+
// - Characters with undefined code points
|
|
28
|
+
const code = char.codePointAt(0);
|
|
29
|
+
// v8 ignore next -- Array.from iterating over a legal string, codePointAt will not return undefined
|
|
30
|
+
if (code === undefined)
|
|
31
|
+
return false;
|
|
32
|
+
// Allow tab, newline, carriage return
|
|
33
|
+
if (code === 0x09 || code === 0x0a || code === 0x0d)
|
|
34
|
+
return true;
|
|
35
|
+
// Filter out control characters (0x00-0x1F, except 0x09, 0x0a, 0x0d)
|
|
36
|
+
if (code <= 0x1f)
|
|
37
|
+
return false;
|
|
38
|
+
// Filter out Unicode format characters
|
|
39
|
+
if (code >= 0xfff9 && code <= 0xfffb)
|
|
40
|
+
return false;
|
|
41
|
+
return true;
|
|
42
|
+
})
|
|
43
|
+
.join("");
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Kill a process and all its children (cross-platform)
|
|
47
|
+
*/
|
|
48
|
+
export function killProcessTree(pid) {
|
|
49
|
+
if (process.platform === "win32") {
|
|
50
|
+
// Use the trusted System32 executable so cleanup does not depend on PATH.
|
|
51
|
+
try {
|
|
52
|
+
const child = spawn(join(process.env.SystemRoot ?? "C:\\Windows", "System32", "taskkill.exe"), ["/F", "/T", "/PID", String(pid)], {
|
|
53
|
+
stdio: "ignore",
|
|
54
|
+
detached: true,
|
|
55
|
+
windowsHide: true,
|
|
56
|
+
});
|
|
57
|
+
// A failed spawn emits "error" asynchronously; consume it to avoid crashing Node.
|
|
58
|
+
child.once("error", () => { });
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Ignore errors if taskkill fails.
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Use SIGKILL on Unix/Linux/Mac
|
|
66
|
+
try {
|
|
67
|
+
process.kill(-pid, "SIGKILL");
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Fallback to killing just the child if process group kill fails
|
|
71
|
+
try {
|
|
72
|
+
process.kill(pid, "SIGKILL");
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Process already dead
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=shell-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-utils.js","sourceRoot":"","sources":["../src/shell-utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAW,EAAU;IACzD,uEAAuE;IACvE,sEAAsE;IACtE,uCAAuC;IACvC,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;SACpB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QACjB,yDAAyD;QACzD,iBAAiB;QACjB,8BAA8B;QAC9B,qDAAqD;QACrD,kCAAkC;QAClC,0CAA0C;QAE1C,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QAEjC,oGAAoG;QACpG,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QAErC,sCAAsC;QACtC,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAEjE,qEAAqE;QACrE,IAAI,IAAI,IAAI,IAAI;YAAE,OAAO,KAAK,CAAC;QAE/B,uCAAuC;QACvC,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM;YAAE,OAAO,KAAK,CAAC;QAEnD,OAAO,IAAI,CAAC;IAAA,CACZ,CAAC;SACD,IAAI,CAAC,EAAE,CAAC,CAAC;AAAA,CACX;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW,EAAQ;IAClD,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAClC,0EAA0E;QAC1E,IAAI,CAAC;YACJ,MAAM,KAAK,GAAG,KAAK,CAClB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,aAAa,EAAE,UAAU,EAAE,cAAc,CAAC,EACzE,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,EACjC;gBACC,KAAK,EAAE,QAAQ;gBACf,QAAQ,EAAE,IAAI;gBACd,WAAW,EAAE,IAAI;aACjB,CACD,CAAC;YACF,kFAAkF;YAClF,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACR,mCAAmC;QACpC,CAAC;IACF,CAAC;SAAM,CAAC;QACP,gCAAgC;QAChC,IAAI,CAAC;YACJ,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACR,iEAAiE;YACjE,IAAI,CAAC;gBACJ,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;YAC9B,CAAC;YAAC,MAAM,CAAC;gBACR,uBAAuB;YACxB,CAAC;QACF,CAAC;IACF,CAAC;AAAA,CACD","sourcesContent":["/**\n * Inlined from agent-forge `src/utils/shell.ts` (D-075 S3 extraction) so this\n * package keeps its dependency closure limited to the plugin SDK and typebox.\n * Semantics are unchanged.\n */\nimport { spawn } from \"node:child_process\";\nimport { join } from \"node:path\";\n\n/**\n * Sanitize binary output for display/storage.\n * Removes characters that crash string-width or cause display issues:\n * - Control characters (except tab, newline, carriage return)\n * - Lone surrogates\n * - Unicode Format characters (crash string-width due to a bug)\n * - Characters with undefined code points\n */\nexport function sanitizeBinaryOutput(str: string): string {\n\t// Use Array.from to properly iterate over code points (not code units)\n\t// This handles surrogate pairs correctly and catches edge cases where\n\t// codePointAt() might return undefined\n\treturn Array.from(str)\n\t\t.filter((char) => {\n\t\t\t// Filter out characters that cause string-width to crash\n\t\t\t// This includes:\n\t\t\t// - Unicode format characters\n\t\t\t// - Lone surrogates (already filtered by Array.from)\n\t\t\t// - Control chars except \\t \\n \\r\n\t\t\t// - Characters with undefined code points\n\n\t\t\tconst code = char.codePointAt(0);\n\n\t\t\t// v8 ignore next -- Array.from iterating over a legal string, codePointAt will not return undefined\n\t\t\tif (code === undefined) return false;\n\n\t\t\t// Allow tab, newline, carriage return\n\t\t\tif (code === 0x09 || code === 0x0a || code === 0x0d) return true;\n\n\t\t\t// Filter out control characters (0x00-0x1F, except 0x09, 0x0a, 0x0d)\n\t\t\tif (code <= 0x1f) return false;\n\n\t\t\t// Filter out Unicode format characters\n\t\t\tif (code >= 0xfff9 && code <= 0xfffb) return false;\n\n\t\t\treturn true;\n\t\t})\n\t\t.join(\"\");\n}\n\n/**\n * Kill a process and all its children (cross-platform)\n */\nexport function killProcessTree(pid: number): void {\n\tif (process.platform === \"win32\") {\n\t\t// Use the trusted System32 executable so cleanup does not depend on PATH.\n\t\ttry {\n\t\t\tconst child = spawn(\n\t\t\t\tjoin(process.env.SystemRoot ?? \"C:\\\\Windows\", \"System32\", \"taskkill.exe\"),\n\t\t\t\t[\"/F\", \"/T\", \"/PID\", String(pid)],\n\t\t\t\t{\n\t\t\t\t\tstdio: \"ignore\",\n\t\t\t\t\tdetached: true,\n\t\t\t\t\twindowsHide: true,\n\t\t\t\t},\n\t\t\t);\n\t\t\t// A failed spawn emits \"error\" asynchronously; consume it to avoid crashing Node.\n\t\t\tchild.once(\"error\", () => {});\n\t\t} catch {\n\t\t\t// Ignore errors if taskkill fails.\n\t\t}\n\t} else {\n\t\t// Use SIGKILL on Unix/Linux/Mac\n\t\ttry {\n\t\t\tprocess.kill(-pid, \"SIGKILL\");\n\t\t} catch {\n\t\t\t// Fallback to killing just the child if process group kill fails\n\t\t\ttry {\n\t\t\t\tprocess.kill(pid, \"SIGKILL\");\n\t\t\t} catch {\n\t\t\t\t// Process already dead\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { PluginErrorV1, PluginResultStatusV1 } from "@agent-forge/plugin-sdk";
|
|
2
|
+
export declare const PROCESS_EXEC_PLUGIN_ID_V1: "agent-forge.plugin.process-exec";
|
|
3
|
+
export declare const PROCESS_EXEC_CAPABILITY_VERSION_V1: 1;
|
|
4
|
+
export declare const PROCESS_EXEC_HOST_ADAPTER_ID_V1: "node-process-exec";
|
|
5
|
+
export declare const PROCESS_EXEC_HOST_ADAPTER_VERSION_V1: "1.0.0";
|
|
6
|
+
export interface ProcessExecRequestV1 {
|
|
7
|
+
readonly version: 1;
|
|
8
|
+
readonly operationId: string;
|
|
9
|
+
readonly command: string;
|
|
10
|
+
readonly args: readonly string[];
|
|
11
|
+
readonly timeoutMs?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A bounded process result. stdout/stderr are previews only; the host may keep
|
|
15
|
+
* complete logs in a private artifact, but never returns them through this DTO.
|
|
16
|
+
*/
|
|
17
|
+
export interface ProcessExecResultV1 {
|
|
18
|
+
readonly version: 1;
|
|
19
|
+
readonly operationId: string;
|
|
20
|
+
readonly status: PluginResultStatusV1;
|
|
21
|
+
readonly summary: string;
|
|
22
|
+
readonly exitCode?: number;
|
|
23
|
+
readonly stdoutBytes: number;
|
|
24
|
+
readonly stderrBytes: number;
|
|
25
|
+
readonly stdoutPreview: string;
|
|
26
|
+
readonly stderrPreview: string;
|
|
27
|
+
readonly stdoutTruncated: boolean;
|
|
28
|
+
readonly stderrTruncated: boolean;
|
|
29
|
+
readonly error?: PluginErrorV1;
|
|
30
|
+
}
|
|
31
|
+
export interface ProcessExecAdapterV1 {
|
|
32
|
+
readonly adapterId: string;
|
|
33
|
+
readonly adapterVersion: string;
|
|
34
|
+
readonly run: (request: ProcessExecRequestV1, signal?: AbortSignal) => Promise<ProcessExecResultV1>;
|
|
35
|
+
}
|
|
36
|
+
export interface ProcessExecPluginManifestV1 {
|
|
37
|
+
readonly id: typeof PROCESS_EXEC_PLUGIN_ID_V1;
|
|
38
|
+
readonly pluginVersion: string;
|
|
39
|
+
readonly apiVersion: "1";
|
|
40
|
+
readonly capabilityRefs: readonly {
|
|
41
|
+
readonly id: "process.exec";
|
|
42
|
+
readonly version: 1;
|
|
43
|
+
}[];
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAEnF,eAAO,MAAM,yBAAyB,mCAA6C,CAAC;AACpF,eAAO,MAAM,kCAAkC,GAAa,CAAC;AAC7D,eAAO,MAAM,+BAA+B,qBAA+B,CAAC;AAC5E,eAAO,MAAM,oCAAoC,SAAmB,CAAC;AAErE,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,oBAAoB,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;CACpG;AAED,MAAM,WAAW,2BAA2B;IAC3C,QAAQ,CAAC,EAAE,EAAE,OAAO,yBAAyB,CAAC;IAC9C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAC;IACzB,QAAQ,CAAC,cAAc,EAAE,SAAS;QAAE,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;KAAE,EAAE,CAAC;CACzF","sourcesContent":["import type { PluginErrorV1, PluginResultStatusV1 } from \"@agent-forge/plugin-sdk\";\n\nexport const PROCESS_EXEC_PLUGIN_ID_V1 = \"agent-forge.plugin.process-exec\" as const;\nexport const PROCESS_EXEC_CAPABILITY_VERSION_V1 = 1 as const;\nexport const PROCESS_EXEC_HOST_ADAPTER_ID_V1 = \"node-process-exec\" as const;\nexport const PROCESS_EXEC_HOST_ADAPTER_VERSION_V1 = \"1.0.0\" as const;\n\nexport interface ProcessExecRequestV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly command: string;\n\treadonly args: readonly string[];\n\treadonly timeoutMs?: number;\n}\n\n/**\n * A bounded process result. stdout/stderr are previews only; the host may keep\n * complete logs in a private artifact, but never returns them through this DTO.\n */\nexport interface ProcessExecResultV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly status: PluginResultStatusV1;\n\treadonly summary: string;\n\treadonly exitCode?: number;\n\treadonly stdoutBytes: number;\n\treadonly stderrBytes: number;\n\treadonly stdoutPreview: string;\n\treadonly stderrPreview: string;\n\treadonly stdoutTruncated: boolean;\n\treadonly stderrTruncated: boolean;\n\treadonly error?: PluginErrorV1;\n}\n\nexport interface ProcessExecAdapterV1 {\n\treadonly adapterId: string;\n\treadonly adapterVersion: string;\n\treadonly run: (request: ProcessExecRequestV1, signal?: AbortSignal) => Promise<ProcessExecResultV1>;\n}\n\nexport interface ProcessExecPluginManifestV1 {\n\treadonly id: typeof PROCESS_EXEC_PLUGIN_ID_V1;\n\treadonly pluginVersion: string;\n\treadonly apiVersion: \"1\";\n\treadonly capabilityRefs: readonly { readonly id: \"process.exec\"; readonly version: 1 }[];\n}\n"]}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export const PROCESS_EXEC_PLUGIN_ID_V1 = "agent-forge.plugin.process-exec";
|
|
2
|
+
export const PROCESS_EXEC_CAPABILITY_VERSION_V1 = 1;
|
|
3
|
+
export const PROCESS_EXEC_HOST_ADAPTER_ID_V1 = "node-process-exec";
|
|
4
|
+
export const PROCESS_EXEC_HOST_ADAPTER_VERSION_V1 = "1.0.0";
|
|
5
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,yBAAyB,GAAG,iCAA0C,CAAC;AACpF,MAAM,CAAC,MAAM,kCAAkC,GAAG,CAAU,CAAC;AAC7D,MAAM,CAAC,MAAM,+BAA+B,GAAG,mBAA4B,CAAC;AAC5E,MAAM,CAAC,MAAM,oCAAoC,GAAG,OAAgB,CAAC","sourcesContent":["import type { PluginErrorV1, PluginResultStatusV1 } from \"@agent-forge/plugin-sdk\";\n\nexport const PROCESS_EXEC_PLUGIN_ID_V1 = \"agent-forge.plugin.process-exec\" as const;\nexport const PROCESS_EXEC_CAPABILITY_VERSION_V1 = 1 as const;\nexport const PROCESS_EXEC_HOST_ADAPTER_ID_V1 = \"node-process-exec\" as const;\nexport const PROCESS_EXEC_HOST_ADAPTER_VERSION_V1 = \"1.0.0\" as const;\n\nexport interface ProcessExecRequestV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly command: string;\n\treadonly args: readonly string[];\n\treadonly timeoutMs?: number;\n}\n\n/**\n * A bounded process result. stdout/stderr are previews only; the host may keep\n * complete logs in a private artifact, but never returns them through this DTO.\n */\nexport interface ProcessExecResultV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly status: PluginResultStatusV1;\n\treadonly summary: string;\n\treadonly exitCode?: number;\n\treadonly stdoutBytes: number;\n\treadonly stderrBytes: number;\n\treadonly stdoutPreview: string;\n\treadonly stderrPreview: string;\n\treadonly stdoutTruncated: boolean;\n\treadonly stderrTruncated: boolean;\n\treadonly error?: PluginErrorV1;\n}\n\nexport interface ProcessExecAdapterV1 {\n\treadonly adapterId: string;\n\treadonly adapterVersion: string;\n\treadonly run: (request: ProcessExecRequestV1, signal?: AbortSignal) => Promise<ProcessExecResultV1>;\n}\n\nexport interface ProcessExecPluginManifestV1 {\n\treadonly id: typeof PROCESS_EXEC_PLUGIN_ID_V1;\n\treadonly pluginVersion: string;\n\treadonly apiVersion: \"1\";\n\treadonly capabilityRefs: readonly { readonly id: \"process.exec\"; readonly version: 1 }[];\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ai-agent-forge/plugin-process-exec",
|
|
3
|
+
"version": "0.85.0",
|
|
4
|
+
"description": "Process execution capability plugin for Agent Forge (full-control process_exec tool)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"agent-forge.json",
|
|
17
|
+
"plugin.json",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"clean": "shx rm -rf dist",
|
|
22
|
+
"build": "tsgo -p tsconfig.build.json",
|
|
23
|
+
"test": "vitest --run",
|
|
24
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"agent-forge",
|
|
28
|
+
"plugin",
|
|
29
|
+
"process",
|
|
30
|
+
"exec"
|
|
31
|
+
],
|
|
32
|
+
"author": "Agent Forge contributors",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/agent-forge/agent-forge.git",
|
|
37
|
+
"directory": "plugins/process-exec"
|
|
38
|
+
},
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/agent-forge/agent-forge/issues"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/agent-forge/agent-forge#readme",
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=22.19.0"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@agent-forge/plugin-sdk": "^0.85.0",
|
|
48
|
+
"typebox": "1.3.27"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@agent-forge/agent-forge": "^0.85.0",
|
|
52
|
+
"shx": "0.4.0",
|
|
53
|
+
"vitest": "4.1.9"
|
|
54
|
+
}
|
|
55
|
+
}
|
package/plugin.json
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "agent-forge.plugin.process-exec",
|
|
3
|
+
"version": "0.84.4",
|
|
4
|
+
"apiVersion": "1-draft",
|
|
5
|
+
"entry": "./dist/entry.js",
|
|
6
|
+
"hostVersion": ">=0.84.4",
|
|
7
|
+
"requiredCapabilities": ["tools"],
|
|
8
|
+
"provides": [{ "id": "process.exec", "version": 1, "kind": "contribution" }]
|
|
9
|
+
}
|