@clinebot/core 0.0.11 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/agents/agent-config-loader.d.ts +1 -1
- package/dist/agents/agent-config-parser.d.ts +5 -2
- package/dist/agents/index.d.ts +1 -1
- package/dist/agents/plugin-config-loader.d.ts +4 -0
- package/dist/agents/plugin-loader.d.ts +1 -0
- package/dist/agents/plugin-sandbox-bootstrap.js +446 -0
- package/dist/agents/plugin-sandbox.d.ts +4 -0
- package/dist/index.node.d.ts +5 -0
- package/dist/index.node.js +685 -413
- package/dist/runtime/commands.d.ts +11 -0
- package/dist/runtime/sandbox/subprocess-sandbox.d.ts +8 -1
- package/dist/runtime/skills.d.ts +13 -0
- package/dist/session/default-session-manager.d.ts +5 -0
- package/dist/session/session-config-builder.d.ts +4 -1
- package/dist/session/session-manager.d.ts +1 -0
- package/dist/session/session-service.d.ts +22 -22
- package/dist/session/unified-session-persistence-service.d.ts +12 -6
- package/dist/session/utils/helpers.d.ts +2 -2
- package/dist/session/utils/types.d.ts +9 -0
- package/dist/tools/definitions.d.ts +2 -2
- package/dist/tools/presets.d.ts +3 -3
- package/dist/tools/schemas.d.ts +15 -14
- package/dist/types/config.d.ts +5 -0
- package/dist/types/events.d.ts +22 -0
- package/package.json +5 -4
- package/src/agents/agent-config-loader.test.ts +2 -0
- package/src/agents/agent-config-loader.ts +1 -0
- package/src/agents/agent-config-parser.ts +12 -5
- package/src/agents/index.ts +1 -0
- package/src/agents/plugin-config-loader.test.ts +49 -0
- package/src/agents/plugin-config-loader.ts +10 -73
- package/src/agents/plugin-loader.test.ts +127 -1
- package/src/agents/plugin-loader.ts +72 -5
- package/src/agents/plugin-sandbox-bootstrap.ts +445 -0
- package/src/agents/plugin-sandbox.test.ts +198 -1
- package/src/agents/plugin-sandbox.ts +223 -353
- package/src/index.node.ts +14 -0
- package/src/runtime/commands.test.ts +98 -0
- package/src/runtime/commands.ts +83 -0
- package/src/runtime/hook-file-hooks.test.ts +1 -1
- package/src/runtime/hook-file-hooks.ts +16 -6
- package/src/runtime/index.ts +10 -0
- package/src/runtime/runtime-builder.test.ts +67 -0
- package/src/runtime/runtime-builder.ts +70 -16
- package/src/runtime/sandbox/subprocess-sandbox.ts +35 -11
- package/src/runtime/skills.ts +44 -0
- package/src/runtime/workflows.ts +20 -29
- package/src/session/default-session-manager.e2e.test.ts +52 -33
- package/src/session/default-session-manager.test.ts +453 -1
- package/src/session/default-session-manager.ts +210 -12
- package/src/session/rpc-session-service.ts +14 -96
- package/src/session/session-config-builder.ts +2 -0
- package/src/session/session-manager.ts +1 -0
- package/src/session/session-service.ts +127 -64
- package/src/session/session-team-coordination.ts +30 -0
- package/src/session/unified-session-persistence-service.test.ts +3 -3
- package/src/session/unified-session-persistence-service.ts +159 -141
- package/src/session/utils/helpers.ts +22 -41
- package/src/session/utils/types.ts +10 -0
- package/src/storage/sqlite-team-store.ts +16 -5
- package/src/tools/definitions.test.ts +137 -8
- package/src/tools/definitions.ts +115 -70
- package/src/tools/presets.test.ts +2 -3
- package/src/tools/presets.ts +3 -3
- package/src/tools/schemas.ts +28 -28
- package/src/types/config.ts +5 -0
- package/src/types/events.ts +23 -0
|
@@ -120,6 +120,8 @@ export class SqliteTeamStore implements TeamStore {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
private ensureSchema(db: SqliteDb): void {
|
|
123
|
+
db.exec("PRAGMA journal_mode = WAL;");
|
|
124
|
+
db.exec("PRAGMA busy_timeout = 5000;");
|
|
123
125
|
db.exec(`
|
|
124
126
|
CREATE TABLE IF NOT EXISTS team_events (
|
|
125
127
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -130,16 +132,20 @@ export class SqliteTeamStore implements TeamStore {
|
|
|
130
132
|
causation_id TEXT,
|
|
131
133
|
correlation_id TEXT
|
|
132
134
|
);
|
|
135
|
+
`);
|
|
136
|
+
db.exec(`
|
|
133
137
|
CREATE INDEX IF NOT EXISTS idx_team_events_name_ts
|
|
134
138
|
ON team_events(team_name, ts DESC);
|
|
135
|
-
|
|
139
|
+
`);
|
|
140
|
+
db.exec(`
|
|
136
141
|
CREATE TABLE IF NOT EXISTS team_runtime_snapshot (
|
|
137
142
|
team_name TEXT PRIMARY KEY,
|
|
138
143
|
state_json TEXT NOT NULL,
|
|
139
144
|
teammates_json TEXT NOT NULL,
|
|
140
145
|
updated_at TEXT NOT NULL
|
|
141
146
|
);
|
|
142
|
-
|
|
147
|
+
`);
|
|
148
|
+
db.exec(`
|
|
143
149
|
CREATE TABLE IF NOT EXISTS team_tasks (
|
|
144
150
|
team_name TEXT NOT NULL,
|
|
145
151
|
task_id TEXT NOT NULL,
|
|
@@ -153,7 +159,8 @@ export class SqliteTeamStore implements TeamStore {
|
|
|
153
159
|
updated_at TEXT NOT NULL,
|
|
154
160
|
PRIMARY KEY(team_name, task_id)
|
|
155
161
|
);
|
|
156
|
-
|
|
162
|
+
`);
|
|
163
|
+
db.exec(`
|
|
157
164
|
CREATE TABLE IF NOT EXISTS team_runs (
|
|
158
165
|
team_name TEXT NOT NULL,
|
|
159
166
|
run_id TEXT NOT NULL,
|
|
@@ -169,9 +176,12 @@ export class SqliteTeamStore implements TeamStore {
|
|
|
169
176
|
version INTEGER NOT NULL DEFAULT 1,
|
|
170
177
|
PRIMARY KEY(team_name, run_id)
|
|
171
178
|
);
|
|
179
|
+
`);
|
|
180
|
+
db.exec(`
|
|
172
181
|
CREATE INDEX IF NOT EXISTS idx_team_runs_status
|
|
173
182
|
ON team_runs(team_name, status);
|
|
174
|
-
|
|
183
|
+
`);
|
|
184
|
+
db.exec(`
|
|
175
185
|
CREATE TABLE IF NOT EXISTS team_outcomes (
|
|
176
186
|
team_name TEXT NOT NULL,
|
|
177
187
|
outcome_id TEXT NOT NULL,
|
|
@@ -182,7 +192,8 @@ export class SqliteTeamStore implements TeamStore {
|
|
|
182
192
|
version INTEGER NOT NULL DEFAULT 1,
|
|
183
193
|
PRIMARY KEY(team_name, outcome_id)
|
|
184
194
|
);
|
|
185
|
-
|
|
195
|
+
`);
|
|
196
|
+
db.exec(`
|
|
186
197
|
CREATE TABLE IF NOT EXISTS team_outcome_fragments (
|
|
187
198
|
team_name TEXT NOT NULL,
|
|
188
199
|
outcome_id TEXT NOT NULL,
|
|
@@ -3,7 +3,19 @@ import {
|
|
|
3
3
|
createBashTool,
|
|
4
4
|
createDefaultTools,
|
|
5
5
|
createReadFilesTool,
|
|
6
|
+
createSkillsTool,
|
|
6
7
|
} from "./definitions.js";
|
|
8
|
+
import { INPUT_ARG_CHAR_LIMIT } from "./schemas.js";
|
|
9
|
+
import type { SkillsExecutorWithMetadata } from "./types.js";
|
|
10
|
+
|
|
11
|
+
function createMockSkillsExecutor(
|
|
12
|
+
fn: (...args: unknown[]) => Promise<string> = async () => "ok",
|
|
13
|
+
configuredSkills?: SkillsExecutorWithMetadata["configuredSkills"],
|
|
14
|
+
): SkillsExecutorWithMetadata {
|
|
15
|
+
const executor = fn as SkillsExecutorWithMetadata;
|
|
16
|
+
executor.configuredSkills = configuredSkills;
|
|
17
|
+
return executor;
|
|
18
|
+
}
|
|
7
19
|
|
|
8
20
|
describe("default skills tool", () => {
|
|
9
21
|
it("is included only when enabled with a skills executor", () => {
|
|
@@ -17,18 +29,43 @@ describe("default skills tool", () => {
|
|
|
17
29
|
|
|
18
30
|
const toolsWithExecutor = createDefaultTools({
|
|
19
31
|
executors: {
|
|
20
|
-
skills:
|
|
32
|
+
skills: createMockSkillsExecutor(),
|
|
21
33
|
},
|
|
22
34
|
enableSkills: true,
|
|
23
35
|
});
|
|
24
36
|
expect(toolsWithExecutor.map((tool) => tool.name)).toContain("skills");
|
|
25
37
|
});
|
|
26
38
|
|
|
39
|
+
it("includes configured skill names in description", () => {
|
|
40
|
+
const executor = createMockSkillsExecutor(
|
|
41
|
+
async () => "ok",
|
|
42
|
+
[
|
|
43
|
+
{ id: "commit", name: "commit", disabled: false },
|
|
44
|
+
{
|
|
45
|
+
id: "review-pr",
|
|
46
|
+
name: "review-pr",
|
|
47
|
+
description: "Review a PR",
|
|
48
|
+
disabled: false,
|
|
49
|
+
},
|
|
50
|
+
{ id: "disabled-skill", name: "disabled-skill", disabled: true },
|
|
51
|
+
],
|
|
52
|
+
);
|
|
53
|
+
const tool = createSkillsTool(executor);
|
|
54
|
+
expect(tool.description).toContain("Available skills: commit, review-pr.");
|
|
55
|
+
expect(tool.description).not.toContain("disabled-skill");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("omits skill list from description when no skills are configured", () => {
|
|
59
|
+
const executor = createMockSkillsExecutor(async () => "ok");
|
|
60
|
+
const tool = createSkillsTool(executor);
|
|
61
|
+
expect(tool.description).not.toContain("Available skills");
|
|
62
|
+
});
|
|
63
|
+
|
|
27
64
|
it("validates and executes skill invocation input", async () => {
|
|
28
65
|
const execute = vi.fn(async () => "loaded");
|
|
29
66
|
const tools = createDefaultTools({
|
|
30
67
|
executors: {
|
|
31
|
-
skills: execute,
|
|
68
|
+
skills: createMockSkillsExecutor(execute),
|
|
32
69
|
},
|
|
33
70
|
enableReadFiles: false,
|
|
34
71
|
enableSearch: false,
|
|
@@ -310,6 +347,48 @@ describe("default read_files tool", () => {
|
|
|
310
347
|
}),
|
|
311
348
|
);
|
|
312
349
|
});
|
|
350
|
+
|
|
351
|
+
it("treats null line bounds as full-file boundaries", async () => {
|
|
352
|
+
const execute = vi.fn(async () => "full file");
|
|
353
|
+
const tool = createReadFilesTool(execute);
|
|
354
|
+
|
|
355
|
+
const result = await tool.execute(
|
|
356
|
+
{
|
|
357
|
+
files: [
|
|
358
|
+
{
|
|
359
|
+
path: "/tmp/example.ts",
|
|
360
|
+
start_line: null,
|
|
361
|
+
end_line: null,
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
agentId: "agent-1",
|
|
367
|
+
conversationId: "conv-1",
|
|
368
|
+
iteration: 1,
|
|
369
|
+
},
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
expect(result).toEqual([
|
|
373
|
+
{
|
|
374
|
+
query: "/tmp/example.ts",
|
|
375
|
+
result: "full file",
|
|
376
|
+
success: true,
|
|
377
|
+
},
|
|
378
|
+
]);
|
|
379
|
+
expect(execute).toHaveBeenCalledWith(
|
|
380
|
+
{
|
|
381
|
+
path: "/tmp/example.ts",
|
|
382
|
+
start_line: null,
|
|
383
|
+
end_line: null,
|
|
384
|
+
},
|
|
385
|
+
expect.objectContaining({
|
|
386
|
+
agentId: "agent-1",
|
|
387
|
+
conversationId: "conv-1",
|
|
388
|
+
iteration: 1,
|
|
389
|
+
}),
|
|
390
|
+
);
|
|
391
|
+
});
|
|
313
392
|
});
|
|
314
393
|
|
|
315
394
|
describe("zod schema conversion", () => {
|
|
@@ -329,19 +408,20 @@ describe("zod schema conversion", () => {
|
|
|
329
408
|
"The absolute file path of a text file to read content from",
|
|
330
409
|
},
|
|
331
410
|
start_line: {
|
|
332
|
-
type: "integer",
|
|
333
|
-
description:
|
|
411
|
+
anyOf: [{ type: "integer" }, { type: "null" }],
|
|
412
|
+
description:
|
|
413
|
+
"Optional one-based starting line number to read from; use null or omit for the start of the file",
|
|
334
414
|
},
|
|
335
415
|
end_line: {
|
|
336
|
-
type: "integer",
|
|
416
|
+
anyOf: [{ type: "integer" }, { type: "null" }],
|
|
337
417
|
description:
|
|
338
|
-
"Optional one-based ending line number to read through",
|
|
418
|
+
"Optional one-based ending line number to read through; use null or omit for the end of the file",
|
|
339
419
|
},
|
|
340
420
|
},
|
|
341
421
|
required: ["path"],
|
|
342
422
|
},
|
|
343
423
|
description:
|
|
344
|
-
"Array of file read requests. Omit start_line
|
|
424
|
+
"Array of file read requests. Omit start_line/end_line or set them to null to return the full file content boundaries; provide integers to return only that inclusive one-based line range. Prefer this tool over running terminal command to get file content for better performance and reliability.",
|
|
345
425
|
});
|
|
346
426
|
expect(inputSchema.required).toEqual(["files"]);
|
|
347
427
|
});
|
|
@@ -349,7 +429,7 @@ describe("zod schema conversion", () => {
|
|
|
349
429
|
it("exposes skills args as optional nullable in tool schemas", () => {
|
|
350
430
|
const tools = createDefaultTools({
|
|
351
431
|
executors: {
|
|
352
|
-
skills:
|
|
432
|
+
skills: createMockSkillsExecutor(),
|
|
353
433
|
},
|
|
354
434
|
enableReadFiles: false,
|
|
355
435
|
enableSearch: false,
|
|
@@ -526,4 +606,53 @@ describe("default editor tool", () => {
|
|
|
526
606
|
expect.anything(),
|
|
527
607
|
);
|
|
528
608
|
});
|
|
609
|
+
|
|
610
|
+
it("returns a recoverable tool error when text exceeds the soft character limit", async () => {
|
|
611
|
+
const execute = vi.fn(async () => "patched");
|
|
612
|
+
const tools = createDefaultTools({
|
|
613
|
+
executors: {
|
|
614
|
+
editor: execute,
|
|
615
|
+
},
|
|
616
|
+
enableReadFiles: false,
|
|
617
|
+
enableSearch: false,
|
|
618
|
+
enableBash: false,
|
|
619
|
+
enableWebFetch: false,
|
|
620
|
+
enableSkills: false,
|
|
621
|
+
enableAskQuestion: false,
|
|
622
|
+
enableApplyPatch: false,
|
|
623
|
+
enableEditor: true,
|
|
624
|
+
});
|
|
625
|
+
const editorTool = tools.find((tool) => tool.name === "editor");
|
|
626
|
+
expect(editorTool).toBeDefined();
|
|
627
|
+
if (!editorTool) {
|
|
628
|
+
throw new Error("Expected editor tool to be defined.");
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const oversizedText = "x".repeat(INPUT_ARG_CHAR_LIMIT + 1);
|
|
632
|
+
const result = await editorTool.execute(
|
|
633
|
+
{
|
|
634
|
+
path: "/tmp/example.ts",
|
|
635
|
+
new_text: oversizedText,
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
agentId: "agent-1",
|
|
639
|
+
conversationId: "conv-1",
|
|
640
|
+
iteration: 1,
|
|
641
|
+
},
|
|
642
|
+
);
|
|
643
|
+
|
|
644
|
+
expect(result).toEqual({
|
|
645
|
+
query: "edit:/tmp/example.ts",
|
|
646
|
+
result: "",
|
|
647
|
+
error: expect.stringContaining("new_text was"),
|
|
648
|
+
success: false,
|
|
649
|
+
});
|
|
650
|
+
if (typeof result !== "object" || result == null || !("error" in result)) {
|
|
651
|
+
throw new Error("Expected editor tool result to include an error.");
|
|
652
|
+
}
|
|
653
|
+
expect(result.error).toContain(
|
|
654
|
+
`recommended limit of ${INPUT_ARG_CHAR_LIMIT}`,
|
|
655
|
+
);
|
|
656
|
+
expect(execute).not.toHaveBeenCalled();
|
|
657
|
+
});
|
|
529
658
|
});
|
package/src/tools/definitions.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
EditFileInputSchema,
|
|
17
17
|
type FetchWebContentInput,
|
|
18
18
|
FetchWebContentInputSchema,
|
|
19
|
+
INPUT_ARG_CHAR_LIMIT,
|
|
19
20
|
type ReadFileRequest,
|
|
20
21
|
type ReadFilesInput,
|
|
21
22
|
ReadFilesInputSchema,
|
|
@@ -38,7 +39,7 @@ import type {
|
|
|
38
39
|
EditorExecutor,
|
|
39
40
|
FileReadExecutor,
|
|
40
41
|
SearchExecutor,
|
|
41
|
-
|
|
42
|
+
SkillsExecutorWithMetadata,
|
|
42
43
|
ToolOperationResult,
|
|
43
44
|
WebFetchExecutor,
|
|
44
45
|
} from "./types.js";
|
|
@@ -57,6 +58,21 @@ function formatError(error: unknown): string {
|
|
|
57
58
|
return String(error);
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
function getEditorSizeError(input: EditFileInput): string | null {
|
|
62
|
+
if (
|
|
63
|
+
typeof input.old_text === "string" &&
|
|
64
|
+
input.old_text.length > INPUT_ARG_CHAR_LIMIT
|
|
65
|
+
) {
|
|
66
|
+
return `Editor input too large: old_text was ${input.old_text.length} characters, exceeding the recommended limit of ${INPUT_ARG_CHAR_LIMIT}. Split the edit into smaller tool calls so later tool calls are less likely to be truncated or time out.`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (input.new_text.length > INPUT_ARG_CHAR_LIMIT) {
|
|
70
|
+
return `Editor input too large: new_text was ${input.new_text.length} characters, exceeding the recommended limit of ${INPUT_ARG_CHAR_LIMIT}. Split the edit into smaller tool calls so later tool calls are less likely to be truncated or time out.`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
60
76
|
/**
|
|
61
77
|
* Create a timeout-wrapped promise
|
|
62
78
|
*/
|
|
@@ -105,7 +121,7 @@ function normalizeReadFileRequests(input: unknown): ReadFileRequest[] {
|
|
|
105
121
|
|
|
106
122
|
function formatReadFileQuery(request: ReadFileRequest): string {
|
|
107
123
|
const { path, start_line, end_line } = request;
|
|
108
|
-
if (start_line
|
|
124
|
+
if (start_line == null && end_line == null) {
|
|
109
125
|
return path;
|
|
110
126
|
}
|
|
111
127
|
const start = start_line ?? 1;
|
|
@@ -113,63 +129,6 @@ function formatReadFileQuery(request: ReadFileRequest): string {
|
|
|
113
129
|
return `${path}:${start}-${end}`;
|
|
114
130
|
}
|
|
115
131
|
|
|
116
|
-
const APPLY_PATCH_TOOL_DESC = `This is a custom utility that makes it more convenient to add, remove, move, or edit code in a single file. \`apply_patch\` effectively allows you to execute a diff/patch against a file, but the format of the diff specification is unique to this task, so pay careful attention to these instructions. To use the \`apply_patch\` command, you should pass a message of the following structure as "input":
|
|
117
|
-
|
|
118
|
-
%%bash
|
|
119
|
-
apply_patch <<"EOF"
|
|
120
|
-
*** Begin Patch
|
|
121
|
-
[YOUR_PATCH]
|
|
122
|
-
*** End Patch
|
|
123
|
-
EOF
|
|
124
|
-
|
|
125
|
-
Where [YOUR_PATCH] is the actual content of your patch, specified in the following V4A diff format.
|
|
126
|
-
|
|
127
|
-
*** [ACTION] File: [path/to/file] -> ACTION can be one of Add, Update, or Delete.
|
|
128
|
-
|
|
129
|
-
In a Add File section, every line of the new file (including blank/empty lines) MUST start with a \`+\` prefix. Do not include any unprefixed lines inside an Add section
|
|
130
|
-
In a Update/Delete section, repeat the following for each snippet of code that needs to be changed:
|
|
131
|
-
[context_before] -> See below for further instructions on context.
|
|
132
|
-
- [old_code] -> Precede the old code with a minus sign.
|
|
133
|
-
+ [new_code] -> Precede the new, replacement code with a plus sign.
|
|
134
|
-
[context_after] -> See below for further instructions on context.
|
|
135
|
-
|
|
136
|
-
For instructions on [context_before] and [context_after]:
|
|
137
|
-
- By default, show 3 lines of code immediately above and 3 lines immediately below each change. If a change is within 3 lines of a previous change, do NOT duplicate the first change’s [context_after] lines in the second change’s [context_before] lines.
|
|
138
|
-
- If 3 lines of context is insufficient to uniquely identify the snippet of code within the file, use the @@ operator to indicate the class or function to which the snippet belongs. For instance, we might have:
|
|
139
|
-
@@ class BaseClass
|
|
140
|
-
[3 lines of pre-context]
|
|
141
|
-
- [old_code]
|
|
142
|
-
+ [new_code]
|
|
143
|
-
[3 lines of post-context]
|
|
144
|
-
|
|
145
|
-
- If a code block is repeated so many times in a class or function such that even a single @@ statement and 3 lines of context cannot uniquely identify the snippet of code, you can use multiple \`@@\` statements to jump to the right context. For instance:
|
|
146
|
-
|
|
147
|
-
@@ class BaseClass
|
|
148
|
-
@@ def method():
|
|
149
|
-
[3 lines of pre-context]
|
|
150
|
-
- [old_code]
|
|
151
|
-
+ [new_code]
|
|
152
|
-
[3 lines of post-context]
|
|
153
|
-
|
|
154
|
-
Note, then, that we do not use line numbers in this diff format, as the context is enough to uniquely identify code. An example of a message that you might pass as "input" to this function, in order to apply a patch, is shown below.
|
|
155
|
-
|
|
156
|
-
%%bash
|
|
157
|
-
apply_patch <<"EOF"
|
|
158
|
-
*** Begin Patch
|
|
159
|
-
*** Update File: pygorithm/searching/binary_search.py
|
|
160
|
-
@@ class BaseClass
|
|
161
|
-
@@ def search():
|
|
162
|
-
- pass
|
|
163
|
-
+ raise NotImplementedError()
|
|
164
|
-
|
|
165
|
-
@@ class Subclass
|
|
166
|
-
@@ def search():
|
|
167
|
-
- pass
|
|
168
|
-
+ raise NotImplementedError()
|
|
169
|
-
|
|
170
|
-
*** End Patch
|
|
171
|
-
EOF`;
|
|
172
|
-
|
|
173
132
|
// =============================================================================
|
|
174
133
|
// Tool Factory Functions
|
|
175
134
|
// =============================================================================
|
|
@@ -404,6 +363,63 @@ export function createWebFetchTool(
|
|
|
404
363
|
});
|
|
405
364
|
}
|
|
406
365
|
|
|
366
|
+
const APPLY_PATCH_TOOL_DESC = `This is a custom utility that makes it more convenient to add, remove, move, or edit code in a single file. \`apply_patch\` effectively allows you to execute a diff/patch against a file, but the format of the diff specification is unique to this task, so pay careful attention to these instructions. To use the \`apply_patch\` command, you should pass a message of the following structure as "input":
|
|
367
|
+
|
|
368
|
+
%%bash
|
|
369
|
+
apply_patch <<"EOF"
|
|
370
|
+
*** Begin Patch
|
|
371
|
+
[YOUR_PATCH]
|
|
372
|
+
*** End Patch
|
|
373
|
+
EOF
|
|
374
|
+
|
|
375
|
+
Where [YOUR_PATCH] is the actual content of your patch, specified in the following V4A diff format.
|
|
376
|
+
|
|
377
|
+
*** [ACTION] File: [path/to/file] -> ACTION can be one of Add, Update, or Delete.
|
|
378
|
+
|
|
379
|
+
In a Add File section, every line of the new file (including blank/empty lines) MUST start with a \`+\` prefix. Do not include any unprefixed lines inside an Add section
|
|
380
|
+
In a Update/Delete section, repeat the following for each snippet of code that needs to be changed:
|
|
381
|
+
[context_before] -> See below for further instructions on context.
|
|
382
|
+
- [old_code] -> Precede the old code with a minus sign.
|
|
383
|
+
+ [new_code] -> Precede the new, replacement code with a plus sign.
|
|
384
|
+
[context_after] -> See below for further instructions on context.
|
|
385
|
+
|
|
386
|
+
For instructions on [context_before] and [context_after]:
|
|
387
|
+
- By default, show 3 lines of code immediately above and 3 lines immediately below each change. If a change is within 3 lines of a previous change, do NOT duplicate the first change’s [context_after] lines in the second change’s [context_before] lines.
|
|
388
|
+
- If 3 lines of context is insufficient to uniquely identify the snippet of code within the file, use the @@ operator to indicate the class or function to which the snippet belongs. For instance, we might have:
|
|
389
|
+
@@ class BaseClass
|
|
390
|
+
[3 lines of pre-context]
|
|
391
|
+
- [old_code]
|
|
392
|
+
+ [new_code]
|
|
393
|
+
[3 lines of post-context]
|
|
394
|
+
|
|
395
|
+
- If a code block is repeated so many times in a class or function such that even a single @@ statement and 3 lines of context cannot uniquely identify the snippet of code, you can use multiple \`@@\` statements to jump to the right context. For instance:
|
|
396
|
+
|
|
397
|
+
@@ class BaseClass
|
|
398
|
+
@@ def method():
|
|
399
|
+
[3 lines of pre-context]
|
|
400
|
+
- [old_code]
|
|
401
|
+
+ [new_code]
|
|
402
|
+
[3 lines of post-context]
|
|
403
|
+
|
|
404
|
+
Note, then, that we do not use line numbers in this diff format, as the context is enough to uniquely identify code. An example of a message that you might pass as "input" to this function, in order to apply a patch, is shown below.
|
|
405
|
+
|
|
406
|
+
%%bash
|
|
407
|
+
apply_patch <<"EOF"
|
|
408
|
+
*** Begin Patch
|
|
409
|
+
*** Update File: pygorithm/searching/binary_search.py
|
|
410
|
+
@@ class BaseClass
|
|
411
|
+
@@ def search():
|
|
412
|
+
- pass
|
|
413
|
+
+ raise NotImplementedError()
|
|
414
|
+
|
|
415
|
+
@@ class Subclass
|
|
416
|
+
@@ def search():
|
|
417
|
+
- pass
|
|
418
|
+
+ raise NotImplementedError()
|
|
419
|
+
|
|
420
|
+
*** End Patch
|
|
421
|
+
EOF`;
|
|
422
|
+
|
|
407
423
|
/**
|
|
408
424
|
* Create the apply_patch tool
|
|
409
425
|
*
|
|
@@ -470,7 +486,9 @@ export function createEditorTool(
|
|
|
470
486
|
description:
|
|
471
487
|
"An editor for controlled filesystem edits on the text file at the provided path. " +
|
|
472
488
|
"Provide `insert_line` to insert `new_text` at a specific line number. " +
|
|
473
|
-
"Otherwise, the tool replaces `old_text` with `new_text`, or creates the file with `new_text` if
|
|
489
|
+
"Otherwise, the tool replaces `old_text` with `new_text`, or creates the file with `new_text` if file does not exist. " +
|
|
490
|
+
"Use this tools for making small, precise edits to existing files or creating new files over shell commands.",
|
|
491
|
+
|
|
474
492
|
inputSchema: zodToJsonSchema(EditFileInputSchema),
|
|
475
493
|
timeoutMs,
|
|
476
494
|
retryable: false, // Editing operations are stateful and should not auto-retry
|
|
@@ -478,6 +496,16 @@ export function createEditorTool(
|
|
|
478
496
|
execute: async (input, context) => {
|
|
479
497
|
const validatedInput = validateWithZod(EditFileInputSchema, input);
|
|
480
498
|
const operation = validatedInput.insert_line == null ? "edit" : "insert";
|
|
499
|
+
const sizeError = getEditorSizeError(validatedInput);
|
|
500
|
+
|
|
501
|
+
if (sizeError) {
|
|
502
|
+
return {
|
|
503
|
+
query: `${operation}:${validatedInput.path}`,
|
|
504
|
+
result: "",
|
|
505
|
+
error: sizeError,
|
|
506
|
+
success: false,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
481
509
|
|
|
482
510
|
try {
|
|
483
511
|
const result = await withTimeout(
|
|
@@ -510,21 +538,22 @@ export function createEditorTool(
|
|
|
510
538
|
* Invokes a configured skill by name and optional arguments.
|
|
511
539
|
*/
|
|
512
540
|
export function createSkillsTool(
|
|
513
|
-
executor:
|
|
541
|
+
executor: SkillsExecutorWithMetadata,
|
|
514
542
|
config: Pick<DefaultToolsConfig, "skillsTimeoutMs"> = {},
|
|
515
543
|
): Tool<SkillsInput, string> {
|
|
516
544
|
const timeoutMs = config.skillsTimeoutMs ?? 15000;
|
|
517
545
|
|
|
518
|
-
|
|
546
|
+
const baseDescription =
|
|
547
|
+
"Execute a skill within the main conversation. " +
|
|
548
|
+
"When users ask you to perform tasks, check if any available skills match. " +
|
|
549
|
+
"When users reference a slash command, invoke it with this tool. " +
|
|
550
|
+
'Input: `skill` (required) and optional `args`. Example: `skill: "pdf"`, `skill: "commit", args: "-m \\"Fix bug\\""`, `skill: "review-pr", args: "123"`, `skill: "ms-office-suite:pdf"`. ' +
|
|
551
|
+
"When a skill matches the user's request, invoking this tool is a blocking requirement before any other response. " +
|
|
552
|
+
"Never mention a skill without invoking this tool.";
|
|
553
|
+
|
|
554
|
+
const tool = createTool<SkillsInput, string>({
|
|
519
555
|
name: "skills",
|
|
520
|
-
description:
|
|
521
|
-
"Execute a skill within the main conversation. " +
|
|
522
|
-
"When users ask you to perform tasks, check if any available skills match. " +
|
|
523
|
-
'When users reference a slash command (for example "/commit" or "/review-pr"), invoke this tool. ' +
|
|
524
|
-
'Input: `skill` (required) and optional `args`. Example: `skill: "pdf"`, `skill: "commit", args: "-m \\"Fix bug\\""`, `skill: "review-pr", args: "123"`, `skill: "ms-office-suite:pdf"`. ' +
|
|
525
|
-
"Available skills are listed in system-reminder messages in the conversation. " +
|
|
526
|
-
"When a skill matches the user's request, invoking this tool is a blocking requirement before any other response. " +
|
|
527
|
-
"Never mention a skill without invoking this tool.",
|
|
556
|
+
description: baseDescription,
|
|
528
557
|
inputSchema: zodToJsonSchema(SkillsInputSchema),
|
|
529
558
|
timeoutMs,
|
|
530
559
|
retryable: false,
|
|
@@ -542,6 +571,22 @@ export function createSkillsTool(
|
|
|
542
571
|
);
|
|
543
572
|
},
|
|
544
573
|
});
|
|
574
|
+
|
|
575
|
+
Object.defineProperty(tool, "description", {
|
|
576
|
+
get() {
|
|
577
|
+
const skills = executor.configuredSkills
|
|
578
|
+
?.filter((s) => !s.disabled)
|
|
579
|
+
.map((s) => s.name);
|
|
580
|
+
if (skills && skills.length > 0) {
|
|
581
|
+
return `${baseDescription} Available skills: ${skills.join(", ")}.`;
|
|
582
|
+
}
|
|
583
|
+
return baseDescription;
|
|
584
|
+
},
|
|
585
|
+
enumerable: true,
|
|
586
|
+
configurable: true,
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
return tool;
|
|
545
590
|
}
|
|
546
591
|
|
|
547
592
|
/**
|
|
@@ -11,10 +11,10 @@ describe("default tool presets", () => {
|
|
|
11
11
|
expect(ToolPresets.development.enableAskQuestion).toBe(true);
|
|
12
12
|
expect(ToolPresets.readonly.enableAskQuestion).toBe(true);
|
|
13
13
|
expect(ToolPresets.minimal.enableAskQuestion).toBe(true);
|
|
14
|
-
expect(ToolPresets.yolo.enableAskQuestion).toBe(
|
|
14
|
+
expect(ToolPresets.yolo.enableAskQuestion).toBe(false);
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
-
it("yolo preset
|
|
17
|
+
it("yolo preset excludes ask_question even when its executor exists", () => {
|
|
18
18
|
const tools = createDefaultToolsWithPreset("yolo", {
|
|
19
19
|
executors: {
|
|
20
20
|
readFile: async () => "ok",
|
|
@@ -35,7 +35,6 @@ describe("default tool presets", () => {
|
|
|
35
35
|
"fetch_web_content",
|
|
36
36
|
"editor",
|
|
37
37
|
"skills",
|
|
38
|
-
"ask_question",
|
|
39
38
|
]);
|
|
40
39
|
});
|
|
41
40
|
});
|
package/src/tools/presets.ts
CHANGED
|
@@ -88,7 +88,7 @@ export const ToolPresets = {
|
|
|
88
88
|
},
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
|
-
* YOLO mode (
|
|
91
|
+
* YOLO mode (automation-focused tools + no approval required)
|
|
92
92
|
* Good for trusted local automation workflows.
|
|
93
93
|
*/
|
|
94
94
|
yolo: {
|
|
@@ -99,7 +99,7 @@ export const ToolPresets = {
|
|
|
99
99
|
enableApplyPatch: false,
|
|
100
100
|
enableEditor: true,
|
|
101
101
|
enableSkills: true,
|
|
102
|
-
enableAskQuestion:
|
|
102
|
+
enableAskQuestion: false,
|
|
103
103
|
},
|
|
104
104
|
} as const satisfies Record<string, DefaultToolsConfig>;
|
|
105
105
|
|
|
@@ -115,7 +115,7 @@ export type ToolPolicyPresetName = "default" | "yolo";
|
|
|
115
115
|
|
|
116
116
|
/**
|
|
117
117
|
* Build tool policies for a preset.
|
|
118
|
-
* `yolo` guarantees
|
|
118
|
+
* `yolo` guarantees tool policies are enabled and auto-approved.
|
|
119
119
|
*/
|
|
120
120
|
export function createToolPoliciesWithPreset(
|
|
121
121
|
presetName: ToolPolicyPresetName,
|