@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,175 @@
|
|
|
1
|
+
//#region ../../packages/ai-workspace/src/tools/json-schemas.ts
|
|
2
|
+
/**
|
|
3
|
+
* The JSON Schemas the workspace tools publish for their input (read by
|
|
4
|
+
* `extractJsonSchema` and sent to the provider with each tool), kept short
|
|
5
|
+
* because they ride along on every model call. They describe what the
|
|
6
|
+
* hand-rolled validators accept; the validators remain the authority.
|
|
7
|
+
*/
|
|
8
|
+
const string = (description) => ({
|
|
9
|
+
type: "string",
|
|
10
|
+
description
|
|
11
|
+
});
|
|
12
|
+
const integer = (description) => ({
|
|
13
|
+
type: "integer",
|
|
14
|
+
description
|
|
15
|
+
});
|
|
16
|
+
const boolean = (description) => ({
|
|
17
|
+
type: "boolean",
|
|
18
|
+
description
|
|
19
|
+
});
|
|
20
|
+
/** `read_file`. */
|
|
21
|
+
const READ_FILE_JSON_SCHEMA = {
|
|
22
|
+
type: "object",
|
|
23
|
+
properties: {
|
|
24
|
+
path: string("Workspace-relative file path."),
|
|
25
|
+
startLine: integer("1-based first line to return."),
|
|
26
|
+
limit: integer("Most lines to return.")
|
|
27
|
+
},
|
|
28
|
+
required: ["path"]
|
|
29
|
+
};
|
|
30
|
+
/** `edit_file`. */
|
|
31
|
+
const EDIT_FILE_JSON_SCHEMA = {
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: {
|
|
34
|
+
path: string("Workspace-relative file path."),
|
|
35
|
+
oldString: string("Exact text to replace; must be unique unless replaceAll."),
|
|
36
|
+
newString: string("Replacement text."),
|
|
37
|
+
replaceAll: boolean("Replace every occurrence."),
|
|
38
|
+
expectHash: string("Hash from read_file; rejects the edit if the file changed.")
|
|
39
|
+
},
|
|
40
|
+
required: [
|
|
41
|
+
"path",
|
|
42
|
+
"oldString",
|
|
43
|
+
"newString"
|
|
44
|
+
]
|
|
45
|
+
};
|
|
46
|
+
/** `multi_edit`. */
|
|
47
|
+
const MULTI_EDIT_JSON_SCHEMA = {
|
|
48
|
+
type: "object",
|
|
49
|
+
properties: {
|
|
50
|
+
path: string("Workspace-relative file path."),
|
|
51
|
+
edits: {
|
|
52
|
+
type: "array",
|
|
53
|
+
description: "Replacements applied in order, each to the result of the previous; all or none.",
|
|
54
|
+
minItems: 1,
|
|
55
|
+
maxItems: 100,
|
|
56
|
+
items: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
oldString: string("Exact text to replace; must be unique unless replaceAll."),
|
|
60
|
+
newString: string("Replacement text."),
|
|
61
|
+
replaceAll: boolean("Replace every occurrence.")
|
|
62
|
+
},
|
|
63
|
+
required: ["oldString", "newString"]
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
expectHash: string("Hash from read_file; rejects every edit if the file changed.")
|
|
67
|
+
},
|
|
68
|
+
required: ["path", "edits"]
|
|
69
|
+
};
|
|
70
|
+
/** `write_file`. */
|
|
71
|
+
const WRITE_FILE_JSON_SCHEMA = {
|
|
72
|
+
type: "object",
|
|
73
|
+
properties: {
|
|
74
|
+
path: string("Workspace-relative file path."),
|
|
75
|
+
content: string("Full file content.")
|
|
76
|
+
},
|
|
77
|
+
required: ["path", "content"]
|
|
78
|
+
};
|
|
79
|
+
/** `list_dir`. */
|
|
80
|
+
const LIST_DIR_JSON_SCHEMA = {
|
|
81
|
+
type: "object",
|
|
82
|
+
properties: {
|
|
83
|
+
path: string("Workspace-relative directory (default \".\")."),
|
|
84
|
+
depth: integer("Levels to list; 1 = direct children (default)."),
|
|
85
|
+
ignore: {
|
|
86
|
+
type: "array",
|
|
87
|
+
items: { type: "string" },
|
|
88
|
+
description: "Extra globs to leave out."
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
/** `run_shell` (the `background` property is only listed when background commands are enabled). */
|
|
93
|
+
function runShellJsonSchema(background) {
|
|
94
|
+
return {
|
|
95
|
+
type: "object",
|
|
96
|
+
properties: {
|
|
97
|
+
command: string("The command line to run."),
|
|
98
|
+
timeoutMs: integer("Wall-clock cap in ms (can only lower the workspace limit)."),
|
|
99
|
+
...background ? { background: boolean("Start it in the background and return its id at once (dev servers, watchers).") } : {}
|
|
100
|
+
},
|
|
101
|
+
required: ["command"]
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/** `shell_output`. */
|
|
105
|
+
const SHELL_OUTPUT_JSON_SCHEMA = {
|
|
106
|
+
type: "object",
|
|
107
|
+
properties: {
|
|
108
|
+
id: string("Background command id from run_shell."),
|
|
109
|
+
since: integer("A cursor from an earlier result; omit for output since the last read."),
|
|
110
|
+
waitMs: integer("Wait up to this long (max 30000) for new output.")
|
|
111
|
+
},
|
|
112
|
+
required: ["id"]
|
|
113
|
+
};
|
|
114
|
+
/** `shell_kill`. */
|
|
115
|
+
const SHELL_KILL_JSON_SCHEMA = {
|
|
116
|
+
type: "object",
|
|
117
|
+
properties: { id: string("Background command id from run_shell.") },
|
|
118
|
+
required: ["id"]
|
|
119
|
+
};
|
|
120
|
+
/** `run_tests`. */
|
|
121
|
+
const RUN_TESTS_JSON_SCHEMA = {
|
|
122
|
+
type: "object",
|
|
123
|
+
properties: { pattern: string("Optional test path or name filter.") }
|
|
124
|
+
};
|
|
125
|
+
/** `grep`. */
|
|
126
|
+
const GREP_JSON_SCHEMA = {
|
|
127
|
+
type: "object",
|
|
128
|
+
properties: {
|
|
129
|
+
pattern: string("Regular expression to search for."),
|
|
130
|
+
glob: string("Only search files matching this glob (e.g. \"src/**/*.ts\")."),
|
|
131
|
+
ignoreCase: boolean("Case-insensitive match.")
|
|
132
|
+
},
|
|
133
|
+
required: ["pattern"]
|
|
134
|
+
};
|
|
135
|
+
/** `glob`. */
|
|
136
|
+
const GLOB_JSON_SCHEMA = {
|
|
137
|
+
type: "object",
|
|
138
|
+
properties: { pattern: string("Glob such as \"src/**/*.ts\".") },
|
|
139
|
+
required: ["pattern"]
|
|
140
|
+
};
|
|
141
|
+
/** `todo_write`. */
|
|
142
|
+
const TODO_WRITE_JSON_SCHEMA = {
|
|
143
|
+
type: "object",
|
|
144
|
+
properties: { todos: {
|
|
145
|
+
type: "array",
|
|
146
|
+
description: "The complete updated list (replaces the old one).",
|
|
147
|
+
maxItems: 100,
|
|
148
|
+
items: {
|
|
149
|
+
type: "object",
|
|
150
|
+
properties: {
|
|
151
|
+
id: string("Stable id; omit to number items by position."),
|
|
152
|
+
content: string("The step."),
|
|
153
|
+
status: {
|
|
154
|
+
type: "string",
|
|
155
|
+
enum: [
|
|
156
|
+
"pending",
|
|
157
|
+
"in_progress",
|
|
158
|
+
"completed"
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
required: ["content", "status"]
|
|
163
|
+
}
|
|
164
|
+
} },
|
|
165
|
+
required: ["todos"]
|
|
166
|
+
};
|
|
167
|
+
/** `todo_read`. */
|
|
168
|
+
const TODO_READ_JSON_SCHEMA = {
|
|
169
|
+
type: "object",
|
|
170
|
+
properties: {}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
//#endregion
|
|
174
|
+
export { EDIT_FILE_JSON_SCHEMA, GLOB_JSON_SCHEMA, GREP_JSON_SCHEMA, LIST_DIR_JSON_SCHEMA, MULTI_EDIT_JSON_SCHEMA, READ_FILE_JSON_SCHEMA, RUN_TESTS_JSON_SCHEMA, SHELL_KILL_JSON_SCHEMA, SHELL_OUTPUT_JSON_SCHEMA, TODO_READ_JSON_SCHEMA, TODO_WRITE_JSON_SCHEMA, WRITE_FILE_JSON_SCHEMA, runShellJsonSchema };
|
|
175
|
+
//# sourceMappingURL=json-schemas.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-schemas.mjs","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/tools/json-schemas.ts"],"sourcesContent":["import type { JsonSchemaObject } from \"./schema\";\n\n/**\n * The JSON Schemas the workspace tools publish for their input (read by\n * `extractJsonSchema` and sent to the provider with each tool), kept short\n * because they ride along on every model call. They describe what the\n * hand-rolled validators accept; the validators remain the authority.\n */\n\nconst string = (description: string): JsonSchemaObject => ({ type: \"string\", description });\nconst integer = (description: string): JsonSchemaObject => ({ type: \"integer\", description });\nconst boolean = (description: string): JsonSchemaObject => ({ type: \"boolean\", description });\n\n/** `read_file`. */\nexport const READ_FILE_JSON_SCHEMA: JsonSchemaObject = {\n type: \"object\",\n properties: {\n path: string(\"Workspace-relative file path.\"),\n startLine: integer(\"1-based first line to return.\"),\n limit: integer(\"Most lines to return.\"),\n },\n required: [\"path\"],\n};\n\n/** `edit_file`. */\nexport const EDIT_FILE_JSON_SCHEMA: JsonSchemaObject = {\n type: \"object\",\n properties: {\n path: string(\"Workspace-relative file path.\"),\n oldString: string(\"Exact text to replace; must be unique unless replaceAll.\"),\n newString: string(\"Replacement text.\"),\n replaceAll: boolean(\"Replace every occurrence.\"),\n expectHash: string(\"Hash from read_file; rejects the edit if the file changed.\"),\n },\n required: [\"path\", \"oldString\", \"newString\"],\n};\n\n/** `multi_edit`. */\nexport const MULTI_EDIT_JSON_SCHEMA: JsonSchemaObject = {\n type: \"object\",\n properties: {\n path: string(\"Workspace-relative file path.\"),\n edits: {\n type: \"array\",\n description:\n \"Replacements applied in order, each to the result of the previous; all or none.\",\n minItems: 1,\n maxItems: 100,\n items: {\n type: \"object\",\n properties: {\n oldString: string(\"Exact text to replace; must be unique unless replaceAll.\"),\n newString: string(\"Replacement text.\"),\n replaceAll: boolean(\"Replace every occurrence.\"),\n },\n required: [\"oldString\", \"newString\"],\n },\n },\n expectHash: string(\"Hash from read_file; rejects every edit if the file changed.\"),\n },\n required: [\"path\", \"edits\"],\n};\n\n/** `write_file`. */\nexport const WRITE_FILE_JSON_SCHEMA: JsonSchemaObject = {\n type: \"object\",\n properties: {\n path: string(\"Workspace-relative file path.\"),\n content: string(\"Full file content.\"),\n },\n required: [\"path\", \"content\"],\n};\n\n/** `list_dir`. */\nexport const LIST_DIR_JSON_SCHEMA: JsonSchemaObject = {\n type: \"object\",\n properties: {\n path: string('Workspace-relative directory (default \".\").'),\n depth: integer(\"Levels to list; 1 = direct children (default).\"),\n ignore: { type: \"array\", items: { type: \"string\" }, description: \"Extra globs to leave out.\" },\n },\n};\n\n/** `run_shell` (the `background` property is only listed when background commands are enabled). */\nexport function runShellJsonSchema(background: boolean): JsonSchemaObject {\n return {\n type: \"object\",\n properties: {\n command: string(\"The command line to run.\"),\n timeoutMs: integer(\"Wall-clock cap in ms (can only lower the workspace limit).\"),\n ...(background\n ? {\n background: boolean(\n \"Start it in the background and return its id at once (dev servers, watchers).\",\n ),\n }\n : {}),\n },\n required: [\"command\"],\n };\n}\n\n/** `shell_output`. */\nexport const SHELL_OUTPUT_JSON_SCHEMA: JsonSchemaObject = {\n type: \"object\",\n properties: {\n id: string(\"Background command id from run_shell.\"),\n since: integer(\"A cursor from an earlier result; omit for output since the last read.\"),\n waitMs: integer(\"Wait up to this long (max 30000) for new output.\"),\n },\n required: [\"id\"],\n};\n\n/** `shell_kill`. */\nexport const SHELL_KILL_JSON_SCHEMA: JsonSchemaObject = {\n type: \"object\",\n properties: { id: string(\"Background command id from run_shell.\") },\n required: [\"id\"],\n};\n\n/** `run_tests`. */\nexport const RUN_TESTS_JSON_SCHEMA: JsonSchemaObject = {\n type: \"object\",\n properties: { pattern: string(\"Optional test path or name filter.\") },\n};\n\n/** `grep`. */\nexport const GREP_JSON_SCHEMA: JsonSchemaObject = {\n type: \"object\",\n properties: {\n pattern: string(\"Regular expression to search for.\"),\n glob: string('Only search files matching this glob (e.g. \"src/**/*.ts\").'),\n ignoreCase: boolean(\"Case-insensitive match.\"),\n },\n required: [\"pattern\"],\n};\n\n/** `glob`. */\nexport const GLOB_JSON_SCHEMA: JsonSchemaObject = {\n type: \"object\",\n properties: { pattern: string('Glob such as \"src/**/*.ts\".') },\n required: [\"pattern\"],\n};\n\n/** `todo_write`. */\nexport const TODO_WRITE_JSON_SCHEMA: JsonSchemaObject = {\n type: \"object\",\n properties: {\n todos: {\n type: \"array\",\n description: \"The complete updated list (replaces the old one).\",\n maxItems: 100,\n items: {\n type: \"object\",\n properties: {\n id: string(\"Stable id; omit to number items by position.\"),\n content: string(\"The step.\"),\n status: { type: \"string\", enum: [\"pending\", \"in_progress\", \"completed\"] },\n },\n required: [\"content\", \"status\"],\n },\n },\n },\n required: [\"todos\"],\n};\n\n/** `todo_read`. */\nexport const TODO_READ_JSON_SCHEMA: JsonSchemaObject = { type: \"object\", properties: {} };\n"],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ListDirInput, ListDirResult } from "../contracts/tool-io.type.mjs";
|
|
2
|
+
import { WorkspaceOps } from "../contracts/workspace-ops.contract.mjs";
|
|
3
|
+
import { ToolContract } from "@warlock.js/ai";
|
|
4
|
+
|
|
5
|
+
//#region ../../packages/ai-workspace/src/tools/list-dir.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Build the agent-facing `list_dir` tool over a workspace's policy-enforced
|
|
8
|
+
* {@link WorkspaceOps}: a bounded, jailed listing of one directory.
|
|
9
|
+
*
|
|
10
|
+
* Entries come back breadth-first (each level sorted by name) with their
|
|
11
|
+
* type and — for files — size, down to `depth` levels (capped by
|
|
12
|
+
* `list.maxDepth`) and at most `list.maxEntries` entries (`truncated` says
|
|
13
|
+
* when the cap hit). `denyPaths` are always left out, as are the
|
|
14
|
+
* `list.ignore` globs (default `node_modules`, `.git`) and any `ignore`
|
|
15
|
+
* globs the model passes. Symlinks are reported as `"symlink"` and never
|
|
16
|
+
* followed. A path outside the jail surfaces as a `WorkspacePolicyError` in
|
|
17
|
+
* the result's `error` field.
|
|
18
|
+
*
|
|
19
|
+
* @param ops - The shared, policy-enforced operation layer.
|
|
20
|
+
* @param options - Optional overrides; `name` renames the LLM-visible tool.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const listDir = makeListDirTool(ops);
|
|
24
|
+
* const { data } = await listDir.invoke({ path: "src", depth: 2 });
|
|
25
|
+
* for (const entry of data?.entries ?? []) console.log(entry.type, entry.path, entry.size);
|
|
26
|
+
*/
|
|
27
|
+
declare function makeListDirTool(ops: WorkspaceOps, options?: {
|
|
28
|
+
name?: string;
|
|
29
|
+
}): ToolContract<ListDirInput, ListDirResult>;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { makeListDirTool };
|
|
32
|
+
//# sourceMappingURL=list-dir.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-dir.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/tools/list-dir.ts"],"mappings":";;;;;;;;;AA4CA;;;;;;;;;;;;;;;;;iBAAgB,eAAA,CACd,GAAA,EAAK,YAAA,EACL,OAAA;EAAY,IAAA;AAAA,IACX,YAAA,CAAa,YAAA,EAAc,aAAA"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { objectSchema, optionalNumberField, optionalStringArrayField, optionalStringField } from "./schema.mjs";
|
|
2
|
+
import { LIST_DIR_JSON_SCHEMA } from "./json-schemas.mjs";
|
|
3
|
+
import { tool } from "@warlock.js/ai";
|
|
4
|
+
|
|
5
|
+
//#region ../../packages/ai-workspace/src/tools/list-dir.ts
|
|
6
|
+
/** Default tool name exposed to the LLM. */
|
|
7
|
+
const DEFAULT_NAME = "list_dir";
|
|
8
|
+
/** Input schema for the `list_dir` tool. */
|
|
9
|
+
const inputSchema = objectSchema({
|
|
10
|
+
path: optionalStringField(),
|
|
11
|
+
depth: optionalNumberField(),
|
|
12
|
+
ignore: optionalStringArrayField()
|
|
13
|
+
}, LIST_DIR_JSON_SCHEMA);
|
|
14
|
+
/**
|
|
15
|
+
* Build the agent-facing `list_dir` tool over a workspace's policy-enforced
|
|
16
|
+
* {@link WorkspaceOps}: a bounded, jailed listing of one directory.
|
|
17
|
+
*
|
|
18
|
+
* Entries come back breadth-first (each level sorted by name) with their
|
|
19
|
+
* type and — for files — size, down to `depth` levels (capped by
|
|
20
|
+
* `list.maxDepth`) and at most `list.maxEntries` entries (`truncated` says
|
|
21
|
+
* when the cap hit). `denyPaths` are always left out, as are the
|
|
22
|
+
* `list.ignore` globs (default `node_modules`, `.git`) and any `ignore`
|
|
23
|
+
* globs the model passes. Symlinks are reported as `"symlink"` and never
|
|
24
|
+
* followed. A path outside the jail surfaces as a `WorkspacePolicyError` in
|
|
25
|
+
* the result's `error` field.
|
|
26
|
+
*
|
|
27
|
+
* @param ops - The shared, policy-enforced operation layer.
|
|
28
|
+
* @param options - Optional overrides; `name` renames the LLM-visible tool.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* const listDir = makeListDirTool(ops);
|
|
32
|
+
* const { data } = await listDir.invoke({ path: "src", depth: 2 });
|
|
33
|
+
* for (const entry of data?.entries ?? []) console.log(entry.type, entry.path, entry.size);
|
|
34
|
+
*/
|
|
35
|
+
function makeListDirTool(ops, options) {
|
|
36
|
+
return tool({
|
|
37
|
+
name: options?.name ?? DEFAULT_NAME,
|
|
38
|
+
description: "List a workspace directory: entries with their type and file size, breadth-first, `depth` levels deep (default 1). Dependency and VCS folders are left out unless you list them directly.",
|
|
39
|
+
action: (input) => `Listing ${typeof input.path === "string" ? input.path : "."}`,
|
|
40
|
+
input: inputSchema,
|
|
41
|
+
execute: (input) => ops.listDir(input.path ?? ".", {
|
|
42
|
+
...input.depth !== void 0 ? { depth: input.depth } : {},
|
|
43
|
+
...input.ignore !== void 0 ? { ignore: input.ignore } : {}
|
|
44
|
+
})
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
export { makeListDirTool };
|
|
50
|
+
//# sourceMappingURL=list-dir.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-dir.mjs","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/tools/list-dir.ts"],"sourcesContent":["import { tool, type ToolContract } from \"@warlock.js/ai\";\nimport type { ListDirInput, ListDirResult, WorkspaceOps } from \"../contracts\";\nimport { LIST_DIR_JSON_SCHEMA } from \"./json-schemas\";\nimport {\n objectSchema,\n optionalNumberField,\n optionalStringArrayField,\n optionalStringField,\n} from \"./schema\";\n\n/** Default tool name exposed to the LLM. */\nconst DEFAULT_NAME = \"list_dir\";\n\n/** Input schema for the `list_dir` tool. */\nconst inputSchema = objectSchema<ListDirInput>(\n {\n path: optionalStringField(),\n depth: optionalNumberField(),\n ignore: optionalStringArrayField(),\n },\n LIST_DIR_JSON_SCHEMA,\n);\n\n/**\n * Build the agent-facing `list_dir` tool over a workspace's policy-enforced\n * {@link WorkspaceOps}: a bounded, jailed listing of one directory.\n *\n * Entries come back breadth-first (each level sorted by name) with their\n * type and — for files — size, down to `depth` levels (capped by\n * `list.maxDepth`) and at most `list.maxEntries` entries (`truncated` says\n * when the cap hit). `denyPaths` are always left out, as are the\n * `list.ignore` globs (default `node_modules`, `.git`) and any `ignore`\n * globs the model passes. Symlinks are reported as `\"symlink\"` and never\n * followed. A path outside the jail surfaces as a `WorkspacePolicyError` in\n * the result's `error` field.\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 listDir = makeListDirTool(ops);\n * const { data } = await listDir.invoke({ path: \"src\", depth: 2 });\n * for (const entry of data?.entries ?? []) console.log(entry.type, entry.path, entry.size);\n */\nexport function makeListDirTool(\n ops: WorkspaceOps,\n options?: { name?: string },\n): ToolContract<ListDirInput, ListDirResult> {\n return tool<ListDirInput, ListDirResult>({\n name: options?.name ?? DEFAULT_NAME,\n description:\n \"List a workspace directory: entries with their type and file size, \" +\n \"breadth-first, `depth` levels deep (default 1). Dependency and VCS \" +\n \"folders are left out unless you list them directly.\",\n action: (input) => `Listing ${typeof input.path === \"string\" ? input.path : \".\"}`,\n input: inputSchema,\n execute: (input) =>\n ops.listDir(input.path ?? \".\", {\n ...(input.depth !== undefined ? { depth: input.depth } : {}),\n ...(input.ignore !== undefined ? { ignore: input.ignore } : {}),\n }),\n });\n}\n"],"mappings":";;;;;;AAWA,MAAM,eAAe;;AAGrB,MAAM,cAAc,aAClB;CACE,MAAM,oBAAoB;CAC1B,OAAO,oBAAoB;CAC3B,QAAQ,yBAAyB;AACnC,GACA,oBACF;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,gBACd,KACA,SAC2C;CAC3C,OAAO,KAAkC;EACvC,MAAM,SAAS,QAAQ;EACvB,aACE;EAGF,SAAS,UAAU,WAAW,OAAO,MAAM,SAAS,WAAW,MAAM,OAAO;EAC5E,OAAO;EACP,UAAU,UACR,IAAI,QAAQ,MAAM,QAAQ,KAAK;GAC7B,GAAI,MAAM,UAAU,SAAY,EAAE,OAAO,MAAM,MAAM,IAAI,CAAC;GAC1D,GAAI,MAAM,WAAW,SAAY,EAAE,QAAQ,MAAM,OAAO,IAAI,CAAC;EAC/D,CAAC;CACL,CAAC;AACH"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { MultiEditInput, MultiEditResult } from "../contracts/tool-io.type.mjs";
|
|
2
|
+
import { WorkspaceOps } from "../contracts/workspace-ops.contract.mjs";
|
|
3
|
+
import { ToolContract } from "@warlock.js/ai";
|
|
4
|
+
|
|
5
|
+
//#region ../../packages/ai-workspace/src/tools/multi-edit.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Build the agent-facing `multi_edit` tool over a workspace's policy-
|
|
8
|
+
* enforced {@link WorkspaceOps}: several exact-string replacements in one
|
|
9
|
+
* file, **atomic** — every edit applies, in order (each against the result
|
|
10
|
+
* of the previous ones), and the file is written once; or one edit fails
|
|
11
|
+
* and nothing is written.
|
|
12
|
+
*
|
|
13
|
+
* Each edit follows the `edit_file` rules: `oldString` must be found, and
|
|
14
|
+
* found exactly once unless `replaceAll`. An optional `expectHash` (from
|
|
15
|
+
* `read_file`) rejects the whole call when the file changed since it was
|
|
16
|
+
* read. A failure surfaces as a `WorkspaceEditError` in the result's
|
|
17
|
+
* `error` field, with `editIndex` naming the failing edit.
|
|
18
|
+
*
|
|
19
|
+
* @param ops - The shared, policy-enforced operation layer.
|
|
20
|
+
* @param options - Optional overrides; `name` renames the LLM-visible tool.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const multiEdit = makeMultiEditTool(ops);
|
|
24
|
+
* const { data, error } = await multiEdit.invoke({
|
|
25
|
+
* path: "src/cart.ts",
|
|
26
|
+
* expectHash,
|
|
27
|
+
* edits: [
|
|
28
|
+
* { oldString: "let total = 0;", newString: "let total = 0n;" },
|
|
29
|
+
* { oldString: "total += price;", newString: "total += BigInt(price);" },
|
|
30
|
+
* ],
|
|
31
|
+
* });
|
|
32
|
+
*/
|
|
33
|
+
declare function makeMultiEditTool(ops: WorkspaceOps, options?: {
|
|
34
|
+
name?: string;
|
|
35
|
+
}): ToolContract<MultiEditInput, MultiEditResult>;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { makeMultiEditTool };
|
|
38
|
+
//# sourceMappingURL=multi-edit.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-edit.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/tools/multi-edit.ts"],"mappings":";;;;;;;;;AA8JA;;;;;;;;;;;;;;;;;AAG+C;;;;;;iBAH/B,iBAAA,CACd,GAAA,EAAK,YAAA,EACL,OAAA;EAAY,IAAA;AAAA,IACX,YAAA,CAAa,cAAA,EAAgB,eAAA"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import "../ops.mjs";
|
|
2
|
+
import { withJsonSchema } from "./schema.mjs";
|
|
3
|
+
import { MULTI_EDIT_JSON_SCHEMA } from "./json-schemas.mjs";
|
|
4
|
+
import { tool } from "@warlock.js/ai";
|
|
5
|
+
|
|
6
|
+
//#region ../../packages/ai-workspace/src/tools/multi-edit.ts
|
|
7
|
+
/** Default tool name exposed to the LLM. */
|
|
8
|
+
const DEFAULT_NAME = "multi_edit";
|
|
9
|
+
/** Validate one element of `edits`. */
|
|
10
|
+
function validateEdit(value, index) {
|
|
11
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return { issues: [{
|
|
12
|
+
message: `edits[${index}] must be an object`,
|
|
13
|
+
path: ["edits", index]
|
|
14
|
+
}] };
|
|
15
|
+
const candidate = value;
|
|
16
|
+
const issues = [];
|
|
17
|
+
if (typeof candidate.oldString !== "string" || candidate.oldString === "") issues.push({
|
|
18
|
+
message: `edits[${index}].oldString must be a non-empty string`,
|
|
19
|
+
path: [
|
|
20
|
+
"edits",
|
|
21
|
+
index,
|
|
22
|
+
"oldString"
|
|
23
|
+
]
|
|
24
|
+
});
|
|
25
|
+
if (typeof candidate.newString !== "string") issues.push({
|
|
26
|
+
message: `edits[${index}].newString must be a string`,
|
|
27
|
+
path: [
|
|
28
|
+
"edits",
|
|
29
|
+
index,
|
|
30
|
+
"newString"
|
|
31
|
+
]
|
|
32
|
+
});
|
|
33
|
+
if (candidate.replaceAll !== void 0 && typeof candidate.replaceAll !== "boolean") issues.push({
|
|
34
|
+
message: `edits[${index}].replaceAll must be a boolean when provided`,
|
|
35
|
+
path: [
|
|
36
|
+
"edits",
|
|
37
|
+
index,
|
|
38
|
+
"replaceAll"
|
|
39
|
+
]
|
|
40
|
+
});
|
|
41
|
+
if (issues.length > 0 || typeof candidate.oldString !== "string" || typeof candidate.newString !== "string") return { issues };
|
|
42
|
+
return { edit: {
|
|
43
|
+
oldString: candidate.oldString,
|
|
44
|
+
newString: candidate.newString,
|
|
45
|
+
...candidate.replaceAll === true ? { replaceAll: true } : {}
|
|
46
|
+
} };
|
|
47
|
+
}
|
|
48
|
+
/** Input schema for the `multi_edit` tool. */
|
|
49
|
+
const inputSchema = withJsonSchema({ "~standard": {
|
|
50
|
+
version: 1,
|
|
51
|
+
vendor: "ai-workspace",
|
|
52
|
+
validate(value) {
|
|
53
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return { issues: [{ message: "input must be an object" }] };
|
|
54
|
+
const candidate = value;
|
|
55
|
+
const issues = [];
|
|
56
|
+
const edits = [];
|
|
57
|
+
if (typeof candidate.path !== "string" || candidate.path === "") issues.push({
|
|
58
|
+
message: "\"path\" must be a non-empty string",
|
|
59
|
+
path: ["path"]
|
|
60
|
+
});
|
|
61
|
+
if (candidate.expectHash !== void 0 && typeof candidate.expectHash !== "string") issues.push({
|
|
62
|
+
message: "\"expectHash\" must be a string when provided",
|
|
63
|
+
path: ["expectHash"]
|
|
64
|
+
});
|
|
65
|
+
if (!Array.isArray(candidate.edits) || candidate.edits.length === 0 || candidate.edits.length > 100) issues.push({
|
|
66
|
+
message: `"edits" must be an array of 1 to ${100} edits`,
|
|
67
|
+
path: ["edits"]
|
|
68
|
+
});
|
|
69
|
+
else candidate.edits.forEach((item, index) => {
|
|
70
|
+
const outcome = validateEdit(item, index);
|
|
71
|
+
if ("issues" in outcome) issues.push(...outcome.issues);
|
|
72
|
+
else edits.push(outcome.edit);
|
|
73
|
+
});
|
|
74
|
+
if (issues.length > 0 || typeof candidate.path !== "string") return { issues };
|
|
75
|
+
return { value: {
|
|
76
|
+
path: candidate.path,
|
|
77
|
+
edits,
|
|
78
|
+
...typeof candidate.expectHash === "string" ? { expectHash: candidate.expectHash } : {}
|
|
79
|
+
} };
|
|
80
|
+
}
|
|
81
|
+
} }, MULTI_EDIT_JSON_SCHEMA);
|
|
82
|
+
/**
|
|
83
|
+
* Build the agent-facing `multi_edit` tool over a workspace's policy-
|
|
84
|
+
* enforced {@link WorkspaceOps}: several exact-string replacements in one
|
|
85
|
+
* file, **atomic** — every edit applies, in order (each against the result
|
|
86
|
+
* of the previous ones), and the file is written once; or one edit fails
|
|
87
|
+
* and nothing is written.
|
|
88
|
+
*
|
|
89
|
+
* Each edit follows the `edit_file` rules: `oldString` must be found, and
|
|
90
|
+
* found exactly once unless `replaceAll`. An optional `expectHash` (from
|
|
91
|
+
* `read_file`) rejects the whole call when the file changed since it was
|
|
92
|
+
* read. A failure surfaces as a `WorkspaceEditError` in the result's
|
|
93
|
+
* `error` field, with `editIndex` naming the failing edit.
|
|
94
|
+
*
|
|
95
|
+
* @param ops - The shared, policy-enforced operation layer.
|
|
96
|
+
* @param options - Optional overrides; `name` renames the LLM-visible tool.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* const multiEdit = makeMultiEditTool(ops);
|
|
100
|
+
* const { data, error } = await multiEdit.invoke({
|
|
101
|
+
* path: "src/cart.ts",
|
|
102
|
+
* expectHash,
|
|
103
|
+
* edits: [
|
|
104
|
+
* { oldString: "let total = 0;", newString: "let total = 0n;" },
|
|
105
|
+
* { oldString: "total += price;", newString: "total += BigInt(price);" },
|
|
106
|
+
* ],
|
|
107
|
+
* });
|
|
108
|
+
*/
|
|
109
|
+
function makeMultiEditTool(ops, options) {
|
|
110
|
+
return tool({
|
|
111
|
+
name: options?.name ?? DEFAULT_NAME,
|
|
112
|
+
description: "Make several exact-string replacements in one workspace file at once. Edits apply in order, each to the result of the previous; if any edit fails (text not found, or not unique without replaceAll) none are applied. Pass expectHash (from read_file) to reject the call if the file changed since you read it.",
|
|
113
|
+
action: (input) => `Editing ${typeof input.path === "string" ? input.path : "a file"}`,
|
|
114
|
+
input: inputSchema,
|
|
115
|
+
execute: (input) => ops.multiEdit(input)
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
//#endregion
|
|
120
|
+
export { makeMultiEditTool };
|
|
121
|
+
//# sourceMappingURL=multi-edit.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-edit.mjs","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/tools/multi-edit.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { tool, type ToolContract } from \"@warlock.js/ai\";\nimport { MAX_MULTI_EDITS } from \"../ops\";\nimport type {\n MultiEditInput,\n MultiEditOperation,\n MultiEditResult,\n WorkspaceOps,\n} from \"../contracts\";\nimport { MULTI_EDIT_JSON_SCHEMA } from \"./json-schemas\";\nimport { withJsonSchema } from \"./schema\";\n\n/** Default tool name exposed to the LLM. */\nconst DEFAULT_NAME = \"multi_edit\";\n\n/** Validate one element of `edits`. */\nfunction validateEdit(\n value: unknown,\n index: number,\n): { edit: MultiEditOperation } | { issues: StandardSchemaV1.Issue[] } {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return { issues: [{ message: `edits[${index}] must be an object`, path: [\"edits\", index] }] };\n }\n\n const candidate = value as Record<string, unknown>;\n const issues: StandardSchemaV1.Issue[] = [];\n\n if (typeof candidate.oldString !== \"string\" || candidate.oldString === \"\") {\n issues.push({\n message: `edits[${index}].oldString must be a non-empty string`,\n path: [\"edits\", index, \"oldString\"],\n });\n }\n\n if (typeof candidate.newString !== \"string\") {\n issues.push({\n message: `edits[${index}].newString must be a string`,\n path: [\"edits\", index, \"newString\"],\n });\n }\n\n if (candidate.replaceAll !== undefined && typeof candidate.replaceAll !== \"boolean\") {\n issues.push({\n message: `edits[${index}].replaceAll must be a boolean when provided`,\n path: [\"edits\", index, \"replaceAll\"],\n });\n }\n\n if (\n issues.length > 0 ||\n typeof candidate.oldString !== \"string\" ||\n typeof candidate.newString !== \"string\"\n ) {\n return { issues };\n }\n\n return {\n edit: {\n oldString: candidate.oldString,\n newString: candidate.newString,\n ...(candidate.replaceAll === true ? { replaceAll: true } : {}),\n },\n };\n}\n\n/** Input schema for the `multi_edit` tool. */\nconst inputSchema = withJsonSchema<MultiEditInput>(\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 candidate = value as Record<string, unknown>;\n const issues: StandardSchemaV1.Issue[] = [];\n const edits: MultiEditOperation[] = [];\n\n if (typeof candidate.path !== \"string\" || candidate.path === \"\") {\n issues.push({ message: '\"path\" must be a non-empty string', path: [\"path\"] });\n }\n\n if (candidate.expectHash !== undefined && typeof candidate.expectHash !== \"string\") {\n issues.push({\n message: '\"expectHash\" must be a string when provided',\n path: [\"expectHash\"],\n });\n }\n\n if (\n !Array.isArray(candidate.edits) ||\n candidate.edits.length === 0 ||\n candidate.edits.length > MAX_MULTI_EDITS\n ) {\n issues.push({\n message: `\"edits\" must be an array of 1 to ${MAX_MULTI_EDITS} edits`,\n path: [\"edits\"],\n });\n } else {\n candidate.edits.forEach((item: unknown, index: number) => {\n const outcome = validateEdit(item, index);\n\n if (\"issues\" in outcome) {\n issues.push(...outcome.issues);\n } else {\n edits.push(outcome.edit);\n }\n });\n }\n\n if (issues.length > 0 || typeof candidate.path !== \"string\") {\n return { issues };\n }\n\n return {\n value: {\n path: candidate.path,\n edits,\n ...(typeof candidate.expectHash === \"string\"\n ? { expectHash: candidate.expectHash }\n : {}),\n },\n };\n },\n },\n },\n MULTI_EDIT_JSON_SCHEMA,\n);\n\n/**\n * Build the agent-facing `multi_edit` tool over a workspace's policy-\n * enforced {@link WorkspaceOps}: several exact-string replacements in one\n * file, **atomic** — every edit applies, in order (each against the result\n * of the previous ones), and the file is written once; or one edit fails\n * and nothing is written.\n *\n * Each edit follows the `edit_file` rules: `oldString` must be found, and\n * found exactly once unless `replaceAll`. An optional `expectHash` (from\n * `read_file`) rejects the whole call when the file changed since it was\n * read. A failure surfaces as a `WorkspaceEditError` in the result's\n * `error` field, with `editIndex` naming the failing edit.\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 multiEdit = makeMultiEditTool(ops);\n * const { data, error } = await multiEdit.invoke({\n * path: \"src/cart.ts\",\n * expectHash,\n * edits: [\n * { oldString: \"let total = 0;\", newString: \"let total = 0n;\" },\n * { oldString: \"total += price;\", newString: \"total += BigInt(price);\" },\n * ],\n * });\n */\nexport function makeMultiEditTool(\n ops: WorkspaceOps,\n options?: { name?: string },\n): ToolContract<MultiEditInput, MultiEditResult> {\n return tool<MultiEditInput, MultiEditResult>({\n name: options?.name ?? DEFAULT_NAME,\n description:\n \"Make several exact-string replacements in one workspace file at once. \" +\n \"Edits apply in order, each to the result of the previous; if any \" +\n \"edit fails (text not found, or not unique without replaceAll) none \" +\n \"are applied. Pass expectHash (from read_file) to reject the call if \" +\n \"the file changed since you read it.\",\n action: (input) => `Editing ${typeof input.path === \"string\" ? input.path : \"a file\"}`,\n input: inputSchema,\n execute: (input) => ops.multiEdit(input),\n });\n}\n"],"mappings":";;;;;;;AAaA,MAAM,eAAe;;AAGrB,SAAS,aACP,OACA,OACqE;CACrE,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK,GACpE,OAAO,EAAE,QAAQ,CAAC;EAAE,SAAS,SAAS,MAAM;EAAsB,MAAM,CAAC,SAAS,KAAK;CAAE,CAAC,EAAE;CAG9F,MAAM,YAAY;CAClB,MAAM,SAAmC,CAAC;CAE1C,IAAI,OAAO,UAAU,cAAc,YAAY,UAAU,cAAc,IACrE,OAAO,KAAK;EACV,SAAS,SAAS,MAAM;EACxB,MAAM;GAAC;GAAS;GAAO;EAAW;CACpC,CAAC;CAGH,IAAI,OAAO,UAAU,cAAc,UACjC,OAAO,KAAK;EACV,SAAS,SAAS,MAAM;EACxB,MAAM;GAAC;GAAS;GAAO;EAAW;CACpC,CAAC;CAGH,IAAI,UAAU,eAAe,UAAa,OAAO,UAAU,eAAe,WACxE,OAAO,KAAK;EACV,SAAS,SAAS,MAAM;EACxB,MAAM;GAAC;GAAS;GAAO;EAAY;CACrC,CAAC;CAGH,IACE,OAAO,SAAS,KAChB,OAAO,UAAU,cAAc,YAC/B,OAAO,UAAU,cAAc,UAE/B,OAAO,EAAE,OAAO;CAGlB,OAAO,EACL,MAAM;EACJ,WAAW,UAAU;EACrB,WAAW,UAAU;EACrB,GAAI,UAAU,eAAe,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC;CAC9D,EACF;AACF;;AAGA,MAAM,cAAc,eAClB,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,YAAY;EAClB,MAAM,SAAmC,CAAC;EAC1C,MAAM,QAA8B,CAAC;EAErC,IAAI,OAAO,UAAU,SAAS,YAAY,UAAU,SAAS,IAC3D,OAAO,KAAK;GAAE,SAAS;GAAqC,MAAM,CAAC,MAAM;EAAE,CAAC;EAG9E,IAAI,UAAU,eAAe,UAAa,OAAO,UAAU,eAAe,UACxE,OAAO,KAAK;GACV,SAAS;GACT,MAAM,CAAC,YAAY;EACrB,CAAC;EAGH,IACE,CAAC,MAAM,QAAQ,UAAU,KAAK,KAC9B,UAAU,MAAM,WAAW,KAC3B,UAAU,MAAM,cAEhB,OAAO,KAAK;GACV,SAAS,wCAAoD;GAC7D,MAAM,CAAC,OAAO;EAChB,CAAC;OAED,UAAU,MAAM,SAAS,MAAe,UAAkB;GACxD,MAAM,UAAU,aAAa,MAAM,KAAK;GAExC,IAAI,YAAY,SACd,OAAO,KAAK,GAAG,QAAQ,MAAM;QAE7B,MAAM,KAAK,QAAQ,IAAI;EAE3B,CAAC;EAGH,IAAI,OAAO,SAAS,KAAK,OAAO,UAAU,SAAS,UACjD,OAAO,EAAE,OAAO;EAGlB,OAAO,EACL,OAAO;GACL,MAAM,UAAU;GAChB;GACA,GAAI,OAAO,UAAU,eAAe,WAChC,EAAE,YAAY,UAAU,WAAW,IACnC,CAAC;EACP,EACF;CACF;AACF,EACF,GACA,sBACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,SAAgB,kBACd,KACA,SAC+C;CAC/C,OAAO,KAAsC;EAC3C,MAAM,SAAS,QAAQ;EACvB,aACE;EAKF,SAAS,UAAU,WAAW,OAAO,MAAM,SAAS,WAAW,MAAM,OAAO;EAC5E,OAAO;EACP,UAAU,UAAU,IAAI,UAAU,KAAK;CACzC,CAAC;AACH"}
|
|
@@ -2,17 +2,17 @@ import { ReadFileInput, ReadFileResult } 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/read-file.d.ts
|
|
6
6
|
/**
|
|
7
7
|
* Build the agent-facing `read_file` tool over a workspace's policy-
|
|
8
8
|
* enforced {@link WorkspaceOps}.
|
|
9
9
|
*
|
|
10
10
|
* The tool validates `{ path, startLine?, limit? }` against a Standard
|
|
11
11
|
* Schema, then delegates to `ops.readFile`, mapping the result into the
|
|
12
|
-
* agent wire shape {@link ReadFileResult} — the `hash` an
|
|
13
|
-
* carry into a later `edit_file` (read-before-edit), plus the
|
|
14
|
-
* / `endLine` / `truncated` window metadata
|
|
15
|
-
*
|
|
12
|
+
* agent wire shape {@link ReadFileResult} — the full file's `hash` an
|
|
13
|
+
* agent must carry into a later `edit_file` (read-before-edit), plus the
|
|
14
|
+
* `startLine` / `endLine` / `truncated` / `bytesCapped` window metadata.
|
|
15
|
+
* The returned `content` never exceeds the policy's `read.maxBytes`.
|
|
16
16
|
*
|
|
17
17
|
* **Errors flow as data.** Policy violations (a jail escape) are thrown
|
|
18
18
|
* by `ops`; the `tool()` wrapper catches them and surfaces them in the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-file.d.mts","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"read-file.d.mts","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/tools/read-file.ts"],"mappings":";;;;;;;;;AA0CA;;;;;;;;;;;;;;;;;AAG6C;;;iBAH7B,gBAAA,CACd,GAAA,EAAK,YAAA,EACL,OAAA;EAAY,IAAA;AAAA,IACX,YAAA,CAAa,aAAA,EAAe,cAAA"}
|
package/esm/tools/read-file.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { objectSchema, optionalNumberField, stringField } from "./schema.mjs";
|
|
2
|
+
import { READ_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/read-file.ts
|
|
5
6
|
/** Default tool name exposed to the LLM. */
|
|
6
7
|
const DEFAULT_NAME = "read_file";
|
|
7
8
|
/** Input schema for the `read_file` tool. */
|
|
@@ -9,17 +10,17 @@ const inputSchema = objectSchema({
|
|
|
9
10
|
path: stringField(),
|
|
10
11
|
startLine: optionalNumberField(),
|
|
11
12
|
limit: optionalNumberField()
|
|
12
|
-
});
|
|
13
|
+
}, READ_FILE_JSON_SCHEMA);
|
|
13
14
|
/**
|
|
14
15
|
* Build the agent-facing `read_file` tool over a workspace's policy-
|
|
15
16
|
* enforced {@link WorkspaceOps}.
|
|
16
17
|
*
|
|
17
18
|
* The tool validates `{ path, startLine?, limit? }` against a Standard
|
|
18
19
|
* Schema, then delegates to `ops.readFile`, mapping the result into the
|
|
19
|
-
* agent wire shape {@link ReadFileResult} — the `hash` an
|
|
20
|
-
* carry into a later `edit_file` (read-before-edit), plus the
|
|
21
|
-
* / `endLine` / `truncated` window metadata
|
|
22
|
-
*
|
|
20
|
+
* agent wire shape {@link ReadFileResult} — the full file's `hash` an
|
|
21
|
+
* agent must carry into a later `edit_file` (read-before-edit), plus the
|
|
22
|
+
* `startLine` / `endLine` / `truncated` / `bytesCapped` window metadata.
|
|
23
|
+
* The returned `content` never exceeds the policy's `read.maxBytes`.
|
|
23
24
|
*
|
|
24
25
|
* **Errors flow as data.** Policy violations (a jail escape) are thrown
|
|
25
26
|
* by `ops`; the `tool()` wrapper catches them and surfaces them in the
|
|
@@ -37,24 +38,13 @@ const inputSchema = objectSchema({
|
|
|
37
38
|
function makeReadFileTool(ops, options) {
|
|
38
39
|
return tool({
|
|
39
40
|
name: options?.name ?? DEFAULT_NAME,
|
|
40
|
-
description: "Read a file from the workspace, returning a numbered line window plus the file's content hash. Pass the hash to edit_file's expectHash to guard against editing a stale version. Use startLine/limit to page through large files.",
|
|
41
|
+
description: "Read a file from the workspace, returning a numbered line window plus the file's content hash. Pass the hash to edit_file's expectHash to guard against editing a stale version. Use startLine/limit to page through large files. Output is byte-capped by the workspace policy; when bytesCapped is true the last returned line may be cut short.",
|
|
41
42
|
input: inputSchema,
|
|
42
43
|
async execute(input) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
offset: startLine,
|
|
44
|
+
return ops.readFile(input.path, {
|
|
45
|
+
offset: input.startLine !== void 0 ? Math.max(1, input.startLine) : 1,
|
|
46
46
|
limit: input.limit
|
|
47
47
|
});
|
|
48
|
-
const returnedLines = content.length === 0 ? 0 : content.split("\n").length;
|
|
49
|
-
const endLine = Math.min(totalLines, startLine + Math.max(returnedLines, 1) - 1);
|
|
50
|
-
return {
|
|
51
|
-
content,
|
|
52
|
-
startLine,
|
|
53
|
-
endLine,
|
|
54
|
-
totalLines,
|
|
55
|
-
truncated: endLine < totalLines,
|
|
56
|
-
hash
|
|
57
|
-
};
|
|
58
48
|
}
|
|
59
49
|
});
|
|
60
50
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-file.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"read-file.mjs","names":[],"sources":["../../../../../../../../packages/ai-workspace/src/tools/read-file.ts"],"sourcesContent":["import { tool, type ToolContract } from \"@warlock.js/ai\";\nimport { objectSchema, optionalNumberField, stringField } from \"./schema\";\nimport { READ_FILE_JSON_SCHEMA } from \"./json-schemas\";\nimport type { ReadFileInput, ReadFileResult, WorkspaceOps } from \"../contracts\";\n\n/** Default tool name exposed to the LLM. */\nconst DEFAULT_NAME = \"read_file\";\n\n/** Input schema for the `read_file` tool. */\nconst inputSchema = objectSchema<ReadFileInput>(\n {\n path: stringField(),\n startLine: optionalNumberField(),\n limit: optionalNumberField(),\n },\n READ_FILE_JSON_SCHEMA,\n);\n\n/**\n * Build the agent-facing `read_file` tool over a workspace's policy-\n * enforced {@link WorkspaceOps}.\n *\n * The tool validates `{ path, startLine?, limit? }` against a Standard\n * Schema, then delegates to `ops.readFile`, mapping the result into the\n * agent wire shape {@link ReadFileResult} — the full file's `hash` an\n * agent must carry into a later `edit_file` (read-before-edit), plus the\n * `startLine` / `endLine` / `truncated` / `bytesCapped` window metadata.\n * The returned `content` never exceeds the policy's `read.maxBytes`.\n *\n * **Errors flow as data.** Policy violations (a jail escape) are thrown\n * by `ops`; the `tool()` wrapper catches them and surfaces them in the\n * returned `{ error }` field — `invoke()` never throws — so the agent can\n * read the failure and self-correct.\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 readTool = makeReadFileTool(ops);\n * const { data, error } = await readTool.invoke({ path: \"src/index.ts\" });\n * if (!error) console.log(data.hash); // feed into edit_file's expectHash\n */\nexport function makeReadFileTool(\n ops: WorkspaceOps,\n options?: { name?: string },\n): ToolContract<ReadFileInput, ReadFileResult> {\n return tool<ReadFileInput, ReadFileResult>({\n name: options?.name ?? DEFAULT_NAME,\n description:\n \"Read a file from the workspace, returning a numbered line window plus \" +\n \"the file's content hash. Pass the hash to edit_file's expectHash to \" +\n \"guard against editing a stale version. Use startLine/limit to page \" +\n \"through large files. Output is byte-capped by the workspace policy; \" +\n \"when bytesCapped is true the last returned line may be cut short.\",\n input: inputSchema,\n async execute(input) {\n // The ops layer owns the window and byte-cap arithmetic, so the tool\n // and the direct `readFile()` return identical results for the same\n // arguments.\n return ops.readFile(input.path, {\n offset: input.startLine !== undefined ? Math.max(1, input.startLine) : 1,\n limit: input.limit,\n });\n },\n });\n}\n"],"mappings":";;;;;;AAMA,MAAM,eAAe;;AAGrB,MAAM,cAAc,aAClB;CACE,MAAM,YAAY;CAClB,WAAW,oBAAoB;CAC/B,OAAO,oBAAoB;AAC7B,GACA,qBACF;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,SAAgB,iBACd,KACA,SAC6C;CAC7C,OAAO,KAAoC;EACzC,MAAM,SAAS,QAAQ;EACvB,aACE;EAKF,OAAO;EACP,MAAM,QAAQ,OAAO;GAInB,OAAO,IAAI,SAAS,MAAM,MAAM;IAC9B,QAAQ,MAAM,cAAc,SAAY,KAAK,IAAI,GAAG,MAAM,SAAS,IAAI;IACvE,OAAO,MAAM;GACf,CAAC;EACH;CACF,CAAC;AACH"}
|