@ai-agent-forge/plugin-workspace-files-write 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 +44 -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 +133 -0
- package/dist/capability.js.map +1 -0
- package/dist/entry.d.ts +14 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +14 -0
- package/dist/entry.js.map +1 -0
- package/dist/host.d.ts +22 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +258 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +5 -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 +13 -0
- package/dist/node-adapter.d.ts.map +1 -0
- package/dist/node-adapter.js +184 -0
- package/dist/node-adapter.js.map +1 -0
- package/dist/types.d.ts +74 -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,44 @@
|
|
|
1
|
+
# @agent-forge/plugin-workspace-files-write
|
|
2
|
+
|
|
3
|
+
Workspace file write capability plugin for [Agent Forge](https://github.com/agent-forge/agent-forge), extracted from
|
|
4
|
+
the host's `src/plugins/workspace-files-write` (D-075 S3). It registers one full-control tool, `workspace_write`, which
|
|
5
|
+
atomically replaces or patches a host-owned file using opaque file and artifact references; bytes and local paths never
|
|
6
|
+
cross the capability boundary.
|
|
7
|
+
|
|
8
|
+
The plugin is deliberately inert at load time. Without a host-bound write seam the tool reports an explicit
|
|
9
|
+
`unsupported` result; hosts that want real writes bind `createNodeWorkspaceFilesWriteHostAdapterV1` (sandboxed root,
|
|
10
|
+
artifact resolution, version-checked atomic commits) through their profile machinery. Readonly hosts get no tool
|
|
11
|
+
registration at all.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
# from a local checkout
|
|
17
|
+
agent-forge plugin install ./plugins/workspace-files-write
|
|
18
|
+
|
|
19
|
+
# from npm
|
|
20
|
+
agent-forge plugin install @agent-forge/plugin-workspace-files-write
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The package ships two manifests: `agent-forge.json` (ecosystem manifest for the deterministic plugin resolver) and
|
|
24
|
+
`plugin.json` (host-load manifest consumed by `loadCapabilityPluginFromManifest`, entry `./dist/entry.js`).
|
|
25
|
+
|
|
26
|
+
## Library surface
|
|
27
|
+
|
|
28
|
+
`import { createNodeWorkspaceFilesWriteHostAdapterV1, createWorkspaceFilesWriteCapabilityFactoryV1 } from "@agent-forge/plugin-workspace-files-write";`
|
|
29
|
+
|
|
30
|
+
- `createNodeWorkspaceFilesWriteAdapterV1(options?)` — host-injection adapter; requires a host `write` seam, otherwise
|
|
31
|
+
returns `unsupported`.
|
|
32
|
+
- `createNodeWorkspaceFilesWriteHostAdapterV1({ root, resolveFileRef, readArtifact, ... })` — real sandboxed host
|
|
33
|
+
writer: atomic rename commits, expectedVersion checks, per-path single-flight serialization, `committed` reporting.
|
|
34
|
+
- `createWorkspaceFilesWriteCapabilityFactoryV1(adapter?)` — registers `workspace_write` on a full-control
|
|
35
|
+
`CapabilityAPI` (defaults to the unbound adapter).
|
|
36
|
+
- Default export of `dist/entry.js` — the precomposed factory used by external plugin loading.
|
|
37
|
+
|
|
38
|
+
## Development
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
npm install --ignore-scripts # repo root, creates the workspace symlink
|
|
42
|
+
npm run build # plugins/workspace-files-write (tsgo)
|
|
43
|
+
npm test # vitest; the entry fidelity test requires `npm run build` first
|
|
44
|
+
```
|
package/agent-forge.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"packageFormatVersion": 1,
|
|
3
|
+
"id": "agent-forge.plugin.workspace-files-write",
|
|
4
|
+
"pluginVersion": "0.84.4",
|
|
5
|
+
"apiVersion": "1",
|
|
6
|
+
"minHostVersion": "0.84.4",
|
|
7
|
+
"capabilityRefs": [{ "id": "workspace.files.write", "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 { WorkspaceFilesWriteAdapterV1 } from "./types.ts";
|
|
3
|
+
/** Registers opaque file writes and patches only for a full-control host. */
|
|
4
|
+
export declare function createWorkspaceFilesWriteCapabilityFactoryV1(runtime?: WorkspaceFilesWriteAdapterV1): 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,EACX,4BAA4B,EAI5B,MAAM,YAAY,CAAC;AAwGpB,6EAA6E;AAC7E,wBAAgB,4CAA4C,CAC3D,OAAO,GAAE,4BAAuE,GAC9E,2BAA2B,CA+C7B","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { CapabilityPluginSyncFactory } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { Value } from \"typebox/value\";\nimport {\n\tcreateNodeWorkspaceFilesWriteAdapterV1,\n\tprojectWorkspaceFilesWriteV1,\n\tWORKSPACE_FILES_WRITE_MAX_OPERATION_ID_CHARS_V1,\n\tWORKSPACE_FILES_WRITE_MAX_REF_CHARS_V1,\n\tWORKSPACE_FILES_WRITE_MAX_VERSION_CHARS_V1,\n} from \"./node-adapter.ts\";\nimport type {\n\tWorkspaceFilesWriteAdapterV1,\n\tWorkspaceFilesWriteModeV1,\n\tWorkspaceFilesWriteRequestV1,\n\tWorkspaceFilesWriteResultV1,\n} from \"./types.ts\";\n\nconst refSchema = Type.String({\n\tminLength: 1,\n\tmaxLength: WORKSPACE_FILES_WRITE_MAX_REF_CHARS_V1,\n\tpattern: \"^[A-Za-z0-9][A-Za-z0-9._:-]*$\",\n});\nconst requestSchema = Type.Object(\n\t{\n\t\tfileRef: refSchema,\n\t\tmode: Type.Union([Type.Literal(\"replace\"), Type.Literal(\"patch\")]),\n\t\tsourceArtifactRef: refSchema,\n\t\texpectedVersion: Type.Optional(\n\t\t\tType.String({\n\t\t\t\tminLength: 1,\n\t\t\t\tmaxLength: WORKSPACE_FILES_WRITE_MAX_VERSION_CHARS_V1,\n\t\t\t\tpattern: \"^[A-Za-z0-9][A-Za-z0-9._:-]*$\",\n\t\t\t}),\n\t\t),\n\t\tcreateIfMissing: Type.Optional(Type.Boolean()),\n\t},\n\t{ additionalProperties: false },\n);\nconst inputSchema = Type.Object({ request: requestSchema }, { additionalProperties: false });\n\nfunction generatedOperationId(): string {\n\treturn `profile-runtime-tool-call-${randomUUID()}`;\n}\n\nfunction operationIdFor(first: unknown, second: unknown): string {\n\tconst explicit = second === undefined ? undefined : first;\n\treturn typeof explicit === \"string\" &&\n\t\texplicit.trim() !== \"\" &&\n\t\texplicit.length <= WORKSPACE_FILES_WRITE_MAX_OPERATION_ID_CHARS_V1\n\t\t? explicit\n\t\t: generatedOperationId();\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(\"Write input must be an object\");\n\treturn value as Record<string, unknown>;\n}\n\nfunction signalFrom(value: unknown): AbortSignal | undefined {\n\treturn value instanceof AbortSignal ? value : undefined;\n}\n\nfunction fallback(\n\toperationId: string,\n\tstatus: \"failed\" | \"cancelled\",\n\trequest?: Partial<WorkspaceFilesWriteRequestV1>,\n): WorkspaceFilesWriteResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus,\n\t\tfileRef: request?.fileRef ?? \"invalid:file\",\n\t\tmode: (request?.mode ?? \"replace\") as WorkspaceFilesWriteModeV1,\n\t\tchanged: false,\n\t\tbytesWritten: 0,\n\t\terror: {\n\t\t\tversion: 1,\n\t\t\tcode: status === \"cancelled\" ? \"cancelled\" : \"adapter_failed\",\n\t\t\tmessage: status === \"cancelled\" ? \"Workspace write was cancelled\" : \"Workspace writer failed\",\n\t\t},\n\t};\n}\n\nfunction invalidInput(operationId: string): WorkspaceFilesWriteResultV1 {\n\treturn {\n\t\t...fallback(operationId, \"failed\"),\n\t\terror: { version: 1, code: \"invalid_input\", message: \"Workspace write request was invalid\" },\n\t};\n}\n\nfunction cancelled(operationId: string, request?: Partial<WorkspaceFilesWriteRequestV1>): WorkspaceFilesWriteResultV1 {\n\treturn fallback(operationId, \"cancelled\", request);\n}\n\nfunction malformed(operationId: string, request?: Partial<WorkspaceFilesWriteRequestV1>): WorkspaceFilesWriteResultV1 {\n\treturn {\n\t\t...fallback(operationId, \"failed\", request),\n\t\terror: { version: 1, code: \"adapter_failed\", message: \"Workspace writer returned an invalid result\" },\n\t};\n}\n\nasync function runWithCancellation(\n\trun: () => Promise<WorkspaceFilesWriteResultV1>,\n\tsignal: AbortSignal | undefined,\n\toperationId: string,\n\trequest: WorkspaceFilesWriteRequestV1,\n): Promise<WorkspaceFilesWriteResultV1> {\n\tconst result = await run();\n\tif (!signal?.aborted || result.status === \"cancelled\") return result;\n\tconst replacement = cancelled(operationId, request);\n\t// An adapter that already committed the write must not lose that fact when\n\t// its result is converted to cancelled after an abort.\n\treturn result.committed === true\n\t\t? { ...replacement, committed: true, changed: result.changed, bytesWritten: result.bytesWritten }\n\t\t: replacement;\n}\n\n/** Registers opaque file writes and patches only for a full-control host. */\nexport function createWorkspaceFilesWriteCapabilityFactoryV1(\n\truntime: WorkspaceFilesWriteAdapterV1 = createNodeWorkspaceFilesWriteAdapterV1(),\n): CapabilityPluginSyncFactory {\n\treturn (api) => {\n\t\tif (api.host.mode === \"readonly\") return;\n\t\tapi.registerTool({\n\t\t\tname: \"workspace_write\",\n\t\t\tlabel: \"Write workspace file\",\n\t\t\tdescription: \"Atomically replace or patch a host-owned file using opaque artifact references\",\n\t\t\tparameters: inputSchema,\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tconst operationId = operationIdFor(first, second);\n\t\t\t\tlet input: Record<string, unknown>;\n\t\t\t\ttry {\n\t\t\t\t\tinput = objectInput(first, second);\n\t\t\t\t\tif (!Value.Check(inputSchema, input)) return invalidInput(operationId);\n\t\t\t\t} catch {\n\t\t\t\t\treturn invalidInput(operationId);\n\t\t\t\t}\n\t\t\t\tconst requestInput = input.request as Record<string, unknown>;\n\t\t\t\tconst request: WorkspaceFilesWriteRequestV1 = {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\toperationId,\n\t\t\t\t\tfileRef: requestInput.fileRef as string,\n\t\t\t\t\tmode: requestInput.mode as WorkspaceFilesWriteModeV1,\n\t\t\t\t\tsourceArtifactRef: requestInput.sourceArtifactRef as string,\n\t\t\t\t\t...(requestInput.expectedVersion === undefined\n\t\t\t\t\t\t? {}\n\t\t\t\t\t\t: { expectedVersion: requestInput.expectedVersion as string }),\n\t\t\t\t\t...(requestInput.createIfMissing === undefined\n\t\t\t\t\t\t? {}\n\t\t\t\t\t\t: { createIfMissing: requestInput.createIfMissing as boolean }),\n\t\t\t\t};\n\t\t\t\tconst signal = signalFrom(third);\n\t\t\t\tif (signal?.aborted) return cancelled(operationId, request);\n\t\t\t\ttry {\n\t\t\t\t\tconst output = await runWithCancellation(\n\t\t\t\t\t\t() => runtime.write(request, signal),\n\t\t\t\t\t\tsignal,\n\t\t\t\t\t\toperationId,\n\t\t\t\t\t\trequest,\n\t\t\t\t\t);\n\t\t\t\t\treturn projectWorkspaceFilesWriteV1(output, request) ?? malformed(operationId, request);\n\t\t\t\t} catch {\n\t\t\t\t\treturn signal?.aborted ? cancelled(operationId, request) : malformed(operationId, request);\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t};\n}\n"]}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { Value } from "typebox/value";
|
|
4
|
+
import { createNodeWorkspaceFilesWriteAdapterV1, projectWorkspaceFilesWriteV1, WORKSPACE_FILES_WRITE_MAX_OPERATION_ID_CHARS_V1, WORKSPACE_FILES_WRITE_MAX_REF_CHARS_V1, WORKSPACE_FILES_WRITE_MAX_VERSION_CHARS_V1, } from "./node-adapter.js";
|
|
5
|
+
const refSchema = Type.String({
|
|
6
|
+
minLength: 1,
|
|
7
|
+
maxLength: WORKSPACE_FILES_WRITE_MAX_REF_CHARS_V1,
|
|
8
|
+
pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
|
|
9
|
+
});
|
|
10
|
+
const requestSchema = Type.Object({
|
|
11
|
+
fileRef: refSchema,
|
|
12
|
+
mode: Type.Union([Type.Literal("replace"), Type.Literal("patch")]),
|
|
13
|
+
sourceArtifactRef: refSchema,
|
|
14
|
+
expectedVersion: Type.Optional(Type.String({
|
|
15
|
+
minLength: 1,
|
|
16
|
+
maxLength: WORKSPACE_FILES_WRITE_MAX_VERSION_CHARS_V1,
|
|
17
|
+
pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
|
|
18
|
+
})),
|
|
19
|
+
createIfMissing: Type.Optional(Type.Boolean()),
|
|
20
|
+
}, { additionalProperties: false });
|
|
21
|
+
const inputSchema = Type.Object({ request: requestSchema }, { additionalProperties: false });
|
|
22
|
+
function generatedOperationId() {
|
|
23
|
+
return `profile-runtime-tool-call-${randomUUID()}`;
|
|
24
|
+
}
|
|
25
|
+
function operationIdFor(first, second) {
|
|
26
|
+
const explicit = second === undefined ? undefined : first;
|
|
27
|
+
return typeof explicit === "string" &&
|
|
28
|
+
explicit.trim() !== "" &&
|
|
29
|
+
explicit.length <= WORKSPACE_FILES_WRITE_MAX_OPERATION_ID_CHARS_V1
|
|
30
|
+
? explicit
|
|
31
|
+
: generatedOperationId();
|
|
32
|
+
}
|
|
33
|
+
function objectInput(first, second) {
|
|
34
|
+
const value = second === undefined ? first : second;
|
|
35
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
36
|
+
throw new Error("Write input must be an object");
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
function signalFrom(value) {
|
|
40
|
+
return value instanceof AbortSignal ? value : undefined;
|
|
41
|
+
}
|
|
42
|
+
function fallback(operationId, status, request) {
|
|
43
|
+
return {
|
|
44
|
+
version: 1,
|
|
45
|
+
operationId,
|
|
46
|
+
status,
|
|
47
|
+
fileRef: request?.fileRef ?? "invalid:file",
|
|
48
|
+
mode: (request?.mode ?? "replace"),
|
|
49
|
+
changed: false,
|
|
50
|
+
bytesWritten: 0,
|
|
51
|
+
error: {
|
|
52
|
+
version: 1,
|
|
53
|
+
code: status === "cancelled" ? "cancelled" : "adapter_failed",
|
|
54
|
+
message: status === "cancelled" ? "Workspace write was cancelled" : "Workspace writer failed",
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function invalidInput(operationId) {
|
|
59
|
+
return {
|
|
60
|
+
...fallback(operationId, "failed"),
|
|
61
|
+
error: { version: 1, code: "invalid_input", message: "Workspace write request was invalid" },
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function cancelled(operationId, request) {
|
|
65
|
+
return fallback(operationId, "cancelled", request);
|
|
66
|
+
}
|
|
67
|
+
function malformed(operationId, request) {
|
|
68
|
+
return {
|
|
69
|
+
...fallback(operationId, "failed", request),
|
|
70
|
+
error: { version: 1, code: "adapter_failed", message: "Workspace writer returned an invalid result" },
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
async function runWithCancellation(run, signal, operationId, request) {
|
|
74
|
+
const result = await run();
|
|
75
|
+
if (!signal?.aborted || result.status === "cancelled")
|
|
76
|
+
return result;
|
|
77
|
+
const replacement = cancelled(operationId, request);
|
|
78
|
+
// An adapter that already committed the write must not lose that fact when
|
|
79
|
+
// its result is converted to cancelled after an abort.
|
|
80
|
+
return result.committed === true
|
|
81
|
+
? { ...replacement, committed: true, changed: result.changed, bytesWritten: result.bytesWritten }
|
|
82
|
+
: replacement;
|
|
83
|
+
}
|
|
84
|
+
/** Registers opaque file writes and patches only for a full-control host. */
|
|
85
|
+
export function createWorkspaceFilesWriteCapabilityFactoryV1(runtime = createNodeWorkspaceFilesWriteAdapterV1()) {
|
|
86
|
+
return (api) => {
|
|
87
|
+
if (api.host.mode === "readonly")
|
|
88
|
+
return;
|
|
89
|
+
api.registerTool({
|
|
90
|
+
name: "workspace_write",
|
|
91
|
+
label: "Write workspace file",
|
|
92
|
+
description: "Atomically replace or patch a host-owned file using opaque artifact references",
|
|
93
|
+
parameters: inputSchema,
|
|
94
|
+
execute: async (first, second, third) => {
|
|
95
|
+
const operationId = operationIdFor(first, second);
|
|
96
|
+
let input;
|
|
97
|
+
try {
|
|
98
|
+
input = objectInput(first, second);
|
|
99
|
+
if (!Value.Check(inputSchema, input))
|
|
100
|
+
return invalidInput(operationId);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return invalidInput(operationId);
|
|
104
|
+
}
|
|
105
|
+
const requestInput = input.request;
|
|
106
|
+
const request = {
|
|
107
|
+
version: 1,
|
|
108
|
+
operationId,
|
|
109
|
+
fileRef: requestInput.fileRef,
|
|
110
|
+
mode: requestInput.mode,
|
|
111
|
+
sourceArtifactRef: requestInput.sourceArtifactRef,
|
|
112
|
+
...(requestInput.expectedVersion === undefined
|
|
113
|
+
? {}
|
|
114
|
+
: { expectedVersion: requestInput.expectedVersion }),
|
|
115
|
+
...(requestInput.createIfMissing === undefined
|
|
116
|
+
? {}
|
|
117
|
+
: { createIfMissing: requestInput.createIfMissing }),
|
|
118
|
+
};
|
|
119
|
+
const signal = signalFrom(third);
|
|
120
|
+
if (signal?.aborted)
|
|
121
|
+
return cancelled(operationId, request);
|
|
122
|
+
try {
|
|
123
|
+
const output = await runWithCancellation(() => runtime.write(request, signal), signal, operationId, request);
|
|
124
|
+
return projectWorkspaceFilesWriteV1(output, request) ?? malformed(operationId, request);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return signal?.aborted ? cancelled(operationId, request) : malformed(operationId, request);
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# 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 { Type } from \"typebox\";\nimport { Value } from \"typebox/value\";\nimport {\n\tcreateNodeWorkspaceFilesWriteAdapterV1,\n\tprojectWorkspaceFilesWriteV1,\n\tWORKSPACE_FILES_WRITE_MAX_OPERATION_ID_CHARS_V1,\n\tWORKSPACE_FILES_WRITE_MAX_REF_CHARS_V1,\n\tWORKSPACE_FILES_WRITE_MAX_VERSION_CHARS_V1,\n} from \"./node-adapter.ts\";\nimport type {\n\tWorkspaceFilesWriteAdapterV1,\n\tWorkspaceFilesWriteModeV1,\n\tWorkspaceFilesWriteRequestV1,\n\tWorkspaceFilesWriteResultV1,\n} from \"./types.ts\";\n\nconst refSchema = Type.String({\n\tminLength: 1,\n\tmaxLength: WORKSPACE_FILES_WRITE_MAX_REF_CHARS_V1,\n\tpattern: \"^[A-Za-z0-9][A-Za-z0-9._:-]*$\",\n});\nconst requestSchema = Type.Object(\n\t{\n\t\tfileRef: refSchema,\n\t\tmode: Type.Union([Type.Literal(\"replace\"), Type.Literal(\"patch\")]),\n\t\tsourceArtifactRef: refSchema,\n\t\texpectedVersion: Type.Optional(\n\t\t\tType.String({\n\t\t\t\tminLength: 1,\n\t\t\t\tmaxLength: WORKSPACE_FILES_WRITE_MAX_VERSION_CHARS_V1,\n\t\t\t\tpattern: \"^[A-Za-z0-9][A-Za-z0-9._:-]*$\",\n\t\t\t}),\n\t\t),\n\t\tcreateIfMissing: Type.Optional(Type.Boolean()),\n\t},\n\t{ additionalProperties: false },\n);\nconst inputSchema = Type.Object({ request: requestSchema }, { additionalProperties: false });\n\nfunction generatedOperationId(): string {\n\treturn `profile-runtime-tool-call-${randomUUID()}`;\n}\n\nfunction operationIdFor(first: unknown, second: unknown): string {\n\tconst explicit = second === undefined ? undefined : first;\n\treturn typeof explicit === \"string\" &&\n\t\texplicit.trim() !== \"\" &&\n\t\texplicit.length <= WORKSPACE_FILES_WRITE_MAX_OPERATION_ID_CHARS_V1\n\t\t? explicit\n\t\t: generatedOperationId();\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(\"Write input must be an object\");\n\treturn value as Record<string, unknown>;\n}\n\nfunction signalFrom(value: unknown): AbortSignal | undefined {\n\treturn value instanceof AbortSignal ? value : undefined;\n}\n\nfunction fallback(\n\toperationId: string,\n\tstatus: \"failed\" | \"cancelled\",\n\trequest?: Partial<WorkspaceFilesWriteRequestV1>,\n): WorkspaceFilesWriteResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus,\n\t\tfileRef: request?.fileRef ?? \"invalid:file\",\n\t\tmode: (request?.mode ?? \"replace\") as WorkspaceFilesWriteModeV1,\n\t\tchanged: false,\n\t\tbytesWritten: 0,\n\t\terror: {\n\t\t\tversion: 1,\n\t\t\tcode: status === \"cancelled\" ? \"cancelled\" : \"adapter_failed\",\n\t\t\tmessage: status === \"cancelled\" ? \"Workspace write was cancelled\" : \"Workspace writer failed\",\n\t\t},\n\t};\n}\n\nfunction invalidInput(operationId: string): WorkspaceFilesWriteResultV1 {\n\treturn {\n\t\t...fallback(operationId, \"failed\"),\n\t\terror: { version: 1, code: \"invalid_input\", message: \"Workspace write request was invalid\" },\n\t};\n}\n\nfunction cancelled(operationId: string, request?: Partial<WorkspaceFilesWriteRequestV1>): WorkspaceFilesWriteResultV1 {\n\treturn fallback(operationId, \"cancelled\", request);\n}\n\nfunction malformed(operationId: string, request?: Partial<WorkspaceFilesWriteRequestV1>): WorkspaceFilesWriteResultV1 {\n\treturn {\n\t\t...fallback(operationId, \"failed\", request),\n\t\terror: { version: 1, code: \"adapter_failed\", message: \"Workspace writer returned an invalid result\" },\n\t};\n}\n\nasync function runWithCancellation(\n\trun: () => Promise<WorkspaceFilesWriteResultV1>,\n\tsignal: AbortSignal | undefined,\n\toperationId: string,\n\trequest: WorkspaceFilesWriteRequestV1,\n): Promise<WorkspaceFilesWriteResultV1> {\n\tconst result = await run();\n\tif (!signal?.aborted || result.status === \"cancelled\") return result;\n\tconst replacement = cancelled(operationId, request);\n\t// An adapter that already committed the write must not lose that fact when\n\t// its result is converted to cancelled after an abort.\n\treturn result.committed === true\n\t\t? { ...replacement, committed: true, changed: result.changed, bytesWritten: result.bytesWritten }\n\t\t: replacement;\n}\n\n/** Registers opaque file writes and patches only for a full-control host. */\nexport function createWorkspaceFilesWriteCapabilityFactoryV1(\n\truntime: WorkspaceFilesWriteAdapterV1 = createNodeWorkspaceFilesWriteAdapterV1(),\n): CapabilityPluginSyncFactory {\n\treturn (api) => {\n\t\tif (api.host.mode === \"readonly\") return;\n\t\tapi.registerTool({\n\t\t\tname: \"workspace_write\",\n\t\t\tlabel: \"Write workspace file\",\n\t\t\tdescription: \"Atomically replace or patch a host-owned file using opaque artifact references\",\n\t\t\tparameters: inputSchema,\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tconst operationId = operationIdFor(first, second);\n\t\t\t\tlet input: Record<string, unknown>;\n\t\t\t\ttry {\n\t\t\t\t\tinput = objectInput(first, second);\n\t\t\t\t\tif (!Value.Check(inputSchema, input)) return invalidInput(operationId);\n\t\t\t\t} catch {\n\t\t\t\t\treturn invalidInput(operationId);\n\t\t\t\t}\n\t\t\t\tconst requestInput = input.request as Record<string, unknown>;\n\t\t\t\tconst request: WorkspaceFilesWriteRequestV1 = {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\toperationId,\n\t\t\t\t\tfileRef: requestInput.fileRef as string,\n\t\t\t\t\tmode: requestInput.mode as WorkspaceFilesWriteModeV1,\n\t\t\t\t\tsourceArtifactRef: requestInput.sourceArtifactRef as string,\n\t\t\t\t\t...(requestInput.expectedVersion === undefined\n\t\t\t\t\t\t? {}\n\t\t\t\t\t\t: { expectedVersion: requestInput.expectedVersion as string }),\n\t\t\t\t\t...(requestInput.createIfMissing === undefined\n\t\t\t\t\t\t? {}\n\t\t\t\t\t\t: { createIfMissing: requestInput.createIfMissing as boolean }),\n\t\t\t\t};\n\t\t\t\tconst signal = signalFrom(third);\n\t\t\t\tif (signal?.aborted) return cancelled(operationId, request);\n\t\t\t\ttry {\n\t\t\t\t\tconst output = await runWithCancellation(\n\t\t\t\t\t\t() => runtime.write(request, signal),\n\t\t\t\t\t\tsignal,\n\t\t\t\t\t\toperationId,\n\t\t\t\t\t\trequest,\n\t\t\t\t\t);\n\t\t\t\t\treturn projectWorkspaceFilesWriteV1(output, request) ?? malformed(operationId, request);\n\t\t\t\t} catch {\n\t\t\t\t\treturn signal?.aborted ? cancelled(operationId, request) : malformed(operationId, request);\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,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.
|
|
3
|
+
*
|
|
4
|
+
* The capability factory already defaults to the Node adapter constructed
|
|
5
|
+
* without host injection: the write seam (artifact resolution, sandboxed
|
|
6
|
+
* paths, atomic writes) is host-owned, so the registered `workspace_write`
|
|
7
|
+
* tool reports an explicit `unsupported` result until a host binds a real
|
|
8
|
+
* writer through its own profile machinery (fail-closed by design, same
|
|
9
|
+
* contract as the embedded build). Readonly hosts get no tool registration
|
|
10
|
+
* at all.
|
|
11
|
+
*/
|
|
12
|
+
declare const _default: import("@agent-forge/plugin-sdk").CapabilityPluginSyncFactory;
|
|
13
|
+
export default _default;
|
|
14
|
+
//# sourceMappingURL=entry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;;AACH,wBAA8D","sourcesContent":["import { createWorkspaceFilesWriteCapabilityFactoryV1 } from \"./capability.ts\";\n\n/**\n * Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.\n *\n * The capability factory already defaults to the Node adapter constructed\n * without host injection: the write seam (artifact resolution, sandboxed\n * paths, atomic writes) is host-owned, so the registered `workspace_write`\n * tool reports an explicit `unsupported` result until a host binds a real\n * writer through its own profile machinery (fail-closed by design, same\n * contract as the embedded build). Readonly hosts get no tool registration\n * at all.\n */\nexport default createWorkspaceFilesWriteCapabilityFactoryV1();\n"]}
|
package/dist/entry.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createWorkspaceFilesWriteCapabilityFactoryV1 } from "./capability.js";
|
|
2
|
+
/**
|
|
3
|
+
* Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.
|
|
4
|
+
*
|
|
5
|
+
* The capability factory already defaults to the Node adapter constructed
|
|
6
|
+
* without host injection: the write seam (artifact resolution, sandboxed
|
|
7
|
+
* paths, atomic writes) is host-owned, so the registered `workspace_write`
|
|
8
|
+
* tool reports an explicit `unsupported` result until a host binds a real
|
|
9
|
+
* writer through its own profile machinery (fail-closed by design, same
|
|
10
|
+
* contract as the embedded build). Readonly hosts get no tool registration
|
|
11
|
+
* at all.
|
|
12
|
+
*/
|
|
13
|
+
export default createWorkspaceFilesWriteCapabilityFactoryV1();
|
|
14
|
+
//# sourceMappingURL=entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,4CAA4C,EAAE,MAAM,iBAAiB,CAAC;AAE/E;;;;;;;;;;GAUG;AACH,eAAe,4CAA4C,EAAE,CAAC","sourcesContent":["import { createWorkspaceFilesWriteCapabilityFactoryV1 } from \"./capability.ts\";\n\n/**\n * Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.\n *\n * The capability factory already defaults to the Node adapter constructed\n * without host injection: the write seam (artifact resolution, sandboxed\n * paths, atomic writes) is host-owned, so the registered `workspace_write`\n * tool reports an explicit `unsupported` result until a host binds a real\n * writer through its own profile machinery (fail-closed by design, same\n * contract as the embedded build). Readonly hosts get no tool registration\n * at all.\n */\nexport default createWorkspaceFilesWriteCapabilityFactoryV1();\n"]}
|
package/dist/host.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { NodeWorkspaceFilesWriteHostOptionsV1, WorkspaceFilesPatchDocumentV1, WorkspaceFilesWriteAdapterV1, WorkspaceFilesWriteRequestV1, WorkspaceFilesWriteResultV1 } from "./types.ts";
|
|
2
|
+
declare function resolveSandboxedPath(root: string, fileRef: string, resolveFileRef: NodeWorkspaceFilesWriteHostOptionsV1["resolveFileRef"], signal: AbortSignal): Promise<string>;
|
|
3
|
+
declare function parsePatch(bytes: Uint8Array): WorkspaceFilesPatchDocumentV1;
|
|
4
|
+
declare function applyPatch(source: Uint8Array, patchBytes: Uint8Array): Uint8Array;
|
|
5
|
+
/**
|
|
6
|
+
* Host-level write: resolves the sandboxed path, then serializes the
|
|
7
|
+
* read-check-commit critical section per resolved canonical path so concurrent
|
|
8
|
+
* requests cannot both pass the expectedVersion check (single-flight). After
|
|
9
|
+
* the rename committed new bytes, a later artifact-record failure or
|
|
10
|
+
* cancellation is reported with `committed: true` and a truthful changed state
|
|
11
|
+
* instead of masking the write that already reached the disk. The timeout and
|
|
12
|
+
* stop race of the full adapter lives in node-adapter; this seam exposes the
|
|
13
|
+
* host mapping itself.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createNodeWorkspaceFilesWriteHostWriteV1(options: NodeWorkspaceFilesWriteHostOptionsV1): (request: WorkspaceFilesWriteRequestV1, signal: AbortSignal) => Promise<WorkspaceFilesWriteResultV1>;
|
|
16
|
+
/**
|
|
17
|
+
* Real Node host adapter. All local paths and bytes stay inside this host;
|
|
18
|
+
* model-facing requests and results remain opaque and bounded.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createNodeWorkspaceFilesWriteHostAdapterV1(options: NodeWorkspaceFilesWriteHostOptionsV1): WorkspaceFilesWriteAdapterV1;
|
|
21
|
+
export { applyPatch, parsePatch, resolveSandboxedPath };
|
|
22
|
+
//# sourceMappingURL=host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACX,oCAAoC,EACpC,6BAA6B,EAE7B,4BAA4B,EAC5B,4BAA4B,EAC5B,2BAA2B,EAC3B,MAAM,YAAY,CAAC;AAmCpB,iBAAe,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,oCAAoC,CAAC,gBAAgB,CAAC,EACtE,MAAM,EAAE,WAAW,GACjB,OAAO,CAAC,MAAM,CAAC,CAoBjB;AAcD,iBAAS,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,6BAA6B,CAoCpE;AAED,iBAAS,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,GAAG,UAAU,CAW1E;AAeD;;;;;;;;;GASG;AACH,wBAAgB,wCAAwC,CACvD,OAAO,EAAE,oCAAoC,GAC3C,CAAC,OAAO,EAAE,4BAA4B,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,2BAA2B,CAAC,CAgItG;AAED;;;GAGG;AACH,wBAAgB,0CAA0C,CACzD,OAAO,EAAE,oCAAoC,GAC3C,4BAA4B,CAK9B;AAED,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,oBAAoB,EAAE,CAAC","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { readFile as fsReadFile, mkdtemp, realpath, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport { basename, dirname, isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport { createNodeWorkspaceFilesWriteAdapterV1 } from \"./node-adapter.ts\";\nimport type {\n\tNodeWorkspaceFilesWriteHostOptionsV1,\n\tWorkspaceFilesPatchDocumentV1,\n\tWorkspaceFilesPatchOperationV1,\n\tWorkspaceFilesWriteAdapterV1,\n\tWorkspaceFilesWriteRequestV1,\n\tWorkspaceFilesWriteResultV1,\n} from \"./types.ts\";\n\nconst MAX_ARTIFACT_BYTES_V1 = 256 * 1024 * 1024;\nconst MAX_PATCH_OPERATIONS_V1 = 16_384;\nconst MAX_PATCH_REPLACEMENT_CHARS_V1 = 1_048_576;\n\nclass HostWriteFailureV1 extends Error {\n\treadonly code: string;\n\n\tconstructor(code: string, message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"HostWriteFailureV1\";\n\t\tthis.code = code;\n\t}\n}\n\nfunction throwIfAborted(signal: AbortSignal): void {\n\tif (signal.aborted) throw new HostWriteFailureV1(\"cancelled\", \"Workspace write was cancelled\");\n}\n\nfunction digest(bytes: Uint8Array): string {\n\treturn createHash(\"sha256\").update(bytes).digest(\"hex\");\n}\n\nfunction isInside(root: string, target: string): boolean {\n\tconst relativePath = relative(root, target);\n\treturn (\n\t\trelativePath !== \"\" && relativePath !== \".\" && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath)\n\t);\n}\n\nfunction isWithin(root: string, target: string): boolean {\n\treturn target === root || isInside(root, target);\n}\n\nasync function resolveSandboxedPath(\n\troot: string,\n\tfileRef: string,\n\tresolveFileRef: NodeWorkspaceFilesWriteHostOptionsV1[\"resolveFileRef\"],\n\tsignal: AbortSignal,\n): Promise<string> {\n\tthrowIfAborted(signal);\n\tconst rootPath = await realpath(root);\n\tconst requested = await resolveFileRef(fileRef, signal);\n\tif (!isAbsolute(requested))\n\t\tthrow new HostWriteFailureV1(\"invalid_file_ref\", \"Resolved workspace file is not absolute\");\n\tconst candidate = resolve(requested);\n\tconst parent = await realpath(dirname(candidate));\n\tif (!isWithin(rootPath, parent))\n\t\tthrow new HostWriteFailureV1(\"sandbox_violation\", \"Workspace parent is outside the host root\");\n\tconst canonicalCandidate = join(parent, basename(candidate));\n\tlet existing: string | undefined;\n\ttry {\n\t\texisting = await realpath(canonicalCandidate);\n\t} catch {\n\t\texisting = undefined;\n\t}\n\tif (existing !== undefined && !isInside(rootPath, existing))\n\t\tthrow new HostWriteFailureV1(\"sandbox_violation\", \"Workspace file resolves outside the host root\");\n\treturn canonicalCandidate;\n}\n\nasync function readSourceArtifact(\n\trequest: WorkspaceFilesWriteRequestV1,\n\toptions: NodeWorkspaceFilesWriteHostOptionsV1,\n\tsignal: AbortSignal,\n): Promise<Uint8Array> {\n\tthrowIfAborted(signal);\n\tconst bytes = await options.readArtifact(request.sourceArtifactRef, signal);\n\tif (!(bytes instanceof Uint8Array) || bytes.byteLength > MAX_ARTIFACT_BYTES_V1)\n\t\tthrow new HostWriteFailureV1(\"artifact_budget_exceeded\", \"Workspace source artifact exceeds the host budget\");\n\treturn bytes;\n}\n\nfunction parsePatch(bytes: Uint8Array): WorkspaceFilesPatchDocumentV1 {\n\tlet value: unknown;\n\ttry {\n\t\tvalue = JSON.parse(new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes));\n\t} catch {\n\t\tthrow new HostWriteFailureV1(\"invalid_patch\", \"Workspace patch artifact is not valid UTF-8 JSON\");\n\t}\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value))\n\t\tthrow new HostWriteFailureV1(\"invalid_patch\", \"Workspace patch document must be an object\");\n\tconst record = value as Record<string, unknown>;\n\tif (record.version !== 1 || !Array.isArray(record.operations) || record.operations.length > MAX_PATCH_OPERATIONS_V1)\n\t\tthrow new HostWriteFailureV1(\"invalid_patch\", \"Workspace patch document is invalid\");\n\tlet previousEnd = 0;\n\tconst operations: WorkspaceFilesPatchOperationV1[] = [];\n\tfor (const item of record.operations) {\n\t\tif (item === null || typeof item !== \"object\" || Array.isArray(item))\n\t\t\tthrow new HostWriteFailureV1(\"invalid_patch\", \"Workspace patch operation is invalid\");\n\t\tconst operation = item as Record<string, unknown>;\n\t\tif (\n\t\t\t!Number.isSafeInteger(operation.start) ||\n\t\t\t!Number.isSafeInteger(operation.end) ||\n\t\t\t(operation.start as number) < 0 ||\n\t\t\t(operation.end as number) < (operation.start as number) ||\n\t\t\ttypeof operation.replacement !== \"string\" ||\n\t\t\t[...(operation.replacement as string)].length > MAX_PATCH_REPLACEMENT_CHARS_V1 ||\n\t\t\t(operation.start as number) < previousEnd\n\t\t)\n\t\t\tthrow new HostWriteFailureV1(\"invalid_patch\", \"Workspace patch operation is invalid\");\n\t\tpreviousEnd = operation.end as number;\n\t\toperations.push({\n\t\t\tstart: operation.start as number,\n\t\t\tend: operation.end as number,\n\t\t\treplacement: operation.replacement as string,\n\t\t});\n\t}\n\treturn { version: 1, operations };\n}\n\nfunction applyPatch(source: Uint8Array, patchBytes: Uint8Array): Uint8Array {\n\tconst patch = parsePatch(patchBytes);\n\tconst text = new TextDecoder(\"utf-8\", { fatal: true }).decode(source);\n\tlet output = text;\n\tfor (let index = patch.operations.length - 1; index >= 0; index -= 1) {\n\t\tconst operation = patch.operations[index]!;\n\t\tif (operation.end > output.length)\n\t\t\tthrow new HostWriteFailureV1(\"invalid_patch\", \"Workspace patch range exceeds the file\");\n\t\toutput = `${output.slice(0, operation.start)}${operation.replacement}${output.slice(operation.end)}`;\n\t}\n\treturn new TextEncoder().encode(output);\n}\n\nasync function atomicWrite(path: string, bytes: Uint8Array, signal: AbortSignal): Promise<void> {\n\tthrowIfAborted(signal);\n\tconst directory = await mkdtemp(join(dirname(path), \".agent-forge-write-\"));\n\tconst temporary = join(directory, basename(path));\n\ttry {\n\t\tawait writeFile(temporary, bytes);\n\t\tthrowIfAborted(signal);\n\t\tawait rename(temporary, path);\n\t} finally {\n\t\tawait rm(directory, { recursive: true, force: true }).catch(() => undefined);\n\t}\n}\n\n/**\n * Host-level write: resolves the sandboxed path, then serializes the\n * read-check-commit critical section per resolved canonical path so concurrent\n * requests cannot both pass the expectedVersion check (single-flight). After\n * the rename committed new bytes, a later artifact-record failure or\n * cancellation is reported with `committed: true` and a truthful changed state\n * instead of masking the write that already reached the disk. The timeout and\n * stop race of the full adapter lives in node-adapter; this seam exposes the\n * host mapping itself.\n */\nexport function createNodeWorkspaceFilesWriteHostWriteV1(\n\toptions: NodeWorkspaceFilesWriteHostOptionsV1,\n): (request: WorkspaceFilesWriteRequestV1, signal: AbortSignal) => Promise<WorkspaceFilesWriteResultV1> {\n\tif (!isAbsolute(options.root)) throw new Error(\"root must be an absolute path\");\n\tconst writeGates = new Map<string, Promise<void>>();\n\tconst runSerialized = async <T>(key: string, work: () => Promise<T>): Promise<T> => {\n\t\tconst previous = writeGates.get(key);\n\t\tlet releaseGate: (() => void) | undefined;\n\t\tconst gate = new Promise<void>((resolveGate) => {\n\t\t\treleaseGate = resolveGate;\n\t\t});\n\t\tconst chained =\n\t\t\tprevious === undefined\n\t\t\t\t? gate\n\t\t\t\t: previous.then(\n\t\t\t\t\t\t() => gate,\n\t\t\t\t\t\t() => gate,\n\t\t\t\t\t);\n\t\twriteGates.set(key, chained);\n\t\ttry {\n\t\t\tawait previous;\n\t\t\treturn await work();\n\t\t} finally {\n\t\t\treleaseGate?.();\n\t\t\tif (writeGates.get(key) === chained) writeGates.delete(key);\n\t\t}\n\t};\n\tconst performWrite = async (\n\t\trequest: WorkspaceFilesWriteRequestV1,\n\t\tsignal: AbortSignal,\n\t): Promise<WorkspaceFilesWriteResultV1> => {\n\t\t/** Set only after the rename committed new bytes, so later failures cannot mask the disk state. */\n\t\tconst commit = { committed: false, bytes: 0 };\n\t\ttry {\n\t\t\tthrowIfAborted(signal);\n\t\t\tconst path = await resolveSandboxedPath(options.root, request.fileRef, options.resolveFileRef, signal);\n\t\t\treturn await runSerialized(path, async () => {\n\t\t\t\tthrowIfAborted(signal);\n\t\t\t\tlet current = new Uint8Array();\n\t\t\t\tlet exists = true;\n\t\t\t\ttry {\n\t\t\t\t\tconst information = await stat(path);\n\t\t\t\t\tif (!information.isFile())\n\t\t\t\t\t\tthrow new HostWriteFailureV1(\"invalid_file_ref\", \"Workspace target is not a file\");\n\t\t\t\t\tcurrent = new Uint8Array(\n\t\t\t\t\t\tawait (options.readWorkspaceFile === undefined\n\t\t\t\t\t\t\t? fsReadFile(path)\n\t\t\t\t\t\t\t: options.readWorkspaceFile(path, signal)),\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof HostWriteFailureV1) throw error;\n\t\t\t\t\tif (error === null || typeof error !== \"object\" || (error as { code?: unknown }).code !== \"ENOENT\")\n\t\t\t\t\t\tthrow new HostWriteFailureV1(\"read_failed\", \"Workspace target could not be read\");\n\t\t\t\t\texists = false;\n\t\t\t\t}\n\t\t\t\tif (!exists && request.createIfMissing !== true)\n\t\t\t\t\tthrow new HostWriteFailureV1(\"file_missing\", \"Workspace target does not exist\");\n\t\t\t\tif (request.expectedVersion !== undefined && request.expectedVersion !== digest(current))\n\t\t\t\t\tthrow new HostWriteFailureV1(\n\t\t\t\t\t\t\"version_conflict\",\n\t\t\t\t\t\t\"Workspace target version does not match expectedVersion\",\n\t\t\t\t\t);\n\t\t\t\tconst source = await readSourceArtifact(request, options, signal);\n\t\t\t\tconst next = request.mode === \"replace\" ? source : applyPatch(current, source);\n\t\t\t\tconst changed = digest(current) !== digest(next);\n\t\t\t\tif (changed) {\n\t\t\t\t\tawait atomicWrite(path, next, signal);\n\t\t\t\t\tcommit.committed = true;\n\t\t\t\t\tcommit.bytes = next.byteLength;\n\t\t\t\t}\n\t\t\t\tconst outputArtifactRef =\n\t\t\t\t\toptions.writeArtifact === undefined\n\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t: (\n\t\t\t\t\t\t\t\tawait options.writeArtifact(\n\t\t\t\t\t\t\t\t\t{ operationId: request.operationId, fileRef: request.fileRef, bytes: next },\n\t\t\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t).artifactRef;\n\t\t\t\treturn {\n\t\t\t\t\tversion: 1 as const,\n\t\t\t\t\toperationId: request.operationId,\n\t\t\t\t\tstatus: \"succeeded\" as const,\n\t\t\t\t\tfileRef: request.fileRef,\n\t\t\t\t\tmode: request.mode,\n\t\t\t\t\tchanged,\n\t\t\t\t\tbytesWritten: changed ? next.byteLength : 0,\n\t\t\t\t\t...(outputArtifactRef === undefined ? {} : { outputArtifactRef }),\n\t\t\t\t\tversionToken: digest(next),\n\t\t\t\t};\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconst cancelled = signal.aborted || (error instanceof HostWriteFailureV1 && error.code === \"cancelled\");\n\t\t\tif (commit.committed) {\n\t\t\t\treturn {\n\t\t\t\t\tversion: 1 as const,\n\t\t\t\t\toperationId: request.operationId,\n\t\t\t\t\tstatus: cancelled ? (\"cancelled\" as const) : (\"failed\" as const),\n\t\t\t\t\tfileRef: request.fileRef,\n\t\t\t\t\tmode: request.mode,\n\t\t\t\t\tchanged: true,\n\t\t\t\t\tbytesWritten: commit.bytes,\n\t\t\t\t\tcommitted: true,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tversion: 1 as const,\n\t\t\t\t\t\tcode: cancelled ? \"cancelled\" : \"artifact_record_failed\",\n\t\t\t\t\t\tmessage: cancelled\n\t\t\t\t\t\t\t? \"Workspace write was cancelled\"\n\t\t\t\t\t\t\t: \"Workspace file was committed but the artifact record failed\",\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst code = cancelled ? \"cancelled\" : error instanceof HostWriteFailureV1 ? error.code : \"adapter_failed\";\n\t\t\treturn {\n\t\t\t\tversion: 1 as const,\n\t\t\t\toperationId: request.operationId,\n\t\t\t\tstatus: cancelled ? (\"cancelled\" as const) : (\"failed\" as const),\n\t\t\t\tfileRef: request.fileRef,\n\t\t\t\tmode: request.mode,\n\t\t\t\tchanged: false,\n\t\t\t\tbytesWritten: 0,\n\t\t\t\terror: {\n\t\t\t\t\tversion: 1 as const,\n\t\t\t\t\tcode,\n\t\t\t\t\tmessage: cancelled ? \"Workspace write was cancelled\" : \"Workspace write failed\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t};\n\treturn performWrite;\n}\n\n/**\n * Real Node host adapter. All local paths and bytes stay inside this host;\n * model-facing requests and results remain opaque and bounded.\n */\nexport function createNodeWorkspaceFilesWriteHostAdapterV1(\n\toptions: NodeWorkspaceFilesWriteHostOptionsV1,\n): WorkspaceFilesWriteAdapterV1 {\n\treturn createNodeWorkspaceFilesWriteAdapterV1({\n\t\tdefaultTimeoutMs: options.defaultTimeoutMs,\n\t\twrite: createNodeWorkspaceFilesWriteHostWriteV1(options),\n\t});\n}\n\nexport { applyPatch, parsePatch, resolveSandboxedPath };\n"]}
|
package/dist/host.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile as fsReadFile, mkdtemp, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { createNodeWorkspaceFilesWriteAdapterV1 } from "./node-adapter.js";
|
|
5
|
+
const MAX_ARTIFACT_BYTES_V1 = 256 * 1024 * 1024;
|
|
6
|
+
const MAX_PATCH_OPERATIONS_V1 = 16_384;
|
|
7
|
+
const MAX_PATCH_REPLACEMENT_CHARS_V1 = 1_048_576;
|
|
8
|
+
class HostWriteFailureV1 extends Error {
|
|
9
|
+
code;
|
|
10
|
+
constructor(code, message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "HostWriteFailureV1";
|
|
13
|
+
this.code = code;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function throwIfAborted(signal) {
|
|
17
|
+
if (signal.aborted)
|
|
18
|
+
throw new HostWriteFailureV1("cancelled", "Workspace write was cancelled");
|
|
19
|
+
}
|
|
20
|
+
function digest(bytes) {
|
|
21
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
22
|
+
}
|
|
23
|
+
function isInside(root, target) {
|
|
24
|
+
const relativePath = relative(root, target);
|
|
25
|
+
return (relativePath !== "" && relativePath !== "." && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath));
|
|
26
|
+
}
|
|
27
|
+
function isWithin(root, target) {
|
|
28
|
+
return target === root || isInside(root, target);
|
|
29
|
+
}
|
|
30
|
+
async function resolveSandboxedPath(root, fileRef, resolveFileRef, signal) {
|
|
31
|
+
throwIfAborted(signal);
|
|
32
|
+
const rootPath = await realpath(root);
|
|
33
|
+
const requested = await resolveFileRef(fileRef, signal);
|
|
34
|
+
if (!isAbsolute(requested))
|
|
35
|
+
throw new HostWriteFailureV1("invalid_file_ref", "Resolved workspace file is not absolute");
|
|
36
|
+
const candidate = resolve(requested);
|
|
37
|
+
const parent = await realpath(dirname(candidate));
|
|
38
|
+
if (!isWithin(rootPath, parent))
|
|
39
|
+
throw new HostWriteFailureV1("sandbox_violation", "Workspace parent is outside the host root");
|
|
40
|
+
const canonicalCandidate = join(parent, basename(candidate));
|
|
41
|
+
let existing;
|
|
42
|
+
try {
|
|
43
|
+
existing = await realpath(canonicalCandidate);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
existing = undefined;
|
|
47
|
+
}
|
|
48
|
+
if (existing !== undefined && !isInside(rootPath, existing))
|
|
49
|
+
throw new HostWriteFailureV1("sandbox_violation", "Workspace file resolves outside the host root");
|
|
50
|
+
return canonicalCandidate;
|
|
51
|
+
}
|
|
52
|
+
async function readSourceArtifact(request, options, signal) {
|
|
53
|
+
throwIfAborted(signal);
|
|
54
|
+
const bytes = await options.readArtifact(request.sourceArtifactRef, signal);
|
|
55
|
+
if (!(bytes instanceof Uint8Array) || bytes.byteLength > MAX_ARTIFACT_BYTES_V1)
|
|
56
|
+
throw new HostWriteFailureV1("artifact_budget_exceeded", "Workspace source artifact exceeds the host budget");
|
|
57
|
+
return bytes;
|
|
58
|
+
}
|
|
59
|
+
function parsePatch(bytes) {
|
|
60
|
+
let value;
|
|
61
|
+
try {
|
|
62
|
+
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
throw new HostWriteFailureV1("invalid_patch", "Workspace patch artifact is not valid UTF-8 JSON");
|
|
66
|
+
}
|
|
67
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
68
|
+
throw new HostWriteFailureV1("invalid_patch", "Workspace patch document must be an object");
|
|
69
|
+
const record = value;
|
|
70
|
+
if (record.version !== 1 || !Array.isArray(record.operations) || record.operations.length > MAX_PATCH_OPERATIONS_V1)
|
|
71
|
+
throw new HostWriteFailureV1("invalid_patch", "Workspace patch document is invalid");
|
|
72
|
+
let previousEnd = 0;
|
|
73
|
+
const operations = [];
|
|
74
|
+
for (const item of record.operations) {
|
|
75
|
+
if (item === null || typeof item !== "object" || Array.isArray(item))
|
|
76
|
+
throw new HostWriteFailureV1("invalid_patch", "Workspace patch operation is invalid");
|
|
77
|
+
const operation = item;
|
|
78
|
+
if (!Number.isSafeInteger(operation.start) ||
|
|
79
|
+
!Number.isSafeInteger(operation.end) ||
|
|
80
|
+
operation.start < 0 ||
|
|
81
|
+
operation.end < operation.start ||
|
|
82
|
+
typeof operation.replacement !== "string" ||
|
|
83
|
+
[...operation.replacement].length > MAX_PATCH_REPLACEMENT_CHARS_V1 ||
|
|
84
|
+
operation.start < previousEnd)
|
|
85
|
+
throw new HostWriteFailureV1("invalid_patch", "Workspace patch operation is invalid");
|
|
86
|
+
previousEnd = operation.end;
|
|
87
|
+
operations.push({
|
|
88
|
+
start: operation.start,
|
|
89
|
+
end: operation.end,
|
|
90
|
+
replacement: operation.replacement,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return { version: 1, operations };
|
|
94
|
+
}
|
|
95
|
+
function applyPatch(source, patchBytes) {
|
|
96
|
+
const patch = parsePatch(patchBytes);
|
|
97
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(source);
|
|
98
|
+
let output = text;
|
|
99
|
+
for (let index = patch.operations.length - 1; index >= 0; index -= 1) {
|
|
100
|
+
const operation = patch.operations[index];
|
|
101
|
+
if (operation.end > output.length)
|
|
102
|
+
throw new HostWriteFailureV1("invalid_patch", "Workspace patch range exceeds the file");
|
|
103
|
+
output = `${output.slice(0, operation.start)}${operation.replacement}${output.slice(operation.end)}`;
|
|
104
|
+
}
|
|
105
|
+
return new TextEncoder().encode(output);
|
|
106
|
+
}
|
|
107
|
+
async function atomicWrite(path, bytes, signal) {
|
|
108
|
+
throwIfAborted(signal);
|
|
109
|
+
const directory = await mkdtemp(join(dirname(path), ".agent-forge-write-"));
|
|
110
|
+
const temporary = join(directory, basename(path));
|
|
111
|
+
try {
|
|
112
|
+
await writeFile(temporary, bytes);
|
|
113
|
+
throwIfAborted(signal);
|
|
114
|
+
await rename(temporary, path);
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
await rm(directory, { recursive: true, force: true }).catch(() => undefined);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Host-level write: resolves the sandboxed path, then serializes the
|
|
122
|
+
* read-check-commit critical section per resolved canonical path so concurrent
|
|
123
|
+
* requests cannot both pass the expectedVersion check (single-flight). After
|
|
124
|
+
* the rename committed new bytes, a later artifact-record failure or
|
|
125
|
+
* cancellation is reported with `committed: true` and a truthful changed state
|
|
126
|
+
* instead of masking the write that already reached the disk. The timeout and
|
|
127
|
+
* stop race of the full adapter lives in node-adapter; this seam exposes the
|
|
128
|
+
* host mapping itself.
|
|
129
|
+
*/
|
|
130
|
+
export function createNodeWorkspaceFilesWriteHostWriteV1(options) {
|
|
131
|
+
if (!isAbsolute(options.root))
|
|
132
|
+
throw new Error("root must be an absolute path");
|
|
133
|
+
const writeGates = new Map();
|
|
134
|
+
const runSerialized = async (key, work) => {
|
|
135
|
+
const previous = writeGates.get(key);
|
|
136
|
+
let releaseGate;
|
|
137
|
+
const gate = new Promise((resolveGate) => {
|
|
138
|
+
releaseGate = resolveGate;
|
|
139
|
+
});
|
|
140
|
+
const chained = previous === undefined
|
|
141
|
+
? gate
|
|
142
|
+
: previous.then(() => gate, () => gate);
|
|
143
|
+
writeGates.set(key, chained);
|
|
144
|
+
try {
|
|
145
|
+
await previous;
|
|
146
|
+
return await work();
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
releaseGate?.();
|
|
150
|
+
if (writeGates.get(key) === chained)
|
|
151
|
+
writeGates.delete(key);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const performWrite = async (request, signal) => {
|
|
155
|
+
/** Set only after the rename committed new bytes, so later failures cannot mask the disk state. */
|
|
156
|
+
const commit = { committed: false, bytes: 0 };
|
|
157
|
+
try {
|
|
158
|
+
throwIfAborted(signal);
|
|
159
|
+
const path = await resolveSandboxedPath(options.root, request.fileRef, options.resolveFileRef, signal);
|
|
160
|
+
return await runSerialized(path, async () => {
|
|
161
|
+
throwIfAborted(signal);
|
|
162
|
+
let current = new Uint8Array();
|
|
163
|
+
let exists = true;
|
|
164
|
+
try {
|
|
165
|
+
const information = await stat(path);
|
|
166
|
+
if (!information.isFile())
|
|
167
|
+
throw new HostWriteFailureV1("invalid_file_ref", "Workspace target is not a file");
|
|
168
|
+
current = new Uint8Array(await (options.readWorkspaceFile === undefined
|
|
169
|
+
? fsReadFile(path)
|
|
170
|
+
: options.readWorkspaceFile(path, signal)));
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
if (error instanceof HostWriteFailureV1)
|
|
174
|
+
throw error;
|
|
175
|
+
if (error === null || typeof error !== "object" || error.code !== "ENOENT")
|
|
176
|
+
throw new HostWriteFailureV1("read_failed", "Workspace target could not be read");
|
|
177
|
+
exists = false;
|
|
178
|
+
}
|
|
179
|
+
if (!exists && request.createIfMissing !== true)
|
|
180
|
+
throw new HostWriteFailureV1("file_missing", "Workspace target does not exist");
|
|
181
|
+
if (request.expectedVersion !== undefined && request.expectedVersion !== digest(current))
|
|
182
|
+
throw new HostWriteFailureV1("version_conflict", "Workspace target version does not match expectedVersion");
|
|
183
|
+
const source = await readSourceArtifact(request, options, signal);
|
|
184
|
+
const next = request.mode === "replace" ? source : applyPatch(current, source);
|
|
185
|
+
const changed = digest(current) !== digest(next);
|
|
186
|
+
if (changed) {
|
|
187
|
+
await atomicWrite(path, next, signal);
|
|
188
|
+
commit.committed = true;
|
|
189
|
+
commit.bytes = next.byteLength;
|
|
190
|
+
}
|
|
191
|
+
const outputArtifactRef = options.writeArtifact === undefined
|
|
192
|
+
? undefined
|
|
193
|
+
: (await options.writeArtifact({ operationId: request.operationId, fileRef: request.fileRef, bytes: next }, signal)).artifactRef;
|
|
194
|
+
return {
|
|
195
|
+
version: 1,
|
|
196
|
+
operationId: request.operationId,
|
|
197
|
+
status: "succeeded",
|
|
198
|
+
fileRef: request.fileRef,
|
|
199
|
+
mode: request.mode,
|
|
200
|
+
changed,
|
|
201
|
+
bytesWritten: changed ? next.byteLength : 0,
|
|
202
|
+
...(outputArtifactRef === undefined ? {} : { outputArtifactRef }),
|
|
203
|
+
versionToken: digest(next),
|
|
204
|
+
};
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
const cancelled = signal.aborted || (error instanceof HostWriteFailureV1 && error.code === "cancelled");
|
|
209
|
+
if (commit.committed) {
|
|
210
|
+
return {
|
|
211
|
+
version: 1,
|
|
212
|
+
operationId: request.operationId,
|
|
213
|
+
status: cancelled ? "cancelled" : "failed",
|
|
214
|
+
fileRef: request.fileRef,
|
|
215
|
+
mode: request.mode,
|
|
216
|
+
changed: true,
|
|
217
|
+
bytesWritten: commit.bytes,
|
|
218
|
+
committed: true,
|
|
219
|
+
error: {
|
|
220
|
+
version: 1,
|
|
221
|
+
code: cancelled ? "cancelled" : "artifact_record_failed",
|
|
222
|
+
message: cancelled
|
|
223
|
+
? "Workspace write was cancelled"
|
|
224
|
+
: "Workspace file was committed but the artifact record failed",
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
const code = cancelled ? "cancelled" : error instanceof HostWriteFailureV1 ? error.code : "adapter_failed";
|
|
229
|
+
return {
|
|
230
|
+
version: 1,
|
|
231
|
+
operationId: request.operationId,
|
|
232
|
+
status: cancelled ? "cancelled" : "failed",
|
|
233
|
+
fileRef: request.fileRef,
|
|
234
|
+
mode: request.mode,
|
|
235
|
+
changed: false,
|
|
236
|
+
bytesWritten: 0,
|
|
237
|
+
error: {
|
|
238
|
+
version: 1,
|
|
239
|
+
code,
|
|
240
|
+
message: cancelled ? "Workspace write was cancelled" : "Workspace write failed",
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
return performWrite;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Real Node host adapter. All local paths and bytes stay inside this host;
|
|
249
|
+
* model-facing requests and results remain opaque and bounded.
|
|
250
|
+
*/
|
|
251
|
+
export function createNodeWorkspaceFilesWriteHostAdapterV1(options) {
|
|
252
|
+
return createNodeWorkspaceFilesWriteAdapterV1({
|
|
253
|
+
defaultTimeoutMs: options.defaultTimeoutMs,
|
|
254
|
+
write: createNodeWorkspaceFilesWriteHostWriteV1(options),
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
export { applyPatch, parsePatch, resolveSandboxedPath };
|
|
258
|
+
//# sourceMappingURL=host.js.map
|
package/dist/host.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,IAAI,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC1G,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACxF,OAAO,EAAE,sCAAsC,EAAE,MAAM,mBAAmB,CAAC;AAU3E,MAAM,qBAAqB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AAChD,MAAM,uBAAuB,GAAG,MAAM,CAAC;AACvC,MAAM,8BAA8B,GAAG,SAAS,CAAC;AAEjD,MAAM,kBAAmB,SAAQ,KAAK;IAC5B,IAAI,CAAS;IAEtB,YAAY,IAAY,EAAE,OAAe,EAAE;QAC1C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAAA,CACjB;CACD;AAED,SAAS,cAAc,CAAC,MAAmB,EAAQ;IAClD,IAAI,MAAM,CAAC,OAAO;QAAE,MAAM,IAAI,kBAAkB,CAAC,WAAW,EAAE,+BAA+B,CAAC,CAAC;AAAA,CAC/F;AAED,SAAS,MAAM,CAAC,KAAiB,EAAU;IAC1C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAAA,CACxD;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,MAAc,EAAW;IACxD,MAAM,YAAY,GAAG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC5C,OAAO,CACN,YAAY,KAAK,EAAE,IAAI,YAAY,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,CAChH,CAAC;AAAA,CACF;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,MAAc,EAAW;IACxD,OAAO,MAAM,KAAK,IAAI,IAAI,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAAA,CACjD;AAED,KAAK,UAAU,oBAAoB,CAClC,IAAY,EACZ,OAAe,EACf,cAAsE,EACtE,MAAmB,EACD;IAClB,cAAc,CAAC,MAAM,CAAC,CAAC;IACvB,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACxD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QACzB,MAAM,IAAI,kBAAkB,CAAC,kBAAkB,EAAE,yCAAyC,CAAC,CAAC;IAC7F,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACrC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;IAClD,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC9B,MAAM,IAAI,kBAAkB,CAAC,mBAAmB,EAAE,2CAA2C,CAAC,CAAC;IAChG,MAAM,kBAAkB,GAAG,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;IAC7D,IAAI,QAA4B,CAAC;IACjC,IAAI,CAAC;QACJ,QAAQ,GAAG,MAAM,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACR,QAAQ,GAAG,SAAS,CAAC;IACtB,CAAC;IACD,IAAI,QAAQ,KAAK,SAAS,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;QAC1D,MAAM,IAAI,kBAAkB,CAAC,mBAAmB,EAAE,+CAA+C,CAAC,CAAC;IACpG,OAAO,kBAAkB,CAAC;AAAA,CAC1B;AAED,KAAK,UAAU,kBAAkB,CAChC,OAAqC,EACrC,OAA6C,EAC7C,MAAmB,EACG;IACtB,cAAc,CAAC,MAAM,CAAC,CAAC;IACvB,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;IAC5E,IAAI,CAAC,CAAC,KAAK,YAAY,UAAU,CAAC,IAAI,KAAK,CAAC,UAAU,GAAG,qBAAqB;QAC7E,MAAM,IAAI,kBAAkB,CAAC,0BAA0B,EAAE,mDAAmD,CAAC,CAAC;IAC/G,OAAO,KAAK,CAAC;AAAA,CACb;AAED,SAAS,UAAU,CAAC,KAAiB,EAAiC;IACrE,IAAI,KAAc,CAAC;IACnB,IAAI,CAAC;QACJ,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC7E,CAAC;IAAC,MAAM,CAAC;QACR,MAAM,IAAI,kBAAkB,CAAC,eAAe,EAAE,kDAAkD,CAAC,CAAC;IACnG,CAAC;IACD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACtE,MAAM,IAAI,kBAAkB,CAAC,eAAe,EAAE,4CAA4C,CAAC,CAAC;IAC7F,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,GAAG,uBAAuB;QAClH,MAAM,IAAI,kBAAkB,CAAC,eAAe,EAAE,qCAAqC,CAAC,CAAC;IACtF,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,MAAM,UAAU,GAAqC,EAAE,CAAC;IACxD,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QACtC,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;YACnE,MAAM,IAAI,kBAAkB,CAAC,eAAe,EAAE,sCAAsC,CAAC,CAAC;QACvF,MAAM,SAAS,GAAG,IAA+B,CAAC;QAClD,IACC,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,KAAK,CAAC;YACtC,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,GAAG,CAAC;YACnC,SAAS,CAAC,KAAgB,GAAG,CAAC;YAC9B,SAAS,CAAC,GAAc,GAAI,SAAS,CAAC,KAAgB;YACvD,OAAO,SAAS,CAAC,WAAW,KAAK,QAAQ;YACzC,CAAC,GAAI,SAAS,CAAC,WAAsB,CAAC,CAAC,MAAM,GAAG,8BAA8B;YAC7E,SAAS,CAAC,KAAgB,GAAG,WAAW;YAEzC,MAAM,IAAI,kBAAkB,CAAC,eAAe,EAAE,sCAAsC,CAAC,CAAC;QACvF,WAAW,GAAG,SAAS,CAAC,GAAa,CAAC;QACtC,UAAU,CAAC,IAAI,CAAC;YACf,KAAK,EAAE,SAAS,CAAC,KAAe;YAChC,GAAG,EAAE,SAAS,CAAC,GAAa;YAC5B,WAAW,EAAE,SAAS,CAAC,WAAqB;SAC5C,CAAC,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC;AAAA,CAClC;AAED,SAAS,UAAU,CAAC,MAAkB,EAAE,UAAsB,EAAc;IAC3E,MAAM,KAAK,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACtE,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,KAAK,IAAI,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACtE,MAAM,SAAS,GAAG,KAAK,CAAC,UAAU,CAAC,KAAK,CAAE,CAAC;QAC3C,IAAI,SAAS,CAAC,GAAG,GAAG,MAAM,CAAC,MAAM;YAChC,MAAM,IAAI,kBAAkB,CAAC,eAAe,EAAE,wCAAwC,CAAC,CAAC;QACzF,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,KAAK,CAAC,GAAG,SAAS,CAAC,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;IACtG,CAAC;IACD,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AAAA,CACxC;AAED,KAAK,UAAU,WAAW,CAAC,IAAY,EAAE,KAAiB,EAAE,MAAmB,EAAiB;IAC/F,cAAc,CAAC,MAAM,CAAC,CAAC;IACvB,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAC5E,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;IAClD,IAAI,CAAC;QACJ,MAAM,SAAS,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAClC,cAAc,CAAC,MAAM,CAAC,CAAC;QACvB,MAAM,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC/B,CAAC;YAAS,CAAC;QACV,MAAM,EAAE,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC9E,CAAC;AAAA,CACD;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,wCAAwC,CACvD,OAA6C,EAC0D;IACvG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IAChF,MAAM,UAAU,GAAG,IAAI,GAAG,EAAyB,CAAC;IACpD,MAAM,aAAa,GAAG,KAAK,EAAK,GAAW,EAAE,IAAsB,EAAc,EAAE,CAAC;QACnF,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,WAAqC,CAAC;QAC1C,MAAM,IAAI,GAAG,IAAI,OAAO,CAAO,CAAC,WAAW,EAAE,EAAE,CAAC;YAC/C,WAAW,GAAG,WAAW,CAAC;QAAA,CAC1B,CAAC,CAAC;QACH,MAAM,OAAO,GACZ,QAAQ,KAAK,SAAS;YACrB,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,QAAQ,CAAC,IAAI,CACb,GAAG,EAAE,CAAC,IAAI,EACV,GAAG,EAAE,CAAC,IAAI,CACV,CAAC;QACL,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAC7B,IAAI,CAAC;YACJ,MAAM,QAAQ,CAAC;YACf,OAAO,MAAM,IAAI,EAAE,CAAC;QACrB,CAAC;gBAAS,CAAC;YACV,WAAW,EAAE,EAAE,CAAC;YAChB,IAAI,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,OAAO;gBAAE,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,CAAC;IAAA,CACD,CAAC;IACF,MAAM,YAAY,GAAG,KAAK,EACzB,OAAqC,EACrC,MAAmB,EACoB,EAAE,CAAC;QAC1C,mGAAmG;QACnG,MAAM,MAAM,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QAC9C,IAAI,CAAC;YACJ,cAAc,CAAC,MAAM,CAAC,CAAC;YACvB,MAAM,IAAI,GAAG,MAAM,oBAAoB,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;YACvG,OAAO,MAAM,aAAa,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;gBAC5C,cAAc,CAAC,MAAM,CAAC,CAAC;gBACvB,IAAI,OAAO,GAAG,IAAI,UAAU,EAAE,CAAC;gBAC/B,IAAI,MAAM,GAAG,IAAI,CAAC;gBAClB,IAAI,CAAC;oBACJ,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;oBACrC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE;wBACxB,MAAM,IAAI,kBAAkB,CAAC,kBAAkB,EAAE,gCAAgC,CAAC,CAAC;oBACpF,OAAO,GAAG,IAAI,UAAU,CACvB,MAAM,CAAC,OAAO,CAAC,iBAAiB,KAAK,SAAS;wBAC7C,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;wBAClB,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAC3C,CAAC;gBACH,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBAChB,IAAI,KAAK,YAAY,kBAAkB;wBAAE,MAAM,KAAK,CAAC;oBACrD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAK,KAA4B,CAAC,IAAI,KAAK,QAAQ;wBACjG,MAAM,IAAI,kBAAkB,CAAC,aAAa,EAAE,oCAAoC,CAAC,CAAC;oBACnF,MAAM,GAAG,KAAK,CAAC;gBAChB,CAAC;gBACD,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,eAAe,KAAK,IAAI;oBAC9C,MAAM,IAAI,kBAAkB,CAAC,cAAc,EAAE,iCAAiC,CAAC,CAAC;gBACjF,IAAI,OAAO,CAAC,eAAe,KAAK,SAAS,IAAI,OAAO,CAAC,eAAe,KAAK,MAAM,CAAC,OAAO,CAAC;oBACvF,MAAM,IAAI,kBAAkB,CAC3B,kBAAkB,EAClB,yDAAyD,CACzD,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;gBAClE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;gBAC/E,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC;gBACjD,IAAI,OAAO,EAAE,CAAC;oBACb,MAAM,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;oBACtC,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC;oBACxB,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC;gBAChC,CAAC;gBACD,MAAM,iBAAiB,GACtB,OAAO,CAAC,aAAa,KAAK,SAAS;oBAClC,CAAC,CAAC,SAAS;oBACX,CAAC,CAAC,CACA,MAAM,OAAO,CAAC,aAAa,CAC1B,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAC3E,MAAM,CACN,CACD,CAAC,WAAW,CAAC;gBACjB,OAAO;oBACN,OAAO,EAAE,CAAU;oBACnB,WAAW,EAAE,OAAO,CAAC,WAAW;oBAChC,MAAM,EAAE,WAAoB;oBAC5B,OAAO,EAAE,OAAO,CAAC,OAAO;oBACxB,IAAI,EAAE,OAAO,CAAC,IAAI;oBAClB,OAAO;oBACP,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;oBAC3C,GAAG,CAAC,iBAAiB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,CAAC;oBACjE,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC;iBAC1B,CAAC;YAAA,CACF,CAAC,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,IAAI,CAAC,KAAK,YAAY,kBAAkB,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC;YACxG,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;gBACtB,OAAO;oBACN,OAAO,EAAE,CAAU;oBACnB,WAAW,EAAE,OAAO,CAAC,WAAW;oBAChC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAE,WAAqB,CAAC,CAAC,CAAE,QAAkB;oBAChE,OAAO,EAAE,OAAO,CAAC,OAAO;oBACxB,IAAI,EAAE,OAAO,CAAC,IAAI;oBAClB,OAAO,EAAE,IAAI;oBACb,YAAY,EAAE,MAAM,CAAC,KAAK;oBAC1B,SAAS,EAAE,IAAI;oBACf,KAAK,EAAE;wBACN,OAAO,EAAE,CAAU;wBACnB,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,wBAAwB;wBACxD,OAAO,EAAE,SAAS;4BACjB,CAAC,CAAC,+BAA+B;4BACjC,CAAC,CAAC,6DAA6D;qBAChE;iBACD,CAAC;YACH,CAAC;YACD,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,YAAY,kBAAkB,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC;YAC3G,OAAO;gBACN,OAAO,EAAE,CAAU;gBACnB,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAE,WAAqB,CAAC,CAAC,CAAE,QAAkB;gBAChE,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,OAAO,EAAE,KAAK;gBACd,YAAY,EAAE,CAAC;gBACf,KAAK,EAAE;oBACN,OAAO,EAAE,CAAU;oBACnB,IAAI;oBACJ,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC,CAAC,wBAAwB;iBAC/E;aACD,CAAC;QACH,CAAC;IAAA,CACD,CAAC;IACF,OAAO,YAAY,CAAC;AAAA,CACpB;AAED;;;GAGG;AACH,MAAM,UAAU,0CAA0C,CACzD,OAA6C,EACd;IAC/B,OAAO,sCAAsC,CAAC;QAC7C,gBAAgB,EAAE,OAAO,CAAC,gBAAgB;QAC1C,KAAK,EAAE,wCAAwC,CAAC,OAAO,CAAC;KACxD,CAAC,CAAC;AAAA,CACH;AAED,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,oBAAoB,EAAE,CAAC","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { readFile as fsReadFile, mkdtemp, realpath, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport { basename, dirname, isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport { createNodeWorkspaceFilesWriteAdapterV1 } from \"./node-adapter.ts\";\nimport type {\n\tNodeWorkspaceFilesWriteHostOptionsV1,\n\tWorkspaceFilesPatchDocumentV1,\n\tWorkspaceFilesPatchOperationV1,\n\tWorkspaceFilesWriteAdapterV1,\n\tWorkspaceFilesWriteRequestV1,\n\tWorkspaceFilesWriteResultV1,\n} from \"./types.ts\";\n\nconst MAX_ARTIFACT_BYTES_V1 = 256 * 1024 * 1024;\nconst MAX_PATCH_OPERATIONS_V1 = 16_384;\nconst MAX_PATCH_REPLACEMENT_CHARS_V1 = 1_048_576;\n\nclass HostWriteFailureV1 extends Error {\n\treadonly code: string;\n\n\tconstructor(code: string, message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"HostWriteFailureV1\";\n\t\tthis.code = code;\n\t}\n}\n\nfunction throwIfAborted(signal: AbortSignal): void {\n\tif (signal.aborted) throw new HostWriteFailureV1(\"cancelled\", \"Workspace write was cancelled\");\n}\n\nfunction digest(bytes: Uint8Array): string {\n\treturn createHash(\"sha256\").update(bytes).digest(\"hex\");\n}\n\nfunction isInside(root: string, target: string): boolean {\n\tconst relativePath = relative(root, target);\n\treturn (\n\t\trelativePath !== \"\" && relativePath !== \".\" && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath)\n\t);\n}\n\nfunction isWithin(root: string, target: string): boolean {\n\treturn target === root || isInside(root, target);\n}\n\nasync function resolveSandboxedPath(\n\troot: string,\n\tfileRef: string,\n\tresolveFileRef: NodeWorkspaceFilesWriteHostOptionsV1[\"resolveFileRef\"],\n\tsignal: AbortSignal,\n): Promise<string> {\n\tthrowIfAborted(signal);\n\tconst rootPath = await realpath(root);\n\tconst requested = await resolveFileRef(fileRef, signal);\n\tif (!isAbsolute(requested))\n\t\tthrow new HostWriteFailureV1(\"invalid_file_ref\", \"Resolved workspace file is not absolute\");\n\tconst candidate = resolve(requested);\n\tconst parent = await realpath(dirname(candidate));\n\tif (!isWithin(rootPath, parent))\n\t\tthrow new HostWriteFailureV1(\"sandbox_violation\", \"Workspace parent is outside the host root\");\n\tconst canonicalCandidate = join(parent, basename(candidate));\n\tlet existing: string | undefined;\n\ttry {\n\t\texisting = await realpath(canonicalCandidate);\n\t} catch {\n\t\texisting = undefined;\n\t}\n\tif (existing !== undefined && !isInside(rootPath, existing))\n\t\tthrow new HostWriteFailureV1(\"sandbox_violation\", \"Workspace file resolves outside the host root\");\n\treturn canonicalCandidate;\n}\n\nasync function readSourceArtifact(\n\trequest: WorkspaceFilesWriteRequestV1,\n\toptions: NodeWorkspaceFilesWriteHostOptionsV1,\n\tsignal: AbortSignal,\n): Promise<Uint8Array> {\n\tthrowIfAborted(signal);\n\tconst bytes = await options.readArtifact(request.sourceArtifactRef, signal);\n\tif (!(bytes instanceof Uint8Array) || bytes.byteLength > MAX_ARTIFACT_BYTES_V1)\n\t\tthrow new HostWriteFailureV1(\"artifact_budget_exceeded\", \"Workspace source artifact exceeds the host budget\");\n\treturn bytes;\n}\n\nfunction parsePatch(bytes: Uint8Array): WorkspaceFilesPatchDocumentV1 {\n\tlet value: unknown;\n\ttry {\n\t\tvalue = JSON.parse(new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes));\n\t} catch {\n\t\tthrow new HostWriteFailureV1(\"invalid_patch\", \"Workspace patch artifact is not valid UTF-8 JSON\");\n\t}\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value))\n\t\tthrow new HostWriteFailureV1(\"invalid_patch\", \"Workspace patch document must be an object\");\n\tconst record = value as Record<string, unknown>;\n\tif (record.version !== 1 || !Array.isArray(record.operations) || record.operations.length > MAX_PATCH_OPERATIONS_V1)\n\t\tthrow new HostWriteFailureV1(\"invalid_patch\", \"Workspace patch document is invalid\");\n\tlet previousEnd = 0;\n\tconst operations: WorkspaceFilesPatchOperationV1[] = [];\n\tfor (const item of record.operations) {\n\t\tif (item === null || typeof item !== \"object\" || Array.isArray(item))\n\t\t\tthrow new HostWriteFailureV1(\"invalid_patch\", \"Workspace patch operation is invalid\");\n\t\tconst operation = item as Record<string, unknown>;\n\t\tif (\n\t\t\t!Number.isSafeInteger(operation.start) ||\n\t\t\t!Number.isSafeInteger(operation.end) ||\n\t\t\t(operation.start as number) < 0 ||\n\t\t\t(operation.end as number) < (operation.start as number) ||\n\t\t\ttypeof operation.replacement !== \"string\" ||\n\t\t\t[...(operation.replacement as string)].length > MAX_PATCH_REPLACEMENT_CHARS_V1 ||\n\t\t\t(operation.start as number) < previousEnd\n\t\t)\n\t\t\tthrow new HostWriteFailureV1(\"invalid_patch\", \"Workspace patch operation is invalid\");\n\t\tpreviousEnd = operation.end as number;\n\t\toperations.push({\n\t\t\tstart: operation.start as number,\n\t\t\tend: operation.end as number,\n\t\t\treplacement: operation.replacement as string,\n\t\t});\n\t}\n\treturn { version: 1, operations };\n}\n\nfunction applyPatch(source: Uint8Array, patchBytes: Uint8Array): Uint8Array {\n\tconst patch = parsePatch(patchBytes);\n\tconst text = new TextDecoder(\"utf-8\", { fatal: true }).decode(source);\n\tlet output = text;\n\tfor (let index = patch.operations.length - 1; index >= 0; index -= 1) {\n\t\tconst operation = patch.operations[index]!;\n\t\tif (operation.end > output.length)\n\t\t\tthrow new HostWriteFailureV1(\"invalid_patch\", \"Workspace patch range exceeds the file\");\n\t\toutput = `${output.slice(0, operation.start)}${operation.replacement}${output.slice(operation.end)}`;\n\t}\n\treturn new TextEncoder().encode(output);\n}\n\nasync function atomicWrite(path: string, bytes: Uint8Array, signal: AbortSignal): Promise<void> {\n\tthrowIfAborted(signal);\n\tconst directory = await mkdtemp(join(dirname(path), \".agent-forge-write-\"));\n\tconst temporary = join(directory, basename(path));\n\ttry {\n\t\tawait writeFile(temporary, bytes);\n\t\tthrowIfAborted(signal);\n\t\tawait rename(temporary, path);\n\t} finally {\n\t\tawait rm(directory, { recursive: true, force: true }).catch(() => undefined);\n\t}\n}\n\n/**\n * Host-level write: resolves the sandboxed path, then serializes the\n * read-check-commit critical section per resolved canonical path so concurrent\n * requests cannot both pass the expectedVersion check (single-flight). After\n * the rename committed new bytes, a later artifact-record failure or\n * cancellation is reported with `committed: true` and a truthful changed state\n * instead of masking the write that already reached the disk. The timeout and\n * stop race of the full adapter lives in node-adapter; this seam exposes the\n * host mapping itself.\n */\nexport function createNodeWorkspaceFilesWriteHostWriteV1(\n\toptions: NodeWorkspaceFilesWriteHostOptionsV1,\n): (request: WorkspaceFilesWriteRequestV1, signal: AbortSignal) => Promise<WorkspaceFilesWriteResultV1> {\n\tif (!isAbsolute(options.root)) throw new Error(\"root must be an absolute path\");\n\tconst writeGates = new Map<string, Promise<void>>();\n\tconst runSerialized = async <T>(key: string, work: () => Promise<T>): Promise<T> => {\n\t\tconst previous = writeGates.get(key);\n\t\tlet releaseGate: (() => void) | undefined;\n\t\tconst gate = new Promise<void>((resolveGate) => {\n\t\t\treleaseGate = resolveGate;\n\t\t});\n\t\tconst chained =\n\t\t\tprevious === undefined\n\t\t\t\t? gate\n\t\t\t\t: previous.then(\n\t\t\t\t\t\t() => gate,\n\t\t\t\t\t\t() => gate,\n\t\t\t\t\t);\n\t\twriteGates.set(key, chained);\n\t\ttry {\n\t\t\tawait previous;\n\t\t\treturn await work();\n\t\t} finally {\n\t\t\treleaseGate?.();\n\t\t\tif (writeGates.get(key) === chained) writeGates.delete(key);\n\t\t}\n\t};\n\tconst performWrite = async (\n\t\trequest: WorkspaceFilesWriteRequestV1,\n\t\tsignal: AbortSignal,\n\t): Promise<WorkspaceFilesWriteResultV1> => {\n\t\t/** Set only after the rename committed new bytes, so later failures cannot mask the disk state. */\n\t\tconst commit = { committed: false, bytes: 0 };\n\t\ttry {\n\t\t\tthrowIfAborted(signal);\n\t\t\tconst path = await resolveSandboxedPath(options.root, request.fileRef, options.resolveFileRef, signal);\n\t\t\treturn await runSerialized(path, async () => {\n\t\t\t\tthrowIfAborted(signal);\n\t\t\t\tlet current = new Uint8Array();\n\t\t\t\tlet exists = true;\n\t\t\t\ttry {\n\t\t\t\t\tconst information = await stat(path);\n\t\t\t\t\tif (!information.isFile())\n\t\t\t\t\t\tthrow new HostWriteFailureV1(\"invalid_file_ref\", \"Workspace target is not a file\");\n\t\t\t\t\tcurrent = new Uint8Array(\n\t\t\t\t\t\tawait (options.readWorkspaceFile === undefined\n\t\t\t\t\t\t\t? fsReadFile(path)\n\t\t\t\t\t\t\t: options.readWorkspaceFile(path, signal)),\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof HostWriteFailureV1) throw error;\n\t\t\t\t\tif (error === null || typeof error !== \"object\" || (error as { code?: unknown }).code !== \"ENOENT\")\n\t\t\t\t\t\tthrow new HostWriteFailureV1(\"read_failed\", \"Workspace target could not be read\");\n\t\t\t\t\texists = false;\n\t\t\t\t}\n\t\t\t\tif (!exists && request.createIfMissing !== true)\n\t\t\t\t\tthrow new HostWriteFailureV1(\"file_missing\", \"Workspace target does not exist\");\n\t\t\t\tif (request.expectedVersion !== undefined && request.expectedVersion !== digest(current))\n\t\t\t\t\tthrow new HostWriteFailureV1(\n\t\t\t\t\t\t\"version_conflict\",\n\t\t\t\t\t\t\"Workspace target version does not match expectedVersion\",\n\t\t\t\t\t);\n\t\t\t\tconst source = await readSourceArtifact(request, options, signal);\n\t\t\t\tconst next = request.mode === \"replace\" ? source : applyPatch(current, source);\n\t\t\t\tconst changed = digest(current) !== digest(next);\n\t\t\t\tif (changed) {\n\t\t\t\t\tawait atomicWrite(path, next, signal);\n\t\t\t\t\tcommit.committed = true;\n\t\t\t\t\tcommit.bytes = next.byteLength;\n\t\t\t\t}\n\t\t\t\tconst outputArtifactRef =\n\t\t\t\t\toptions.writeArtifact === undefined\n\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t: (\n\t\t\t\t\t\t\t\tawait options.writeArtifact(\n\t\t\t\t\t\t\t\t\t{ operationId: request.operationId, fileRef: request.fileRef, bytes: next },\n\t\t\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t).artifactRef;\n\t\t\t\treturn {\n\t\t\t\t\tversion: 1 as const,\n\t\t\t\t\toperationId: request.operationId,\n\t\t\t\t\tstatus: \"succeeded\" as const,\n\t\t\t\t\tfileRef: request.fileRef,\n\t\t\t\t\tmode: request.mode,\n\t\t\t\t\tchanged,\n\t\t\t\t\tbytesWritten: changed ? next.byteLength : 0,\n\t\t\t\t\t...(outputArtifactRef === undefined ? {} : { outputArtifactRef }),\n\t\t\t\t\tversionToken: digest(next),\n\t\t\t\t};\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconst cancelled = signal.aborted || (error instanceof HostWriteFailureV1 && error.code === \"cancelled\");\n\t\t\tif (commit.committed) {\n\t\t\t\treturn {\n\t\t\t\t\tversion: 1 as const,\n\t\t\t\t\toperationId: request.operationId,\n\t\t\t\t\tstatus: cancelled ? (\"cancelled\" as const) : (\"failed\" as const),\n\t\t\t\t\tfileRef: request.fileRef,\n\t\t\t\t\tmode: request.mode,\n\t\t\t\t\tchanged: true,\n\t\t\t\t\tbytesWritten: commit.bytes,\n\t\t\t\t\tcommitted: true,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tversion: 1 as const,\n\t\t\t\t\t\tcode: cancelled ? \"cancelled\" : \"artifact_record_failed\",\n\t\t\t\t\t\tmessage: cancelled\n\t\t\t\t\t\t\t? \"Workspace write was cancelled\"\n\t\t\t\t\t\t\t: \"Workspace file was committed but the artifact record failed\",\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst code = cancelled ? \"cancelled\" : error instanceof HostWriteFailureV1 ? error.code : \"adapter_failed\";\n\t\t\treturn {\n\t\t\t\tversion: 1 as const,\n\t\t\t\toperationId: request.operationId,\n\t\t\t\tstatus: cancelled ? (\"cancelled\" as const) : (\"failed\" as const),\n\t\t\t\tfileRef: request.fileRef,\n\t\t\t\tmode: request.mode,\n\t\t\t\tchanged: false,\n\t\t\t\tbytesWritten: 0,\n\t\t\t\terror: {\n\t\t\t\t\tversion: 1 as const,\n\t\t\t\t\tcode,\n\t\t\t\t\tmessage: cancelled ? \"Workspace write was cancelled\" : \"Workspace write failed\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t};\n\treturn performWrite;\n}\n\n/**\n * Real Node host adapter. All local paths and bytes stay inside this host;\n * model-facing requests and results remain opaque and bounded.\n */\nexport function createNodeWorkspaceFilesWriteHostAdapterV1(\n\toptions: NodeWorkspaceFilesWriteHostOptionsV1,\n): WorkspaceFilesWriteAdapterV1 {\n\treturn createNodeWorkspaceFilesWriteAdapterV1({\n\t\tdefaultTimeoutMs: options.defaultTimeoutMs,\n\t\twrite: createNodeWorkspaceFilesWriteHostWriteV1(options),\n\t});\n}\n\nexport { applyPatch, parsePatch, resolveSandboxedPath };\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createWorkspaceFilesWriteCapabilityFactoryV1 } from "./capability.ts";
|
|
2
|
+
export { applyPatch, createNodeWorkspaceFilesWriteHostAdapterV1, createNodeWorkspaceFilesWriteHostWriteV1, parsePatch, resolveSandboxedPath, } from "./host.ts";
|
|
3
|
+
export { createNodeWorkspaceFilesWriteAdapterV1, projectWorkspaceFilesWriteV1, WORKSPACE_FILES_WRITE_MAX_BYTES_V1, WORKSPACE_FILES_WRITE_MAX_OPERATION_ID_CHARS_V1, WORKSPACE_FILES_WRITE_MAX_OUTPUT_BYTES_V1, WORKSPACE_FILES_WRITE_MAX_REF_CHARS_V1, WORKSPACE_FILES_WRITE_MAX_VERSION_CHARS_V1, } from "./node-adapter.ts";
|
|
4
|
+
export type * from "./types.ts";
|
|
5
|
+
//# 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,4CAA4C,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EACN,UAAU,EACV,0CAA0C,EAC1C,wCAAwC,EACxC,UAAU,EACV,oBAAoB,GACpB,MAAM,WAAW,CAAC;AACnB,OAAO,EACN,sCAAsC,EACtC,4BAA4B,EAC5B,kCAAkC,EAClC,+CAA+C,EAC/C,yCAAyC,EACzC,sCAAsC,EACtC,0CAA0C,GAC1C,MAAM,mBAAmB,CAAC;AAC3B,mBAAmB,YAAY,CAAC","sourcesContent":["export { createWorkspaceFilesWriteCapabilityFactoryV1 } from \"./capability.ts\";\nexport {\n\tapplyPatch,\n\tcreateNodeWorkspaceFilesWriteHostAdapterV1,\n\tcreateNodeWorkspaceFilesWriteHostWriteV1,\n\tparsePatch,\n\tresolveSandboxedPath,\n} from \"./host.ts\";\nexport {\n\tcreateNodeWorkspaceFilesWriteAdapterV1,\n\tprojectWorkspaceFilesWriteV1,\n\tWORKSPACE_FILES_WRITE_MAX_BYTES_V1,\n\tWORKSPACE_FILES_WRITE_MAX_OPERATION_ID_CHARS_V1,\n\tWORKSPACE_FILES_WRITE_MAX_OUTPUT_BYTES_V1,\n\tWORKSPACE_FILES_WRITE_MAX_REF_CHARS_V1,\n\tWORKSPACE_FILES_WRITE_MAX_VERSION_CHARS_V1,\n} from \"./node-adapter.ts\";\nexport type * from \"./types.ts\";\n"]}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createWorkspaceFilesWriteCapabilityFactoryV1 } from "./capability.js";
|
|
2
|
+
export { applyPatch, createNodeWorkspaceFilesWriteHostAdapterV1, createNodeWorkspaceFilesWriteHostWriteV1, parsePatch, resolveSandboxedPath, } from "./host.js";
|
|
3
|
+
export { createNodeWorkspaceFilesWriteAdapterV1, projectWorkspaceFilesWriteV1, WORKSPACE_FILES_WRITE_MAX_BYTES_V1, WORKSPACE_FILES_WRITE_MAX_OPERATION_ID_CHARS_V1, WORKSPACE_FILES_WRITE_MAX_OUTPUT_BYTES_V1, WORKSPACE_FILES_WRITE_MAX_REF_CHARS_V1, WORKSPACE_FILES_WRITE_MAX_VERSION_CHARS_V1, } from "./node-adapter.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,4CAA4C,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EACN,UAAU,EACV,0CAA0C,EAC1C,wCAAwC,EACxC,UAAU,EACV,oBAAoB,GACpB,MAAM,WAAW,CAAC;AACnB,OAAO,EACN,sCAAsC,EACtC,4BAA4B,EAC5B,kCAAkC,EAClC,+CAA+C,EAC/C,yCAAyC,EACzC,sCAAsC,EACtC,0CAA0C,GAC1C,MAAM,mBAAmB,CAAC","sourcesContent":["export { createWorkspaceFilesWriteCapabilityFactoryV1 } from \"./capability.ts\";\nexport {\n\tapplyPatch,\n\tcreateNodeWorkspaceFilesWriteHostAdapterV1,\n\tcreateNodeWorkspaceFilesWriteHostWriteV1,\n\tparsePatch,\n\tresolveSandboxedPath,\n} from \"./host.ts\";\nexport {\n\tcreateNodeWorkspaceFilesWriteAdapterV1,\n\tprojectWorkspaceFilesWriteV1,\n\tWORKSPACE_FILES_WRITE_MAX_BYTES_V1,\n\tWORKSPACE_FILES_WRITE_MAX_OPERATION_ID_CHARS_V1,\n\tWORKSPACE_FILES_WRITE_MAX_OUTPUT_BYTES_V1,\n\tWORKSPACE_FILES_WRITE_MAX_REF_CHARS_V1,\n\tWORKSPACE_FILES_WRITE_MAX_VERSION_CHARS_V1,\n} from \"./node-adapter.ts\";\nexport type * from \"./types.ts\";\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type NodeWorkspaceFilesWriteAdapterOptionsV1, type WorkspaceFilesWriteAdapterV1, type WorkspaceFilesWriteRequestV1, type WorkspaceFilesWriteResultV1 } from "./types.ts";
|
|
2
|
+
export declare const WORKSPACE_FILES_WRITE_MAX_OPERATION_ID_CHARS_V1 = 256;
|
|
3
|
+
export declare const WORKSPACE_FILES_WRITE_MAX_REF_CHARS_V1 = 256;
|
|
4
|
+
export declare const WORKSPACE_FILES_WRITE_MAX_VERSION_CHARS_V1 = 256;
|
|
5
|
+
export declare const WORKSPACE_FILES_WRITE_MAX_OUTPUT_BYTES_V1: number;
|
|
6
|
+
export declare const WORKSPACE_FILES_WRITE_MAX_BYTES_V1: number;
|
|
7
|
+
declare function safeRef(value: unknown): value is string;
|
|
8
|
+
declare function validateRequest(request: WorkspaceFilesWriteRequestV1): string | undefined;
|
|
9
|
+
/** Projects host output down to status and bounded metadata; it never returns content. */
|
|
10
|
+
export declare function projectWorkspaceFilesWriteV1(value: unknown, request: WorkspaceFilesWriteRequestV1): WorkspaceFilesWriteResultV1 | undefined;
|
|
11
|
+
export declare function createNodeWorkspaceFilesWriteAdapterV1(options?: NodeWorkspaceFilesWriteAdapterOptionsV1): WorkspaceFilesWriteAdapterV1;
|
|
12
|
+
export { safeRef, validateRequest };
|
|
13
|
+
//# 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":"AAEA,OAAO,EACN,KAAK,uCAAuC,EAG5C,KAAK,4BAA4B,EAEjC,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,+CAA+C,MAAM,CAAC;AACnE,eAAO,MAAM,sCAAsC,MAAM,CAAC;AAC1D,eAAO,MAAM,0CAA0C,MAAM,CAAC;AAC9D,eAAO,MAAM,yCAAyC,QAAY,CAAC;AACnE,eAAO,MAAM,kCAAkC,QAAyB,CAAC;AASzE,iBAAS,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEhD;AAuCD,iBAAS,eAAe,CAAC,OAAO,EAAE,4BAA4B,GAAG,MAAM,GAAG,SAAS,CAUlF;AAsBD,0FAA0F;AAC1F,wBAAgB,4BAA4B,CAC3C,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,4BAA4B,GACnC,2BAA2B,GAAG,SAAS,CA4CzC;AA4FD,wBAAgB,sCAAsC,CACrD,OAAO,GAAE,uCAA4C,GACnD,4BAA4B,CAmB9B;AAED,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC","sourcesContent":["import type { JsonValue } from \"@agent-forge/plugin-sdk\";\nimport { projectPluginContextV1 } from \"@agent-forge/plugin-sdk\";\nimport {\n\ttype NodeWorkspaceFilesWriteAdapterOptionsV1,\n\tWORKSPACE_FILES_WRITE_HOST_ADAPTER_ID_V1,\n\tWORKSPACE_FILES_WRITE_HOST_ADAPTER_VERSION_V1,\n\ttype WorkspaceFilesWriteAdapterV1,\n\ttype WorkspaceFilesWriteModeV1,\n\ttype WorkspaceFilesWriteRequestV1,\n\ttype WorkspaceFilesWriteResultV1,\n} from \"./types.ts\";\n\nexport const WORKSPACE_FILES_WRITE_MAX_OPERATION_ID_CHARS_V1 = 256;\nexport const WORKSPACE_FILES_WRITE_MAX_REF_CHARS_V1 = 256;\nexport const WORKSPACE_FILES_WRITE_MAX_VERSION_CHARS_V1 = 256;\nexport const WORKSPACE_FILES_WRITE_MAX_OUTPUT_BYTES_V1 = 64 * 1024;\nexport const WORKSPACE_FILES_WRITE_MAX_BYTES_V1 = 4 * 1024 * 1024 * 1024;\n\nconst OPAQUE_REF_V1 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u;\nconst VERSION_TOKEN_V1 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction safeRef(value: unknown): value is string {\n\treturn typeof value === \"string\" && OPAQUE_REF_V1.test(value);\n}\n\nfunction safeVersionToken(value: unknown): value is string | undefined {\n\treturn value === undefined || (typeof value === \"string\" && VERSION_TOKEN_V1.test(value));\n}\n\nfunction validOperationId(value: unknown): value is string {\n\treturn (\n\t\ttypeof value === \"string\" &&\n\t\tvalue.trim() !== \"\" &&\n\t\tvalue.length <= WORKSPACE_FILES_WRITE_MAX_OPERATION_ID_CHARS_V1 &&\n\t\t!/[\\u0000-\\u001f\\u007f]/u.test(value)\n\t);\n}\n\nfunction validMode(value: unknown): value is WorkspaceFilesWriteModeV1 {\n\treturn value === \"replace\" || value === \"patch\";\n}\n\nfunction error(\n\toperationId: string,\n\tfileRef: string,\n\tmode: WorkspaceFilesWriteModeV1,\n\tstatus: WorkspaceFilesWriteResultV1[\"status\"],\n\tcode: string,\n\tmessage: string,\n): WorkspaceFilesWriteResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus,\n\t\tfileRef,\n\t\tmode,\n\t\tchanged: false,\n\t\tbytesWritten: 0,\n\t\terror: { version: 1, code, message },\n\t};\n}\n\nfunction validateRequest(request: WorkspaceFilesWriteRequestV1): string | undefined {\n\tif (request.version !== 1) return \"version must be 1\";\n\tif (!validOperationId(request.operationId)) return \"operationId is invalid\";\n\tif (!safeRef(request.fileRef)) return \"fileRef must be an opaque host handle\";\n\tif (!validMode(request.mode)) return \"mode must be replace or patch\";\n\tif (!safeRef(request.sourceArtifactRef)) return \"sourceArtifactRef must be an opaque artifact handle\";\n\tif (!safeVersionToken(request.expectedVersion)) return \"expectedVersion is invalid\";\n\tif (request.createIfMissing !== undefined && typeof request.createIfMissing !== \"boolean\")\n\t\treturn \"createIfMissing is invalid\";\n\treturn undefined;\n}\n\nfunction boundedError(value: unknown): WorkspaceFilesWriteResultV1[\"error\"] | undefined {\n\tif (!isRecord(value)) return undefined;\n\tif (\n\t\tvalue.version !== 1 ||\n\t\ttypeof value.code !== \"string\" ||\n\t\t!/^[a-z][a-z0-9_]{0,127}$/u.test(value.code) ||\n\t\ttypeof value.message !== \"string\" ||\n\t\tvalue.message.trim() === \"\" ||\n\t\tvalue.message.length > 512\n\t)\n\t\treturn undefined;\n\treturn { version: 1, code: value.code, message: value.message.slice(0, 512) };\n}\n\nfunction nonNegativeBytes(value: unknown): value is number {\n\treturn (\n\t\tNumber.isSafeInteger(value) && (value as number) >= 0 && (value as number) <= WORKSPACE_FILES_WRITE_MAX_BYTES_V1\n\t);\n}\n\n/** Projects host output down to status and bounded metadata; it never returns content. */\nexport function projectWorkspaceFilesWriteV1(\n\tvalue: unknown,\n\trequest: WorkspaceFilesWriteRequestV1,\n): WorkspaceFilesWriteResultV1 | undefined {\n\tif (!isRecord(value)) return undefined;\n\tif (\n\t\tvalue.version !== 1 ||\n\t\tvalue.operationId !== request.operationId ||\n\t\tvalue.fileRef !== request.fileRef ||\n\t\tvalue.mode !== request.mode ||\n\t\ttypeof value.status !== \"string\" ||\n\t\t![\"succeeded\", \"failed\", \"cancelled\", \"needs_user\", \"unsupported\"].includes(value.status) ||\n\t\ttypeof value.changed !== \"boolean\" ||\n\t\t!nonNegativeBytes(value.bytesWritten) ||\n\t\t(!safeRef(value.outputArtifactRef) && value.outputArtifactRef !== undefined) ||\n\t\t!safeVersionToken(value.versionToken) ||\n\t\t(value.committed !== undefined && typeof value.committed !== \"boolean\")\n\t)\n\t\treturn undefined;\n\tconst result: WorkspaceFilesWriteResultV1 = {\n\t\tversion: 1,\n\t\toperationId: request.operationId,\n\t\tstatus: value.status as WorkspaceFilesWriteResultV1[\"status\"],\n\t\tfileRef: request.fileRef,\n\t\tmode: request.mode,\n\t\tchanged: value.changed,\n\t\tbytesWritten: value.bytesWritten,\n\t\t...(value.committed === true ? { committed: true } : {}),\n\t\t...(value.outputArtifactRef === undefined ? {} : { outputArtifactRef: value.outputArtifactRef }),\n\t\t...(value.versionToken === undefined ? {} : { versionToken: value.versionToken }),\n\t\t...(value.error === undefined ? {} : { error: boundedError(value.error) }),\n\t};\n\tif (result.status !== \"succeeded\" && result.error === undefined) return undefined;\n\tif (result.status === \"succeeded\" && result.error !== undefined) return undefined;\n\tif (\n\t\tprojectPluginContextV1(\n\t\t\t{\n\t\t\t\tversion: 1,\n\t\t\t\toperationId: request.operationId,\n\t\t\t\titems: [result] as unknown as JsonValue[],\n\t\t\t\ttruncated: false,\n\t\t\t},\n\t\t\t{ maxOutputBytes: WORKSPACE_FILES_WRITE_MAX_OUTPUT_BYTES_V1, maxInlineItems: 1, maxCellChars: 512 },\n\t\t).status !== \"succeeded\"\n\t)\n\t\treturn undefined;\n\treturn result;\n}\n\nfunction timeoutMs(value: number | undefined): number {\n\tif (value === undefined) return 30_000;\n\tif (!Number.isSafeInteger(value) || value < 1 || value > 120_000) throw new Error(\"defaultTimeoutMs is invalid\");\n\treturn value;\n}\n\nasync function runHost(\n\tcallback: ((signal: AbortSignal) => Promise<unknown>) | undefined,\n\ttimeout: number,\n\trequest: WorkspaceFilesWriteRequestV1,\n\tsignal: AbortSignal | undefined,\n): Promise<WorkspaceFilesWriteResultV1> {\n\tif (callback === undefined)\n\t\treturn error(\n\t\t\trequest.operationId,\n\t\t\trequest.fileRef,\n\t\t\trequest.mode,\n\t\t\t\"unsupported\",\n\t\t\t\"workspace_files_write_unavailable\",\n\t\t\t\"No workspace file writer is bound\",\n\t\t);\n\tif (signal?.aborted)\n\t\treturn error(\n\t\t\trequest.operationId,\n\t\t\trequest.fileRef,\n\t\t\trequest.mode,\n\t\t\t\"cancelled\",\n\t\t\t\"cancelled\",\n\t\t\t\"Workspace write was cancelled\",\n\t\t);\n\tconst controller = new AbortController();\n\tlet stop: \"timeout\" | \"cancelled\" | undefined;\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tlet removeAbort: (() => void) | undefined;\n\tconst stopPromise = new Promise<void>((resolve) => {\n\t\tconst cancel = () => {\n\t\t\tstop ??= \"cancelled\";\n\t\t\tcontroller.abort();\n\t\t\tresolve();\n\t\t};\n\t\tif (signal !== undefined) {\n\t\t\tsignal.addEventListener(\"abort\", cancel, { once: true });\n\t\t\tremoveAbort = () => signal.removeEventListener(\"abort\", cancel);\n\t\t}\n\t\ttimer = setTimeout(() => {\n\t\t\tif (stop === undefined) {\n\t\t\t\tstop = \"timeout\";\n\t\t\t\tcontroller.abort();\n\t\t\t\tresolve();\n\t\t\t}\n\t\t}, timeout);\n\t});\n\tconst callbackPromise = callback(controller.signal).then(\n\t\t(value) => ({ kind: \"value\" as const, value }),\n\t\t() => ({ kind: \"failed\" as const }),\n\t);\n\ttry {\n\t\tconst outcome = await Promise.race([callbackPromise, stopPromise.then(() => ({ kind: \"stopped\" as const }))]);\n\t\tif (outcome.kind !== \"value\" || stop !== undefined) {\n\t\t\tconst failure = outcome.kind === \"failed\" && stop === undefined;\n\t\t\treturn error(\n\t\t\t\trequest.operationId,\n\t\t\t\trequest.fileRef,\n\t\t\t\trequest.mode,\n\t\t\t\tstop === \"cancelled\" ? \"cancelled\" : \"failed\",\n\t\t\t\tfailure ? \"adapter_failed\" : stop === \"cancelled\" ? \"cancelled\" : \"timeout\",\n\t\t\t\tfailure\n\t\t\t\t\t? \"Workspace writer failed\"\n\t\t\t\t\t: stop === \"cancelled\"\n\t\t\t\t\t\t? \"Workspace write was cancelled\"\n\t\t\t\t\t\t: \"Workspace write timed out\",\n\t\t\t);\n\t\t}\n\t\treturn (\n\t\t\tprojectWorkspaceFilesWriteV1(outcome.value, request) ??\n\t\t\terror(\n\t\t\t\trequest.operationId,\n\t\t\t\trequest.fileRef,\n\t\t\t\trequest.mode,\n\t\t\t\t\"failed\",\n\t\t\t\t\"adapter_failed\",\n\t\t\t\t\"Workspace writer returned an invalid result\",\n\t\t\t)\n\t\t);\n\t} finally {\n\t\tif (timer !== undefined) clearTimeout(timer);\n\t\tremoveAbort?.();\n\t}\n}\n\nexport function createNodeWorkspaceFilesWriteAdapterV1(\n\toptions: NodeWorkspaceFilesWriteAdapterOptionsV1 = {},\n): WorkspaceFilesWriteAdapterV1 {\n\tconst timeout = timeoutMs(options.defaultTimeoutMs);\n\treturn {\n\t\tadapterId: WORKSPACE_FILES_WRITE_HOST_ADAPTER_ID_V1,\n\t\tadapterVersion: WORKSPACE_FILES_WRITE_HOST_ADAPTER_VERSION_V1,\n\t\twrite: async (request, signal) => {\n\t\t\tconst invalid = validateRequest(request);\n\t\t\tif (invalid !== undefined)\n\t\t\t\treturn error(request.operationId, request.fileRef, request.mode, \"failed\", \"invalid_input\", invalid);\n\t\t\treturn runHost(\n\t\t\t\toptions.write === undefined\n\t\t\t\t\t? undefined\n\t\t\t\t\t: (hostSignal) => options.write?.(request, hostSignal) ?? Promise.resolve(undefined),\n\t\t\t\ttimeout,\n\t\t\t\trequest,\n\t\t\t\tsignal,\n\t\t\t);\n\t\t},\n\t};\n}\n\nexport { safeRef, validateRequest };\n"]}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { projectPluginContextV1 } from "@agent-forge/plugin-sdk";
|
|
2
|
+
import { WORKSPACE_FILES_WRITE_HOST_ADAPTER_ID_V1, WORKSPACE_FILES_WRITE_HOST_ADAPTER_VERSION_V1, } from "./types.js";
|
|
3
|
+
export const WORKSPACE_FILES_WRITE_MAX_OPERATION_ID_CHARS_V1 = 256;
|
|
4
|
+
export const WORKSPACE_FILES_WRITE_MAX_REF_CHARS_V1 = 256;
|
|
5
|
+
export const WORKSPACE_FILES_WRITE_MAX_VERSION_CHARS_V1 = 256;
|
|
6
|
+
export const WORKSPACE_FILES_WRITE_MAX_OUTPUT_BYTES_V1 = 64 * 1024;
|
|
7
|
+
export const WORKSPACE_FILES_WRITE_MAX_BYTES_V1 = 4 * 1024 * 1024 * 1024;
|
|
8
|
+
const OPAQUE_REF_V1 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u;
|
|
9
|
+
const VERSION_TOKEN_V1 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u;
|
|
10
|
+
function isRecord(value) {
|
|
11
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
function safeRef(value) {
|
|
14
|
+
return typeof value === "string" && OPAQUE_REF_V1.test(value);
|
|
15
|
+
}
|
|
16
|
+
function safeVersionToken(value) {
|
|
17
|
+
return value === undefined || (typeof value === "string" && VERSION_TOKEN_V1.test(value));
|
|
18
|
+
}
|
|
19
|
+
function validOperationId(value) {
|
|
20
|
+
return (typeof value === "string" &&
|
|
21
|
+
value.trim() !== "" &&
|
|
22
|
+
value.length <= WORKSPACE_FILES_WRITE_MAX_OPERATION_ID_CHARS_V1 &&
|
|
23
|
+
!/[\u0000-\u001f\u007f]/u.test(value));
|
|
24
|
+
}
|
|
25
|
+
function validMode(value) {
|
|
26
|
+
return value === "replace" || value === "patch";
|
|
27
|
+
}
|
|
28
|
+
function error(operationId, fileRef, mode, status, code, message) {
|
|
29
|
+
return {
|
|
30
|
+
version: 1,
|
|
31
|
+
operationId,
|
|
32
|
+
status,
|
|
33
|
+
fileRef,
|
|
34
|
+
mode,
|
|
35
|
+
changed: false,
|
|
36
|
+
bytesWritten: 0,
|
|
37
|
+
error: { version: 1, code, message },
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function validateRequest(request) {
|
|
41
|
+
if (request.version !== 1)
|
|
42
|
+
return "version must be 1";
|
|
43
|
+
if (!validOperationId(request.operationId))
|
|
44
|
+
return "operationId is invalid";
|
|
45
|
+
if (!safeRef(request.fileRef))
|
|
46
|
+
return "fileRef must be an opaque host handle";
|
|
47
|
+
if (!validMode(request.mode))
|
|
48
|
+
return "mode must be replace or patch";
|
|
49
|
+
if (!safeRef(request.sourceArtifactRef))
|
|
50
|
+
return "sourceArtifactRef must be an opaque artifact handle";
|
|
51
|
+
if (!safeVersionToken(request.expectedVersion))
|
|
52
|
+
return "expectedVersion is invalid";
|
|
53
|
+
if (request.createIfMissing !== undefined && typeof request.createIfMissing !== "boolean")
|
|
54
|
+
return "createIfMissing is invalid";
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
function boundedError(value) {
|
|
58
|
+
if (!isRecord(value))
|
|
59
|
+
return undefined;
|
|
60
|
+
if (value.version !== 1 ||
|
|
61
|
+
typeof value.code !== "string" ||
|
|
62
|
+
!/^[a-z][a-z0-9_]{0,127}$/u.test(value.code) ||
|
|
63
|
+
typeof value.message !== "string" ||
|
|
64
|
+
value.message.trim() === "" ||
|
|
65
|
+
value.message.length > 512)
|
|
66
|
+
return undefined;
|
|
67
|
+
return { version: 1, code: value.code, message: value.message.slice(0, 512) };
|
|
68
|
+
}
|
|
69
|
+
function nonNegativeBytes(value) {
|
|
70
|
+
return (Number.isSafeInteger(value) && value >= 0 && value <= WORKSPACE_FILES_WRITE_MAX_BYTES_V1);
|
|
71
|
+
}
|
|
72
|
+
/** Projects host output down to status and bounded metadata; it never returns content. */
|
|
73
|
+
export function projectWorkspaceFilesWriteV1(value, request) {
|
|
74
|
+
if (!isRecord(value))
|
|
75
|
+
return undefined;
|
|
76
|
+
if (value.version !== 1 ||
|
|
77
|
+
value.operationId !== request.operationId ||
|
|
78
|
+
value.fileRef !== request.fileRef ||
|
|
79
|
+
value.mode !== request.mode ||
|
|
80
|
+
typeof value.status !== "string" ||
|
|
81
|
+
!["succeeded", "failed", "cancelled", "needs_user", "unsupported"].includes(value.status) ||
|
|
82
|
+
typeof value.changed !== "boolean" ||
|
|
83
|
+
!nonNegativeBytes(value.bytesWritten) ||
|
|
84
|
+
(!safeRef(value.outputArtifactRef) && value.outputArtifactRef !== undefined) ||
|
|
85
|
+
!safeVersionToken(value.versionToken) ||
|
|
86
|
+
(value.committed !== undefined && typeof value.committed !== "boolean"))
|
|
87
|
+
return undefined;
|
|
88
|
+
const result = {
|
|
89
|
+
version: 1,
|
|
90
|
+
operationId: request.operationId,
|
|
91
|
+
status: value.status,
|
|
92
|
+
fileRef: request.fileRef,
|
|
93
|
+
mode: request.mode,
|
|
94
|
+
changed: value.changed,
|
|
95
|
+
bytesWritten: value.bytesWritten,
|
|
96
|
+
...(value.committed === true ? { committed: true } : {}),
|
|
97
|
+
...(value.outputArtifactRef === undefined ? {} : { outputArtifactRef: value.outputArtifactRef }),
|
|
98
|
+
...(value.versionToken === undefined ? {} : { versionToken: value.versionToken }),
|
|
99
|
+
...(value.error === undefined ? {} : { error: boundedError(value.error) }),
|
|
100
|
+
};
|
|
101
|
+
if (result.status !== "succeeded" && result.error === undefined)
|
|
102
|
+
return undefined;
|
|
103
|
+
if (result.status === "succeeded" && result.error !== undefined)
|
|
104
|
+
return undefined;
|
|
105
|
+
if (projectPluginContextV1({
|
|
106
|
+
version: 1,
|
|
107
|
+
operationId: request.operationId,
|
|
108
|
+
items: [result],
|
|
109
|
+
truncated: false,
|
|
110
|
+
}, { maxOutputBytes: WORKSPACE_FILES_WRITE_MAX_OUTPUT_BYTES_V1, maxInlineItems: 1, maxCellChars: 512 }).status !== "succeeded")
|
|
111
|
+
return undefined;
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
function timeoutMs(value) {
|
|
115
|
+
if (value === undefined)
|
|
116
|
+
return 30_000;
|
|
117
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > 120_000)
|
|
118
|
+
throw new Error("defaultTimeoutMs is invalid");
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
async function runHost(callback, timeout, request, signal) {
|
|
122
|
+
if (callback === undefined)
|
|
123
|
+
return error(request.operationId, request.fileRef, request.mode, "unsupported", "workspace_files_write_unavailable", "No workspace file writer is bound");
|
|
124
|
+
if (signal?.aborted)
|
|
125
|
+
return error(request.operationId, request.fileRef, request.mode, "cancelled", "cancelled", "Workspace write was cancelled");
|
|
126
|
+
const controller = new AbortController();
|
|
127
|
+
let stop;
|
|
128
|
+
let timer;
|
|
129
|
+
let removeAbort;
|
|
130
|
+
const stopPromise = new Promise((resolve) => {
|
|
131
|
+
const cancel = () => {
|
|
132
|
+
stop ??= "cancelled";
|
|
133
|
+
controller.abort();
|
|
134
|
+
resolve();
|
|
135
|
+
};
|
|
136
|
+
if (signal !== undefined) {
|
|
137
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
138
|
+
removeAbort = () => signal.removeEventListener("abort", cancel);
|
|
139
|
+
}
|
|
140
|
+
timer = setTimeout(() => {
|
|
141
|
+
if (stop === undefined) {
|
|
142
|
+
stop = "timeout";
|
|
143
|
+
controller.abort();
|
|
144
|
+
resolve();
|
|
145
|
+
}
|
|
146
|
+
}, timeout);
|
|
147
|
+
});
|
|
148
|
+
const callbackPromise = callback(controller.signal).then((value) => ({ kind: "value", value }), () => ({ kind: "failed" }));
|
|
149
|
+
try {
|
|
150
|
+
const outcome = await Promise.race([callbackPromise, stopPromise.then(() => ({ kind: "stopped" }))]);
|
|
151
|
+
if (outcome.kind !== "value" || stop !== undefined) {
|
|
152
|
+
const failure = outcome.kind === "failed" && stop === undefined;
|
|
153
|
+
return error(request.operationId, request.fileRef, request.mode, stop === "cancelled" ? "cancelled" : "failed", failure ? "adapter_failed" : stop === "cancelled" ? "cancelled" : "timeout", failure
|
|
154
|
+
? "Workspace writer failed"
|
|
155
|
+
: stop === "cancelled"
|
|
156
|
+
? "Workspace write was cancelled"
|
|
157
|
+
: "Workspace write timed out");
|
|
158
|
+
}
|
|
159
|
+
return (projectWorkspaceFilesWriteV1(outcome.value, request) ??
|
|
160
|
+
error(request.operationId, request.fileRef, request.mode, "failed", "adapter_failed", "Workspace writer returned an invalid result"));
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
if (timer !== undefined)
|
|
164
|
+
clearTimeout(timer);
|
|
165
|
+
removeAbort?.();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
export function createNodeWorkspaceFilesWriteAdapterV1(options = {}) {
|
|
169
|
+
const timeout = timeoutMs(options.defaultTimeoutMs);
|
|
170
|
+
return {
|
|
171
|
+
adapterId: WORKSPACE_FILES_WRITE_HOST_ADAPTER_ID_V1,
|
|
172
|
+
adapterVersion: WORKSPACE_FILES_WRITE_HOST_ADAPTER_VERSION_V1,
|
|
173
|
+
write: async (request, signal) => {
|
|
174
|
+
const invalid = validateRequest(request);
|
|
175
|
+
if (invalid !== undefined)
|
|
176
|
+
return error(request.operationId, request.fileRef, request.mode, "failed", "invalid_input", invalid);
|
|
177
|
+
return runHost(options.write === undefined
|
|
178
|
+
? undefined
|
|
179
|
+
: (hostSignal) => options.write?.(request, hostSignal) ?? Promise.resolve(undefined), timeout, request, signal);
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
export { safeRef, validateRequest };
|
|
184
|
+
//# 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 type { JsonValue } from \"@agent-forge/plugin-sdk\";\nimport { projectPluginContextV1 } from \"@agent-forge/plugin-sdk\";\nimport {\n\ttype NodeWorkspaceFilesWriteAdapterOptionsV1,\n\tWORKSPACE_FILES_WRITE_HOST_ADAPTER_ID_V1,\n\tWORKSPACE_FILES_WRITE_HOST_ADAPTER_VERSION_V1,\n\ttype WorkspaceFilesWriteAdapterV1,\n\ttype WorkspaceFilesWriteModeV1,\n\ttype WorkspaceFilesWriteRequestV1,\n\ttype WorkspaceFilesWriteResultV1,\n} from \"./types.ts\";\n\nexport const WORKSPACE_FILES_WRITE_MAX_OPERATION_ID_CHARS_V1 = 256;\nexport const WORKSPACE_FILES_WRITE_MAX_REF_CHARS_V1 = 256;\nexport const WORKSPACE_FILES_WRITE_MAX_VERSION_CHARS_V1 = 256;\nexport const WORKSPACE_FILES_WRITE_MAX_OUTPUT_BYTES_V1 = 64 * 1024;\nexport const WORKSPACE_FILES_WRITE_MAX_BYTES_V1 = 4 * 1024 * 1024 * 1024;\n\nconst OPAQUE_REF_V1 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u;\nconst VERSION_TOKEN_V1 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction safeRef(value: unknown): value is string {\n\treturn typeof value === \"string\" && OPAQUE_REF_V1.test(value);\n}\n\nfunction safeVersionToken(value: unknown): value is string | undefined {\n\treturn value === undefined || (typeof value === \"string\" && VERSION_TOKEN_V1.test(value));\n}\n\nfunction validOperationId(value: unknown): value is string {\n\treturn (\n\t\ttypeof value === \"string\" &&\n\t\tvalue.trim() !== \"\" &&\n\t\tvalue.length <= WORKSPACE_FILES_WRITE_MAX_OPERATION_ID_CHARS_V1 &&\n\t\t!/[\\u0000-\\u001f\\u007f]/u.test(value)\n\t);\n}\n\nfunction validMode(value: unknown): value is WorkspaceFilesWriteModeV1 {\n\treturn value === \"replace\" || value === \"patch\";\n}\n\nfunction error(\n\toperationId: string,\n\tfileRef: string,\n\tmode: WorkspaceFilesWriteModeV1,\n\tstatus: WorkspaceFilesWriteResultV1[\"status\"],\n\tcode: string,\n\tmessage: string,\n): WorkspaceFilesWriteResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus,\n\t\tfileRef,\n\t\tmode,\n\t\tchanged: false,\n\t\tbytesWritten: 0,\n\t\terror: { version: 1, code, message },\n\t};\n}\n\nfunction validateRequest(request: WorkspaceFilesWriteRequestV1): string | undefined {\n\tif (request.version !== 1) return \"version must be 1\";\n\tif (!validOperationId(request.operationId)) return \"operationId is invalid\";\n\tif (!safeRef(request.fileRef)) return \"fileRef must be an opaque host handle\";\n\tif (!validMode(request.mode)) return \"mode must be replace or patch\";\n\tif (!safeRef(request.sourceArtifactRef)) return \"sourceArtifactRef must be an opaque artifact handle\";\n\tif (!safeVersionToken(request.expectedVersion)) return \"expectedVersion is invalid\";\n\tif (request.createIfMissing !== undefined && typeof request.createIfMissing !== \"boolean\")\n\t\treturn \"createIfMissing is invalid\";\n\treturn undefined;\n}\n\nfunction boundedError(value: unknown): WorkspaceFilesWriteResultV1[\"error\"] | undefined {\n\tif (!isRecord(value)) return undefined;\n\tif (\n\t\tvalue.version !== 1 ||\n\t\ttypeof value.code !== \"string\" ||\n\t\t!/^[a-z][a-z0-9_]{0,127}$/u.test(value.code) ||\n\t\ttypeof value.message !== \"string\" ||\n\t\tvalue.message.trim() === \"\" ||\n\t\tvalue.message.length > 512\n\t)\n\t\treturn undefined;\n\treturn { version: 1, code: value.code, message: value.message.slice(0, 512) };\n}\n\nfunction nonNegativeBytes(value: unknown): value is number {\n\treturn (\n\t\tNumber.isSafeInteger(value) && (value as number) >= 0 && (value as number) <= WORKSPACE_FILES_WRITE_MAX_BYTES_V1\n\t);\n}\n\n/** Projects host output down to status and bounded metadata; it never returns content. */\nexport function projectWorkspaceFilesWriteV1(\n\tvalue: unknown,\n\trequest: WorkspaceFilesWriteRequestV1,\n): WorkspaceFilesWriteResultV1 | undefined {\n\tif (!isRecord(value)) return undefined;\n\tif (\n\t\tvalue.version !== 1 ||\n\t\tvalue.operationId !== request.operationId ||\n\t\tvalue.fileRef !== request.fileRef ||\n\t\tvalue.mode !== request.mode ||\n\t\ttypeof value.status !== \"string\" ||\n\t\t![\"succeeded\", \"failed\", \"cancelled\", \"needs_user\", \"unsupported\"].includes(value.status) ||\n\t\ttypeof value.changed !== \"boolean\" ||\n\t\t!nonNegativeBytes(value.bytesWritten) ||\n\t\t(!safeRef(value.outputArtifactRef) && value.outputArtifactRef !== undefined) ||\n\t\t!safeVersionToken(value.versionToken) ||\n\t\t(value.committed !== undefined && typeof value.committed !== \"boolean\")\n\t)\n\t\treturn undefined;\n\tconst result: WorkspaceFilesWriteResultV1 = {\n\t\tversion: 1,\n\t\toperationId: request.operationId,\n\t\tstatus: value.status as WorkspaceFilesWriteResultV1[\"status\"],\n\t\tfileRef: request.fileRef,\n\t\tmode: request.mode,\n\t\tchanged: value.changed,\n\t\tbytesWritten: value.bytesWritten,\n\t\t...(value.committed === true ? { committed: true } : {}),\n\t\t...(value.outputArtifactRef === undefined ? {} : { outputArtifactRef: value.outputArtifactRef }),\n\t\t...(value.versionToken === undefined ? {} : { versionToken: value.versionToken }),\n\t\t...(value.error === undefined ? {} : { error: boundedError(value.error) }),\n\t};\n\tif (result.status !== \"succeeded\" && result.error === undefined) return undefined;\n\tif (result.status === \"succeeded\" && result.error !== undefined) return undefined;\n\tif (\n\t\tprojectPluginContextV1(\n\t\t\t{\n\t\t\t\tversion: 1,\n\t\t\t\toperationId: request.operationId,\n\t\t\t\titems: [result] as unknown as JsonValue[],\n\t\t\t\ttruncated: false,\n\t\t\t},\n\t\t\t{ maxOutputBytes: WORKSPACE_FILES_WRITE_MAX_OUTPUT_BYTES_V1, maxInlineItems: 1, maxCellChars: 512 },\n\t\t).status !== \"succeeded\"\n\t)\n\t\treturn undefined;\n\treturn result;\n}\n\nfunction timeoutMs(value: number | undefined): number {\n\tif (value === undefined) return 30_000;\n\tif (!Number.isSafeInteger(value) || value < 1 || value > 120_000) throw new Error(\"defaultTimeoutMs is invalid\");\n\treturn value;\n}\n\nasync function runHost(\n\tcallback: ((signal: AbortSignal) => Promise<unknown>) | undefined,\n\ttimeout: number,\n\trequest: WorkspaceFilesWriteRequestV1,\n\tsignal: AbortSignal | undefined,\n): Promise<WorkspaceFilesWriteResultV1> {\n\tif (callback === undefined)\n\t\treturn error(\n\t\t\trequest.operationId,\n\t\t\trequest.fileRef,\n\t\t\trequest.mode,\n\t\t\t\"unsupported\",\n\t\t\t\"workspace_files_write_unavailable\",\n\t\t\t\"No workspace file writer is bound\",\n\t\t);\n\tif (signal?.aborted)\n\t\treturn error(\n\t\t\trequest.operationId,\n\t\t\trequest.fileRef,\n\t\t\trequest.mode,\n\t\t\t\"cancelled\",\n\t\t\t\"cancelled\",\n\t\t\t\"Workspace write was cancelled\",\n\t\t);\n\tconst controller = new AbortController();\n\tlet stop: \"timeout\" | \"cancelled\" | undefined;\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tlet removeAbort: (() => void) | undefined;\n\tconst stopPromise = new Promise<void>((resolve) => {\n\t\tconst cancel = () => {\n\t\t\tstop ??= \"cancelled\";\n\t\t\tcontroller.abort();\n\t\t\tresolve();\n\t\t};\n\t\tif (signal !== undefined) {\n\t\t\tsignal.addEventListener(\"abort\", cancel, { once: true });\n\t\t\tremoveAbort = () => signal.removeEventListener(\"abort\", cancel);\n\t\t}\n\t\ttimer = setTimeout(() => {\n\t\t\tif (stop === undefined) {\n\t\t\t\tstop = \"timeout\";\n\t\t\t\tcontroller.abort();\n\t\t\t\tresolve();\n\t\t\t}\n\t\t}, timeout);\n\t});\n\tconst callbackPromise = callback(controller.signal).then(\n\t\t(value) => ({ kind: \"value\" as const, value }),\n\t\t() => ({ kind: \"failed\" as const }),\n\t);\n\ttry {\n\t\tconst outcome = await Promise.race([callbackPromise, stopPromise.then(() => ({ kind: \"stopped\" as const }))]);\n\t\tif (outcome.kind !== \"value\" || stop !== undefined) {\n\t\t\tconst failure = outcome.kind === \"failed\" && stop === undefined;\n\t\t\treturn error(\n\t\t\t\trequest.operationId,\n\t\t\t\trequest.fileRef,\n\t\t\t\trequest.mode,\n\t\t\t\tstop === \"cancelled\" ? \"cancelled\" : \"failed\",\n\t\t\t\tfailure ? \"adapter_failed\" : stop === \"cancelled\" ? \"cancelled\" : \"timeout\",\n\t\t\t\tfailure\n\t\t\t\t\t? \"Workspace writer failed\"\n\t\t\t\t\t: stop === \"cancelled\"\n\t\t\t\t\t\t? \"Workspace write was cancelled\"\n\t\t\t\t\t\t: \"Workspace write timed out\",\n\t\t\t);\n\t\t}\n\t\treturn (\n\t\t\tprojectWorkspaceFilesWriteV1(outcome.value, request) ??\n\t\t\terror(\n\t\t\t\trequest.operationId,\n\t\t\t\trequest.fileRef,\n\t\t\t\trequest.mode,\n\t\t\t\t\"failed\",\n\t\t\t\t\"adapter_failed\",\n\t\t\t\t\"Workspace writer returned an invalid result\",\n\t\t\t)\n\t\t);\n\t} finally {\n\t\tif (timer !== undefined) clearTimeout(timer);\n\t\tremoveAbort?.();\n\t}\n}\n\nexport function createNodeWorkspaceFilesWriteAdapterV1(\n\toptions: NodeWorkspaceFilesWriteAdapterOptionsV1 = {},\n): WorkspaceFilesWriteAdapterV1 {\n\tconst timeout = timeoutMs(options.defaultTimeoutMs);\n\treturn {\n\t\tadapterId: WORKSPACE_FILES_WRITE_HOST_ADAPTER_ID_V1,\n\t\tadapterVersion: WORKSPACE_FILES_WRITE_HOST_ADAPTER_VERSION_V1,\n\t\twrite: async (request, signal) => {\n\t\t\tconst invalid = validateRequest(request);\n\t\t\tif (invalid !== undefined)\n\t\t\t\treturn error(request.operationId, request.fileRef, request.mode, \"failed\", \"invalid_input\", invalid);\n\t\t\treturn runHost(\n\t\t\t\toptions.write === undefined\n\t\t\t\t\t? undefined\n\t\t\t\t\t: (hostSignal) => options.write?.(request, hostSignal) ?? Promise.resolve(undefined),\n\t\t\t\ttimeout,\n\t\t\t\trequest,\n\t\t\t\tsignal,\n\t\t\t);\n\t\t},\n\t};\n}\n\nexport { safeRef, validateRequest };\n"]}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { PluginErrorV1, PluginResultStatusV1 } from "@agent-forge/plugin-sdk";
|
|
2
|
+
export declare const WORKSPACE_FILES_WRITE_PLUGIN_ID_V1: "agent-forge.plugin.workspace-files-write";
|
|
3
|
+
export declare const WORKSPACE_FILES_WRITE_CAPABILITY_VERSION_V1: 1;
|
|
4
|
+
export declare const WORKSPACE_FILES_WRITE_HOST_ADAPTER_ID_V1: "node-workspace-files-write";
|
|
5
|
+
export declare const WORKSPACE_FILES_WRITE_HOST_ADAPTER_VERSION_V1: "1.0.0";
|
|
6
|
+
export type WorkspaceFilesWriteModeV1 = "replace" | "patch";
|
|
7
|
+
/**
|
|
8
|
+
* A write request carries only opaque references. The host resolves the source
|
|
9
|
+
* artifact and file handle; bytes and local paths never cross the capability
|
|
10
|
+
* boundary or enter the model context.
|
|
11
|
+
*/
|
|
12
|
+
export interface WorkspaceFilesWriteRequestV1 {
|
|
13
|
+
readonly version: 1;
|
|
14
|
+
readonly operationId: string;
|
|
15
|
+
readonly fileRef: string;
|
|
16
|
+
readonly mode: WorkspaceFilesWriteModeV1;
|
|
17
|
+
readonly sourceArtifactRef: string;
|
|
18
|
+
readonly expectedVersion?: string;
|
|
19
|
+
readonly createIfMissing?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface WorkspaceFilesWriteResultV1 {
|
|
22
|
+
readonly version: 1;
|
|
23
|
+
readonly operationId: string;
|
|
24
|
+
readonly status: PluginResultStatusV1;
|
|
25
|
+
readonly fileRef: string;
|
|
26
|
+
readonly mode: WorkspaceFilesWriteModeV1;
|
|
27
|
+
readonly changed: boolean;
|
|
28
|
+
readonly bytesWritten: number;
|
|
29
|
+
/**
|
|
30
|
+
* True only when the host already renamed the new bytes onto the target and a
|
|
31
|
+
* later step (artifact record or cancellation) failed; it keeps a failed or
|
|
32
|
+
* cancelled result distinguishable from a failure that never touched the disk.
|
|
33
|
+
*/
|
|
34
|
+
readonly committed?: boolean;
|
|
35
|
+
readonly outputArtifactRef?: string;
|
|
36
|
+
readonly versionToken?: string;
|
|
37
|
+
readonly error?: PluginErrorV1;
|
|
38
|
+
}
|
|
39
|
+
export interface WorkspaceFilesWriteAdapterV1 {
|
|
40
|
+
readonly adapterId: string;
|
|
41
|
+
readonly adapterVersion: string;
|
|
42
|
+
readonly write: (request: WorkspaceFilesWriteRequestV1, signal?: AbortSignal) => Promise<WorkspaceFilesWriteResultV1>;
|
|
43
|
+
}
|
|
44
|
+
export interface NodeWorkspaceFilesWriteAdapterOptionsV1 {
|
|
45
|
+
/** Host-owned resolver and writer; the model never supplies a local path. */
|
|
46
|
+
readonly write?: (request: WorkspaceFilesWriteRequestV1, signal: AbortSignal) => Promise<unknown>;
|
|
47
|
+
readonly defaultTimeoutMs?: number;
|
|
48
|
+
}
|
|
49
|
+
export interface WorkspaceFilesPatchOperationV1 {
|
|
50
|
+
readonly start: number;
|
|
51
|
+
readonly end: number;
|
|
52
|
+
readonly replacement: string;
|
|
53
|
+
}
|
|
54
|
+
export interface WorkspaceFilesPatchDocumentV1 {
|
|
55
|
+
readonly version: 1;
|
|
56
|
+
readonly operations: readonly WorkspaceFilesPatchOperationV1[];
|
|
57
|
+
}
|
|
58
|
+
/** Host-only resolver: it may return a local path, but that path never enters a model-facing DTO. */
|
|
59
|
+
export interface NodeWorkspaceFilesWriteHostOptionsV1 {
|
|
60
|
+
readonly root: string;
|
|
61
|
+
readonly resolveFileRef: (fileRef: string, signal: AbortSignal) => Promise<string> | string;
|
|
62
|
+
/** Optional host file reader seam for virtual filesystems and deterministic tests. */
|
|
63
|
+
readonly readWorkspaceFile?: (path: string, signal: AbortSignal) => Promise<Uint8Array>;
|
|
64
|
+
readonly readArtifact: (artifactRef: string, signal: AbortSignal) => Promise<Uint8Array>;
|
|
65
|
+
readonly writeArtifact?: (input: {
|
|
66
|
+
readonly operationId: string;
|
|
67
|
+
readonly fileRef: string;
|
|
68
|
+
readonly bytes: Uint8Array;
|
|
69
|
+
}, signal: AbortSignal) => Promise<{
|
|
70
|
+
readonly artifactRef: string;
|
|
71
|
+
}>;
|
|
72
|
+
readonly defaultTimeoutMs?: number;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PluginErrorV1, PluginResultStatusV1 } from \"@agent-forge/plugin-sdk\";\n\nexport const WORKSPACE_FILES_WRITE_PLUGIN_ID_V1 = \"agent-forge.plugin.workspace-files-write\" as const;\nexport const WORKSPACE_FILES_WRITE_CAPABILITY_VERSION_V1 = 1 as const;\nexport const WORKSPACE_FILES_WRITE_HOST_ADAPTER_ID_V1 = \"node-workspace-files-write\" as const;\nexport const WORKSPACE_FILES_WRITE_HOST_ADAPTER_VERSION_V1 = \"1.0.0\" as const;\n\nexport type WorkspaceFilesWriteModeV1 = \"replace\" | \"patch\";\n\n/**\n * A write request carries only opaque references. The host resolves the source\n * artifact and file handle; bytes and local paths never cross the capability\n * boundary or enter the model context.\n */\nexport interface WorkspaceFilesWriteRequestV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly fileRef: string;\n\treadonly mode: WorkspaceFilesWriteModeV1;\n\treadonly sourceArtifactRef: string;\n\treadonly expectedVersion?: string;\n\treadonly createIfMissing?: boolean;\n}\n\nexport interface WorkspaceFilesWriteResultV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly status: PluginResultStatusV1;\n\treadonly fileRef: string;\n\treadonly mode: WorkspaceFilesWriteModeV1;\n\treadonly changed: boolean;\n\treadonly bytesWritten: number;\n\t/**\n\t * True only when the host already renamed the new bytes onto the target and a\n\t * later step (artifact record or cancellation) failed; it keeps a failed or\n\t * cancelled result distinguishable from a failure that never touched the disk.\n\t */\n\treadonly committed?: boolean;\n\treadonly outputArtifactRef?: string;\n\treadonly versionToken?: string;\n\treadonly error?: PluginErrorV1;\n}\n\nexport interface WorkspaceFilesWriteAdapterV1 {\n\treadonly adapterId: string;\n\treadonly adapterVersion: string;\n\treadonly write: (\n\t\trequest: WorkspaceFilesWriteRequestV1,\n\t\tsignal?: AbortSignal,\n\t) => Promise<WorkspaceFilesWriteResultV1>;\n}\n\nexport interface NodeWorkspaceFilesWriteAdapterOptionsV1 {\n\t/** Host-owned resolver and writer; the model never supplies a local path. */\n\treadonly write?: (request: WorkspaceFilesWriteRequestV1, signal: AbortSignal) => Promise<unknown>;\n\treadonly defaultTimeoutMs?: number;\n}\n\nexport interface WorkspaceFilesPatchOperationV1 {\n\treadonly start: number;\n\treadonly end: number;\n\treadonly replacement: string;\n}\n\nexport interface WorkspaceFilesPatchDocumentV1 {\n\treadonly version: 1;\n\treadonly operations: readonly WorkspaceFilesPatchOperationV1[];\n}\n\n/** Host-only resolver: it may return a local path, but that path never enters a model-facing DTO. */\nexport interface NodeWorkspaceFilesWriteHostOptionsV1 {\n\treadonly root: string;\n\treadonly resolveFileRef: (fileRef: string, signal: AbortSignal) => Promise<string> | string;\n\t/** Optional host file reader seam for virtual filesystems and deterministic tests. */\n\treadonly readWorkspaceFile?: (path: string, signal: AbortSignal) => Promise<Uint8Array>;\n\treadonly readArtifact: (artifactRef: string, signal: AbortSignal) => Promise<Uint8Array>;\n\treadonly writeArtifact?: (\n\t\tinput: { readonly operationId: string; readonly fileRef: string; readonly bytes: Uint8Array },\n\t\tsignal: AbortSignal,\n\t) => Promise<{ readonly artifactRef: string }>;\n\treadonly defaultTimeoutMs?: number;\n}\n"]}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export const WORKSPACE_FILES_WRITE_PLUGIN_ID_V1 = "agent-forge.plugin.workspace-files-write";
|
|
2
|
+
export const WORKSPACE_FILES_WRITE_CAPABILITY_VERSION_V1 = 1;
|
|
3
|
+
export const WORKSPACE_FILES_WRITE_HOST_ADAPTER_ID_V1 = "node-workspace-files-write";
|
|
4
|
+
export const WORKSPACE_FILES_WRITE_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,kCAAkC,GAAG,0CAAmD,CAAC;AACtG,MAAM,CAAC,MAAM,2CAA2C,GAAG,CAAU,CAAC;AACtE,MAAM,CAAC,MAAM,wCAAwC,GAAG,4BAAqC,CAAC;AAC9F,MAAM,CAAC,MAAM,6CAA6C,GAAG,OAAgB,CAAC","sourcesContent":["import type { PluginErrorV1, PluginResultStatusV1 } from \"@agent-forge/plugin-sdk\";\n\nexport const WORKSPACE_FILES_WRITE_PLUGIN_ID_V1 = \"agent-forge.plugin.workspace-files-write\" as const;\nexport const WORKSPACE_FILES_WRITE_CAPABILITY_VERSION_V1 = 1 as const;\nexport const WORKSPACE_FILES_WRITE_HOST_ADAPTER_ID_V1 = \"node-workspace-files-write\" as const;\nexport const WORKSPACE_FILES_WRITE_HOST_ADAPTER_VERSION_V1 = \"1.0.0\" as const;\n\nexport type WorkspaceFilesWriteModeV1 = \"replace\" | \"patch\";\n\n/**\n * A write request carries only opaque references. The host resolves the source\n * artifact and file handle; bytes and local paths never cross the capability\n * boundary or enter the model context.\n */\nexport interface WorkspaceFilesWriteRequestV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly fileRef: string;\n\treadonly mode: WorkspaceFilesWriteModeV1;\n\treadonly sourceArtifactRef: string;\n\treadonly expectedVersion?: string;\n\treadonly createIfMissing?: boolean;\n}\n\nexport interface WorkspaceFilesWriteResultV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly status: PluginResultStatusV1;\n\treadonly fileRef: string;\n\treadonly mode: WorkspaceFilesWriteModeV1;\n\treadonly changed: boolean;\n\treadonly bytesWritten: number;\n\t/**\n\t * True only when the host already renamed the new bytes onto the target and a\n\t * later step (artifact record or cancellation) failed; it keeps a failed or\n\t * cancelled result distinguishable from a failure that never touched the disk.\n\t */\n\treadonly committed?: boolean;\n\treadonly outputArtifactRef?: string;\n\treadonly versionToken?: string;\n\treadonly error?: PluginErrorV1;\n}\n\nexport interface WorkspaceFilesWriteAdapterV1 {\n\treadonly adapterId: string;\n\treadonly adapterVersion: string;\n\treadonly write: (\n\t\trequest: WorkspaceFilesWriteRequestV1,\n\t\tsignal?: AbortSignal,\n\t) => Promise<WorkspaceFilesWriteResultV1>;\n}\n\nexport interface NodeWorkspaceFilesWriteAdapterOptionsV1 {\n\t/** Host-owned resolver and writer; the model never supplies a local path. */\n\treadonly write?: (request: WorkspaceFilesWriteRequestV1, signal: AbortSignal) => Promise<unknown>;\n\treadonly defaultTimeoutMs?: number;\n}\n\nexport interface WorkspaceFilesPatchOperationV1 {\n\treadonly start: number;\n\treadonly end: number;\n\treadonly replacement: string;\n}\n\nexport interface WorkspaceFilesPatchDocumentV1 {\n\treadonly version: 1;\n\treadonly operations: readonly WorkspaceFilesPatchOperationV1[];\n}\n\n/** Host-only resolver: it may return a local path, but that path never enters a model-facing DTO. */\nexport interface NodeWorkspaceFilesWriteHostOptionsV1 {\n\treadonly root: string;\n\treadonly resolveFileRef: (fileRef: string, signal: AbortSignal) => Promise<string> | string;\n\t/** Optional host file reader seam for virtual filesystems and deterministic tests. */\n\treadonly readWorkspaceFile?: (path: string, signal: AbortSignal) => Promise<Uint8Array>;\n\treadonly readArtifact: (artifactRef: string, signal: AbortSignal) => Promise<Uint8Array>;\n\treadonly writeArtifact?: (\n\t\tinput: { readonly operationId: string; readonly fileRef: string; readonly bytes: Uint8Array },\n\t\tsignal: AbortSignal,\n\t) => Promise<{ readonly artifactRef: string }>;\n\treadonly defaultTimeoutMs?: number;\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ai-agent-forge/plugin-workspace-files-write",
|
|
3
|
+
"version": "0.85.0",
|
|
4
|
+
"description": "Workspace file write capability plugin for Agent Forge (full-control workspace_write tool with atomic replace/patch)",
|
|
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
|
+
"workspace",
|
|
30
|
+
"write"
|
|
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/workspace-files-write"
|
|
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.workspace-files-write",
|
|
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": "workspace.files.write", "version": 1, "kind": "contribution" }]
|
|
9
|
+
}
|