@warlock.js/ai-workspace 5.29.0 → 5.31.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/CHANGELOG.md +68 -0
- package/README.md +40 -21
- package/cjs/index.cjs +3295 -360
- package/cjs/index.cjs.map +1 -1
- package/esm/backends/local.d.mts +1 -1
- package/esm/backends/local.d.mts.map +1 -1
- package/esm/backends/local.mjs +199 -31
- package/esm/backends/local.mjs.map +1 -1
- package/esm/backends/mock.d.mts +12 -6
- package/esm/backends/mock.d.mts.map +1 -1
- package/esm/backends/mock.mjs +45 -3
- package/esm/backends/mock.mjs.map +1 -1
- package/esm/contracts/index.d.mts +3 -3
- package/esm/contracts/tool-io.type.d.mts +248 -11
- package/esm/contracts/tool-io.type.d.mts.map +1 -1
- package/esm/contracts/workspace-backend.contract.d.mts +78 -2
- package/esm/contracts/workspace-backend.contract.d.mts.map +1 -1
- package/esm/contracts/workspace-ops.contract.d.mts +41 -16
- package/esm/contracts/workspace-ops.contract.d.mts.map +1 -1
- package/esm/contracts/workspace-policy.type.d.mts +142 -14
- package/esm/contracts/workspace-policy.type.d.mts.map +1 -1
- package/esm/contracts/workspace.contract.d.mts +75 -22
- package/esm/contracts/workspace.contract.d.mts.map +1 -1
- package/esm/errors.d.mts +80 -6
- package/esm/errors.d.mts.map +1 -1
- package/esm/errors.mjs +23 -2
- package/esm/errors.mjs.map +1 -1
- package/esm/index.d.mts +13 -5
- package/esm/index.mjs +10 -2
- package/esm/ops.d.mts +22 -3
- package/esm/ops.d.mts.map +1 -1
- package/esm/ops.mjs +341 -82
- package/esm/ops.mjs.map +1 -1
- package/esm/policy/jail.mjs +73 -0
- package/esm/policy/jail.mjs.map +1 -0
- package/esm/policy/policy.d.mts +26 -16
- package/esm/policy/policy.d.mts.map +1 -1
- package/esm/policy/policy.mjs +62 -86
- package/esm/policy/policy.mjs.map +1 -1
- package/esm/policy/shell-check.d.mts +64 -0
- package/esm/policy/shell-check.d.mts.map +1 -0
- package/esm/policy/shell-check.mjs +627 -0
- package/esm/policy/shell-check.mjs.map +1 -0
- package/esm/policy/shell-parser.mjs +802 -0
- package/esm/policy/shell-parser.mjs.map +1 -0
- package/esm/policy/tokenize-command.d.mts +63 -0
- package/esm/policy/tokenize-command.d.mts.map +1 -0
- package/esm/policy/tokenize-command.mjs +76 -24
- package/esm/policy/tokenize-command.mjs.map +1 -1
- package/esm/shell-jobs.d.mts +45 -0
- package/esm/shell-jobs.d.mts.map +1 -0
- package/esm/shell-jobs.mjs +247 -0
- package/esm/shell-jobs.mjs.map +1 -0
- package/esm/todos.d.mts +22 -0
- package/esm/todos.d.mts.map +1 -0
- package/esm/todos.mjs +73 -0
- package/esm/todos.mjs.map +1 -0
- package/esm/tools/edit-file.d.mts +1 -1
- package/esm/tools/edit-file.d.mts.map +1 -1
- package/esm/tools/edit-file.mjs +3 -2
- package/esm/tools/edit-file.mjs.map +1 -1
- package/esm/tools/glob.d.mts +1 -1
- package/esm/tools/glob.d.mts.map +1 -1
- package/esm/tools/glob.mjs +3 -2
- package/esm/tools/glob.mjs.map +1 -1
- package/esm/tools/grep.d.mts +1 -1
- package/esm/tools/grep.d.mts.map +1 -1
- package/esm/tools/grep.mjs +3 -2
- package/esm/tools/grep.mjs.map +1 -1
- package/esm/tools/json-schemas.mjs +175 -0
- package/esm/tools/json-schemas.mjs.map +1 -0
- package/esm/tools/list-dir.d.mts +32 -0
- package/esm/tools/list-dir.d.mts.map +1 -0
- package/esm/tools/list-dir.mjs +50 -0
- package/esm/tools/list-dir.mjs.map +1 -0
- package/esm/tools/multi-edit.d.mts +38 -0
- package/esm/tools/multi-edit.d.mts.map +1 -0
- package/esm/tools/multi-edit.mjs +121 -0
- package/esm/tools/multi-edit.mjs.map +1 -0
- package/esm/tools/read-file.d.mts +5 -5
- package/esm/tools/read-file.d.mts.map +1 -1
- package/esm/tools/read-file.mjs +10 -20
- package/esm/tools/read-file.mjs.map +1 -1
- package/esm/tools/run-shell.d.mts +39 -13
- package/esm/tools/run-shell.d.mts.map +1 -1
- package/esm/tools/run-shell.mjs +60 -47
- package/esm/tools/run-shell.mjs.map +1 -1
- package/esm/tools/run-tests.d.mts +11 -2
- package/esm/tools/run-tests.d.mts.map +1 -1
- package/esm/tools/run-tests.mjs +15 -7
- package/esm/tools/run-tests.mjs.map +1 -1
- package/esm/tools/schema.mjs +24 -19
- package/esm/tools/schema.mjs.map +1 -1
- package/esm/tools/shell-kill.d.mts +25 -0
- package/esm/tools/shell-kill.d.mts.map +1 -0
- package/esm/tools/shell-kill.mjs +36 -0
- package/esm/tools/shell-kill.mjs.map +1 -0
- package/esm/tools/shell-output.d.mts +30 -0
- package/esm/tools/shell-output.d.mts.map +1 -0
- package/esm/tools/shell-output.mjs +49 -0
- package/esm/tools/shell-output.mjs.map +1 -0
- package/esm/tools/todo.d.mts +43 -0
- package/esm/tools/todo.d.mts.map +1 -0
- package/esm/tools/todo.mjs +126 -0
- package/esm/tools/todo.mjs.map +1 -0
- package/esm/tools/write-file.d.mts +1 -1
- package/esm/tools/write-file.d.mts.map +1 -1
- package/esm/tools/write-file.mjs +3 -2
- package/esm/tools/write-file.mjs.map +1 -1
- package/esm/workspace.d.mts +12 -9
- package/esm/workspace.d.mts.map +1 -1
- package/esm/workspace.mjs +109 -28
- package/esm/workspace.mjs.map +1 -1
- package/llms-full.txt +192 -35
- package/llms.txt +1 -1
- package/package.json +5 -4
- package/skills/README.md +2 -2
- package/skills/build-loop-agent/SKILL.md +7 -7
- package/skills/index.md +6 -1
- package/skills/use-a-workspace/SKILL.md +185 -28
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { objectSchema, optionalNumberField, stringField } from "./schema.mjs";
|
|
2
|
+
import { SHELL_OUTPUT_JSON_SCHEMA } from "./json-schemas.mjs";
|
|
3
|
+
import { tool } from "@warlock.js/ai";
|
|
4
|
+
|
|
5
|
+
//#region ../../packages/ai-workspace/src/tools/shell-output.ts
|
|
6
|
+
/** Default tool name exposed to the LLM. */
|
|
7
|
+
const DEFAULT_NAME = "shell_output";
|
|
8
|
+
/** Input schema for the `shell_output` tool. */
|
|
9
|
+
const inputSchema = objectSchema({
|
|
10
|
+
id: stringField(),
|
|
11
|
+
since: optionalNumberField(),
|
|
12
|
+
waitMs: optionalNumberField()
|
|
13
|
+
}, SHELL_OUTPUT_JSON_SCHEMA);
|
|
14
|
+
/**
|
|
15
|
+
* Build the agent-facing `shell_output` tool: the new output of a
|
|
16
|
+
* background command started with `run_shell({ background: true })`, plus
|
|
17
|
+
* its state (`running` / `exited` / `killed` / `timed-out`, `exitCode`).
|
|
18
|
+
*
|
|
19
|
+
* Without `since` it returns everything produced since the previous read;
|
|
20
|
+
* `cursor` in the result is the position to pass as `since` next time.
|
|
21
|
+
* `waitMs` (at most 30 s) waits for new output or for the command to stop
|
|
22
|
+
* when there is nothing new yet — the agent run's signal ends the wait
|
|
23
|
+
* early. Only the newest `shell.maxOutputBytes` of a command's output are
|
|
24
|
+
* kept; `droppedBytes` says how much the reader missed.
|
|
25
|
+
*
|
|
26
|
+
* @param ops - The shared, policy-enforced operation layer.
|
|
27
|
+
* @param options - Optional overrides; `name` renames the LLM-visible tool.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* const { data } = await makeShellOutputTool(ops).invoke({ id: "job-1", waitMs: 2_000 });
|
|
31
|
+
* if (data?.stdout.includes("ready")) console.log("dev server is up");
|
|
32
|
+
*/
|
|
33
|
+
function makeShellOutputTool(ops, options) {
|
|
34
|
+
return tool({
|
|
35
|
+
name: options?.name ?? DEFAULT_NAME,
|
|
36
|
+
description: "Read new output of a background command started with run_shell (background: true) and see whether it is still running. Returns output since the last read; waitMs waits briefly for more.",
|
|
37
|
+
action: (input) => `Reading output of ${typeof input.id === "string" ? input.id : "a background command"}`,
|
|
38
|
+
input: inputSchema,
|
|
39
|
+
execute: (input, ctx) => ops.shellOutput(input.id, {
|
|
40
|
+
...input.since !== void 0 ? { since: input.since } : {},
|
|
41
|
+
...input.waitMs !== void 0 ? { waitMs: input.waitMs } : {},
|
|
42
|
+
...ctx?.signal ? { signal: ctx.signal } : {}
|
|
43
|
+
})
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
48
|
+
export { makeShellOutputTool };
|
|
49
|
+
//# sourceMappingURL=shell-output.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-output.mjs","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/tools/shell-output.ts"],"sourcesContent":["import { tool, type ToolContract } from \"@warlock.js/ai\";\nimport type { ShellOutputInput, ShellOutputResult, WorkspaceOps } from \"../contracts\";\nimport { SHELL_OUTPUT_JSON_SCHEMA } from \"./json-schemas\";\nimport { objectSchema, optionalNumberField, stringField } from \"./schema\";\n\n/** Default tool name exposed to the LLM. */\nconst DEFAULT_NAME = \"shell_output\";\n\n/** Input schema for the `shell_output` tool. */\nconst inputSchema = objectSchema<ShellOutputInput>(\n {\n id: stringField(),\n since: optionalNumberField(),\n waitMs: optionalNumberField(),\n },\n SHELL_OUTPUT_JSON_SCHEMA,\n);\n\n/**\n * Build the agent-facing `shell_output` tool: the new output of a\n * background command started with `run_shell({ background: true })`, plus\n * its state (`running` / `exited` / `killed` / `timed-out`, `exitCode`).\n *\n * Without `since` it returns everything produced since the previous read;\n * `cursor` in the result is the position to pass as `since` next time.\n * `waitMs` (at most 30 s) waits for new output or for the command to stop\n * when there is nothing new yet — the agent run's signal ends the wait\n * early. Only the newest `shell.maxOutputBytes` of a command's output are\n * kept; `droppedBytes` says how much the reader missed.\n *\n * @param ops - The shared, policy-enforced operation layer.\n * @param options - Optional overrides; `name` renames the LLM-visible tool.\n *\n * @example\n * const { data } = await makeShellOutputTool(ops).invoke({ id: \"job-1\", waitMs: 2_000 });\n * if (data?.stdout.includes(\"ready\")) console.log(\"dev server is up\");\n */\nexport function makeShellOutputTool(\n ops: WorkspaceOps,\n options?: { name?: string },\n): ToolContract<ShellOutputInput, ShellOutputResult> {\n return tool<ShellOutputInput, ShellOutputResult>({\n name: options?.name ?? DEFAULT_NAME,\n description:\n \"Read new output of a background command started with run_shell \" +\n \"(background: true) and see whether it is still running. Returns \" +\n \"output since the last read; waitMs waits briefly for more.\",\n action: (input) =>\n `Reading output of ${typeof input.id === \"string\" ? input.id : \"a background command\"}`,\n input: inputSchema,\n execute: (input, ctx) =>\n ops.shellOutput(input.id, {\n ...(input.since !== undefined ? { since: input.since } : {}),\n ...(input.waitMs !== undefined ? { waitMs: input.waitMs } : {}),\n ...(ctx?.signal ? { signal: ctx.signal } : {}),\n }),\n });\n}\n"],"mappings":";;;;;;AAMA,MAAM,eAAe;;AAGrB,MAAM,cAAc,aAClB;CACE,IAAI,YAAY;CAChB,OAAO,oBAAoB;CAC3B,QAAQ,oBAAoB;AAC9B,GACA,wBACF;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,oBACd,KACA,SACmD;CACnD,OAAO,KAA0C;EAC/C,MAAM,SAAS,QAAQ;EACvB,aACE;EAGF,SAAS,UACP,qBAAqB,OAAO,MAAM,OAAO,WAAW,MAAM,KAAK;EACjE,OAAO;EACP,UAAU,OAAO,QACf,IAAI,YAAY,MAAM,IAAI;GACxB,GAAI,MAAM,UAAU,SAAY,EAAE,OAAO,MAAM,MAAM,IAAI,CAAC;GAC1D,GAAI,MAAM,WAAW,SAAY,EAAE,QAAQ,MAAM,OAAO,IAAI,CAAC;GAC7D,GAAI,KAAK,SAAS,EAAE,QAAQ,IAAI,OAAO,IAAI,CAAC;EAC9C,CAAC;CACL,CAAC;AACH"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { TodoListResult, TodoWriteInput } from "../contracts/tool-io.type.mjs";
|
|
2
|
+
import { TodoStore } from "../todos.mjs";
|
|
3
|
+
import { ToolContract } from "@warlock.js/ai";
|
|
4
|
+
|
|
5
|
+
//#region ../../packages/ai-workspace/src/tools/todo.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Build the agent-facing `todo_write` tool over a workspace's todo list:
|
|
8
|
+
* the model sends the **complete** updated list (each item `content` +
|
|
9
|
+
* `status` of `pending` / `in_progress` / `completed`, optional `id`) and it
|
|
10
|
+
* replaces the old one. The list lives in memory, per workspace (shared by
|
|
11
|
+
* its `readonly()` / `scope()` projections), for the length of the run.
|
|
12
|
+
*
|
|
13
|
+
* @param store - The workspace's todo list.
|
|
14
|
+
* @param options - Optional overrides; `name` renames the LLM-visible tool.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* await makeTodoWriteTool(store).invoke({
|
|
18
|
+
* todos: [
|
|
19
|
+
* { content: "Reproduce the failing test", status: "completed" },
|
|
20
|
+
* { content: "Fix the rounding bug", status: "in_progress" },
|
|
21
|
+
* ],
|
|
22
|
+
* });
|
|
23
|
+
*/
|
|
24
|
+
declare function makeTodoWriteTool(store: TodoStore, options?: {
|
|
25
|
+
name?: string;
|
|
26
|
+
}): ToolContract<TodoWriteInput, TodoListResult>;
|
|
27
|
+
/**
|
|
28
|
+
* Build the agent-facing `todo_read` tool: the workspace's current todo
|
|
29
|
+
* list and a count per status.
|
|
30
|
+
*
|
|
31
|
+
* @param store - The workspace's todo list.
|
|
32
|
+
* @param options - Optional overrides; `name` renames the LLM-visible tool.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* const { data } = await makeTodoReadTool(store).invoke({});
|
|
36
|
+
* console.log(data?.counts.pending);
|
|
37
|
+
*/
|
|
38
|
+
declare function makeTodoReadTool(store: TodoStore, options?: {
|
|
39
|
+
name?: string;
|
|
40
|
+
}): ToolContract<Record<string, never>, TodoListResult>;
|
|
41
|
+
//#endregion
|
|
42
|
+
export { makeTodoReadTool, makeTodoWriteTool };
|
|
43
|
+
//# sourceMappingURL=todo.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"todo.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/tools/todo.ts"],"mappings":";;;;;;;;AA8HA;;;;;;;;;;;;;;;iBAAgB,iBAAA,CACd,KAAA,EAAO,SAAA,EACP,OAAA;EAAY,IAAA;AAAA,IACX,YAAA,CAAa,cAAA,EAAgB,cAAA;AAwBhC;;;;;;;;;;;AAAA,iBAAgB,gBAAA,CACd,KAAA,EAAO,SAAA,EACP,OAAA;EAAY,IAAA;AAAA,IACX,YAAA,CAAa,MAAA,iBAAuB,cAAA"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { withJsonSchema } from "./schema.mjs";
|
|
2
|
+
import { TODO_READ_JSON_SCHEMA, TODO_WRITE_JSON_SCHEMA } from "./json-schemas.mjs";
|
|
3
|
+
import { TODO_STATUSES, todoListProblems } from "../todos.mjs";
|
|
4
|
+
import { tool } from "@warlock.js/ai";
|
|
5
|
+
|
|
6
|
+
//#region ../../packages/ai-workspace/src/tools/todo.ts
|
|
7
|
+
/** Whether `value` is a todo status. */
|
|
8
|
+
function isTodoStatus(value) {
|
|
9
|
+
return typeof value === "string" && TODO_STATUSES.includes(value);
|
|
10
|
+
}
|
|
11
|
+
/** Input schema for the `todo_write` tool. */
|
|
12
|
+
const todoWriteSchema = withJsonSchema({ "~standard": {
|
|
13
|
+
version: 1,
|
|
14
|
+
vendor: "ai-workspace",
|
|
15
|
+
validate(value) {
|
|
16
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return { issues: [{ message: "input must be an object" }] };
|
|
17
|
+
const raw = value.todos;
|
|
18
|
+
if (!Array.isArray(raw) || raw.length > 100) return { issues: [{
|
|
19
|
+
message: `"todos" must be an array of at most ${100} items`,
|
|
20
|
+
path: ["todos"]
|
|
21
|
+
}] };
|
|
22
|
+
const issues = [];
|
|
23
|
+
const todos = [];
|
|
24
|
+
raw.forEach((item, index) => {
|
|
25
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) {
|
|
26
|
+
issues.push({
|
|
27
|
+
message: `todos[${index}] must be an object`,
|
|
28
|
+
path: ["todos", index]
|
|
29
|
+
});
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const candidate = item;
|
|
33
|
+
if (typeof candidate.content !== "string") issues.push({
|
|
34
|
+
message: `todos[${index}].content must be a string`,
|
|
35
|
+
path: [
|
|
36
|
+
"todos",
|
|
37
|
+
index,
|
|
38
|
+
"content"
|
|
39
|
+
]
|
|
40
|
+
});
|
|
41
|
+
if (!isTodoStatus(candidate.status)) issues.push({
|
|
42
|
+
message: `todos[${index}].status must be one of ${TODO_STATUSES.join(", ")}`,
|
|
43
|
+
path: [
|
|
44
|
+
"todos",
|
|
45
|
+
index,
|
|
46
|
+
"status"
|
|
47
|
+
]
|
|
48
|
+
});
|
|
49
|
+
if (candidate.id !== void 0 && typeof candidate.id !== "string") issues.push({
|
|
50
|
+
message: `todos[${index}].id must be a string when provided`,
|
|
51
|
+
path: [
|
|
52
|
+
"todos",
|
|
53
|
+
index,
|
|
54
|
+
"id"
|
|
55
|
+
]
|
|
56
|
+
});
|
|
57
|
+
if (typeof candidate.content === "string" && isTodoStatus(candidate.status)) todos.push({
|
|
58
|
+
content: candidate.content,
|
|
59
|
+
status: candidate.status,
|
|
60
|
+
...typeof candidate.id === "string" ? { id: candidate.id } : {}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
if (issues.length === 0) for (const problem of todoListProblems(todos)) issues.push({
|
|
64
|
+
message: problem,
|
|
65
|
+
path: ["todos"]
|
|
66
|
+
});
|
|
67
|
+
return issues.length > 0 ? { issues } : { value: { todos } };
|
|
68
|
+
}
|
|
69
|
+
} }, TODO_WRITE_JSON_SCHEMA);
|
|
70
|
+
/** Input schema for the `todo_read` tool (no arguments). */
|
|
71
|
+
const todoReadSchema = withJsonSchema({ "~standard": {
|
|
72
|
+
version: 1,
|
|
73
|
+
vendor: "ai-workspace",
|
|
74
|
+
validate: () => ({ value: {} })
|
|
75
|
+
} }, TODO_READ_JSON_SCHEMA);
|
|
76
|
+
/**
|
|
77
|
+
* Build the agent-facing `todo_write` tool over a workspace's todo list:
|
|
78
|
+
* the model sends the **complete** updated list (each item `content` +
|
|
79
|
+
* `status` of `pending` / `in_progress` / `completed`, optional `id`) and it
|
|
80
|
+
* replaces the old one. The list lives in memory, per workspace (shared by
|
|
81
|
+
* its `readonly()` / `scope()` projections), for the length of the run.
|
|
82
|
+
*
|
|
83
|
+
* @param store - The workspace's todo list.
|
|
84
|
+
* @param options - Optional overrides; `name` renames the LLM-visible tool.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* await makeTodoWriteTool(store).invoke({
|
|
88
|
+
* todos: [
|
|
89
|
+
* { content: "Reproduce the failing test", status: "completed" },
|
|
90
|
+
* { content: "Fix the rounding bug", status: "in_progress" },
|
|
91
|
+
* ],
|
|
92
|
+
* });
|
|
93
|
+
*/
|
|
94
|
+
function makeTodoWriteTool(store, options) {
|
|
95
|
+
return tool({
|
|
96
|
+
name: options?.name ?? "todo_write",
|
|
97
|
+
description: "Replace your todo list for this task with the complete updated list (each item: content and status pending | in_progress | completed). Use it to plan multi-step work and to mark progress.",
|
|
98
|
+
action: "Updating the todo list",
|
|
99
|
+
input: todoWriteSchema,
|
|
100
|
+
execute: async (input) => store.write(input.todos)
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Build the agent-facing `todo_read` tool: the workspace's current todo
|
|
105
|
+
* list and a count per status.
|
|
106
|
+
*
|
|
107
|
+
* @param store - The workspace's todo list.
|
|
108
|
+
* @param options - Optional overrides; `name` renames the LLM-visible tool.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* const { data } = await makeTodoReadTool(store).invoke({});
|
|
112
|
+
* console.log(data?.counts.pending);
|
|
113
|
+
*/
|
|
114
|
+
function makeTodoReadTool(store, options) {
|
|
115
|
+
return tool({
|
|
116
|
+
name: options?.name ?? "todo_read",
|
|
117
|
+
description: "Read your current todo list for this task.",
|
|
118
|
+
action: "Reading the todo list",
|
|
119
|
+
input: todoReadSchema,
|
|
120
|
+
execute: async () => store.read()
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
//#endregion
|
|
125
|
+
export { makeTodoReadTool, makeTodoWriteTool };
|
|
126
|
+
//# sourceMappingURL=todo.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"todo.mjs","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/tools/todo.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { tool, type ToolContract } from \"@warlock.js/ai\";\nimport { MAX_TODOS, TODO_STATUSES, type TodoStore, todoListProblems } from \"../todos\";\nimport type {\n TodoListResult,\n TodoWriteInput,\n WorkspaceTodoInput,\n WorkspaceTodoStatus,\n} from \"../contracts\";\nimport { TODO_READ_JSON_SCHEMA, TODO_WRITE_JSON_SCHEMA } from \"./json-schemas\";\nimport { withJsonSchema } from \"./schema\";\n\n/** Whether `value` is a todo status. */\nfunction isTodoStatus(value: unknown): value is WorkspaceTodoStatus {\n return typeof value === \"string\" && (TODO_STATUSES as readonly string[]).includes(value);\n}\n\n/** Input schema for the `todo_write` tool. */\nconst todoWriteSchema = withJsonSchema<TodoWriteInput>(\n {\n \"~standard\": {\n version: 1,\n vendor: \"ai-workspace\",\n validate(value) {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return { issues: [{ message: \"input must be an object\" }] };\n }\n\n const raw = (value as Record<string, unknown>).todos;\n\n if (!Array.isArray(raw) || raw.length > MAX_TODOS) {\n return {\n issues: [\n {\n message: `\"todos\" must be an array of at most ${MAX_TODOS} items`,\n path: [\"todos\"],\n },\n ],\n };\n }\n\n const issues: StandardSchemaV1.Issue[] = [];\n const todos: WorkspaceTodoInput[] = [];\n\n raw.forEach((item: unknown, index: number) => {\n if (typeof item !== \"object\" || item === null || Array.isArray(item)) {\n issues.push({ message: `todos[${index}] must be an object`, path: [\"todos\", index] });\n\n return;\n }\n\n const candidate = item as Record<string, unknown>;\n\n if (typeof candidate.content !== \"string\") {\n issues.push({\n message: `todos[${index}].content must be a string`,\n path: [\"todos\", index, \"content\"],\n });\n }\n\n if (!isTodoStatus(candidate.status)) {\n issues.push({\n message: `todos[${index}].status must be one of ${TODO_STATUSES.join(\", \")}`,\n path: [\"todos\", index, \"status\"],\n });\n }\n\n if (candidate.id !== undefined && typeof candidate.id !== \"string\") {\n issues.push({\n message: `todos[${index}].id must be a string when provided`,\n path: [\"todos\", index, \"id\"],\n });\n }\n\n if (typeof candidate.content === \"string\" && isTodoStatus(candidate.status)) {\n todos.push({\n content: candidate.content,\n status: candidate.status,\n ...(typeof candidate.id === \"string\" ? { id: candidate.id } : {}),\n });\n }\n });\n\n if (issues.length === 0) {\n for (const problem of todoListProblems(todos)) {\n issues.push({ message: problem, path: [\"todos\"] });\n }\n }\n\n return issues.length > 0 ? { issues } : { value: { todos } };\n },\n },\n },\n TODO_WRITE_JSON_SCHEMA,\n);\n\n/** Input schema for the `todo_read` tool (no arguments). */\nconst todoReadSchema = withJsonSchema<Record<string, never>>(\n {\n \"~standard\": {\n version: 1,\n vendor: \"ai-workspace\",\n validate: () => ({ value: {} }),\n },\n },\n TODO_READ_JSON_SCHEMA,\n);\n\n/**\n * Build the agent-facing `todo_write` tool over a workspace's todo list:\n * the model sends the **complete** updated list (each item `content` +\n * `status` of `pending` / `in_progress` / `completed`, optional `id`) and it\n * replaces the old one. The list lives in memory, per workspace (shared by\n * its `readonly()` / `scope()` projections), for the length of the run.\n *\n * @param store - The workspace's todo list.\n * @param options - Optional overrides; `name` renames the LLM-visible tool.\n *\n * @example\n * await makeTodoWriteTool(store).invoke({\n * todos: [\n * { content: \"Reproduce the failing test\", status: \"completed\" },\n * { content: \"Fix the rounding bug\", status: \"in_progress\" },\n * ],\n * });\n */\nexport function makeTodoWriteTool(\n store: TodoStore,\n options?: { name?: string },\n): ToolContract<TodoWriteInput, TodoListResult> {\n return tool<TodoWriteInput, TodoListResult>({\n name: options?.name ?? \"todo_write\",\n description:\n \"Replace your todo list for this task with the complete updated list \" +\n \"(each item: content and status pending | in_progress | completed). \" +\n \"Use it to plan multi-step work and to mark progress.\",\n action: \"Updating the todo list\",\n input: todoWriteSchema,\n execute: async (input) => store.write(input.todos),\n });\n}\n\n/**\n * Build the agent-facing `todo_read` tool: the workspace's current todo\n * list and a count per status.\n *\n * @param store - The workspace's todo list.\n * @param options - Optional overrides; `name` renames the LLM-visible tool.\n *\n * @example\n * const { data } = await makeTodoReadTool(store).invoke({});\n * console.log(data?.counts.pending);\n */\nexport function makeTodoReadTool(\n store: TodoStore,\n options?: { name?: string },\n): ToolContract<Record<string, never>, TodoListResult> {\n return tool<Record<string, never>, TodoListResult>({\n name: options?.name ?? \"todo_read\",\n description: \"Read your current todo list for this task.\",\n action: \"Reading the todo list\",\n input: todoReadSchema,\n execute: async () => store.read(),\n });\n}\n"],"mappings":";;;;;;;AAaA,SAAS,aAAa,OAA8C;CAClE,OAAO,OAAO,UAAU,YAAa,cAAoC,SAAS,KAAK;AACzF;;AAGA,MAAM,kBAAkB,eACtB,EACE,aAAa;CACX,SAAS;CACT,QAAQ;CACR,SAAS,OAAO;EACd,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK,GACpE,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,0BAA0B,CAAC,EAAE;EAG5D,MAAM,MAAO,MAAkC;EAE/C,IAAI,CAAC,MAAM,QAAQ,GAAG,KAAK,IAAI,cAC7B,OAAO,EACL,QAAQ,CACN;GACE,SAAS,2CAAiD;GAC1D,MAAM,CAAC,OAAO;EAChB,CACF,EACF;EAGF,MAAM,SAAmC,CAAC;EAC1C,MAAM,QAA8B,CAAC;EAErC,IAAI,SAAS,MAAe,UAAkB;GAC5C,IAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,MAAM,QAAQ,IAAI,GAAG;IACpE,OAAO,KAAK;KAAE,SAAS,SAAS,MAAM;KAAsB,MAAM,CAAC,SAAS,KAAK;IAAE,CAAC;IAEpF;GACF;GAEA,MAAM,YAAY;GAElB,IAAI,OAAO,UAAU,YAAY,UAC/B,OAAO,KAAK;IACV,SAAS,SAAS,MAAM;IACxB,MAAM;KAAC;KAAS;KAAO;IAAS;GAClC,CAAC;GAGH,IAAI,CAAC,aAAa,UAAU,MAAM,GAChC,OAAO,KAAK;IACV,SAAS,SAAS,MAAM,0BAA0B,cAAc,KAAK,IAAI;IACzE,MAAM;KAAC;KAAS;KAAO;IAAQ;GACjC,CAAC;GAGH,IAAI,UAAU,OAAO,UAAa,OAAO,UAAU,OAAO,UACxD,OAAO,KAAK;IACV,SAAS,SAAS,MAAM;IACxB,MAAM;KAAC;KAAS;KAAO;IAAI;GAC7B,CAAC;GAGH,IAAI,OAAO,UAAU,YAAY,YAAY,aAAa,UAAU,MAAM,GACxE,MAAM,KAAK;IACT,SAAS,UAAU;IACnB,QAAQ,UAAU;IAClB,GAAI,OAAO,UAAU,OAAO,WAAW,EAAE,IAAI,UAAU,GAAG,IAAI,CAAC;GACjE,CAAC;EAEL,CAAC;EAED,IAAI,OAAO,WAAW,GACpB,KAAK,MAAM,WAAW,iBAAiB,KAAK,GAC1C,OAAO,KAAK;GAAE,SAAS;GAAS,MAAM,CAAC,OAAO;EAAE,CAAC;EAIrD,OAAO,OAAO,SAAS,IAAI,EAAE,OAAO,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE;CAC7D;AACF,EACF,GACA,sBACF;;AAGA,MAAM,iBAAiB,eACrB,EACE,aAAa;CACX,SAAS;CACT,QAAQ;CACR,iBAAiB,EAAE,OAAO,CAAC,EAAE;AAC/B,EACF,GACA,qBACF;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,kBACd,OACA,SAC8C;CAC9C,OAAO,KAAqC;EAC1C,MAAM,SAAS,QAAQ;EACvB,aACE;EAGF,QAAQ;EACR,OAAO;EACP,SAAS,OAAO,UAAU,MAAM,MAAM,MAAM,KAAK;CACnD,CAAC;AACH;;;;;;;;;;;;AAaA,SAAgB,iBACd,OACA,SACqD;CACrD,OAAO,KAA4C;EACjD,MAAM,SAAS,QAAQ;EACvB,aAAa;EACb,QAAQ;EACR,OAAO;EACP,SAAS,YAAY,MAAM,KAAK;CAClC,CAAC;AACH"}
|
|
@@ -2,7 +2,7 @@ import { WriteFileInput, WriteFileResult } from "../contracts/tool-io.type.mjs";
|
|
|
2
2
|
import { WorkspaceOps } from "../contracts/workspace-ops.contract.mjs";
|
|
3
3
|
import { ToolContract } from "@warlock.js/ai";
|
|
4
4
|
|
|
5
|
-
//#region
|
|
5
|
+
//#region ../../packages/ai-workspace/src/tools/write-file.d.ts
|
|
6
6
|
/**
|
|
7
7
|
* Build the agent-facing `write_file` tool over a workspace's policy-
|
|
8
8
|
* enforced {@link WorkspaceOps}.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"write-file.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"write-file.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/tools/write-file.ts"],"mappings":";;;;;;;;;AAuCA;;;;;;;;;;;;;;;;;AAG+C;iBAH/B,iBAAA,CACd,GAAA,EAAK,YAAA,EACL,OAAA;EAAY,IAAA;AAAA,IACX,YAAA,CAAa,cAAA,EAAgB,eAAA"}
|
package/esm/tools/write-file.mjs
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { objectSchema, stringField } from "./schema.mjs";
|
|
2
|
+
import { WRITE_FILE_JSON_SCHEMA } from "./json-schemas.mjs";
|
|
2
3
|
import { tool } from "@warlock.js/ai";
|
|
3
4
|
|
|
4
|
-
//#region
|
|
5
|
+
//#region ../../packages/ai-workspace/src/tools/write-file.ts
|
|
5
6
|
/** Default tool name exposed to the LLM. */
|
|
6
7
|
const DEFAULT_NAME = "write_file";
|
|
7
8
|
/** Input schema for the `write_file` tool. */
|
|
8
9
|
const inputSchema = objectSchema({
|
|
9
10
|
path: stringField(),
|
|
10
11
|
content: stringField()
|
|
11
|
-
});
|
|
12
|
+
}, WRITE_FILE_JSON_SCHEMA);
|
|
12
13
|
/**
|
|
13
14
|
* Build the agent-facing `write_file` tool over a workspace's policy-
|
|
14
15
|
* enforced {@link WorkspaceOps}.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"write-file.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"write-file.mjs","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/tools/write-file.ts"],"sourcesContent":["import { tool, type ToolContract } from \"@warlock.js/ai\";\nimport { objectSchema, stringField } from \"./schema\";\nimport { WRITE_FILE_JSON_SCHEMA } from \"./json-schemas\";\nimport type { WorkspaceOps, WriteFileInput, WriteFileResult } from \"../contracts\";\n\n/** Default tool name exposed to the LLM. */\nconst DEFAULT_NAME = \"write_file\";\n\n/** Input schema for the `write_file` tool. */\nconst inputSchema = objectSchema<WriteFileInput>(\n {\n path: stringField(),\n content: stringField(),\n },\n WRITE_FILE_JSON_SCHEMA,\n);\n\n/**\n * Build the agent-facing `write_file` tool over a workspace's policy-\n * enforced {@link WorkspaceOps}.\n *\n * The tool validates `{ path, content }` against a Standard Schema, then\n * delegates to `ops.writeFile`, which atomically writes the full content\n * (creating parent directories) and returns the byte count and content\n * `hash`. The tool re-attaches the workspace-relative `path` so the\n * result matches the {@link WriteFileResult} wire shape.\n *\n * **Errors flow as data.** A jail escape is thrown by `ops`; the\n * `tool()` wrapper catches it and surfaces it in the returned `{ error }`\n * field — `invoke()` never throws.\n *\n * @param ops - The shared, policy-enforced operation layer.\n * @param options - Optional overrides; `name` renames the LLM-visible tool.\n *\n * @example\n * const writeTool = makeWriteFileTool(ops);\n * const { data } = await writeTool.invoke({ path: \"src/new.ts\", content: \"export {};\" });\n * console.log(data.bytesWritten, data.hash);\n */\nexport function makeWriteFileTool(\n ops: WorkspaceOps,\n options?: { name?: string },\n): ToolContract<WriteFileInput, WriteFileResult> {\n return tool<WriteFileInput, WriteFileResult>({\n name: options?.name ?? DEFAULT_NAME,\n description:\n \"Write full content to a workspace file, creating it (and any parent \" +\n \"directories) if absent and overwriting it otherwise. The write is \" +\n \"atomic. Returns the bytes written and the new content hash.\",\n input: inputSchema,\n async execute(input) {\n const { hash, bytesWritten } = await ops.writeFile(input.path, input.content);\n\n return { path: input.path, bytesWritten, hash };\n },\n });\n}\n"],"mappings":";;;;;;AAMA,MAAM,eAAe;;AAGrB,MAAM,cAAc,aAClB;CACE,MAAM,YAAY;CAClB,SAAS,YAAY;AACvB,GACA,sBACF;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,kBACd,KACA,SAC+C;CAC/C,OAAO,KAAsC;EAC3C,MAAM,SAAS,QAAQ;EACvB,aACE;EAGF,OAAO;EACP,MAAM,QAAQ,OAAO;GACnB,MAAM,EAAE,MAAM,iBAAiB,MAAM,IAAI,UAAU,MAAM,MAAM,MAAM,OAAO;GAE5E,OAAO;IAAE,MAAM,MAAM;IAAM;IAAc;GAAK;EAChD;CACF,CAAC;AACH"}
|
package/esm/workspace.d.mts
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
import { WorkspacePolicy } from "./contracts/workspace-policy.type.mjs";
|
|
2
2
|
import { Workspace } from "./contracts/workspace.contract.mjs";
|
|
3
|
-
//#region
|
|
3
|
+
//#region ../../packages/ai-workspace/src/workspace.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Build a {@link Workspace} — the integrator that wires a
|
|
6
6
|
* {@link WorkspacePolicy} to a backend, the shared policy-enforced ops
|
|
7
|
-
* layer, and the
|
|
7
|
+
* layer, and the agent-facing tool factories.
|
|
8
8
|
*
|
|
9
9
|
* The backend is chosen from `policy.backend`: `"mock"` runs in memory
|
|
10
10
|
* (hermetic tests); the `"local"` default (and any absent value) runs
|
|
11
11
|
* over the real disk via `@warlock.js/fs` + `node:child_process`. The
|
|
12
12
|
* returned workspace exposes:
|
|
13
13
|
*
|
|
14
|
-
* - **`tools.*`** — `readFile` / `editFile` / `
|
|
15
|
-
* `
|
|
16
|
-
* `
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
14
|
+
* - **`tools.*`** — `readFile` / `editFile` / `multiEdit` / `writeFile` /
|
|
15
|
+
* `listDir` / `runShell` / `shellOutput` / `shellKill` / `runTests` /
|
|
16
|
+
* `grep` / `glob` / `todoWrite` / `todoRead`, plus `all()` (every tool)
|
|
17
|
+
* and `pick(...)` (a least-privilege subset).
|
|
18
|
+
* - **direct methods** — `readFile` / `writeFile` / `editFile` /
|
|
19
|
+
* `multiEdit` / `listDir` / `exec` / `startShell` / `shellOutput` /
|
|
20
|
+
* `shellKill` / `listShells` / `grep` / `glob` / `exists` / `mkdir` /
|
|
21
|
+
* `remove` / `readTodos` / `writeTodos`, each delegating to the shared
|
|
22
|
+
* ops layer, and `dispose()`.
|
|
23
|
+
* - **`readonly()`** — a projection that vends only the read-side tools and
|
|
21
24
|
* rejects every mutating direct method.
|
|
22
25
|
* - **`scope(subdir)`** — a sub-jailed workspace rooted at `subdir`.
|
|
23
26
|
*
|
package/esm/workspace.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"workspace.d.mts","names":[],"sources":["../../../../../../../packages/ai-workspace/src/workspace.ts"],"mappings":";;;;;;;AAuZA;;;;;;;;AAA6D;AAE5D;;;;;;;;;;;AAWgD;;;;;;;;;;;;;;iBAbjC,SAAA,CAAU,MAAA,EAAQ,eAAA,GAAkB,SAAS;;;;;;;;YAWjD,EAAA;;IAER,SAAA,CAAU,MAAA,EAAQ,eAAA,GAAkB,SAAS;EAAA;AAAA"}
|
package/esm/workspace.mjs
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import { WorkspacePolicyError } from "./errors.mjs";
|
|
2
|
+
import { ShellJobs } from "./shell-jobs.mjs";
|
|
2
3
|
import { createOps } from "./ops.mjs";
|
|
3
4
|
import { createLocalBackend } from "./backends/local.mjs";
|
|
4
5
|
import { createMockBackend } from "./backends/mock.mjs";
|
|
5
6
|
import { makeEditFileTool } from "./tools/edit-file.mjs";
|
|
6
7
|
import { makeGlobTool } from "./tools/glob.mjs";
|
|
7
8
|
import { makeGrepTool } from "./tools/grep.mjs";
|
|
9
|
+
import { makeListDirTool } from "./tools/list-dir.mjs";
|
|
10
|
+
import { makeMultiEditTool } from "./tools/multi-edit.mjs";
|
|
8
11
|
import { makeReadFileTool } from "./tools/read-file.mjs";
|
|
9
12
|
import { makeRunShellTool } from "./tools/run-shell.mjs";
|
|
10
13
|
import { makeRunTestsTool } from "./tools/run-tests.mjs";
|
|
14
|
+
import { makeShellKillTool } from "./tools/shell-kill.mjs";
|
|
15
|
+
import { makeShellOutputTool } from "./tools/shell-output.mjs";
|
|
16
|
+
import { TodoStore } from "./todos.mjs";
|
|
17
|
+
import { makeTodoReadTool, makeTodoWriteTool } from "./tools/todo.mjs";
|
|
11
18
|
import { makeWriteFileTool } from "./tools/write-file.mjs";
|
|
12
19
|
import { ai } from "@warlock.js/ai";
|
|
13
20
|
import path from "node:path";
|
|
14
21
|
|
|
15
|
-
//#region
|
|
22
|
+
//#region ../../packages/ai-workspace/src/workspace.ts
|
|
16
23
|
/**
|
|
17
24
|
* The full set of tool names a writable workspace vends, in a stable
|
|
18
25
|
* canonical order so `tools.all()` is deterministic.
|
|
@@ -20,23 +27,35 @@ import path from "node:path";
|
|
|
20
27
|
const ALL_TOOL_NAMES = [
|
|
21
28
|
"readFile",
|
|
22
29
|
"editFile",
|
|
30
|
+
"multiEdit",
|
|
23
31
|
"writeFile",
|
|
32
|
+
"listDir",
|
|
24
33
|
"runShell",
|
|
34
|
+
"shellOutput",
|
|
35
|
+
"shellKill",
|
|
25
36
|
"runTests",
|
|
26
37
|
"grep",
|
|
27
|
-
"glob"
|
|
38
|
+
"glob",
|
|
39
|
+
"todoWrite",
|
|
40
|
+
"todoRead"
|
|
28
41
|
];
|
|
29
42
|
/**
|
|
30
43
|
* The subset a {@link Workspace.readonly} projection exposes — the
|
|
31
|
-
*
|
|
32
|
-
* `runTests` are deliberately omitted so a
|
|
33
|
-
* change
|
|
44
|
+
* tools that cannot change the tree. `editFile` / `multiEdit` /
|
|
45
|
+
* `writeFile` / the shell tools / `runTests` are deliberately omitted so a
|
|
46
|
+
* reviewer agent has no path to change it. The todo tools stay: they only
|
|
47
|
+
* touch the in-memory plan.
|
|
34
48
|
*/
|
|
35
49
|
const READONLY_TOOL_NAMES = [
|
|
36
50
|
"readFile",
|
|
51
|
+
"listDir",
|
|
37
52
|
"grep",
|
|
38
|
-
"glob"
|
|
53
|
+
"glob",
|
|
54
|
+
"todoWrite",
|
|
55
|
+
"todoRead"
|
|
39
56
|
];
|
|
57
|
+
/** Tools vended only when the policy enables background commands. */
|
|
58
|
+
const BACKGROUND_TOOL_NAMES = new Set(["shellOutput", "shellKill"]);
|
|
40
59
|
/**
|
|
41
60
|
* Choose the dumb IO executor for a policy. `"mock"` selects the
|
|
42
61
|
* in-memory backend (hermetic tests); anything else — including the
|
|
@@ -61,11 +80,15 @@ function selectBackend(policy) {
|
|
|
61
80
|
* Constructed via {@link workspace}; the class itself is internal.
|
|
62
81
|
*/
|
|
63
82
|
var WorkspaceImpl = class WorkspaceImpl {
|
|
64
|
-
constructor(policy, allowedTools = ALL_TOOL_NAMES) {
|
|
83
|
+
constructor(policy, allowedTools = ALL_TOOL_NAMES, state) {
|
|
65
84
|
this.policy = policy;
|
|
66
85
|
this.allowedTools = new Set(allowedTools);
|
|
86
|
+
this.state = state ?? {
|
|
87
|
+
jobs: new ShellJobs(),
|
|
88
|
+
todos: new TodoStore()
|
|
89
|
+
};
|
|
67
90
|
const backend = selectBackend(policy);
|
|
68
|
-
this.ops = createOps(backend, policy);
|
|
91
|
+
this.ops = createOps(backend, policy, { jobs: this.state.jobs });
|
|
69
92
|
this.tools = this.buildTools();
|
|
70
93
|
}
|
|
71
94
|
/**
|
|
@@ -77,26 +100,49 @@ var WorkspaceImpl = class WorkspaceImpl {
|
|
|
77
100
|
*/
|
|
78
101
|
buildTools() {
|
|
79
102
|
const erase = (contract) => contract;
|
|
103
|
+
const shell = this.policy.shell;
|
|
104
|
+
const background = shell?.background !== void 0;
|
|
105
|
+
const interpreter = shell?.interpreter ?? "none";
|
|
80
106
|
const factories = {
|
|
81
107
|
readFile: (opts) => erase(makeReadFileTool(this.ops, opts)),
|
|
82
108
|
editFile: (opts) => erase(makeEditFileTool(this.ops, opts)),
|
|
109
|
+
multiEdit: (opts) => erase(makeMultiEditTool(this.ops, opts)),
|
|
83
110
|
writeFile: (opts) => erase(makeWriteFileTool(this.ops, opts)),
|
|
84
|
-
|
|
85
|
-
|
|
111
|
+
listDir: (opts) => erase(makeListDirTool(this.ops, opts)),
|
|
112
|
+
runShell: (opts) => erase(makeRunShellTool(this.ops, {
|
|
113
|
+
...opts?.name !== void 0 ? { name: opts.name } : {},
|
|
114
|
+
...shell?.timeoutMs !== void 0 ? { maxTimeoutMs: shell.timeoutMs } : {},
|
|
115
|
+
background
|
|
116
|
+
})),
|
|
117
|
+
shellOutput: (opts) => erase(makeShellOutputTool(this.ops, opts)),
|
|
118
|
+
shellKill: (opts) => erase(makeShellKillTool(this.ops, opts)),
|
|
119
|
+
runTests: (opts) => erase(makeRunTestsTool(this.ops, {
|
|
120
|
+
...opts,
|
|
121
|
+
interpreter
|
|
122
|
+
})),
|
|
86
123
|
grep: (opts) => erase(makeGrepTool(this.ops, opts)),
|
|
87
|
-
glob: (opts) => erase(makeGlobTool(this.ops, opts))
|
|
124
|
+
glob: (opts) => erase(makeGlobTool(this.ops, opts)),
|
|
125
|
+
todoWrite: (opts) => erase(makeTodoWriteTool(this.state.todos, opts)),
|
|
126
|
+
todoRead: (opts) => erase(makeTodoReadTool(this.state.todos, opts))
|
|
88
127
|
};
|
|
89
128
|
const build = (name, opts) => factories[name](opts);
|
|
129
|
+
const vendedByAll = (name) => this.allowedTools.has(name) && (background || !BACKGROUND_TOOL_NAMES.has(name));
|
|
90
130
|
return {
|
|
91
|
-
all: () => ALL_TOOL_NAMES.filter(
|
|
131
|
+
all: () => ALL_TOOL_NAMES.filter(vendedByAll).map((name) => build(name)),
|
|
92
132
|
pick: (...names) => names.filter((name) => this.allowedTools.has(name)).map((name) => build(name)),
|
|
93
133
|
readFile: (opts) => build("readFile", opts),
|
|
94
134
|
editFile: (opts) => build("editFile", opts),
|
|
135
|
+
multiEdit: (opts) => build("multiEdit", opts),
|
|
95
136
|
writeFile: (opts) => build("writeFile", opts),
|
|
137
|
+
listDir: (opts) => build("listDir", opts),
|
|
96
138
|
runShell: (opts) => build("runShell", opts),
|
|
139
|
+
shellOutput: (opts) => build("shellOutput", opts),
|
|
140
|
+
shellKill: (opts) => build("shellKill", opts),
|
|
97
141
|
runTests: (opts) => build("runTests", opts),
|
|
98
142
|
grep: (opts) => build("grep", opts),
|
|
99
|
-
glob: (opts) => build("glob", opts)
|
|
143
|
+
glob: (opts) => build("glob", opts),
|
|
144
|
+
todoWrite: (opts) => build("todoWrite", opts),
|
|
145
|
+
todoRead: (opts) => build("todoRead", opts)
|
|
100
146
|
};
|
|
101
147
|
}
|
|
102
148
|
/**
|
|
@@ -122,10 +168,40 @@ var WorkspaceImpl = class WorkspaceImpl {
|
|
|
122
168
|
this.assertWritable("editFile");
|
|
123
169
|
return this.ops.editFile(input);
|
|
124
170
|
}
|
|
171
|
+
async multiEdit(input) {
|
|
172
|
+
this.assertWritable("multiEdit");
|
|
173
|
+
return this.ops.multiEdit(input);
|
|
174
|
+
}
|
|
175
|
+
listDir(dirPath, opts) {
|
|
176
|
+
return this.ops.listDir(dirPath, opts);
|
|
177
|
+
}
|
|
125
178
|
async exec(command, opts) {
|
|
126
179
|
this.assertWritable("exec");
|
|
127
180
|
return this.ops.exec(command, opts);
|
|
128
181
|
}
|
|
182
|
+
async startShell(command, opts) {
|
|
183
|
+
this.assertWritable("startShell");
|
|
184
|
+
return this.ops.startShell(command, opts);
|
|
185
|
+
}
|
|
186
|
+
shellOutput(id, opts) {
|
|
187
|
+
return this.ops.shellOutput(id, opts);
|
|
188
|
+
}
|
|
189
|
+
async shellKill(id) {
|
|
190
|
+
this.assertWritable("shellKill");
|
|
191
|
+
return this.ops.shellKill(id);
|
|
192
|
+
}
|
|
193
|
+
listShells() {
|
|
194
|
+
return this.ops.listShells();
|
|
195
|
+
}
|
|
196
|
+
readTodos() {
|
|
197
|
+
return this.state.todos.read();
|
|
198
|
+
}
|
|
199
|
+
writeTodos(todos) {
|
|
200
|
+
return this.state.todos.write(todos);
|
|
201
|
+
}
|
|
202
|
+
dispose() {
|
|
203
|
+
return this.ops.dispose();
|
|
204
|
+
}
|
|
129
205
|
grep(pattern, opts) {
|
|
130
206
|
return this.ops.grep(pattern, opts);
|
|
131
207
|
}
|
|
@@ -144,44 +220,49 @@ var WorkspaceImpl = class WorkspaceImpl {
|
|
|
144
220
|
return this.ops.remove(filePath);
|
|
145
221
|
}
|
|
146
222
|
/**
|
|
147
|
-
* A read-only projection over the SAME policy — only the read/
|
|
148
|
-
* tools are vended and every mutating direct method
|
|
149
|
-
* {@link WorkspacePolicyError}. A fresh ops/backend is
|
|
150
|
-
* identical policy, so the projection sees the same jailed
|
|
223
|
+
* A read-only projection over the SAME policy — only the read / list /
|
|
224
|
+
* grep / glob / todo tools are vended and every mutating direct method
|
|
225
|
+
* rejects with a {@link WorkspacePolicyError}. A fresh ops/backend is
|
|
226
|
+
* built from the identical policy, so the projection sees the same jailed
|
|
227
|
+
* tree; it shares this workspace's background commands and todo list.
|
|
151
228
|
*/
|
|
152
229
|
readonly() {
|
|
153
|
-
return new WorkspaceImpl(this.policy, READONLY_TOOL_NAMES);
|
|
230
|
+
return new WorkspaceImpl(this.policy, READONLY_TOOL_NAMES.filter((name) => this.allowedTools.has(name)), this.state);
|
|
154
231
|
}
|
|
155
232
|
/**
|
|
156
233
|
* A sub-jailed view rooted at `subdir` (relative to this workspace's
|
|
157
234
|
* `cwd`). Returns a brand-new workspace whose policy is this policy
|
|
158
235
|
* with `cwd` narrowed to `join(cwd, subdir)` — same backend selection,
|
|
159
|
-
* same allow/deny/shell/read sub-policies, but a tighter jail root.
|
|
236
|
+
* same allow/deny/shell/read sub-policies, but a tighter jail root. It
|
|
237
|
+
* shares this workspace's background commands and todo list.
|
|
160
238
|
*/
|
|
161
239
|
scope(subdir) {
|
|
162
240
|
return new WorkspaceImpl({
|
|
163
241
|
...this.policy,
|
|
164
242
|
cwd: path.join(this.policy.cwd, subdir)
|
|
165
|
-
}, [...this.allowedTools]);
|
|
243
|
+
}, [...this.allowedTools], this.state);
|
|
166
244
|
}
|
|
167
245
|
};
|
|
168
246
|
/**
|
|
169
247
|
* Build a {@link Workspace} — the integrator that wires a
|
|
170
248
|
* {@link WorkspacePolicy} to a backend, the shared policy-enforced ops
|
|
171
|
-
* layer, and the
|
|
249
|
+
* layer, and the agent-facing tool factories.
|
|
172
250
|
*
|
|
173
251
|
* The backend is chosen from `policy.backend`: `"mock"` runs in memory
|
|
174
252
|
* (hermetic tests); the `"local"` default (and any absent value) runs
|
|
175
253
|
* over the real disk via `@warlock.js/fs` + `node:child_process`. The
|
|
176
254
|
* returned workspace exposes:
|
|
177
255
|
*
|
|
178
|
-
* - **`tools.*`** — `readFile` / `editFile` / `
|
|
179
|
-
* `
|
|
180
|
-
* `
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
256
|
+
* - **`tools.*`** — `readFile` / `editFile` / `multiEdit` / `writeFile` /
|
|
257
|
+
* `listDir` / `runShell` / `shellOutput` / `shellKill` / `runTests` /
|
|
258
|
+
* `grep` / `glob` / `todoWrite` / `todoRead`, plus `all()` (every tool)
|
|
259
|
+
* and `pick(...)` (a least-privilege subset).
|
|
260
|
+
* - **direct methods** — `readFile` / `writeFile` / `editFile` /
|
|
261
|
+
* `multiEdit` / `listDir` / `exec` / `startShell` / `shellOutput` /
|
|
262
|
+
* `shellKill` / `listShells` / `grep` / `glob` / `exists` / `mkdir` /
|
|
263
|
+
* `remove` / `readTodos` / `writeTodos`, each delegating to the shared
|
|
264
|
+
* ops layer, and `dispose()`.
|
|
265
|
+
* - **`readonly()`** — a projection that vends only the read-side tools and
|
|
185
266
|
* rejects every mutating direct method.
|
|
186
267
|
* - **`scope(subdir)`** — a sub-jailed workspace rooted at `subdir`.
|
|
187
268
|
*
|