@mikode13/harness 11.0.1 → 11.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +186 -16
- package/dist/agent/domain/agent.d.ts +41 -3
- package/dist/agent/domain/agent.js.map +1 -1
- package/dist/agent/domain/approval.d.ts +20 -0
- package/dist/agent/domain/approval.js.map +1 -1
- package/dist/agent/domain/inbox.d.ts +42 -0
- package/dist/agent/domain/inbox.js +81 -0
- package/dist/agent/domain/inbox.js.map +1 -0
- package/dist/agent/domain/oneRunAtATime.d.ts +17 -0
- package/dist/agent/domain/oneRunAtATime.js +32 -0
- package/dist/agent/domain/oneRunAtATime.js.map +1 -0
- package/dist/agent/domain/runContext.d.ts +9 -0
- package/dist/agent/domain/runContext.js +5 -0
- package/dist/agent/domain/runContext.js.map +1 -1
- package/dist/agent/domain/session.d.ts +15 -0
- package/dist/agent/domain/session.js +80 -0
- package/dist/agent/domain/session.js.map +1 -0
- package/dist/engines/claude/infrastructure/model/claudeAgent.d.ts +6 -2
- package/dist/engines/claude/infrastructure/model/claudeAgent.js +20 -5
- package/dist/engines/claude/infrastructure/model/claudeAgent.js.map +1 -1
- package/dist/engines/codex/infrastructure/model/codexAgent.d.ts +5 -1
- package/dist/engines/codex/infrastructure/model/codexAgent.js +13 -2
- package/dist/engines/codex/infrastructure/model/codexAgent.js.map +1 -1
- package/dist/engines/domain/model/backgroundAgents.d.ts +5 -2
- package/dist/engines/domain/model/backgroundAgents.js +11 -3
- package/dist/engines/domain/model/backgroundAgents.js.map +1 -1
- package/dist/engines/domain/model/compactor.d.ts +40 -0
- package/dist/engines/domain/model/compactor.js +5 -0
- package/dist/engines/domain/model/compactor.js.map +1 -0
- package/dist/engines/domain/model/llmAgent.d.ts +53 -2
- package/dist/engines/domain/model/llmAgent.js +419 -24
- package/dist/engines/domain/model/llmAgent.js.map +1 -1
- package/dist/engines/domain/model/recentCompaction.d.ts +39 -0
- package/dist/engines/domain/model/recentCompaction.js +119 -0
- package/dist/engines/domain/model/recentCompaction.js.map +1 -0
- package/dist/engines/domain/model/stepContext.js +3 -0
- package/dist/engines/domain/model/stepContext.js.map +1 -1
- package/dist/engines/domain/model/triagedCompaction.d.ts +27 -0
- package/dist/engines/domain/model/triagedCompaction.js +60 -0
- package/dist/engines/domain/model/triagedCompaction.js.map +1 -0
- package/dist/factory/infrastructure/agentLLMFactory.d.ts +56 -15
- package/dist/factory/infrastructure/agentLLMFactory.js +144 -25
- package/dist/factory/infrastructure/agentLLMFactory.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/llm/domain/contextTriage.d.ts +26 -0
- package/dist/llm/domain/contextTriage.js +3 -0
- package/dist/llm/domain/contextTriage.js.map +1 -0
- package/dist/llm/domain/llm.d.ts +18 -1
- package/dist/llm/domain/llm.js.map +1 -1
- package/dist/llm/domain/message.d.ts +18 -1
- package/dist/llm/domain/message.js +20 -0
- package/dist/llm/domain/message.js.map +1 -1
- package/dist/llm/infrastructure/claudeLLMClient.d.ts +4 -5
- package/dist/llm/infrastructure/claudeLLMClient.js +39 -13
- package/dist/llm/infrastructure/claudeLLMClient.js.map +1 -1
- package/dist/llm/infrastructure/jevContextTriage.d.ts +18 -0
- package/dist/llm/infrastructure/jevContextTriage.js +92 -0
- package/dist/llm/infrastructure/jevContextTriage.js.map +1 -0
- package/dist/llm/infrastructure/openAILLMClient.d.ts +4 -5
- package/dist/llm/infrastructure/openAILLMClient.js +20 -9
- package/dist/llm/infrastructure/openAILLMClient.js.map +1 -1
- package/dist/orchestration/domain/model/orchestratorAgent.d.ts +9 -1
- package/dist/orchestration/domain/model/orchestratorAgent.js +20 -3
- package/dist/orchestration/domain/model/orchestratorAgent.js.map +1 -1
- package/dist/recovery/domain/history.d.ts +10 -3
- package/dist/recovery/domain/history.js +1 -0
- package/dist/recovery/domain/history.js.map +1 -1
- package/dist/recovery/domain/recoveryStore.d.ts +14 -2
- package/dist/recovery/domain/recoveryStore.js.map +1 -1
- package/dist/recovery/infrastructure/fileRecoveryStore.d.ts +21 -2
- package/dist/recovery/infrastructure/fileRecoveryStore.js +58 -6
- package/dist/recovery/infrastructure/fileRecoveryStore.js.map +1 -1
- package/dist/recovery/infrastructure/folderClaims.d.ts +27 -0
- package/dist/recovery/infrastructure/folderClaims.js +124 -0
- package/dist/recovery/infrastructure/folderClaims.js.map +1 -0
- package/dist/shared/domain/session.d.ts +12 -0
- package/dist/shared/domain/session.js +2 -0
- package/dist/shared/domain/session.js.map +1 -0
- package/dist/tools/domain/preparedCall.d.ts +5 -2
- package/dist/tools/domain/preparedCall.js +7 -1
- package/dist/tools/domain/preparedCall.js.map +1 -1
- package/dist/tools/domain/readRegistry.d.ts +11 -0
- package/dist/tools/domain/readRegistry.js.map +1 -1
- package/dist/tools/infrastructure/agentAsTool.d.ts +6 -0
- package/dist/tools/infrastructure/agentAsTool.js +9 -0
- package/dist/tools/infrastructure/agentAsTool.js.map +1 -1
- package/dist/tools/infrastructure/commandTool.js +6 -1
- package/dist/tools/infrastructure/commandTool.js.map +1 -1
- package/dist/tools/infrastructure/exploreTool.js +2 -0
- package/dist/tools/infrastructure/exploreTool.js.map +1 -1
- package/dist/tools/infrastructure/fileTools.d.ts +2 -0
- package/dist/tools/infrastructure/fileTools.js +10 -1
- package/dist/tools/infrastructure/fileTools.js.map +1 -1
- package/dist/tools/infrastructure/removeWorktreeTool.d.ts +23 -0
- package/dist/tools/infrastructure/removeWorktreeTool.js +178 -0
- package/dist/tools/infrastructure/removeWorktreeTool.js.map +1 -0
- package/dist/tools/infrastructure/subagents.d.ts +3 -1
- package/dist/tools/infrastructure/subagents.js +2 -1
- package/dist/tools/infrastructure/subagents.js.map +1 -1
- package/dist/tools/infrastructure/trackedEdits.js +1 -0
- package/dist/tools/infrastructure/trackedEdits.js.map +1 -1
- package/dist/tools/infrastructure/workspaceWrites.d.ts +17 -1
- package/dist/tools/infrastructure/workspaceWrites.js +48 -17
- package/dist/tools/infrastructure/workspaceWrites.js.map +1 -1
- package/dist/tools/infrastructure/worktrees.d.ts +49 -0
- package/dist/tools/infrastructure/worktrees.js +154 -0
- package/dist/tools/infrastructure/worktrees.js.map +1 -0
- package/dist/tools/infrastructure/writeCeiling.d.ts +16 -0
- package/dist/tools/infrastructure/writeCeiling.js +49 -0
- package/dist/tools/infrastructure/writeCeiling.js.map +1 -0
- package/dist/tools/infrastructure/writeSession.d.ts +5 -1
- package/dist/tools/infrastructure/writeSession.js +7 -3
- package/dist/tools/infrastructure/writeSession.js.map +1 -1
- package/dist/workflow/domain/workflow.d.ts +3 -0
- package/dist/workflow/domain/workflow.js.map +1 -1
- package/dist/workflow/domain/workflowRunner.js +8 -2
- package/dist/workflow/domain/workflowRunner.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { mkdir, readdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join, sep } from 'node:path';
|
|
4
|
+
import { WorkspaceBusyError } from '#src/recovery/domain/recoveryStore';
|
|
5
|
+
// The claims this process holds, so one left by a run of this process that ended is seen as gone.
|
|
6
|
+
const claimedHere = new Set();
|
|
7
|
+
/** A claim as read from disk. Throws for one that is not a claim: it may still be live. */
|
|
8
|
+
function parseClaim(name, text) {
|
|
9
|
+
try {
|
|
10
|
+
const { pid, folders } = JSON.parse(text);
|
|
11
|
+
if (Number.isInteger(pid) &&
|
|
12
|
+
Array.isArray(folders) &&
|
|
13
|
+
folders.every(folder => typeof folder === 'string')) {
|
|
14
|
+
return { pid: pid, folders };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Not JSON: refused below like any other unreadable claim.
|
|
19
|
+
}
|
|
20
|
+
throw new Error(`The claim ${name} cannot be read, so whether it is live cannot be told`);
|
|
21
|
+
}
|
|
22
|
+
function overlap(a, b) {
|
|
23
|
+
return a === b || a.startsWith(`${b}${sep}`) || b.startsWith(`${a}${sep}`);
|
|
24
|
+
}
|
|
25
|
+
/** Whether a process still exists. One we may not signal exists too. */
|
|
26
|
+
function isAlive(pid) {
|
|
27
|
+
try {
|
|
28
|
+
process.kill(pid, 0);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
return error.code === 'EPERM';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Who works in which folders, across every history kept in one state directory, so a folder is
|
|
37
|
+
* removed only while no run writes in it, and no run starts writing in a folder being removed.
|
|
38
|
+
* A run, or a move, claims the folders it may write while it holds its workspace; a removal
|
|
39
|
+
* claims the folder while it removes it. Each writes its own claim before it reads the others',
|
|
40
|
+
* so of two that start at once at least one sees the other, and gives way.
|
|
41
|
+
*/
|
|
42
|
+
export class FolderClaims {
|
|
43
|
+
directory;
|
|
44
|
+
/** `stateDirectory` is the real path of the state directory. */
|
|
45
|
+
constructor(stateDirectory) {
|
|
46
|
+
this.directory = join(stateDirectory, 'claims');
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Claims `folders` for writing, and returns what releases the claim.
|
|
50
|
+
*
|
|
51
|
+
* @throws {WorkspaceBusyError} when a removal in progress covers one of them.
|
|
52
|
+
*/
|
|
53
|
+
claimWriting(id, folders) {
|
|
54
|
+
return this.claim('writing', id, folders, 'removal');
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Claims `folder` for its removal, and returns what releases the claim.
|
|
58
|
+
*
|
|
59
|
+
* @throws {WorkspaceBusyError} when a run, or a move, may write in it.
|
|
60
|
+
*/
|
|
61
|
+
claimRemoval(folder) {
|
|
62
|
+
return this.claim('removal', `removal-${randomBytes(6).toString('hex')}`, [folder], 'writing');
|
|
63
|
+
}
|
|
64
|
+
async claim(kind, id, folders, against) {
|
|
65
|
+
await mkdir(this.directory, { recursive: true, mode: 0o700 });
|
|
66
|
+
const name = `${kind}-${id}.json`;
|
|
67
|
+
const path = join(this.directory, name);
|
|
68
|
+
const claim = { pid: process.pid, folders: [...folders] };
|
|
69
|
+
// Whole before it appears, so a claim another process reads is never half written.
|
|
70
|
+
const written = `${path}.${randomBytes(4).toString('hex')}.tmp`;
|
|
71
|
+
await writeFile(written, JSON.stringify(claim), { mode: 0o600 });
|
|
72
|
+
claimedHere.add(name);
|
|
73
|
+
await rename(written, path);
|
|
74
|
+
const release = async () => {
|
|
75
|
+
claimedHere.delete(name);
|
|
76
|
+
await unlink(path).catch((error) => {
|
|
77
|
+
if (error.code !== 'ENOENT')
|
|
78
|
+
throw error;
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
let holder;
|
|
82
|
+
try {
|
|
83
|
+
holder = await this.liveHolder(against, folders);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
// Published, so it must go however reading the others failed, or it would hold the folders
|
|
87
|
+
// until this process ends. The failure is what the caller needs to see.
|
|
88
|
+
await release().catch(() => undefined);
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
if (holder !== undefined) {
|
|
92
|
+
await release();
|
|
93
|
+
throw new WorkspaceBusyError(holder);
|
|
94
|
+
}
|
|
95
|
+
return release;
|
|
96
|
+
}
|
|
97
|
+
/** A live claim of `kind` covering one of `folders`, by its name, if any. */
|
|
98
|
+
async liveHolder(kind, folders) {
|
|
99
|
+
for (const name of await readdir(this.directory)) {
|
|
100
|
+
if (!name.startsWith(`${kind}-`) || !name.endsWith('.json'))
|
|
101
|
+
continue;
|
|
102
|
+
const text = await readFile(join(this.directory, name), 'utf8').catch((error) => {
|
|
103
|
+
// Released meanwhile. Any other failure leaves a claim unread, which may be live.
|
|
104
|
+
if (error.code === 'ENOENT')
|
|
105
|
+
return undefined;
|
|
106
|
+
throw error;
|
|
107
|
+
});
|
|
108
|
+
if (text === undefined)
|
|
109
|
+
continue;
|
|
110
|
+
const claim = parseClaim(name, text);
|
|
111
|
+
const live = claim.pid === process.pid ? claimedHere.has(name) : isAlive(claim.pid);
|
|
112
|
+
if (!live) {
|
|
113
|
+
// Left by a run whose process died, or that ended without releasing it.
|
|
114
|
+
await unlink(join(this.directory, name)).catch(() => undefined);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (claim.folders.some(claimed => folders.some(folder => overlap(claimed, folder)))) {
|
|
118
|
+
return name.slice(0, -'.json'.length);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=folderClaims.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"folderClaims.js","sourceRoot":"","sources":["../../../src/recovery/infrastructure/folderClaims.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { mkdir, readdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';\nimport { join, sep } from 'node:path';\nimport { WorkspaceBusyError } from '#src/recovery/domain/recoveryStore';\n\n/** One claim on disk: who made it, and the folders it covers, as real paths. */\ninterface Claim {\n\tpid: number;\n\tfolders: string[];\n}\n\n// The claims this process holds, so one left by a run of this process that ended is seen as gone.\nconst claimedHere = new Set<string>();\n\n/** A claim as read from disk. Throws for one that is not a claim: it may still be live. */\nfunction parseClaim(name: string, text: string): Claim {\n\ttry {\n\t\tconst { pid, folders } = JSON.parse(text) as { pid?: unknown; folders?: unknown };\n\t\tif (\n\t\t\tNumber.isInteger(pid) &&\n\t\t\tArray.isArray(folders) &&\n\t\t\tfolders.every(folder => typeof folder === 'string')\n\t\t) {\n\t\t\treturn { pid: pid as number, folders };\n\t\t}\n\t} catch {\n\t\t// Not JSON: refused below like any other unreadable claim.\n\t}\n\tthrow new Error(`The claim ${name} cannot be read, so whether it is live cannot be told`);\n}\n\nfunction overlap(a: string, b: string): boolean {\n\treturn a === b || a.startsWith(`${b}${sep}`) || b.startsWith(`${a}${sep}`);\n}\n\n/** Whether a process still exists. One we may not signal exists too. */\nfunction isAlive(pid: number): boolean {\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (error) {\n\t\treturn (error as NodeJS.ErrnoException).code === 'EPERM';\n\t}\n}\n\n/**\n * Who works in which folders, across every history kept in one state directory, so a folder is\n * removed only while no run writes in it, and no run starts writing in a folder being removed.\n * A run, or a move, claims the folders it may write while it holds its workspace; a removal\n * claims the folder while it removes it. Each writes its own claim before it reads the others',\n * so of two that start at once at least one sees the other, and gives way.\n */\nexport class FolderClaims {\n\tprivate readonly directory: string;\n\n\t/** `stateDirectory` is the real path of the state directory. */\n\tconstructor(stateDirectory: string) {\n\t\tthis.directory = join(stateDirectory, 'claims');\n\t}\n\n\t/**\n\t * Claims `folders` for writing, and returns what releases the claim.\n\t *\n\t * @throws {WorkspaceBusyError} when a removal in progress covers one of them.\n\t */\n\tclaimWriting(id: string, folders: readonly string[]): Promise<() => Promise<void>> {\n\t\treturn this.claim('writing', id, folders, 'removal');\n\t}\n\n\t/**\n\t * Claims `folder` for its removal, and returns what releases the claim.\n\t *\n\t * @throws {WorkspaceBusyError} when a run, or a move, may write in it.\n\t */\n\tclaimRemoval(folder: string): Promise<() => Promise<void>> {\n\t\treturn this.claim('removal', `removal-${randomBytes(6).toString('hex')}`, [folder], 'writing');\n\t}\n\n\tprivate async claim(\n\t\tkind: 'writing' | 'removal',\n\t\tid: string,\n\t\tfolders: readonly string[],\n\t\tagainst: 'writing' | 'removal',\n\t): Promise<() => Promise<void>> {\n\t\tawait mkdir(this.directory, { recursive: true, mode: 0o700 });\n\t\tconst name = `${kind}-${id}.json`;\n\t\tconst path = join(this.directory, name);\n\t\tconst claim: Claim = { pid: process.pid, folders: [...folders] };\n\t\t// Whole before it appears, so a claim another process reads is never half written.\n\t\tconst written = `${path}.${randomBytes(4).toString('hex')}.tmp`;\n\t\tawait writeFile(written, JSON.stringify(claim), { mode: 0o600 });\n\t\tclaimedHere.add(name);\n\t\tawait rename(written, path);\n\t\tconst release = async () => {\n\t\t\tclaimedHere.delete(name);\n\t\t\tawait unlink(path).catch((error: unknown) => {\n\t\t\t\tif ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n\t\t\t});\n\t\t};\n\n\t\tlet holder: string | undefined;\n\t\ttry {\n\t\t\tholder = await this.liveHolder(against, folders);\n\t\t} catch (error) {\n\t\t\t// Published, so it must go however reading the others failed, or it would hold the folders\n\t\t\t// until this process ends. The failure is what the caller needs to see.\n\t\t\tawait release().catch(() => undefined);\n\t\t\tthrow error;\n\t\t}\n\t\tif (holder !== undefined) {\n\t\t\tawait release();\n\t\t\tthrow new WorkspaceBusyError(holder);\n\t\t}\n\t\treturn release;\n\t}\n\n\t/** A live claim of `kind` covering one of `folders`, by its name, if any. */\n\tprivate async liveHolder(\n\t\tkind: 'writing' | 'removal',\n\t\tfolders: readonly string[],\n\t): Promise<string | undefined> {\n\t\tfor (const name of await readdir(this.directory)) {\n\t\t\tif (!name.startsWith(`${kind}-`) || !name.endsWith('.json')) continue;\n\t\t\tconst text = await readFile(join(this.directory, name), 'utf8').catch((error: unknown) => {\n\t\t\t\t// Released meanwhile. Any other failure leaves a claim unread, which may be live.\n\t\t\t\tif ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n\t\t\t\tthrow error;\n\t\t\t});\n\t\t\tif (text === undefined) continue;\n\t\t\tconst claim = parseClaim(name, text);\n\t\t\tconst live = claim.pid === process.pid ? claimedHere.has(name) : isAlive(claim.pid);\n\t\t\tif (!live) {\n\t\t\t\t// Left by a run whose process died, or that ended without releasing it.\n\t\t\t\tawait unlink(join(this.directory, name)).catch(() => undefined);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (claim.folders.some(claimed => folders.some(folder => overlap(claimed, folder)))) {\n\t\t\t\treturn name.slice(0, -'.json'.length);\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One conversation with an agent, as the consumer knows it: the API's session, a TUI's chat.
|
|
3
|
+
* Each run that writes records it in the workspace's history, so a host can tell which session
|
|
4
|
+
* the change it is about to undo came from. Not a provider's session, which stays inside its
|
|
5
|
+
* engine.
|
|
6
|
+
*/
|
|
7
|
+
export interface Session {
|
|
8
|
+
/** What tells it apart. Stays the same across the session's runs. */
|
|
9
|
+
id: string;
|
|
10
|
+
/** What a host shows for it. Without one, or with a blank one, a host shows its `id`. */
|
|
11
|
+
name?: string;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/shared/domain/session.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * One conversation with an agent, as the consumer knows it: the API's session, a TUI's chat.\n * Each run that writes records it in the workspace's history, so a host can tell which session\n * the change it is about to undo came from. Not a provider's session, which stays inside its\n * engine.\n */\nexport interface Session {\n\t/** What tells it apart. Stays the same across the session's runs. */\n\tid: string;\n\t/** What a host shows for it. Without one, or with a blank one, a host shows its `id`. */\n\tname?: string;\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CommandLine, ToolRisk } from '#src/agent/domain/approval';
|
|
1
|
+
import type { CommandLine, ToolRisk, WorktreeRemoval } from '#src/agent/domain/approval';
|
|
2
2
|
import type { ToolDefinition } from '#src/llm/domain/tool';
|
|
3
3
|
import type { ToolCallContext } from './readRegistry.ts';
|
|
4
4
|
import type { Tool, ToolSummarizer } from './tool.ts';
|
|
@@ -33,6 +33,8 @@ export interface PreparedCall {
|
|
|
33
33
|
readonly diff?: string;
|
|
34
34
|
/** For a call that runs a configured command, what it will run, fixed when prepared. */
|
|
35
35
|
readonly command?: CommandLine;
|
|
36
|
+
/** For a call that removes a worktree, which one and what is lost with it, when prepared. */
|
|
37
|
+
readonly worktree?: WorktreeRemoval;
|
|
36
38
|
/**
|
|
37
39
|
* For a call that runs a configured command, how it ended once it has. `undefined` before it
|
|
38
40
|
* runs, and when it never did.
|
|
@@ -117,6 +119,7 @@ export type AgentTool = Tool | PreparingTool | DelegatingTool;
|
|
|
117
119
|
export declare function isDelegating(tool: AgentTool): tool is DelegatingTool;
|
|
118
120
|
/**
|
|
119
121
|
* Brings any tool into the one lifecycle of prepare, approve, run. A consumer's `Tool` has its
|
|
120
|
-
* risk judged now, and later runs on the input as the model sent it
|
|
122
|
+
* risk judged now, and later runs on the input as the model sent it; one that is not `safe` is
|
|
123
|
+
* refused for an agent started by an agent that only reads.
|
|
121
124
|
*/
|
|
122
125
|
export declare function prepareCall(tool: Tool | PreparingTool, input: unknown, signal: AbortSignal, call: ToolCallContext): Promise<PreparedCall>;
|
|
@@ -30,12 +30,18 @@ function isPreparing(tool) {
|
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
32
|
* Brings any tool into the one lifecycle of prepare, approve, run. A consumer's `Tool` has its
|
|
33
|
-
* risk judged now, and later runs on the input as the model sent it
|
|
33
|
+
* risk judged now, and later runs on the input as the model sent it; one that is not `safe` is
|
|
34
|
+
* refused for an agent started by an agent that only reads.
|
|
34
35
|
*/
|
|
35
36
|
export async function prepareCall(tool, input, signal, call) {
|
|
36
37
|
if (isPreparing(tool))
|
|
37
38
|
return tool.prepare(input, signal, call);
|
|
38
39
|
const risk = tool.risk(input);
|
|
40
|
+
// Where a consumer's tool writes is unknown, so under an agent that only reads, one that may
|
|
41
|
+
// write is refused whole. Under one that writes, it writes where the consumer let it.
|
|
42
|
+
if (risk !== 'safe' && call.writeCeiling?.length === 0) {
|
|
43
|
+
throw new Error(`Nothing was changed: the agent that handed you this task only reads, and ${tool.name} may change things. An agent another agent started never writes more than that agent may. Answer with what you found, and say what you would have changed.`);
|
|
44
|
+
}
|
|
39
45
|
return { risk, unrecorded: true, run: runSignal => tool.execute(input, runSignal) };
|
|
40
46
|
}
|
|
41
47
|
//# sourceMappingURL=preparedCall.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preparedCall.js","sourceRoot":"","sources":["../../../src/tools/domain/preparedCall.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"preparedCall.js","sourceRoot":"","sources":["../../../src/tools/domain/preparedCall.ts"],"names":[],"mappings":"AAiDA;;;;GAIG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IACnC,QAAQ,CAAqB;IAEtC,YAAY,OAAe,EAAE,EAAE,QAAQ,KAA4B,EAAE;QACpE,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC1B,CAAC;CACD;AAED;;;;GAIG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IACnC,OAAO,CAAU;IAE1B,YAAY,OAAgB;QAC3B,KAAK,CAAC,2CAA2C,CAAC,CAAC;QACnD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACxB,CAAC;CACD;AA4DD,MAAM,UAAU,YAAY,CAAC,IAAe;IAC3C,OAAO,UAAU,IAAI,IAAI,CAAC;AAC3B,CAAC;AAED,SAAS,WAAW,CAAC,IAAe;IACnC,OAAO,SAAS,IAAI,IAAI,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAChC,IAA0B,EAC1B,KAAc,EACd,MAAmB,EACnB,IAAqB;IAErB,IAAI,WAAW,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;IAEhE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9B,6FAA6F;IAC7F,sFAAsF;IACtF,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,YAAY,EAAE,MAAM,KAAK,CAAC,EAAE,CAAC;QACxD,MAAM,IAAI,KAAK,CACd,4EAA4E,IAAI,CAAC,IAAI,4JAA4J,CACjP,CAAC;IACH,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;AACrF,CAAC","sourcesContent":["import type { CommandLine, ToolRisk, WorktreeRemoval } from '#src/agent/domain/approval';\nimport type { ToolDefinition } from '#src/llm/domain/tool';\nimport type { ToolCallContext } from './readRegistry.ts';\nimport type { Tool, ToolSummarizer } from './tool.ts';\n\n/** How a configured command ended, for telling a command run again for nothing. */\nexport interface CommandEnding {\n\t/** It exited with code 0. */\n\tpassed: boolean;\n\t/** How it ended, such as \"exited with code 1\" or \"ran past its time limit\". */\n\thow: string;\n\t/** What it printed, or the end of it, without colors, as the user is shown it. */\n\toutput: string;\n\t/**\n\t * A hash of everything it printed, line by line, without colors or the times a known test\n\t * runner adds: what tells two endings apart.\n\t */\n\tfingerprint: string;\n\t/**\n\t * What the files the run changed held when it ended, as `RunJournal.contentState` gives it.\n\t * Missing when it changed something the history could not record.\n\t */\n\tstate?: string;\n\t/** The file of every change the run had applied by then, in order, as the model names it. */\n\tchanged: readonly string[];\n}\n\n/**\n * A tool call checked and fixed before anyone approves it: its risk is judged on what will\n * really happen, and `run` does exactly that, nothing worked out again.\n */\nexport interface PreparedCall {\n\treadonly risk: ToolRisk;\n\t/** For a change to a file, what it will change as a unified diff, fixed when prepared. */\n\treadonly diff?: string;\n\t/** For a call that runs a configured command, what it will run, fixed when prepared. */\n\treadonly command?: CommandLine;\n\t/** For a call that removes a worktree, which one and what is lost with it, when prepared. */\n\treadonly worktree?: WorktreeRemoval;\n\t/**\n\t * For a call that runs a configured command, how it ended once it has. `undefined` before it\n\t * runs, and when it never did.\n\t */\n\treadonly ending?: () => CommandEnding | undefined;\n\t/** A consumer's call: whatever it changes, the history does not record. */\n\treadonly unrecorded?: true;\n\trun(signal: AbortSignal): Promise<string>;\n}\n\n/**\n * A tool ran and produced a result the model should see as an error. Internal tools use this\n * instead of a plain error when the result also carries narrative metadata, such as a command's\n * non-zero exit code.\n */\nexport class ToolExecutionError extends Error {\n\treadonly exitCode: number | undefined;\n\n\tconstructor(message: string, { exitCode }: { exitCode?: number } = {}) {\n\t\tsuper(message);\n\t\tthis.exitCode = exitCode;\n\t}\n}\n\n/**\n * A failure inside a call that ends the run instead of the call, such as an approver that failed\n * while an agent the call started was asking it: host code failed, and the model must not carry\n * on as if a tool had. Internal.\n */\nexport class RunEndingToolError extends Error {\n\treadonly failure: unknown;\n\n\tconstructor(failure: unknown) {\n\t\tsuper('A failure inside a tool call ends the run');\n\t\tthis.failure = failure;\n\t}\n}\n\n/**\n * A tool built by the harness, such as a write tool, which prepares each call against the\n * state of the run before it is approved. Internal: consumers build a `Tool`.\n */\nexport interface PreparingTool extends ToolDefinition {\n\t/** Describes the model's input without exposing preparation details to the provider. */\n\tsummarize?: ToolSummarizer;\n\t/** Rejects with a message for the model when the call cannot run. */\n\tprepare(input: unknown, signal: AbortSignal, call: ToolCallContext): Promise<PreparedCall>;\n}\n\n/** What an agent a call handed a task to answered, for the model that sent it. */\nexport interface DelegatedAnswer {\n\treadonly text: string;\n\t/** It failed, and `text` says why. */\n\treadonly failed?: true;\n}\n\n/** An agent a call handed a task to, working in the background until it answers. */\nexport interface Delegated {\n\t/** Which agent it is, as its `subagent` events name it: `explorer`, or the tool's name. */\n\treadonly name: string;\n\t/** Its number among the agents of its call, from 1, as its `subagent` events carry it. */\n\treadonly subagent: number;\n\t/** The task it was given, as the model wrote it. */\n\treadonly task: string;\n\t/**\n\t * Its answer for the model that sent it, or why it failed. It rejects only when the run was\n\t * cancelled, or with a `RunEndingToolError`.\n\t */\n\treadonly answer: Promise<DelegatedAnswer>;\n}\n\n/**\n * A call that hands tasks to agents, checked before anyone approves it. Once approved, `start`\n * sends them and the call answers at once: they work in the background, and each one's answer\n * reaches the model when it finishes.\n */\nexport interface PreparedDelegation {\n\treadonly risk: ToolRisk;\n\t/** The agents may run a consumer's tools, whose changes the history does not record. */\n\treadonly unrecorded?: true;\n\tstart(signal: AbortSignal): readonly Delegated[];\n}\n\n/**\n * A tool built by the harness whose calls hand tasks to agents working in the background, such\n * as `explore`. Internal.\n */\nexport interface DelegatingTool extends ToolDefinition {\n\tsummarize?: ToolSummarizer;\n\t/** Rejects with a message for the model when the tasks cannot be handed over. */\n\tdelegate(input: unknown, signal: AbortSignal, call: ToolCallContext): Promise<PreparedDelegation>;\n}\n\n/** What an agent can run: a consumer's `Tool`, or one the harness built. */\nexport type AgentTool = Tool | PreparingTool | DelegatingTool;\n\nexport function isDelegating(tool: AgentTool): tool is DelegatingTool {\n\treturn 'delegate' in tool;\n}\n\nfunction isPreparing(tool: AgentTool): tool is PreparingTool {\n\treturn 'prepare' in tool;\n}\n\n/**\n * Brings any tool into the one lifecycle of prepare, approve, run. A consumer's `Tool` has its\n * risk judged now, and later runs on the input as the model sent it; one that is not `safe` is\n * refused for an agent started by an agent that only reads.\n */\nexport async function prepareCall(\n\ttool: Tool | PreparingTool,\n\tinput: unknown,\n\tsignal: AbortSignal,\n\tcall: ToolCallContext,\n): Promise<PreparedCall> {\n\tif (isPreparing(tool)) return tool.prepare(input, signal, call);\n\n\tconst risk = tool.risk(input);\n\t// Where a consumer's tool writes is unknown, so under an agent that only reads, one that may\n\t// write is refused whole. Under one that writes, it writes where the consumer let it.\n\tif (risk !== 'safe' && call.writeCeiling?.length === 0) {\n\t\tthrow new Error(\n\t\t\t`Nothing was changed: the agent that handed you this task only reads, and ${tool.name} may change things. An agent another agent started never writes more than that agent may. Answer with what you found, and say what you would have changed.`,\n\t\t);\n\t}\n\treturn { risk, unrecorded: true, run: runSignal => tool.execute(input, runSignal) };\n}\n"]}
|
|
@@ -65,4 +65,15 @@ export interface ToolCallContext {
|
|
|
65
65
|
readonly ask?: Asker;
|
|
66
66
|
/** How many delegations deep the agent that made the call runs: 0 for the top-level one. */
|
|
67
67
|
readonly depth: number;
|
|
68
|
+
/**
|
|
69
|
+
* The folders the call may write in, as real paths, set by the agents that delegated to the
|
|
70
|
+
* one that made it: empty when it may write nowhere, absent when none set a limit.
|
|
71
|
+
*/
|
|
72
|
+
readonly writeCeiling?: readonly string[];
|
|
73
|
+
/**
|
|
74
|
+
* The `write` roots of the workspace of the agent that made the call, as it was given them:
|
|
75
|
+
* an agent the call starts writes only within them. Empty when it only reads, absent when it
|
|
76
|
+
* has no workspace.
|
|
77
|
+
*/
|
|
78
|
+
readonly agentWriteRoots?: readonly string[];
|
|
68
79
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"readRegistry.js","sourceRoot":"","sources":["../../../src/tools/domain/readRegistry.ts"],"names":[],"mappings":"AAMA;;;;;;;GAOG;AACH,MAAM,OAAO,YAAY;IAChB,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEzC,0FAA0F;IAC1F,KAAK;QACJ,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE;YACrC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QAClB,CAAC,CAAC,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,KAAK;QACJ,IAAI,CAAC,IAAI,GAAG,IAAI,GAAG,EAAE,CAAC;IACvB,CAAC;CACD;AAED;;;;GAIG;AACH,MAAM,OAAO,QAAQ;IACH,IAAI,CAA8B;IAClC,GAAG,GAAG,IAAI,GAAG,EAA8B,CAAC;IAC5C,IAAI,CAAsC;IAE3D,YAAY,IAAiC,EAAE,IAAyC;QACvF,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC;IAED,mEAAmE;IACnE,SAAS,CAAC,QAAgB;QACzB,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAClF,CAAC;IAED,iFAAiF;IACjF,MAAM,CAAC,QAAgB,EAAE,IAAY;QACpC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED,sDAAsD;IACtD,MAAM,CAAC,QAAgB;QACtB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACnC,CAAC;IAED,+DAA+D;IAC/D,MAAM;QACL,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChC,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YACrC,IAAI,IAAI,KAAK,SAAS;gBAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;;gBACrC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC3B,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC;CACD","sourcesContent":["import type { ProgressEvent } from '#src/agent/domain/agent';\nimport type { Approver } from '#src/agent/domain/approval';\nimport type { Asker } from '#src/agent/domain/question';\nimport type { RunContext } from '#src/agent/domain/runContext';\nimport type { Tokens } from '#src/shared/domain/tokens';\n\n/**\n * What one conversation has seen of each file: the hash of the content a read showed it, or an\n * edit of its own left. An edit is made only from that version, so the model never changes a\n * file it has not read, or one that changed since.\n *\n * Keyed by the file's real path, so two spellings of one file are one entry. It belongs to the\n * conversation, not to the run: each role, with its own conversation, has its own.\n */\nexport class ReadRegistry {\n\tprivate seen = new Map<string, string>();\n\n\t/** The reads of one run, which count for the conversation only once the run succeeded. */\n\tbegin(): RunReads {\n\t\treturn new RunReads(this.seen, seen => {\n\t\t\tthis.seen = seen;\n\t\t});\n\t}\n\n\t/**\n\t * Forgets every file: the workspace moved through its history, so what the conversation saw\n\t * may no longer be what the files hold.\n\t */\n\tclear(): void {\n\t\tthis.seen = new Map();\n\t}\n}\n\n/**\n * The versions one run has seen, on top of what its conversation saw before it. A failed run\n * records nothing in its conversation, so the model does not remember what it read there: its\n * reads are kept only by `commit`.\n */\nexport class RunReads {\n\tprivate readonly base: ReadonlyMap<string, string>;\n\tprivate readonly own = new Map<string, string | undefined>();\n\tprivate readonly save: (seen: Map<string, string>) => void;\n\n\tconstructor(base: ReadonlyMap<string, string>, save: (seen: Map<string, string>) => void) {\n\t\tthis.base = base;\n\t\tthis.save = save;\n\t}\n\n\t/** The version of `absolute` the conversation has seen, if any. */\n\tversionOf(absolute: string): string | undefined {\n\t\treturn this.own.has(absolute) ? this.own.get(absolute) : this.base.get(absolute);\n\t}\n\n\t/** The conversation now knows `absolute` as holding the content `hash` names. */\n\trecord(absolute: string, hash: string): void {\n\t\tthis.own.set(absolute, hash);\n\t}\n\n\t/** The file is gone, as the conversation knows it. */\n\tforget(absolute: string): void {\n\t\tthis.own.set(absolute, undefined);\n\t}\n\n\t/** Keeps this run's reads for the conversation's next runs. */\n\tcommit(): void {\n\t\tconst seen = new Map(this.base);\n\t\tfor (const [path, hash] of this.own) {\n\t\t\tif (hash === undefined) seen.delete(path);\n\t\t\telse seen.set(path, hash);\n\t\t}\n\t\tthis.save(seen);\n\t}\n}\n\n/**\n * What a harness-built tool gets for one call: the run it belongs to, shared by every role, the\n * reads of the conversation that made the call, and the run's narration and usage, for a call\n * that does its own work on the model, such as running subagents.\n */\nexport interface ToolCallContext {\n\treadonly run: RunContext;\n\treadonly reads: RunReads;\n\t/** The id of the call, which its `tool` events carry. */\n\treadonly callId: string;\n\t/**\n\t * Narrates an event in the run's progress. It never throws: a failing callback ends the run\n\t * once the call returns.\n\t */\n\treadonly onProgress: (event: ProgressEvent) => void;\n\t/**\n\t * Adds what the call spent on the model to the run's usage. `unreported` when a call it made\n\t * came back without usage, which leaves the run's total unknown.\n\t */\n\treadonly spend: (tokens: Tokens | undefined, unreported: boolean) => void;\n\t/** The run's approver and asker, for an agent the call starts. */\n\treadonly approve?: Approver;\n\treadonly ask?: Asker;\n\t/** How many delegations deep the agent that made the call runs: 0 for the top-level one. */\n\treadonly depth: number;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"readRegistry.js","sourceRoot":"","sources":["../../../src/tools/domain/readRegistry.ts"],"names":[],"mappings":"AAMA;;;;;;;GAOG;AACH,MAAM,OAAO,YAAY;IAChB,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEzC,0FAA0F;IAC1F,KAAK;QACJ,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE;YACrC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QAClB,CAAC,CAAC,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,KAAK;QACJ,IAAI,CAAC,IAAI,GAAG,IAAI,GAAG,EAAE,CAAC;IACvB,CAAC;CACD;AAED;;;;GAIG;AACH,MAAM,OAAO,QAAQ;IACH,IAAI,CAA8B;IAClC,GAAG,GAAG,IAAI,GAAG,EAA8B,CAAC;IAC5C,IAAI,CAAsC;IAE3D,YAAY,IAAiC,EAAE,IAAyC;QACvF,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC;IAED,mEAAmE;IACnE,SAAS,CAAC,QAAgB;QACzB,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAClF,CAAC;IAED,iFAAiF;IACjF,MAAM,CAAC,QAAgB,EAAE,IAAY;QACpC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED,sDAAsD;IACtD,MAAM,CAAC,QAAgB;QACtB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACnC,CAAC;IAED,+DAA+D;IAC/D,MAAM;QACL,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChC,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YACrC,IAAI,IAAI,KAAK,SAAS;gBAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;;gBACrC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC3B,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC;CACD","sourcesContent":["import type { ProgressEvent } from '#src/agent/domain/agent';\nimport type { Approver } from '#src/agent/domain/approval';\nimport type { Asker } from '#src/agent/domain/question';\nimport type { RunContext } from '#src/agent/domain/runContext';\nimport type { Tokens } from '#src/shared/domain/tokens';\n\n/**\n * What one conversation has seen of each file: the hash of the content a read showed it, or an\n * edit of its own left. An edit is made only from that version, so the model never changes a\n * file it has not read, or one that changed since.\n *\n * Keyed by the file's real path, so two spellings of one file are one entry. It belongs to the\n * conversation, not to the run: each role, with its own conversation, has its own.\n */\nexport class ReadRegistry {\n\tprivate seen = new Map<string, string>();\n\n\t/** The reads of one run, which count for the conversation only once the run succeeded. */\n\tbegin(): RunReads {\n\t\treturn new RunReads(this.seen, seen => {\n\t\t\tthis.seen = seen;\n\t\t});\n\t}\n\n\t/**\n\t * Forgets every file: the workspace moved through its history, so what the conversation saw\n\t * may no longer be what the files hold.\n\t */\n\tclear(): void {\n\t\tthis.seen = new Map();\n\t}\n}\n\n/**\n * The versions one run has seen, on top of what its conversation saw before it. A failed run\n * records nothing in its conversation, so the model does not remember what it read there: its\n * reads are kept only by `commit`.\n */\nexport class RunReads {\n\tprivate readonly base: ReadonlyMap<string, string>;\n\tprivate readonly own = new Map<string, string | undefined>();\n\tprivate readonly save: (seen: Map<string, string>) => void;\n\n\tconstructor(base: ReadonlyMap<string, string>, save: (seen: Map<string, string>) => void) {\n\t\tthis.base = base;\n\t\tthis.save = save;\n\t}\n\n\t/** The version of `absolute` the conversation has seen, if any. */\n\tversionOf(absolute: string): string | undefined {\n\t\treturn this.own.has(absolute) ? this.own.get(absolute) : this.base.get(absolute);\n\t}\n\n\t/** The conversation now knows `absolute` as holding the content `hash` names. */\n\trecord(absolute: string, hash: string): void {\n\t\tthis.own.set(absolute, hash);\n\t}\n\n\t/** The file is gone, as the conversation knows it. */\n\tforget(absolute: string): void {\n\t\tthis.own.set(absolute, undefined);\n\t}\n\n\t/** Keeps this run's reads for the conversation's next runs. */\n\tcommit(): void {\n\t\tconst seen = new Map(this.base);\n\t\tfor (const [path, hash] of this.own) {\n\t\t\tif (hash === undefined) seen.delete(path);\n\t\t\telse seen.set(path, hash);\n\t\t}\n\t\tthis.save(seen);\n\t}\n}\n\n/**\n * What a harness-built tool gets for one call: the run it belongs to, shared by every role, the\n * reads of the conversation that made the call, and the run's narration and usage, for a call\n * that does its own work on the model, such as running subagents.\n */\nexport interface ToolCallContext {\n\treadonly run: RunContext;\n\treadonly reads: RunReads;\n\t/** The id of the call, which its `tool` events carry. */\n\treadonly callId: string;\n\t/**\n\t * Narrates an event in the run's progress. It never throws: a failing callback ends the run\n\t * once the call returns.\n\t */\n\treadonly onProgress: (event: ProgressEvent) => void;\n\t/**\n\t * Adds what the call spent on the model to the run's usage. `unreported` when a call it made\n\t * came back without usage, which leaves the run's total unknown.\n\t */\n\treadonly spend: (tokens: Tokens | undefined, unreported: boolean) => void;\n\t/** The run's approver and asker, for an agent the call starts. */\n\treadonly approve?: Approver;\n\treadonly ask?: Asker;\n\t/** How many delegations deep the agent that made the call runs: 0 for the top-level one. */\n\treadonly depth: number;\n\t/**\n\t * The folders the call may write in, as real paths, set by the agents that delegated to the\n\t * one that made it: empty when it may write nowhere, absent when none set a limit.\n\t */\n\treadonly writeCeiling?: readonly string[];\n\t/**\n\t * The `write` roots of the workspace of the agent that made the call, as it was given them:\n\t * an agent the call starts writes only within them. Empty when it only reads, absent when it\n\t * has no workspace.\n\t */\n\treadonly agentWriteRoots?: readonly string[];\n}\n"]}
|
|
@@ -42,6 +42,12 @@ export interface AgentAsToolOptions {
|
|
|
42
42
|
*
|
|
43
43
|
* - its changes go into the same run of the workspace's history, so one undo reverts both, and
|
|
44
44
|
* each agent keeps its own tools' access policy and its own reads;
|
|
45
|
+
* - it never writes more than the calling agent may: only within the `write` roots of that
|
|
46
|
+
* agent's `workspace`, and nowhere when they are none, as for the planner and the reviewer.
|
|
47
|
+
* A calling agent with no workspace sets no limit of its own. Its tools that may write elsewhere are
|
|
48
|
+
* refused, a command included, and the model is told why. A consumer's `Tool` that is not
|
|
49
|
+
* `safe` is refused under a calling agent that only reads, since where it writes is unknown;
|
|
50
|
+
* under one that writes, it writes wherever it does;
|
|
45
51
|
* - what it spends counts in the caller's `tokens`, a failed attempt included;
|
|
46
52
|
* - its progress reaches the consumer as `subagent` events of the call, and a transcript it gives
|
|
47
53
|
* names the call in `delegation`;
|
|
@@ -7,6 +7,7 @@ import { classifyHostFailure } from '#src/shared/domain/providerFailure';
|
|
|
7
7
|
import { RunEndingToolError } from "../domain/preparedCall.js";
|
|
8
8
|
import { defineDelegatingTool } from "./defineTool.js";
|
|
9
9
|
import { cutAnswer, failureOf, subagentOptions } from "./subagents.js";
|
|
10
|
+
import { delegateCeiling } from "./writeCeiling.js";
|
|
10
11
|
/** How deep agents may delegate unless a tool says otherwise: planner → coder → committer. */
|
|
11
12
|
export const defaultMaxDepth = 2;
|
|
12
13
|
/**
|
|
@@ -17,6 +18,12 @@ export const defaultMaxDepth = 2;
|
|
|
17
18
|
*
|
|
18
19
|
* - its changes go into the same run of the workspace's history, so one undo reverts both, and
|
|
19
20
|
* each agent keeps its own tools' access policy and its own reads;
|
|
21
|
+
* - it never writes more than the calling agent may: only within the `write` roots of that
|
|
22
|
+
* agent's `workspace`, and nowhere when they are none, as for the planner and the reviewer.
|
|
23
|
+
* A calling agent with no workspace sets no limit of its own. Its tools that may write elsewhere are
|
|
24
|
+
* refused, a command included, and the model is told why. A consumer's `Tool` that is not
|
|
25
|
+
* `safe` is refused under a calling agent that only reads, since where it writes is unknown;
|
|
26
|
+
* under one that writes, it writes wherever it does;
|
|
20
27
|
* - what it spends counts in the caller's `tokens`, a failed attempt included;
|
|
21
28
|
* - its progress reaches the consumer as `subagent` events of the call, and a transcript it gives
|
|
22
29
|
* names the call in `delegation`;
|
|
@@ -67,6 +74,7 @@ export function agentAsTool({ name, description, createAgent, risk = 'mutating',
|
|
|
67
74
|
// The run's approver and asker are host code: one that fails inside the agent ends the
|
|
68
75
|
// run, not just the agent's task.
|
|
69
76
|
const failed = { host: false };
|
|
77
|
+
const writeCeiling = await delegateCeiling(call);
|
|
70
78
|
const options = subagentOptions(call, {
|
|
71
79
|
tool: name,
|
|
72
80
|
name,
|
|
@@ -77,6 +85,7 @@ export function agentAsTool({ name, description, createAgent, risk = 'mutating',
|
|
|
77
85
|
...(call.approve ? { approve: watchedApprover(call.approve, failed) } : {}),
|
|
78
86
|
...(call.ask ? { ask: watchedAsker(call.ask, failed) } : {}),
|
|
79
87
|
},
|
|
88
|
+
writeCeiling,
|
|
80
89
|
});
|
|
81
90
|
let agent;
|
|
82
91
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agentAsTool.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/agentAsTool.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod';\nimport type { Agent, RunOptions } from '#src/agent/domain/agent';\nimport { type Approver, readDecision, type ToolRisk } from '#src/agent/domain/approval';\nimport { type Asker, readAnswers } from '#src/agent/domain/question';\nimport {\n\tInvalidAgentConfigError,\n\tRecoverableError,\n\tUnrecoverableError,\n\twithSpentTokens,\n} from '#src/shared/domain/errors';\nimport { isAbortError } from '#src/shared/domain/isAbortError';\nimport { classifyHostFailure } from '#src/shared/domain/providerFailure';\nimport type { ToolDefinition } from '#src/llm/domain/tool';\nimport { type DelegatedAnswer, RunEndingToolError } from '../domain/preparedCall.ts';\nimport type { ToolCallContext } from '../domain/readRegistry.ts';\nimport { defineDelegatingTool } from './defineTool.ts';\nimport { cutAnswer, failureOf, subagentOptions } from './subagents.ts';\n\ndeclare const delegationToolBrand: unique symbol;\n\n/**\n * A tool from `agentAsTool`, which hands a task to another agent. It goes to an agent from\n * `createLLMAgent` beside its other tools; what it does inside is the harness's.\n */\nexport type DelegationTool = ToolDefinition & { readonly [delegationToolBrand]: true };\n\n/** How deep agents may delegate unless a tool says otherwise: planner → coder → committer. */\nexport const defaultMaxDepth = 2;\n\nexport interface AgentAsToolOptions {\n\t/** The name the model calls it by, such as `commit`. */\n\tname: string;\n\t/** What the agent does and when to hand it a task, for the model that may call it. */\n\tdescription: string;\n\t/**\n\t * A new agent for each call, such as one from `createLLMAgent`: it starts from the task alone\n\t * and never shares a conversation with another call.\n\t */\n\tcreateAgent: () => Agent;\n\t/**\n\t * How the call itself is judged before it runs. `mutating` unless given: handing over a task\n\t * asks no one, and the agent's own `destructive` calls are approved through the run's\n\t * `approve`, as the calling agent's would be. `destructive` asks before every task is handed\n\t * over.\n\t */\n\trisk?: ToolRisk;\n\t/**\n\t * How many delegations deep the agent may run, 1 for one the top-level agent calls. A call\n\t * that would go deeper is refused, and the model is told why. Defaults to 2.\n\t */\n\tmaxDepth?: number;\n}\n\n/**\n * Offers an agent to another agent as a tool. The calling model writes the task, and the call\n * answers at once: the agent works on it in the background, as part of the caller's run, while\n * the caller keeps working. Its answer reaches the caller's model in a message of its own once it\n * finishes, and nothing else of its work does:\n *\n * - its changes go into the same run of the workspace's history, so one undo reverts both, and\n * each agent keeps its own tools' access policy and its own reads;\n * - what it spends counts in the caller's `tokens`, a failed attempt included;\n * - its progress reaches the consumer as `subagent` events of the call, and a transcript it gives\n * names the call in `delegation`;\n * - it is asked and approved through the run's `ask` and `approve`. Delegating never grants\n * `autoApprove`: the agent has it only if the `createAgent` option gave it.\n *\n * The caller's run ends only once every agent it sent has finished. Every change and command of\n * the run is made one at a time, so the history stays exact, but agents working at once can see\n * each other's unfinished work. An agent that fails answers with why, for the model to act on; a\n * cancellation, or a run's approver or asker that failed inside it, ends the run and stops every\n * agent still working.\n *\n * Only for agents on the model APIs, from `createLLMAgent`: an Agent SDK agent from the\n * `createAgent` factory runs as its own run, outside these guarantees.\n *\n * @throws {InvalidAgentConfigError} when `maxDepth` is not a positive integer.\n */\nexport function agentAsTool({\n\tname,\n\tdescription,\n\tcreateAgent,\n\trisk = 'mutating',\n\tmaxDepth = defaultMaxDepth,\n}: AgentAsToolOptions): DelegationTool {\n\tif (!Number.isInteger(maxDepth) || maxDepth < 1) {\n\t\tthrow new InvalidAgentConfigError(\n\t\t\t`maxDepth of \"${name}\" must be a positive integer; got ${String(maxDepth)}`,\n\t\t);\n\t}\n\tconst tool = defineDelegatingTool({\n\t\tname,\n\t\tdescription,\n\t\tinput: z.object({\n\t\t\ttask: z\n\t\t\t\t.string()\n\t\t\t\t.describe(\n\t\t\t\t\t'The whole task. The agent starts from it alone, so say what to do and what to report back.',\n\t\t\t\t),\n\t\t}),\n\t\tsummarize: ({ task }) => task,\n\t\tdelegate: ({ task }, _, call) => {\n\t\t\tconst asked = task.trim();\n\t\t\tif (asked === '') return Promise.reject(new Error('The task needs some text.'));\n\t\t\tif (call.depth >= maxDepth) {\n\t\t\t\treturn Promise.reject(\n\t\t\t\t\tnew Error(\n\t\t\t\t\t\t`\"${name}\" may run at most ${String(maxDepth)} delegations deep, and this agent already runs ${String(call.depth)} deep. Do the work yourself, or answer with what you have.`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn Promise.resolve({\n\t\t\t\trisk,\n\t\t\t\t// What the agent changes is in the history, but it may run tools of the consumer's\n\t\t\t\t// that change what the history never sees.\n\t\t\t\tunrecorded: true,\n\t\t\t\tstart: signal => [{ name, subagent: 1, task: asked, answer: hand(asked, signal, call) }],\n\t\t\t});\n\t\t},\n\t});\n\n\tasync function hand(\n\t\ttask: string,\n\t\tsignal: AbortSignal,\n\t\tcall: ToolCallContext,\n\t): Promise<DelegatedAnswer> {\n\t\t// The run's approver and asker are host code: one that fails inside the agent ends the\n\t\t// run, not just the agent's task.\n\t\tconst failed = { host: false };\n\t\tconst options = subagentOptions(call, {\n\t\t\ttool: name,\n\t\t\tname,\n\t\t\tsubagent: 1,\n\t\t\ttask,\n\t\t\toptions: {\n\t\t\t\tsignal,\n\t\t\t\t...(call.approve ? { approve: watchedApprover(call.approve, failed) } : {}),\n\t\t\t\t...(call.ask ? { ask: watchedAsker(call.ask, failed) } : {}),\n\t\t\t} satisfies RunOptions,\n\t\t});\n\n\t\tlet agent: Agent;\n\t\ttry {\n\t\t\tagent = createAgent();\n\t\t} catch (error) {\n\t\t\tthrow new RunEndingToolError(\n\t\t\t\tclassifyHostFailure(error, `The agent for \"${name}\" could not be created`),\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\tconst { response, tokens } = await agent.run(task, options);\n\t\t\tcall.spend(tokens, tokens === undefined);\n\t\t\treturn { text: cutAnswer(response || 'It finished without an answer.') };\n\t\t} catch (error) {\n\t\t\tif (isAbortError(error)) throw error;\n\t\t\tconst classified = error instanceof RecoverableError || error instanceof UnrecoverableError;\n\t\t\t// Its failure carries what it spent, which the run's failure keeps.\n\t\t\tif (classified && failed.host) throw new RunEndingToolError(error);\n\t\t\tif (!classified) {\n\t\t\t\t// It broke the contract of `Agent`, so what it spent is unknown, not nothing.\n\t\t\t\tthrow new RunEndingToolError(\n\t\t\t\t\twithSpentTokens(\n\t\t\t\t\t\tclassifyHostFailure(error, `The agent for \"${name}\" failed`),\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t\tcall.spend(error.tokens, error.usageUnreported);\n\t\t\treturn { text: `The agent failed: ${failureOf(error)}`, failed: true };\n\t\t}\n\t}\n\n\t// A file tool's kind of brand: the harness built it, and only `createLLMAgent` takes it.\n\treturn tool as unknown as DelegationTool;\n}\n\n/** `approve`, noting in `failed` when it throws or answers with no decision. */\nfunction watchedApprover(approve: Approver, failed: { host: boolean }): Approver {\n\treturn async (request, signal) => {\n\t\ttry {\n\t\t\tconst answer = await approve(request, signal);\n\t\t\treadDecision(answer);\n\t\t\treturn answer;\n\t\t} catch (error) {\n\t\t\t// One that gives up on a cancellation did not fail.\n\t\t\tif (!signal.aborted) failed.host = true;\n\t\t\tthrow error;\n\t\t}\n\t};\n}\n\n/** `ask`, noting in `failed` when it throws or answers in a shape no agent accepts. */\nfunction watchedAsker(ask: Asker, failed: { host: boolean }): Asker {\n\treturn async (questions, signal) => {\n\t\ttry {\n\t\t\tconst answers = await ask(questions, signal);\n\t\t\tif (!readAnswers(answers, questions)) failed.host = true;\n\t\t\treturn answers;\n\t\t} catch (error) {\n\t\t\tif (!signal.aborted) failed.host = true;\n\t\t\tthrow error;\n\t\t}\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"agentAsTool.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/agentAsTool.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod';\nimport type { Agent, RunOptions } from '#src/agent/domain/agent';\nimport { type Approver, readDecision, type ToolRisk } from '#src/agent/domain/approval';\nimport { type Asker, readAnswers } from '#src/agent/domain/question';\nimport {\n\tInvalidAgentConfigError,\n\tRecoverableError,\n\tUnrecoverableError,\n\twithSpentTokens,\n} from '#src/shared/domain/errors';\nimport { isAbortError } from '#src/shared/domain/isAbortError';\nimport { classifyHostFailure } from '#src/shared/domain/providerFailure';\nimport type { ToolDefinition } from '#src/llm/domain/tool';\nimport { type DelegatedAnswer, RunEndingToolError } from '../domain/preparedCall.ts';\nimport type { ToolCallContext } from '../domain/readRegistry.ts';\nimport { defineDelegatingTool } from './defineTool.ts';\nimport { cutAnswer, failureOf, subagentOptions } from './subagents.ts';\nimport { delegateCeiling } from './writeCeiling.ts';\n\ndeclare const delegationToolBrand: unique symbol;\n\n/**\n * A tool from `agentAsTool`, which hands a task to another agent. It goes to an agent from\n * `createLLMAgent` beside its other tools; what it does inside is the harness's.\n */\nexport type DelegationTool = ToolDefinition & { readonly [delegationToolBrand]: true };\n\n/** How deep agents may delegate unless a tool says otherwise: planner → coder → committer. */\nexport const defaultMaxDepth = 2;\n\nexport interface AgentAsToolOptions {\n\t/** The name the model calls it by, such as `commit`. */\n\tname: string;\n\t/** What the agent does and when to hand it a task, for the model that may call it. */\n\tdescription: string;\n\t/**\n\t * A new agent for each call, such as one from `createLLMAgent`: it starts from the task alone\n\t * and never shares a conversation with another call.\n\t */\n\tcreateAgent: () => Agent;\n\t/**\n\t * How the call itself is judged before it runs. `mutating` unless given: handing over a task\n\t * asks no one, and the agent's own `destructive` calls are approved through the run's\n\t * `approve`, as the calling agent's would be. `destructive` asks before every task is handed\n\t * over.\n\t */\n\trisk?: ToolRisk;\n\t/**\n\t * How many delegations deep the agent may run, 1 for one the top-level agent calls. A call\n\t * that would go deeper is refused, and the model is told why. Defaults to 2.\n\t */\n\tmaxDepth?: number;\n}\n\n/**\n * Offers an agent to another agent as a tool. The calling model writes the task, and the call\n * answers at once: the agent works on it in the background, as part of the caller's run, while\n * the caller keeps working. Its answer reaches the caller's model in a message of its own once it\n * finishes, and nothing else of its work does:\n *\n * - its changes go into the same run of the workspace's history, so one undo reverts both, and\n * each agent keeps its own tools' access policy and its own reads;\n * - it never writes more than the calling agent may: only within the `write` roots of that\n * agent's `workspace`, and nowhere when they are none, as for the planner and the reviewer.\n * A calling agent with no workspace sets no limit of its own. Its tools that may write elsewhere are\n * refused, a command included, and the model is told why. A consumer's `Tool` that is not\n * `safe` is refused under a calling agent that only reads, since where it writes is unknown;\n * under one that writes, it writes wherever it does;\n * - what it spends counts in the caller's `tokens`, a failed attempt included;\n * - its progress reaches the consumer as `subagent` events of the call, and a transcript it gives\n * names the call in `delegation`;\n * - it is asked and approved through the run's `ask` and `approve`. Delegating never grants\n * `autoApprove`: the agent has it only if the `createAgent` option gave it.\n *\n * The caller's run ends only once every agent it sent has finished. Every change and command of\n * the run is made one at a time, so the history stays exact, but agents working at once can see\n * each other's unfinished work. An agent that fails answers with why, for the model to act on; a\n * cancellation, or a run's approver or asker that failed inside it, ends the run and stops every\n * agent still working.\n *\n * Only for agents on the model APIs, from `createLLMAgent`: an Agent SDK agent from the\n * `createAgent` factory runs as its own run, outside these guarantees.\n *\n * @throws {InvalidAgentConfigError} when `maxDepth` is not a positive integer.\n */\nexport function agentAsTool({\n\tname,\n\tdescription,\n\tcreateAgent,\n\trisk = 'mutating',\n\tmaxDepth = defaultMaxDepth,\n}: AgentAsToolOptions): DelegationTool {\n\tif (!Number.isInteger(maxDepth) || maxDepth < 1) {\n\t\tthrow new InvalidAgentConfigError(\n\t\t\t`maxDepth of \"${name}\" must be a positive integer; got ${String(maxDepth)}`,\n\t\t);\n\t}\n\tconst tool = defineDelegatingTool({\n\t\tname,\n\t\tdescription,\n\t\tinput: z.object({\n\t\t\ttask: z\n\t\t\t\t.string()\n\t\t\t\t.describe(\n\t\t\t\t\t'The whole task. The agent starts from it alone, so say what to do and what to report back.',\n\t\t\t\t),\n\t\t}),\n\t\tsummarize: ({ task }) => task,\n\t\tdelegate: ({ task }, _, call) => {\n\t\t\tconst asked = task.trim();\n\t\t\tif (asked === '') return Promise.reject(new Error('The task needs some text.'));\n\t\t\tif (call.depth >= maxDepth) {\n\t\t\t\treturn Promise.reject(\n\t\t\t\t\tnew Error(\n\t\t\t\t\t\t`\"${name}\" may run at most ${String(maxDepth)} delegations deep, and this agent already runs ${String(call.depth)} deep. Do the work yourself, or answer with what you have.`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn Promise.resolve({\n\t\t\t\trisk,\n\t\t\t\t// What the agent changes is in the history, but it may run tools of the consumer's\n\t\t\t\t// that change what the history never sees.\n\t\t\t\tunrecorded: true,\n\t\t\t\tstart: signal => [{ name, subagent: 1, task: asked, answer: hand(asked, signal, call) }],\n\t\t\t});\n\t\t},\n\t});\n\n\tasync function hand(\n\t\ttask: string,\n\t\tsignal: AbortSignal,\n\t\tcall: ToolCallContext,\n\t): Promise<DelegatedAnswer> {\n\t\t// The run's approver and asker are host code: one that fails inside the agent ends the\n\t\t// run, not just the agent's task.\n\t\tconst failed = { host: false };\n\t\tconst writeCeiling = await delegateCeiling(call);\n\t\tconst options = subagentOptions(call, {\n\t\t\ttool: name,\n\t\t\tname,\n\t\t\tsubagent: 1,\n\t\t\ttask,\n\t\t\toptions: {\n\t\t\t\tsignal,\n\t\t\t\t...(call.approve ? { approve: watchedApprover(call.approve, failed) } : {}),\n\t\t\t\t...(call.ask ? { ask: watchedAsker(call.ask, failed) } : {}),\n\t\t\t} satisfies RunOptions,\n\t\t\twriteCeiling,\n\t\t});\n\n\t\tlet agent: Agent;\n\t\ttry {\n\t\t\tagent = createAgent();\n\t\t} catch (error) {\n\t\t\tthrow new RunEndingToolError(\n\t\t\t\tclassifyHostFailure(error, `The agent for \"${name}\" could not be created`),\n\t\t\t);\n\t\t}\n\n\t\ttry {\n\t\t\tconst { response, tokens } = await agent.run(task, options);\n\t\t\tcall.spend(tokens, tokens === undefined);\n\t\t\treturn { text: cutAnswer(response || 'It finished without an answer.') };\n\t\t} catch (error) {\n\t\t\tif (isAbortError(error)) throw error;\n\t\t\tconst classified = error instanceof RecoverableError || error instanceof UnrecoverableError;\n\t\t\t// Its failure carries what it spent, which the run's failure keeps.\n\t\t\tif (classified && failed.host) throw new RunEndingToolError(error);\n\t\t\tif (!classified) {\n\t\t\t\t// It broke the contract of `Agent`, so what it spent is unknown, not nothing.\n\t\t\t\tthrow new RunEndingToolError(\n\t\t\t\t\twithSpentTokens(\n\t\t\t\t\t\tclassifyHostFailure(error, `The agent for \"${name}\" failed`),\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\ttrue,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t\tcall.spend(error.tokens, error.usageUnreported);\n\t\t\treturn { text: `The agent failed: ${failureOf(error)}`, failed: true };\n\t\t}\n\t}\n\n\t// A file tool's kind of brand: the harness built it, and only `createLLMAgent` takes it.\n\treturn tool as unknown as DelegationTool;\n}\n\n/** `approve`, noting in `failed` when it throws or answers with no decision. */\nfunction watchedApprover(approve: Approver, failed: { host: boolean }): Approver {\n\treturn async (request, signal) => {\n\t\ttry {\n\t\t\tconst answer = await approve(request, signal);\n\t\t\treadDecision(answer);\n\t\t\treturn answer;\n\t\t} catch (error) {\n\t\t\t// One that gives up on a cancellation did not fail.\n\t\t\tif (!signal.aborted) failed.host = true;\n\t\t\tthrow error;\n\t\t}\n\t};\n}\n\n/** `ask`, noting in `failed` when it throws or answers in a shape no agent accepts. */\nfunction watchedAsker(ask: Asker, failed: { host: boolean }): Asker {\n\treturn async (questions, signal) => {\n\t\ttry {\n\t\t\tconst answers = await ask(questions, signal);\n\t\t\tif (!readAnswers(answers, questions)) failed.host = true;\n\t\t\treturn answers;\n\t\t} catch (error) {\n\t\t\tif (!signal.aborted) failed.host = true;\n\t\t\tthrow error;\n\t\t}\n\t};\n}\n"]}
|
|
@@ -54,11 +54,14 @@ export async function createCommandTool({ commands, policy, writes, covered, bas
|
|
|
54
54
|
// use a tool outside LLMAgent. This description never uses the resolved cwd.
|
|
55
55
|
return definition ? visibleCommand(definition, values) : name;
|
|
56
56
|
},
|
|
57
|
-
prepare: async ({ command: { name, ...values } }, signal,
|
|
57
|
+
prepare: async ({ command: { name, ...values } }, signal, call) => {
|
|
58
|
+
const { run } = call;
|
|
58
59
|
const command = resolved.get(name);
|
|
59
60
|
// The schema allows only the configured names.
|
|
60
61
|
if (!command)
|
|
61
62
|
throw new Error(`There is no command named "${name}"`);
|
|
63
|
+
// A command can change anything in the write roots.
|
|
64
|
+
await writes.checkCeiling(call);
|
|
62
65
|
const paramValues = {};
|
|
63
66
|
for (const parameter of parametersOf(command.definition)) {
|
|
64
67
|
const value = values[parameter.name] ?? null;
|
|
@@ -72,6 +75,8 @@ export async function createCommandTool({ commands, policy, writes, covered, bas
|
|
|
72
75
|
args: Object.freeze(buildArguments(command.definition, paramValues)),
|
|
73
76
|
cwd: command.cwd,
|
|
74
77
|
});
|
|
78
|
+
// Now, so a set of tools that cannot join the run's writes is refused before the user is asked.
|
|
79
|
+
writes.sessionFor(run);
|
|
75
80
|
let ending;
|
|
76
81
|
return {
|
|
77
82
|
// A command can change anything its user can: it is always asked about.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commandTool.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/commandTool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAEpE,OAAO,EACN,cAAc,EAId,yBAAyB,EACzB,uBAAuB,EAEvB,YAAY,EACZ,gBAAgB,GAChB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAEN,kBAAkB,GAElB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,iBAAiB,EAAqB,MAAM,wBAAwB,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAA6B,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACnF,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAItD,2FAA2F;AAC3F,wCAAwC;AACxC,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;AAC/E,8EAA8E;AAC9E,MAAM,UAAU,GAAG,MAAM,CAAC;AAQ1B;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,EACvC,QAAQ,EACR,MAAM,EACN,MAAM,EACN,OAAO,EACP,IAAI,EACJ,QAAQ,GAAG,OAAO,CAAC,QAAQ,EAC3B,UAAU,GAAG,iBAAiB,GAW9B;IACA,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QAC1B,MAAM,IAAI,uBAAuB,CAChC,yHAAyH,CACzH,CAAC;IACH,CAAC;IACD,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC3B,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA2B,CAAC;IACpD,KAAK,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3D,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE;YAClB,UAAU;YACV,GAAG,EAAE,MAAM,UAAU,CAAC,YAAY,IAAI,GAAG,EAAE,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC;SACxE,CAAC,CAAC;IACJ,CAAC;IAED,OAAO,mBAAmB,CAAC;QAC1B,IAAI,EAAE,YAAY;QAClB,WAAW,EAAE,YAAY,CAAC,QAAQ,CAAC;QACnC,KAAK,EAAE,WAAW,CAAC,QAAQ,CAAC;QAC5B,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,EAAE,EAAE,EAAE;YAC/C,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;YAClC,qFAAqF;YACrF,6EAA6E;YAC7E,OAAO,UAAU,CAAC,CAAC,CAAC,cAAc,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC/D,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YACpE,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACnC,+CAA+C;YAC/C,IAAI,CAAC,OAAO;gBAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,IAAI,GAAG,CAAC,CAAC;YACrE,MAAM,WAAW,GAAmC,EAAE,CAAC;YACvD,KAAK,MAAM,SAAS,IAAI,YAAY,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC1D,MAAM,KAAK,GACT,MAAqD,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;gBAChF,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC;oBAC1B,SAAS,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;wBACjD,CAAC,CAAC,MAAM,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC;wBACxD,CAAC,CAAC,KAAK,CAAC;YACX,CAAC;YACD,MAAM,IAAI,GAAgB,MAAM,CAAC,MAAM,CAAC;gBACvC,UAAU,EAAE,OAAO,CAAC,UAAU,CAAC,UAAU;gBACzC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,UAAU,EAAE,WAAW,CAAC,CAAa;gBAChF,GAAG,EAAE,OAAO,CAAC,GAAG;aAChB,CAAC,CAAC;YAEH,IAAI,MAAiC,CAAC;YACtC,OAAO;gBACN,wEAAwE;gBACxE,IAAI,EAAE,aAAa;gBACnB,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM;gBACpB,GAAG,EAAE,SAAS,CAAC,EAAE,CAChB,UAAU,CAAC;oBACV,IAAI;oBACJ,IAAI;oBACJ,UAAU,EAAE,OAAO,CAAC,UAAU;oBAC9B,OAAO,EAAE,GAAG;oBACZ,MAAM,EAAE,SAAS;oBACjB,KAAK,EAAE,GAAG,CAAC,EAAE;wBACZ,MAAM,GAAG,GAAG,CAAC;oBACd,CAAC;iBACD,CAAC;aACH,CAAC;QACH,CAAC;KACD,CAAC,CAAC;IAYH,yFAAyF;IACzF,SAAS,UAAU,CAAC,GAAe;QAClC,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC/C,OAAO,OAAO,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3E,CAAC;IAED,KAAK,UAAU,eAAe,CAC7B,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAc,EACrD,OAAqB;QAErB,MAAM,CAAC,cAAc,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,gBAAgB,EAAE,CAAC;QACjD,IAAI,UAA6B,CAAC;QAClC,IAAI,CAAC;YACJ,UAAU,GAAG,MAAM,iBAAiB,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QACpF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,CAAC,cAAc,EAAE,CAAC;YACxB,MAAM,IAAI,gBAAgB,CACzB,mDAAmD,IAAI,sBAAsB,EAC7E,EAAE,KAAK,EAAE,KAAK,EAAE,CAChB,CAAC;QACH,CAAC;QAED,IAAI,MAAwC,CAAC;QAC7C,IAAI,YAAqB,CAAC;QAC1B,MAAM,WAAW,GAAG,IAAI,iBAAiB,EAAE,CAAC;QAC5C,IAAI,CAAC;YACJ,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,EAAE;gBACrD,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,GAAG,EAAE,cAAc,CAAC,UAAU,CAAC;gBAC/B,SAAS,EAAE,UAAU,CAAC,SAAS,IAAI,uBAAuB;gBAC1D,WAAW,EAAE,UAAU,CAAC,WAAW,IAAI,yBAAyB;gBAChE,MAAM;gBACN,QAAQ,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;oBAC3B,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;gBACjC,CAAC;aACD,CAAC,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,YAAY,GAAG,KAAK,CAAC;QACtB,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,EAAE,OAAO,IAAI,IAAI,CAAC;QACzC,IAAI,QAAuD,CAAC;QAC5D,IAAI,CAAC;YACJ,QAAQ,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC/C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,OAAO,CAAC,IAAI,CAAC,sEAAsE,CAAC,CAAC;YACrF,MAAM,IAAI,oBAAoB,CAC7B,IAAI,IAAI,2FAA2F,EACnG,EAAE,KAAK,EAAE,KAAK,EAAE,CAChB,CAAC;QACH,CAAC;QAED,IAAI,CAAC,MAAM,EAAE,CAAC;YACb,MAAM,CAAC,cAAc,EAAE,CAAC;YACxB,MAAM,IAAI,gBAAgB,CAAC,IAAI,IAAI,wBAAwB,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;QACvF,CAAC;QACD,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;QAC/D,IAAI,CAAC,QAAQ,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;YAChE,MAAM,IAAI,oBAAoB,CAC7B,GAAG,MAAM,MAAM,IAAI,qJAAqJ,CACxK,CAAC;QACH,CAAC;QACD,qFAAqF;QACrF,IAAI,MAAM,CAAC,OAAO,KAAK,WAAW;YAAE,MAAM,CAAC,cAAc,EAAE,CAAC;QAC5D,KAAK,CAAC;YACL,MAAM,EAAE,MAAM,CAAC,OAAO,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC;YAC5D,GAAG,EAAE,UAAU,CAAC,MAAM,CAAC;YACvB,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC;YACpC,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE;YACjC,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9E,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;SAClE,CAAC,CAAC;QACH,IAAI,MAAM,CAAC,OAAO,KAAK,QAAQ;YAAE,MAAM,IAAI,kBAAkB,CAAC,MAAM,CAAC,CAAC;QACtE,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,kBAAkB,CAAC,MAAM,EAAE;gBACpC,GAAG,CAAC,MAAM,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;aAClE,CAAC,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,CAAC;IACf,CAAC;IAED,SAAS,WAAW,CACnB,IAAY,EACZ,UAA6B,EAC7B,MAA4B,EAC5B,EAAE,OAAO,EAAE,UAAU,EAAiD;QAEtE,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACtD,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,IAAI,MAAM,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;YACnC,MAAM,KAAK,GAAG,CAAC,UAAU,CAAC,SAAS,IAAI,uBAAuB,CAAC,GAAG,IAAI,CAAC;YACvE,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,2BAA2B,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;QACnF,CAAC;aAAM,IAAI,MAAM,CAAC,OAAO,KAAK,WAAW,EAAE,CAAC;YAC3C,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,uCAAuC,CAAC,CAAC;QAC7D,CAAC;aAAM,IAAI,MAAM,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;YACrC,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,sBAAsB,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,OAAO,KAAK,CAAC,CAAC;QACzF,CAAC;aAAM,CAAC;YACP,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,mBAAmB,MAAM,CAAC,MAAM,IAAI,UAAU,UAAU,OAAO,KAAK,CAAC,CAAC;QAC1F,CAAC;QAED,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACxD,KAAK,CAAC,IAAI,CACT,OAAO,CAAC,MAAM,KAAK,CAAC;YACnB,CAAC,CAAC,wCAAwC;YAC1C,CAAC,CAAC,8CAA8C,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACxG,CAAC;QACF,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CACT,gHAAgH,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,iCAAiC,CACjL,CAAC;QACH,CAAC;QAED,MAAM,KAAK,GAAG,UAAU,CAAC,WAAW,IAAI,yBAAyB,CAAC;QAClE,IAAI,MAAM,CAAC,YAAY,KAAK,CAAC,EAAE,CAAC;YAC/B,KAAK,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;QACnC,CAAC;aAAM,CAAC;YACP,KAAK,CAAC,IAAI,CACT,MAAM,CAAC,YAAY,GAAG,KAAK;gBAC1B,CAAC,CAAC,oBAAoB,SAAS,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG;gBAC9E,CAAC,CAAC,SAAS,EACZ,MAAM,CAAC,MAAM,CACb,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;AACF,CAAC;AAED,SAAS,UAAU,CAAC,MAA4B;IAC/C,IAAI,MAAM,CAAC,OAAO,KAAK,UAAU;QAAE,OAAO,yBAAyB,CAAC;IACpE,IAAI,MAAM,CAAC,OAAO,KAAK,WAAW;QAAE,OAAO,eAAe,CAAC;IAC3D,IAAI,MAAM,CAAC,QAAQ,KAAK,IAAI;QAAE,OAAO,oBAAoB,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;IACnF,OAAO,iBAAiB,MAAM,CAAC,MAAM,IAAI,UAAU,EAAE,CAAC;AACvD,CAAC;AAED,SAAS,SAAS,CAAC,KAAa;IAC/B,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,KAAK,CAAC;AAChD,CAAC;AAED,gGAAgG;AAChG,SAAS,cAAc,CACtB,UAA6B,EAC7B,MAAkD;IAElD,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,KAAK,MAAM,QAAQ,IAAI,UAAU,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;QAC9C,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAClC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACpB,SAAS;QACV,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;QAC5C,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC/B,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;gBAAE,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;QAC/C,CAAC;aAAM,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACxD,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClB,CAAC;IACF,CAAC;IAED,OAAO,CAAC,UAAU,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACnD,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC/B,OAAe,EACf,GAAuB,EACvB,MAAoB,EACpB,IAAY;IAEZ,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,GAAG,IAAI,GAAG,CAAC,CAAC;IACxC,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACJ,IAAI,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC7B,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;IACpE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,IAAI,uBAAuB,CAChC,GAAG,OAAO,mDAAmD,EAC7D,EAAE,KAAK,EAAE,KAAK,EAAE,CAChB,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,YAAY,CAC1B,KAAe,EACf,GAAW,EACX,MAAoB,EACpB,MAAmB;IAEnB,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QAC9D,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,GAAG,CAAC;QAC/C,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAC3F,CAAC;IACD,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,cAAc,CAC7B,UAA0C,EAC1C,MAAyB,OAAO,CAAC,GAAG;IAEpC,MAAM,SAAS,GAAsB,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACrD,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QAClC,IAAI,kBAAkB,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YACzE,SAAS,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC;QAC7B,CAAC;IACF,CAAC;IACD,OAAO,EAAE,GAAG,SAAS,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC;AAC5C,CAAC;AAED,SAAS,YAAY,CAAC,QAAkB;IACvC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;SACnC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,IAAI,KAAK,WAAW,EAAE,CAAC;SAC7D,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,OAAO;QACN,6FAA6F;QAC7F,oGAAoG;QACpG,WAAW;QACX,IAAI;KACJ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,SAAS,WAAW,CAAC,QAAkB;IACtC,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,UAAU,CAAC,EAAE,EAAE,CACpE,CAAC,CAAC,MAAM,CAAC;QACR,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QACpB,GAAG,MAAM,CAAC,WAAW,CACpB,YAAY,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC,CAAC,CACvF;KACD,CAAC,CACF,CAAC;IACF,MAAM,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,GAAG,QAAQ,CAAC;IAClC,qDAAqD;IACrD,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,uBAAuB,CAAC,0BAA0B,CAAC,CAAC;IAC1E,OAAO,CAAC,CAAC,MAAM,CAAC;QACf,OAAO,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CACxE,yCAAyC,CACzC;KACD,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,SAA2B;IACnD,QAAQ,SAAS,CAAC,IAAI,EAAE,CAAC;QACxB,KAAK,OAAO;YACX,OAAO,CAAC;iBACN,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;iBACjB,QAAQ,CAAC,GAAG,SAAS,CAAC,WAAW,2CAA2C,CAAC,CAAC;QACjF,KAAK,QAAQ,CAAC,CAAC,CAAC;YACf,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAA+B,CAAC,CAAC;YACjE,OAAO,SAAS,CAAC,QAAQ,KAAK,IAAI;gBACjC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,WAAW,CAAC;gBACxC,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,GAAG,SAAS,CAAC,WAAW,wBAAwB,CAAC,CAAC;QACjF,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,CAAC;YACb,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC;YACxB,MAAM,WAAW,GAAG,GAAG,SAAS,CAAC,WAAW,sCAAsC,SAAS,CAAC,OAAO,2BAA2B,CAAC;YAC/H,OAAO,SAAS,CAAC,QAAQ,KAAK,IAAI;gBACjC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC;gBAC5B,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,GAAG,WAAW,wBAAwB,CAAC,CAAC;QACrE,CAAC;IACF,CAAC;AACF,CAAC","sourcesContent":["import { realpath } from 'node:fs/promises';\nimport { isAbsolute, relative, resolve } from 'node:path';\nimport { z } from 'zod';\nimport type { CommandLine } from '#src/agent/domain/approval';\nimport type { RunContext } from '#src/agent/domain/runContext';\nimport { displayPath } from '#src/recovery/domain/runChanges';\nimport { InvalidAgentConfigError } from '#src/shared/domain/errors';\nimport type { AccessPolicy } from '../domain/accessPolicy.ts';\nimport {\n\tbuildArguments,\n\ttype CommandDefinition,\n\ttype CommandParameter,\n\ttype Commands,\n\tdefaultCommandOutputBytes,\n\tdefaultCommandTimeoutMs,\n\ttype ParameterValue,\n\tparametersOf,\n\tvalidateCommands,\n} from '../domain/commands.ts';\nimport { EditRefusedError, EditUnconfirmedError } from '../domain/fileEdits.ts';\nimport {\n\ttype CommandEnding,\n\tToolExecutionError,\n\ttype PreparingTool,\n} from '../domain/preparedCall.ts';\nimport { CommandCheckpoint, type CoveredFiles } from './commandCheckpoint.ts';\nimport { OutputFingerprint, withoutColors } from './commandOutput.ts';\nimport { type CommandProcessResult, runCommandProcess } from './commandProcess.ts';\nimport { definePreparingTool } from './defineTool.ts';\nimport type { WorkspaceWrites } from './workspaceWrites.ts';\nimport type { WriteSession } from './writeSession.ts';\n\n// Only what a toolchain needs to find itself and its locale. Everything else, the provider\n// keys included, stays in this process.\nconst inheritedVariables = ['PATH', 'HOME', 'TMPDIR', 'USER', 'SHELL', 'LANG'];\n// Long enough for a slow disk, short enough not to hold up building an agent.\nconst cwdCheckMs = 10_000;\n\n/** A command ready to run: its definition, and where it runs as a real path. */\ninterface ResolvedCommand {\n\tdefinition: CommandDefinition;\n\tcwd: string;\n}\n\n/**\n * `runCommand`, which runs one of the commands the consumer configured, by name, with the\n * parameters each one declares. Every call asks the approver, showing the program exactly as\n * it will run; `autoApprove` skips that.\n *\n * Around each command, the covered files are captured before and compared after, so what it\n * changed is in the run's history and can be undone: see `CommandCheckpoint`. If the capture\n * fails, the command does not run. A timeout or a cancellation stops its whole process group,\n * and is never reported as a completed command. If the group cannot be confirmed stopped, what\n * changed so far is recorded, the observation stays open, and writing stops for the run.\n *\n * This is not a sandbox: a command runs the repository's code, with everything this user can\n * reach. See \"Configured commands are trusted, not sandboxed\" in decisions.md.\n *\n * @throws {InvalidAgentConfigError} for an invalid configuration, a working directory outside\n * every `write` root, or on Windows, where a process group cannot be stopped.\n */\nexport async function createCommandTool({\n\tcommands,\n\tpolicy,\n\twrites,\n\tcovered,\n\tbase,\n\tplatform = process.platform,\n\trunProcess = runCommandProcess,\n}: {\n\tcommands: Commands;\n\tpolicy: AccessPolicy;\n\twrites: WorkspaceWrites;\n\tcovered: CoveredFiles;\n\t/** The real path of the first root, where a relative `cwd` starts. */\n\tbase: string;\n\tplatform?: NodeJS.Platform;\n\t/** Runs the program. Replaced in tests, to reach what a real process rarely does. */\n\trunProcess?: typeof runCommandProcess;\n}): Promise<PreparingTool> {\n\tif (platform === 'win32') {\n\t\tthrow new InvalidAgentConfigError(\n\t\t\t'Configured commands are not supported on Windows yet: the harness cannot stop a command and everything it started there',\n\t\t);\n\t}\n\tvalidateCommands(commands);\n\tconst resolved = new Map<string, ResolvedCommand>();\n\tfor (const [name, definition] of Object.entries(commands)) {\n\t\tresolved.set(name, {\n\t\t\tdefinition,\n\t\t\tcwd: await resolveCwd(`Command \"${name}\"`, definition.cwd, policy, base),\n\t\t});\n\t}\n\n\treturn definePreparingTool({\n\t\tname: 'runCommand',\n\t\tdescription: describeTool(commands),\n\t\tinput: inputSchema(commands),\n\t\tsummarize: ({ command: { name, ...values } }) => {\n\t\t\tconst definition = commands[name];\n\t\t\t// The schema only permits configured names, but keep this defensive for callers that\n\t\t\t// use a tool outside LLMAgent. This description never uses the resolved cwd.\n\t\t\treturn definition ? visibleCommand(definition, values) : name;\n\t\t},\n\t\tprepare: async ({ command: { name, ...values } }, signal, { run }) => {\n\t\t\tconst command = resolved.get(name);\n\t\t\t// The schema allows only the configured names.\n\t\t\tif (!command) throw new Error(`There is no command named \"${name}\"`);\n\t\t\tconst paramValues: Record<string, ParameterValue> = {};\n\t\t\tfor (const parameter of parametersOf(command.definition)) {\n\t\t\t\tconst value =\n\t\t\t\t\t(values as Record<string, ParameterValue | undefined>)[parameter.name] ?? null;\n\t\t\t\tparamValues[parameter.name] =\n\t\t\t\t\tparameter.type === 'paths' && Array.isArray(value)\n\t\t\t\t\t\t? await resolvePaths(value, command.cwd, policy, signal)\n\t\t\t\t\t\t: value;\n\t\t\t}\n\t\t\tconst line: CommandLine = Object.freeze({\n\t\t\t\texecutable: command.definition.executable,\n\t\t\t\targs: Object.freeze(buildArguments(command.definition, paramValues)) as string[],\n\t\t\t\tcwd: command.cwd,\n\t\t\t});\n\n\t\t\tlet ending: CommandEnding | undefined;\n\t\t\treturn {\n\t\t\t\t// A command can change anything its user can: it is always asked about.\n\t\t\t\trisk: 'destructive',\n\t\t\t\tcommand: line,\n\t\t\t\tending: () => ending,\n\t\t\t\trun: runSignal =>\n\t\t\t\t\trunCommand({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tline,\n\t\t\t\t\t\tdefinition: command.definition,\n\t\t\t\t\t\tcontext: run,\n\t\t\t\t\t\tsignal: runSignal,\n\t\t\t\t\t\tended: end => {\n\t\t\t\t\t\t\tending = end;\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t};\n\t\t},\n\t});\n\n\tinterface CommandRun {\n\t\tname: string;\n\t\tline: CommandLine;\n\t\tdefinition: CommandDefinition;\n\t\tcontext: RunContext;\n\t\tsignal: AbortSignal;\n\t\t/** Told how the command ended, once it ran to an end and everything it changed is recorded. */\n\t\tended: (ending: CommandEnding) => void;\n\t}\n\n\t// Alone: a change another agent of the run made while it ran would be taken for its own.\n\tfunction runCommand(run: CommandRun): Promise<string> {\n\t\tconst session = writes.sessionFor(run.context);\n\t\treturn session.exclusive(() => runCommandAlone(run, session), run.signal);\n\t}\n\n\tasync function runCommandAlone(\n\t\t{ name, line, definition, signal, ended }: CommandRun,\n\t\tsession: WriteSession,\n\t): Promise<string> {\n\t\tsignal.throwIfAborted();\n\t\tconst journal = await session.journalForChange();\n\t\tlet checkpoint: CommandCheckpoint;\n\t\ttry {\n\t\t\tcheckpoint = await CommandCheckpoint.begin({ journal, covered, by: name, signal });\n\t\t} catch (error) {\n\t\t\tsignal.throwIfAborted();\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`The harness could not capture the files before \"${name}\", so it did not run`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\n\t\tlet result: CommandProcessResult | undefined;\n\t\tlet startFailure: unknown;\n\t\tconst fingerprint = new OutputFingerprint();\n\t\ttry {\n\t\t\tresult = await runProcess(line.executable, line.args, {\n\t\t\t\tcwd: line.cwd,\n\t\t\t\tenv: environmentFor(definition),\n\t\t\t\ttimeoutMs: definition.timeoutMs ?? defaultCommandTimeoutMs,\n\t\t\t\toutputBytes: definition.outputBytes ?? defaultCommandOutputBytes,\n\t\t\t\tsignal,\n\t\t\t\tonOutput: (stream, chunk) => {\n\t\t\t\t\tfingerprint.push(stream, chunk);\n\t\t\t\t},\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tstartFailure = error;\n\t\t}\n\n\t\tconst complete = result?.stopped ?? true;\n\t\tlet observed: Awaited<ReturnType<CommandCheckpoint['end']>>;\n\t\ttry {\n\t\t\tobserved = await checkpoint.end({ complete });\n\t\t} catch (error) {\n\t\t\tsession.stop('Writing stopped: the harness could not record what a command changed');\n\t\t\tthrow new EditUnconfirmedError(\n\t\t\t\t`\"${name}\" ran, but the harness could not record what it changed; writing has stopped for this run`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\n\t\tif (!result) {\n\t\t\tsignal.throwIfAborted();\n\t\t\tthrow new EditRefusedError(`\"${name}\" could not be started`, { cause: startFailure });\n\t\t}\n\t\tconst report = describeRun(name, definition, result, observed);\n\t\tif (!complete) {\n\t\t\tsession.stop('Writing stopped: a command could not be stopped');\n\t\t\tthrow new EditUnconfirmedError(\n\t\t\t\t`${report}\\n\"${name}\" could not be stopped: something it started may still be changing files, and the history may not hold all of it. Writing has stopped for this run.`,\n\t\t\t);\n\t\t}\n\t\t// After its changes are recorded: a cancelled command is never reported as finished.\n\t\tif (result.outcome === 'cancelled') signal.throwIfAborted();\n\t\tended({\n\t\t\tpassed: result.outcome === 'exited' && result.exitCode === 0,\n\t\t\thow: howItEnded(result),\n\t\t\toutput: withoutColors(result.output),\n\t\t\tfingerprint: fingerprint.digest(),\n\t\t\t...(observed.unrecorded.length === 0 ? { state: journal.contentState() } : {}),\n\t\t\tchanged: journal.appliedPaths.map(path => displayPath(base, path)),\n\t\t});\n\t\tif (result.outcome !== 'exited') throw new ToolExecutionError(report);\n\t\tif (result.exitCode !== 0) {\n\t\t\tthrow new ToolExecutionError(report, {\n\t\t\t\t...(result.exitCode === null ? {} : { exitCode: result.exitCode }),\n\t\t\t});\n\t\t}\n\t\treturn report;\n\t}\n\n\tfunction describeRun(\n\t\tname: string,\n\t\tdefinition: CommandDefinition,\n\t\tresult: CommandProcessResult,\n\t\t{ changes, unrecorded }: Awaited<ReturnType<CommandCheckpoint['end']>>,\n\t): string {\n\t\tconst seconds = (result.durationMs / 1000).toFixed(1);\n\t\tconst lines: string[] = [];\n\t\tif (result.outcome === 'timedOut') {\n\t\t\tconst limit = (definition.timeoutMs ?? defaultCommandTimeoutMs) / 1000;\n\t\t\tlines.push(`\"${name}\" ran past its limit of ${String(limit)} s and was stopped.`);\n\t\t} else if (result.outcome === 'cancelled') {\n\t\t\tlines.push(`\"${name}\" was stopped: the run was cancelled.`);\n\t\t} else if (result.exitCode !== null) {\n\t\t\tlines.push(`\"${name}\" exited with code ${String(result.exitCode)} after ${seconds} s.`);\n\t\t} else {\n\t\t\tlines.push(`\"${name}\" was killed by ${result.signal ?? 'a signal'} after ${seconds} s.`);\n\t\t}\n\n\t\tconst shown = (path: string) => displayPath(base, path);\n\t\tlines.push(\n\t\t\tchanges.length === 0\n\t\t\t\t? 'It changed no file the history covers.'\n\t\t\t\t: `Files it changed, recorded in the history: ${changes.map(change => shown(change.path)).join(', ')}.`,\n\t\t);\n\t\tif (unrecorded.length > 0) {\n\t\t\tlines.push(\n\t\t\t\t`Not recorded, because the history cannot copy them (a folder, a link, or a file over 64 MB) before or after: ${unrecorded.map(shown).join(', ')}. Undo leaves them as they are.`,\n\t\t\t);\n\t\t}\n\n\t\tconst limit = definition.outputBytes ?? defaultCommandOutputBytes;\n\t\tif (result.printedBytes === 0) {\n\t\t\tlines.push('It printed nothing.');\n\t\t} else {\n\t\t\tlines.push(\n\t\t\t\tresult.printedBytes > limit\n\t\t\t\t\t? `Output, the last ${kilobytes(limit)} of ${kilobytes(result.printedBytes)}:`\n\t\t\t\t\t: 'Output:',\n\t\t\t\tresult.output,\n\t\t\t);\n\t\t}\n\t\treturn lines.join('\\n');\n\t}\n}\n\nfunction howItEnded(result: CommandProcessResult): string {\n\tif (result.outcome === 'timedOut') return 'ran past its time limit';\n\tif (result.outcome === 'cancelled') return 'was cancelled';\n\tif (result.exitCode !== null) return `exited with code ${String(result.exitCode)}`;\n\treturn `was killed by ${result.signal ?? 'a signal'}`;\n}\n\nfunction kilobytes(bytes: number): string {\n\treturn `${String(Math.ceil(bytes / 1024))} KB`;\n}\n\n/** The command line the model can see, before paths are resolved against the host workspace. */\nfunction visibleCommand(\n\tdefinition: CommandDefinition,\n\tvalues: Record<string, ParameterValue | undefined>,\n): string {\n\tconst args: string[] = [];\n\tfor (const argument of definition.args ?? []) {\n\t\tif (typeof argument === 'string') {\n\t\t\targs.push(argument);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst value = values[argument.name] ?? null;\n\t\tif (argument.type === 'paths') {\n\t\t\tif (Array.isArray(value)) args.push(...value);\n\t\t} else if (value !== null && typeof value === 'string') {\n\t\t\targs.push(value);\n\t\t}\n\t}\n\n\treturn [definition.executable, ...args].join(' ');\n}\n\n/**\n * Where a configured program runs, as a real path inside a `write` root. `program` names it in\n * the error, as `Command \"test\"`.\n */\nexport async function resolveCwd(\n\tprogram: string,\n\tcwd: string | undefined,\n\tpolicy: AccessPolicy,\n\tbase: string,\n): Promise<string> {\n\tconst named = resolve(base, cwd ?? '.');\n\tlet real: string;\n\ttry {\n\t\treal = await realpath(named);\n\t\tawait policy.check(real, 'write', AbortSignal.timeout(cwdCheckMs));\n\t} catch (error) {\n\t\tthrow new InvalidAgentConfigError(\n\t\t\t`${program} needs a cwd inside a write root of the workspace`,\n\t\t\t{ cause: error },\n\t\t);\n\t}\n\treturn real;\n}\n\n/**\n * Each path, once the policy lets the agent read it, relative to where the command runs. One\n * that would read as a flag is given as `./` and its name.\n */\nasync function resolvePaths(\n\tpaths: string[],\n\tcwd: string,\n\tpolicy: AccessPolicy,\n\tsignal: AbortSignal,\n): Promise<string[]> {\n\tconst resolved: string[] = [];\n\tfor (const path of paths) {\n\t\tconst { absolute } = await policy.check(path, 'read', signal);\n\t\tconst fromCwd = relative(cwd, absolute) || '.';\n\t\tresolved.push(fromCwd.startsWith('-') && !isAbsolute(fromCwd) ? `./${fromCwd}` : fromCwd);\n\t}\n\treturn resolved;\n}\n\n/** A small environment of its own: the inherited few, then what the consumer set. */\nexport function environmentFor(\n\tdefinition: Pick<CommandDefinition, 'env'>,\n\tenv: NodeJS.ProcessEnv = process.env,\n): NodeJS.ProcessEnv {\n\tconst inherited: NodeJS.ProcessEnv = {};\n\tfor (const [variable, value] of Object.entries(env)) {\n\t\tif (value === undefined) continue;\n\t\tif (inheritedVariables.includes(variable) || variable.startsWith('LC_')) {\n\t\t\tinherited[variable] = value;\n\t\t}\n\t}\n\treturn { ...inherited, ...definition.env };\n}\n\nfunction describeTool(commands: Commands): string {\n\tconst list = Object.entries(commands)\n\t\t.map(([name, { description }]) => `- ${name}: ${description}`)\n\t\t.join('\\n');\n\treturn [\n\t\t\"Runs one of the project's configured commands, by name, such as its tests or its formatter.\",\n\t\t'Files it changes are recorded in the history, so they can be undone. Its output is cut to its end.',\n\t\t'Commands:',\n\t\tlist,\n\t].join('\\n');\n}\n\n/**\n * One object per command, told apart by `name`, each with the parameters that command takes.\n * Strict mode needs every key present, so a parameter that may be left out takes `null`.\n */\nfunction inputSchema(commands: Commands) {\n\tconst variants = Object.entries(commands).map(([name, definition]) =>\n\t\tz.object({\n\t\t\tname: z.enum([name]),\n\t\t\t...Object.fromEntries(\n\t\t\t\tparametersOf(definition).map(parameter => [parameter.name, parameterSchema(parameter)]),\n\t\t\t),\n\t\t}),\n\t);\n\tconst [first, ...rest] = variants;\n\t// `validateCommands` refuses an empty configuration.\n\tif (!first) throw new InvalidAgentConfigError('No command is configured');\n\treturn z.object({\n\t\tcommand: (rest.length === 0 ? first : z.union([first, ...rest])).describe(\n\t\t\t'The command to run, and its parameters.',\n\t\t),\n\t});\n}\n\nfunction parameterSchema(parameter: CommandParameter): z.ZodType {\n\tswitch (parameter.type) {\n\t\tcase 'paths':\n\t\t\treturn z\n\t\t\t\t.array(z.string())\n\t\t\t\t.describe(`${parameter.description} Paths inside the workspace; [] for none.`);\n\t\tcase 'choice': {\n\t\t\tconst values = z.enum(parameter.values as [string, ...string[]]);\n\t\t\treturn parameter.required === true\n\t\t\t\t? values.describe(parameter.description)\n\t\t\t\t: values.nullable().describe(`${parameter.description} null to leave it out.`);\n\t\t}\n\t\tcase 'text': {\n\t\t\tconst text = z.string();\n\t\t\tconst description = `${parameter.description} Must match the regular expression ${parameter.pattern}, and not start with \"-\".`;\n\t\t\treturn parameter.required === true\n\t\t\t\t? text.describe(description)\n\t\t\t\t: text.nullable().describe(`${description} null to leave it out.`);\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"commandTool.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/commandTool.ts"],"names":[],"mappings":"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","sourcesContent":["import { realpath } from 'node:fs/promises';\nimport { isAbsolute, relative, resolve } from 'node:path';\nimport { z } from 'zod';\nimport type { CommandLine } from '#src/agent/domain/approval';\nimport type { RunContext } from '#src/agent/domain/runContext';\nimport { displayPath } from '#src/recovery/domain/runChanges';\nimport { InvalidAgentConfigError } from '#src/shared/domain/errors';\nimport type { AccessPolicy } from '../domain/accessPolicy.ts';\nimport {\n\tbuildArguments,\n\ttype CommandDefinition,\n\ttype CommandParameter,\n\ttype Commands,\n\tdefaultCommandOutputBytes,\n\tdefaultCommandTimeoutMs,\n\ttype ParameterValue,\n\tparametersOf,\n\tvalidateCommands,\n} from '../domain/commands.ts';\nimport { EditRefusedError, EditUnconfirmedError } from '../domain/fileEdits.ts';\nimport {\n\ttype CommandEnding,\n\tToolExecutionError,\n\ttype PreparingTool,\n} from '../domain/preparedCall.ts';\nimport { CommandCheckpoint, type CoveredFiles } from './commandCheckpoint.ts';\nimport { OutputFingerprint, withoutColors } from './commandOutput.ts';\nimport { type CommandProcessResult, runCommandProcess } from './commandProcess.ts';\nimport { definePreparingTool } from './defineTool.ts';\nimport type { WorkspaceWrites } from './workspaceWrites.ts';\nimport type { WriteSession } from './writeSession.ts';\n\n// Only what a toolchain needs to find itself and its locale. Everything else, the provider\n// keys included, stays in this process.\nconst inheritedVariables = ['PATH', 'HOME', 'TMPDIR', 'USER', 'SHELL', 'LANG'];\n// Long enough for a slow disk, short enough not to hold up building an agent.\nconst cwdCheckMs = 10_000;\n\n/** A command ready to run: its definition, and where it runs as a real path. */\ninterface ResolvedCommand {\n\tdefinition: CommandDefinition;\n\tcwd: string;\n}\n\n/**\n * `runCommand`, which runs one of the commands the consumer configured, by name, with the\n * parameters each one declares. Every call asks the approver, showing the program exactly as\n * it will run; `autoApprove` skips that.\n *\n * Around each command, the covered files are captured before and compared after, so what it\n * changed is in the run's history and can be undone: see `CommandCheckpoint`. If the capture\n * fails, the command does not run. A timeout or a cancellation stops its whole process group,\n * and is never reported as a completed command. If the group cannot be confirmed stopped, what\n * changed so far is recorded, the observation stays open, and writing stops for the run.\n *\n * This is not a sandbox: a command runs the repository's code, with everything this user can\n * reach. See \"Configured commands are trusted, not sandboxed\" in decisions.md.\n *\n * @throws {InvalidAgentConfigError} for an invalid configuration, a working directory outside\n * every `write` root, or on Windows, where a process group cannot be stopped.\n */\nexport async function createCommandTool({\n\tcommands,\n\tpolicy,\n\twrites,\n\tcovered,\n\tbase,\n\tplatform = process.platform,\n\trunProcess = runCommandProcess,\n}: {\n\tcommands: Commands;\n\tpolicy: AccessPolicy;\n\twrites: WorkspaceWrites;\n\tcovered: CoveredFiles;\n\t/** The real path of the first root, where a relative `cwd` starts. */\n\tbase: string;\n\tplatform?: NodeJS.Platform;\n\t/** Runs the program. Replaced in tests, to reach what a real process rarely does. */\n\trunProcess?: typeof runCommandProcess;\n}): Promise<PreparingTool> {\n\tif (platform === 'win32') {\n\t\tthrow new InvalidAgentConfigError(\n\t\t\t'Configured commands are not supported on Windows yet: the harness cannot stop a command and everything it started there',\n\t\t);\n\t}\n\tvalidateCommands(commands);\n\tconst resolved = new Map<string, ResolvedCommand>();\n\tfor (const [name, definition] of Object.entries(commands)) {\n\t\tresolved.set(name, {\n\t\t\tdefinition,\n\t\t\tcwd: await resolveCwd(`Command \"${name}\"`, definition.cwd, policy, base),\n\t\t});\n\t}\n\n\treturn definePreparingTool({\n\t\tname: 'runCommand',\n\t\tdescription: describeTool(commands),\n\t\tinput: inputSchema(commands),\n\t\tsummarize: ({ command: { name, ...values } }) => {\n\t\t\tconst definition = commands[name];\n\t\t\t// The schema only permits configured names, but keep this defensive for callers that\n\t\t\t// use a tool outside LLMAgent. This description never uses the resolved cwd.\n\t\t\treturn definition ? visibleCommand(definition, values) : name;\n\t\t},\n\t\tprepare: async ({ command: { name, ...values } }, signal, call) => {\n\t\t\tconst { run } = call;\n\t\t\tconst command = resolved.get(name);\n\t\t\t// The schema allows only the configured names.\n\t\t\tif (!command) throw new Error(`There is no command named \"${name}\"`);\n\t\t\t// A command can change anything in the write roots.\n\t\t\tawait writes.checkCeiling(call);\n\t\t\tconst paramValues: Record<string, ParameterValue> = {};\n\t\t\tfor (const parameter of parametersOf(command.definition)) {\n\t\t\t\tconst value =\n\t\t\t\t\t(values as Record<string, ParameterValue | undefined>)[parameter.name] ?? null;\n\t\t\t\tparamValues[parameter.name] =\n\t\t\t\t\tparameter.type === 'paths' && Array.isArray(value)\n\t\t\t\t\t\t? await resolvePaths(value, command.cwd, policy, signal)\n\t\t\t\t\t\t: value;\n\t\t\t}\n\t\t\tconst line: CommandLine = Object.freeze({\n\t\t\t\texecutable: command.definition.executable,\n\t\t\t\targs: Object.freeze(buildArguments(command.definition, paramValues)) as string[],\n\t\t\t\tcwd: command.cwd,\n\t\t\t});\n\n\t\t\t// Now, so a set of tools that cannot join the run's writes is refused before the user is asked.\n\t\t\twrites.sessionFor(run);\n\t\t\tlet ending: CommandEnding | undefined;\n\t\t\treturn {\n\t\t\t\t// A command can change anything its user can: it is always asked about.\n\t\t\t\trisk: 'destructive',\n\t\t\t\tcommand: line,\n\t\t\t\tending: () => ending,\n\t\t\t\trun: runSignal =>\n\t\t\t\t\trunCommand({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tline,\n\t\t\t\t\t\tdefinition: command.definition,\n\t\t\t\t\t\tcontext: run,\n\t\t\t\t\t\tsignal: runSignal,\n\t\t\t\t\t\tended: end => {\n\t\t\t\t\t\t\tending = end;\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t};\n\t\t},\n\t});\n\n\tinterface CommandRun {\n\t\tname: string;\n\t\tline: CommandLine;\n\t\tdefinition: CommandDefinition;\n\t\tcontext: RunContext;\n\t\tsignal: AbortSignal;\n\t\t/** Told how the command ended, once it ran to an end and everything it changed is recorded. */\n\t\tended: (ending: CommandEnding) => void;\n\t}\n\n\t// Alone: a change another agent of the run made while it ran would be taken for its own.\n\tfunction runCommand(run: CommandRun): Promise<string> {\n\t\tconst session = writes.sessionFor(run.context);\n\t\treturn session.exclusive(() => runCommandAlone(run, session), run.signal);\n\t}\n\n\tasync function runCommandAlone(\n\t\t{ name, line, definition, signal, ended }: CommandRun,\n\t\tsession: WriteSession,\n\t): Promise<string> {\n\t\tsignal.throwIfAborted();\n\t\tconst journal = await session.journalForChange();\n\t\tlet checkpoint: CommandCheckpoint;\n\t\ttry {\n\t\t\tcheckpoint = await CommandCheckpoint.begin({ journal, covered, by: name, signal });\n\t\t} catch (error) {\n\t\t\tsignal.throwIfAborted();\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`The harness could not capture the files before \"${name}\", so it did not run`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\n\t\tlet result: CommandProcessResult | undefined;\n\t\tlet startFailure: unknown;\n\t\tconst fingerprint = new OutputFingerprint();\n\t\ttry {\n\t\t\tresult = await runProcess(line.executable, line.args, {\n\t\t\t\tcwd: line.cwd,\n\t\t\t\tenv: environmentFor(definition),\n\t\t\t\ttimeoutMs: definition.timeoutMs ?? defaultCommandTimeoutMs,\n\t\t\t\toutputBytes: definition.outputBytes ?? defaultCommandOutputBytes,\n\t\t\t\tsignal,\n\t\t\t\tonOutput: (stream, chunk) => {\n\t\t\t\t\tfingerprint.push(stream, chunk);\n\t\t\t\t},\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tstartFailure = error;\n\t\t}\n\n\t\tconst complete = result?.stopped ?? true;\n\t\tlet observed: Awaited<ReturnType<CommandCheckpoint['end']>>;\n\t\ttry {\n\t\t\tobserved = await checkpoint.end({ complete });\n\t\t} catch (error) {\n\t\t\tsession.stop('Writing stopped: the harness could not record what a command changed');\n\t\t\tthrow new EditUnconfirmedError(\n\t\t\t\t`\"${name}\" ran, but the harness could not record what it changed; writing has stopped for this run`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\n\t\tif (!result) {\n\t\t\tsignal.throwIfAborted();\n\t\t\tthrow new EditRefusedError(`\"${name}\" could not be started`, { cause: startFailure });\n\t\t}\n\t\tconst report = describeRun(name, definition, result, observed);\n\t\tif (!complete) {\n\t\t\tsession.stop('Writing stopped: a command could not be stopped');\n\t\t\tthrow new EditUnconfirmedError(\n\t\t\t\t`${report}\\n\"${name}\" could not be stopped: something it started may still be changing files, and the history may not hold all of it. Writing has stopped for this run.`,\n\t\t\t);\n\t\t}\n\t\t// After its changes are recorded: a cancelled command is never reported as finished.\n\t\tif (result.outcome === 'cancelled') signal.throwIfAborted();\n\t\tended({\n\t\t\tpassed: result.outcome === 'exited' && result.exitCode === 0,\n\t\t\thow: howItEnded(result),\n\t\t\toutput: withoutColors(result.output),\n\t\t\tfingerprint: fingerprint.digest(),\n\t\t\t...(observed.unrecorded.length === 0 ? { state: journal.contentState() } : {}),\n\t\t\tchanged: journal.appliedPaths.map(path => displayPath(base, path)),\n\t\t});\n\t\tif (result.outcome !== 'exited') throw new ToolExecutionError(report);\n\t\tif (result.exitCode !== 0) {\n\t\t\tthrow new ToolExecutionError(report, {\n\t\t\t\t...(result.exitCode === null ? {} : { exitCode: result.exitCode }),\n\t\t\t});\n\t\t}\n\t\treturn report;\n\t}\n\n\tfunction describeRun(\n\t\tname: string,\n\t\tdefinition: CommandDefinition,\n\t\tresult: CommandProcessResult,\n\t\t{ changes, unrecorded }: Awaited<ReturnType<CommandCheckpoint['end']>>,\n\t): string {\n\t\tconst seconds = (result.durationMs / 1000).toFixed(1);\n\t\tconst lines: string[] = [];\n\t\tif (result.outcome === 'timedOut') {\n\t\t\tconst limit = (definition.timeoutMs ?? defaultCommandTimeoutMs) / 1000;\n\t\t\tlines.push(`\"${name}\" ran past its limit of ${String(limit)} s and was stopped.`);\n\t\t} else if (result.outcome === 'cancelled') {\n\t\t\tlines.push(`\"${name}\" was stopped: the run was cancelled.`);\n\t\t} else if (result.exitCode !== null) {\n\t\t\tlines.push(`\"${name}\" exited with code ${String(result.exitCode)} after ${seconds} s.`);\n\t\t} else {\n\t\t\tlines.push(`\"${name}\" was killed by ${result.signal ?? 'a signal'} after ${seconds} s.`);\n\t\t}\n\n\t\tconst shown = (path: string) => displayPath(base, path);\n\t\tlines.push(\n\t\t\tchanges.length === 0\n\t\t\t\t? 'It changed no file the history covers.'\n\t\t\t\t: `Files it changed, recorded in the history: ${changes.map(change => shown(change.path)).join(', ')}.`,\n\t\t);\n\t\tif (unrecorded.length > 0) {\n\t\t\tlines.push(\n\t\t\t\t`Not recorded, because the history cannot copy them (a folder, a link, or a file over 64 MB) before or after: ${unrecorded.map(shown).join(', ')}. Undo leaves them as they are.`,\n\t\t\t);\n\t\t}\n\n\t\tconst limit = definition.outputBytes ?? defaultCommandOutputBytes;\n\t\tif (result.printedBytes === 0) {\n\t\t\tlines.push('It printed nothing.');\n\t\t} else {\n\t\t\tlines.push(\n\t\t\t\tresult.printedBytes > limit\n\t\t\t\t\t? `Output, the last ${kilobytes(limit)} of ${kilobytes(result.printedBytes)}:`\n\t\t\t\t\t: 'Output:',\n\t\t\t\tresult.output,\n\t\t\t);\n\t\t}\n\t\treturn lines.join('\\n');\n\t}\n}\n\nfunction howItEnded(result: CommandProcessResult): string {\n\tif (result.outcome === 'timedOut') return 'ran past its time limit';\n\tif (result.outcome === 'cancelled') return 'was cancelled';\n\tif (result.exitCode !== null) return `exited with code ${String(result.exitCode)}`;\n\treturn `was killed by ${result.signal ?? 'a signal'}`;\n}\n\nfunction kilobytes(bytes: number): string {\n\treturn `${String(Math.ceil(bytes / 1024))} KB`;\n}\n\n/** The command line the model can see, before paths are resolved against the host workspace. */\nfunction visibleCommand(\n\tdefinition: CommandDefinition,\n\tvalues: Record<string, ParameterValue | undefined>,\n): string {\n\tconst args: string[] = [];\n\tfor (const argument of definition.args ?? []) {\n\t\tif (typeof argument === 'string') {\n\t\t\targs.push(argument);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst value = values[argument.name] ?? null;\n\t\tif (argument.type === 'paths') {\n\t\t\tif (Array.isArray(value)) args.push(...value);\n\t\t} else if (value !== null && typeof value === 'string') {\n\t\t\targs.push(value);\n\t\t}\n\t}\n\n\treturn [definition.executable, ...args].join(' ');\n}\n\n/**\n * Where a configured program runs, as a real path inside a `write` root. `program` names it in\n * the error, as `Command \"test\"`.\n */\nexport async function resolveCwd(\n\tprogram: string,\n\tcwd: string | undefined,\n\tpolicy: AccessPolicy,\n\tbase: string,\n): Promise<string> {\n\tconst named = resolve(base, cwd ?? '.');\n\tlet real: string;\n\ttry {\n\t\treal = await realpath(named);\n\t\tawait policy.check(real, 'write', AbortSignal.timeout(cwdCheckMs));\n\t} catch (error) {\n\t\tthrow new InvalidAgentConfigError(\n\t\t\t`${program} needs a cwd inside a write root of the workspace`,\n\t\t\t{ cause: error },\n\t\t);\n\t}\n\treturn real;\n}\n\n/**\n * Each path, once the policy lets the agent read it, relative to where the command runs. One\n * that would read as a flag is given as `./` and its name.\n */\nasync function resolvePaths(\n\tpaths: string[],\n\tcwd: string,\n\tpolicy: AccessPolicy,\n\tsignal: AbortSignal,\n): Promise<string[]> {\n\tconst resolved: string[] = [];\n\tfor (const path of paths) {\n\t\tconst { absolute } = await policy.check(path, 'read', signal);\n\t\tconst fromCwd = relative(cwd, absolute) || '.';\n\t\tresolved.push(fromCwd.startsWith('-') && !isAbsolute(fromCwd) ? `./${fromCwd}` : fromCwd);\n\t}\n\treturn resolved;\n}\n\n/** A small environment of its own: the inherited few, then what the consumer set. */\nexport function environmentFor(\n\tdefinition: Pick<CommandDefinition, 'env'>,\n\tenv: NodeJS.ProcessEnv = process.env,\n): NodeJS.ProcessEnv {\n\tconst inherited: NodeJS.ProcessEnv = {};\n\tfor (const [variable, value] of Object.entries(env)) {\n\t\tif (value === undefined) continue;\n\t\tif (inheritedVariables.includes(variable) || variable.startsWith('LC_')) {\n\t\t\tinherited[variable] = value;\n\t\t}\n\t}\n\treturn { ...inherited, ...definition.env };\n}\n\nfunction describeTool(commands: Commands): string {\n\tconst list = Object.entries(commands)\n\t\t.map(([name, { description }]) => `- ${name}: ${description}`)\n\t\t.join('\\n');\n\treturn [\n\t\t\"Runs one of the project's configured commands, by name, such as its tests or its formatter.\",\n\t\t'Files it changes are recorded in the history, so they can be undone. Its output is cut to its end.',\n\t\t'Commands:',\n\t\tlist,\n\t].join('\\n');\n}\n\n/**\n * One object per command, told apart by `name`, each with the parameters that command takes.\n * Strict mode needs every key present, so a parameter that may be left out takes `null`.\n */\nfunction inputSchema(commands: Commands) {\n\tconst variants = Object.entries(commands).map(([name, definition]) =>\n\t\tz.object({\n\t\t\tname: z.enum([name]),\n\t\t\t...Object.fromEntries(\n\t\t\t\tparametersOf(definition).map(parameter => [parameter.name, parameterSchema(parameter)]),\n\t\t\t),\n\t\t}),\n\t);\n\tconst [first, ...rest] = variants;\n\t// `validateCommands` refuses an empty configuration.\n\tif (!first) throw new InvalidAgentConfigError('No command is configured');\n\treturn z.object({\n\t\tcommand: (rest.length === 0 ? first : z.union([first, ...rest])).describe(\n\t\t\t'The command to run, and its parameters.',\n\t\t),\n\t});\n}\n\nfunction parameterSchema(parameter: CommandParameter): z.ZodType {\n\tswitch (parameter.type) {\n\t\tcase 'paths':\n\t\t\treturn z\n\t\t\t\t.array(z.string())\n\t\t\t\t.describe(`${parameter.description} Paths inside the workspace; [] for none.`);\n\t\tcase 'choice': {\n\t\t\tconst values = z.enum(parameter.values as [string, ...string[]]);\n\t\t\treturn parameter.required === true\n\t\t\t\t? values.describe(parameter.description)\n\t\t\t\t: values.nullable().describe(`${parameter.description} null to leave it out.`);\n\t\t}\n\t\tcase 'text': {\n\t\t\tconst text = z.string();\n\t\t\tconst description = `${parameter.description} Must match the regular expression ${parameter.pattern}, and not start with \"-\".`;\n\t\t\treturn parameter.required === true\n\t\t\t\t? text.describe(description)\n\t\t\t\t: text.nullable().describe(`${description} null to leave it out.`);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -17,6 +17,8 @@ async function explore(question, subagent, explorer, signal, call) {
|
|
|
17
17
|
subagent,
|
|
18
18
|
task: question,
|
|
19
19
|
options: { signal },
|
|
20
|
+
// It only finds and reads.
|
|
21
|
+
writeCeiling: [],
|
|
20
22
|
});
|
|
21
23
|
try {
|
|
22
24
|
const { response, tokens } = await explorer().run(question, options);
|