@mikode13/harness 4.0.0 → 5.0.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 +285 -20
- package/dist/agent/domain/agent.d.ts +12 -0
- package/dist/agent/domain/agent.js.map +1 -1
- package/dist/agent/domain/approval.d.ts +14 -0
- package/dist/agent/domain/approval.js +28 -3
- package/dist/agent/domain/approval.js.map +1 -1
- package/dist/agent/domain/runContext.d.ts +31 -0
- package/dist/agent/domain/runContext.js +47 -0
- package/dist/agent/domain/runContext.js.map +1 -0
- package/dist/diff/domain/unifiedDiff.d.ts +19 -0
- package/dist/diff/domain/unifiedDiff.js +166 -0
- package/dist/diff/domain/unifiedDiff.js.map +1 -0
- package/dist/engines/domain/model/historyFollower.d.ts +41 -0
- package/dist/engines/domain/model/historyFollower.js +158 -0
- package/dist/engines/domain/model/historyFollower.js.map +1 -0
- package/dist/engines/domain/model/llmAgent.d.ts +42 -4
- package/dist/engines/domain/model/llmAgent.js +224 -36
- package/dist/engines/domain/model/llmAgent.js.map +1 -1
- package/dist/factory/infrastructure/agentFactory.d.ts +14 -0
- package/dist/factory/infrastructure/agentFactory.js +20 -2
- package/dist/factory/infrastructure/agentFactory.js.map +1 -1
- package/dist/factory/infrastructure/agentLLMFactory.d.ts +119 -14
- package/dist/factory/infrastructure/agentLLMFactory.js +284 -57
- package/dist/factory/infrastructure/agentLLMFactory.js.map +1 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/llm/domain/conversation.d.ts +44 -4
- package/dist/llm/domain/conversation.js +56 -7
- package/dist/llm/domain/conversation.js.map +1 -1
- package/dist/llm/domain/llm.d.ts +3 -1
- package/dist/llm/domain/llm.js.map +1 -1
- package/dist/llm/domain/tool.d.ts +13 -0
- package/dist/llm/domain/tool.js +12 -1
- package/dist/llm/domain/tool.js.map +1 -1
- package/dist/llm/infrastructure/claudeLLMClient.d.ts +3 -2
- package/dist/llm/infrastructure/claudeLLMClient.js +38 -10
- package/dist/llm/infrastructure/claudeLLMClient.js.map +1 -1
- package/dist/llm/infrastructure/openAILLMClient.d.ts +2 -1
- package/dist/llm/infrastructure/openAILLMClient.js +66 -16
- package/dist/llm/infrastructure/openAILLMClient.js.map +1 -1
- package/dist/orchestration/domain/model/orchestratorAgent.d.ts +11 -2
- package/dist/orchestration/domain/model/orchestratorAgent.js +43 -4
- package/dist/orchestration/domain/model/orchestratorAgent.js.map +1 -1
- package/dist/recovery/domain/fileChanges.d.ts +31 -0
- package/dist/recovery/domain/fileChanges.js +48 -0
- package/dist/recovery/domain/fileChanges.js.map +1 -0
- package/dist/recovery/domain/history.d.ts +114 -0
- package/dist/recovery/domain/history.js +62 -0
- package/dist/recovery/domain/history.js.map +1 -0
- package/dist/recovery/domain/recoveryStore.d.ts +233 -0
- package/dist/recovery/domain/recoveryStore.js +32 -0
- package/dist/recovery/domain/recoveryStore.js.map +1 -0
- package/dist/recovery/domain/retention.d.ts +33 -0
- package/dist/recovery/domain/retention.js +90 -0
- package/dist/recovery/domain/retention.js.map +1 -0
- package/dist/recovery/domain/runChanges.d.ts +37 -0
- package/dist/recovery/domain/runChanges.js +109 -0
- package/dist/recovery/domain/runChanges.js.map +1 -0
- package/dist/recovery/domain/runTree.d.ts +32 -0
- package/dist/recovery/domain/runTree.js +90 -0
- package/dist/recovery/domain/runTree.js.map +1 -0
- package/dist/recovery/infrastructure/createHistory.d.ts +11 -0
- package/dist/recovery/infrastructure/createHistory.js +17 -0
- package/dist/recovery/infrastructure/createHistory.js.map +1 -0
- package/dist/recovery/infrastructure/fileRecoveryStore.d.ts +133 -0
- package/dist/recovery/infrastructure/fileRecoveryStore.js +1043 -0
- package/dist/recovery/infrastructure/fileRecoveryStore.js.map +1 -0
- package/dist/recovery/infrastructure/workspaceFiles.d.ts +30 -0
- package/dist/recovery/infrastructure/workspaceFiles.js +125 -0
- package/dist/recovery/infrastructure/workspaceFiles.js.map +1 -0
- package/dist/tools/domain/accessPolicy.d.ts +30 -0
- package/dist/tools/domain/accessPolicy.js +11 -0
- package/dist/tools/domain/accessPolicy.js.map +1 -0
- package/dist/tools/domain/commands.d.ts +93 -0
- package/dist/tools/domain/commands.js +191 -0
- package/dist/tools/domain/commands.js.map +1 -0
- package/dist/tools/domain/editResponse.d.ts +14 -0
- package/dist/tools/domain/editResponse.js +79 -0
- package/dist/tools/domain/editResponse.js.map +1 -0
- package/dist/tools/domain/fileEdits.d.ts +64 -0
- package/dist/tools/domain/fileEdits.js +21 -0
- package/dist/tools/domain/fileEdits.js.map +1 -0
- package/dist/tools/domain/lineLength.d.ts +7 -0
- package/dist/tools/domain/lineLength.js +26 -0
- package/dist/tools/domain/lineLength.js.map +1 -0
- package/dist/tools/domain/preparedCall.d.ts +31 -0
- package/dist/tools/domain/preparedCall.js +14 -0
- package/dist/tools/domain/preparedCall.js.map +1 -0
- package/dist/tools/domain/readRegistry.d.ts +46 -0
- package/dist/tools/domain/readRegistry.js +62 -0
- package/dist/tools/domain/readRegistry.js.map +1 -0
- package/dist/tools/domain/textLines.d.ts +26 -0
- package/dist/tools/domain/textLines.js +44 -0
- package/dist/tools/domain/textLines.js.map +1 -0
- package/dist/tools/domain/v4aPatch.d.ts +17 -0
- package/dist/tools/domain/v4aPatch.js +236 -0
- package/dist/tools/domain/v4aPatch.js.map +1 -0
- package/dist/tools/infrastructure/boundedWorkspace.d.ts +11 -2
- package/dist/tools/infrastructure/boundedWorkspace.js +9 -1
- package/dist/tools/infrastructure/boundedWorkspace.js.map +1 -1
- package/dist/tools/infrastructure/commandCheckpoint.d.ts +55 -0
- package/dist/tools/infrastructure/commandCheckpoint.js +178 -0
- package/dist/tools/infrastructure/commandCheckpoint.js.map +1 -0
- package/dist/tools/infrastructure/commandProcess.d.ts +47 -0
- package/dist/tools/infrastructure/commandProcess.js +217 -0
- package/dist/tools/infrastructure/commandProcess.js.map +1 -0
- package/dist/tools/infrastructure/commandTool.d.ts +36 -0
- package/dist/tools/infrastructure/commandTool.js +254 -0
- package/dist/tools/infrastructure/commandTool.js.map +1 -0
- package/dist/tools/infrastructure/createWorkspace.d.ts +10 -0
- package/dist/tools/infrastructure/createWorkspace.js +11 -3
- package/dist/tools/infrastructure/createWorkspace.js.map +1 -1
- package/dist/tools/infrastructure/defineTool.d.ts +14 -0
- package/dist/tools/infrastructure/defineTool.js +28 -5
- package/dist/tools/infrastructure/defineTool.js.map +1 -1
- package/dist/tools/infrastructure/editFormats.d.ts +22 -0
- package/dist/tools/infrastructure/editFormats.js +211 -0
- package/dist/tools/infrastructure/editFormats.js.map +1 -0
- package/dist/tools/infrastructure/fileEditor.d.ts +64 -0
- package/dist/tools/infrastructure/fileEditor.js +306 -0
- package/dist/tools/infrastructure/fileEditor.js.map +1 -0
- package/dist/tools/infrastructure/fileTools.d.ts +81 -0
- package/dist/tools/infrastructure/fileTools.js +152 -0
- package/dist/tools/infrastructure/fileTools.js.map +1 -0
- package/dist/tools/infrastructure/gitIgnoreRules.d.ts +15 -0
- package/dist/tools/infrastructure/gitIgnoreRules.js +74 -0
- package/dist/tools/infrastructure/gitIgnoreRules.js.map +1 -0
- package/dist/tools/infrastructure/gitWorkspace.d.ts +1 -1
- package/dist/tools/infrastructure/gitWorkspace.js +1 -1
- package/dist/tools/infrastructure/gitWorkspace.js.map +1 -1
- package/dist/tools/infrastructure/ignoreFileChanges.d.ts +14 -0
- package/dist/tools/infrastructure/ignoreFileChanges.js +62 -0
- package/dist/tools/infrastructure/ignoreFileChanges.js.map +1 -0
- package/dist/tools/infrastructure/policyWorkspace.d.ts +52 -0
- package/dist/tools/infrastructure/policyWorkspace.js +32 -0
- package/dist/tools/infrastructure/policyWorkspace.js.map +1 -0
- package/dist/tools/infrastructure/ripgrepWorkspace.d.ts +2 -1
- package/dist/tools/infrastructure/ripgrepWorkspace.js +1 -1
- package/dist/tools/infrastructure/ripgrepWorkspace.js.map +1 -1
- package/dist/tools/infrastructure/rootsAccessPolicy.d.ts +100 -0
- package/dist/tools/infrastructure/rootsAccessPolicy.js +301 -0
- package/dist/tools/infrastructure/rootsAccessPolicy.js.map +1 -0
- package/dist/tools/infrastructure/runProcess.d.ts +4 -1
- package/dist/tools/infrastructure/runProcess.js +4 -2
- package/dist/tools/infrastructure/runProcess.js.map +1 -1
- package/dist/tools/infrastructure/secretPaths.d.ts +8 -0
- package/dist/tools/infrastructure/secretPaths.js +76 -0
- package/dist/tools/infrastructure/secretPaths.js.map +1 -0
- package/dist/tools/infrastructure/showChangesTool.d.ts +12 -0
- package/dist/tools/infrastructure/showChangesTool.js +43 -0
- package/dist/tools/infrastructure/showChangesTool.js.map +1 -0
- package/dist/tools/infrastructure/trackedEdits.d.ts +33 -0
- package/dist/tools/infrastructure/trackedEdits.js +70 -0
- package/dist/tools/infrastructure/trackedEdits.js.map +1 -0
- package/dist/tools/infrastructure/trackedReadFile.d.ts +21 -0
- package/dist/tools/infrastructure/trackedReadFile.js +78 -0
- package/dist/tools/infrastructure/trackedReadFile.js.map +1 -0
- package/dist/tools/infrastructure/workspaceTools.d.ts +28 -0
- package/dist/tools/infrastructure/workspaceTools.js +53 -40
- package/dist/tools/infrastructure/workspaceTools.js.map +1 -1
- package/dist/tools/infrastructure/workspaceWrites.d.ts +23 -0
- package/dist/tools/infrastructure/workspaceWrites.js +42 -0
- package/dist/tools/infrastructure/workspaceWrites.js.map +1 -0
- package/dist/tools/infrastructure/writeSession.d.ts +35 -0
- package/dist/tools/infrastructure/writeSession.js +81 -0
- package/dist/tools/infrastructure/writeSession.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { realpath } from 'node:fs/promises';
|
|
2
|
+
import { isAbsolute, relative, resolve } from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { displayPath } from '#src/recovery/domain/runChanges';
|
|
5
|
+
import { InvalidAgentConfigError } from '#src/shared/domain/errors';
|
|
6
|
+
import { buildArguments, defaultCommandOutputBytes, defaultCommandTimeoutMs, parametersOf, validateCommands, } from "../domain/commands.js";
|
|
7
|
+
import { EditRefusedError, EditUnconfirmedError } from "../domain/fileEdits.js";
|
|
8
|
+
import { CommandCheckpoint } from "./commandCheckpoint.js";
|
|
9
|
+
import { runCommandProcess } from "./commandProcess.js";
|
|
10
|
+
import { definePreparingTool } from "./defineTool.js";
|
|
11
|
+
// Only what a toolchain needs to find itself and its locale. Everything else, the provider
|
|
12
|
+
// keys included, stays in this process.
|
|
13
|
+
const inheritedVariables = ['PATH', 'HOME', 'TMPDIR', 'USER', 'SHELL', 'LANG'];
|
|
14
|
+
// Long enough for a slow disk, short enough not to hold up building an agent.
|
|
15
|
+
const cwdCheckMs = 10_000;
|
|
16
|
+
/**
|
|
17
|
+
* `runCommand`, which runs one of the commands the consumer configured, by name, with the
|
|
18
|
+
* parameters each one declares. Every call asks the approver, showing the program exactly as
|
|
19
|
+
* it will run; `autoApprove` skips that.
|
|
20
|
+
*
|
|
21
|
+
* Around each command, the covered files are captured before and compared after, so what it
|
|
22
|
+
* changed is in the run's history and can be undone: see `CommandCheckpoint`. If the capture
|
|
23
|
+
* fails, the command does not run. A timeout or a cancellation stops its whole process group,
|
|
24
|
+
* and is never reported as a completed command. If the group cannot be confirmed stopped, what
|
|
25
|
+
* changed so far is recorded, the observation stays open, and writing stops for the run.
|
|
26
|
+
*
|
|
27
|
+
* This is not a sandbox: a command runs the repository's code, with everything this user can
|
|
28
|
+
* reach. See "Configured commands are trusted, not sandboxed" in decisions.md.
|
|
29
|
+
*
|
|
30
|
+
* @throws {InvalidAgentConfigError} for an invalid configuration, a working directory outside
|
|
31
|
+
* every `write` root, or on Windows, where a process group cannot be stopped.
|
|
32
|
+
*/
|
|
33
|
+
export async function createCommandTool({ commands, policy, writes, covered, base, platform = process.platform, runProcess = runCommandProcess, }) {
|
|
34
|
+
if (platform === 'win32') {
|
|
35
|
+
throw new InvalidAgentConfigError('Configured commands are not supported on Windows yet: the harness cannot stop a command and everything it started there');
|
|
36
|
+
}
|
|
37
|
+
validateCommands(commands);
|
|
38
|
+
const resolved = new Map();
|
|
39
|
+
for (const [name, definition] of Object.entries(commands)) {
|
|
40
|
+
resolved.set(name, { definition, cwd: await resolveCwd(name, definition, policy, base) });
|
|
41
|
+
}
|
|
42
|
+
return definePreparingTool({
|
|
43
|
+
name: 'runCommand',
|
|
44
|
+
description: describeTool(commands),
|
|
45
|
+
input: inputSchema(commands),
|
|
46
|
+
prepare: async ({ command: { name, ...values } }, signal, { run }) => {
|
|
47
|
+
const command = resolved.get(name);
|
|
48
|
+
// The schema allows only the configured names.
|
|
49
|
+
if (!command)
|
|
50
|
+
throw new Error(`There is no command named "${name}"`);
|
|
51
|
+
const paramValues = {};
|
|
52
|
+
for (const parameter of parametersOf(command.definition)) {
|
|
53
|
+
const value = values[parameter.name] ?? null;
|
|
54
|
+
paramValues[parameter.name] =
|
|
55
|
+
parameter.type === 'paths' && Array.isArray(value)
|
|
56
|
+
? await resolvePaths(value, command.cwd, policy, signal)
|
|
57
|
+
: value;
|
|
58
|
+
}
|
|
59
|
+
const line = Object.freeze({
|
|
60
|
+
executable: command.definition.executable,
|
|
61
|
+
args: Object.freeze(buildArguments(command.definition, paramValues)),
|
|
62
|
+
cwd: command.cwd,
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
// A command can change anything its user can: it is always asked about.
|
|
66
|
+
risk: 'destructive',
|
|
67
|
+
command: line,
|
|
68
|
+
run: runSignal => runCommand({
|
|
69
|
+
name,
|
|
70
|
+
line,
|
|
71
|
+
definition: command.definition,
|
|
72
|
+
context: run,
|
|
73
|
+
signal: runSignal,
|
|
74
|
+
}),
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
async function runCommand({ name, line, definition, context, signal, }) {
|
|
79
|
+
signal.throwIfAborted();
|
|
80
|
+
const session = writes.sessionFor(context);
|
|
81
|
+
const journal = await session.journalForChange();
|
|
82
|
+
let checkpoint;
|
|
83
|
+
try {
|
|
84
|
+
checkpoint = await CommandCheckpoint.begin({ journal, covered, by: name, signal });
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
signal.throwIfAborted();
|
|
88
|
+
throw new EditRefusedError(`The harness could not capture the files before "${name}", so it did not run`, { cause: error });
|
|
89
|
+
}
|
|
90
|
+
let result;
|
|
91
|
+
let startFailure;
|
|
92
|
+
try {
|
|
93
|
+
result = await runProcess(line.executable, line.args, {
|
|
94
|
+
cwd: line.cwd,
|
|
95
|
+
env: environmentFor(definition),
|
|
96
|
+
timeoutMs: definition.timeoutMs ?? defaultCommandTimeoutMs,
|
|
97
|
+
outputBytes: definition.outputBytes ?? defaultCommandOutputBytes,
|
|
98
|
+
signal,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
startFailure = error;
|
|
103
|
+
}
|
|
104
|
+
const complete = result?.stopped ?? true;
|
|
105
|
+
let observed;
|
|
106
|
+
try {
|
|
107
|
+
observed = await checkpoint.end({ complete });
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
session.stop('Writing stopped: the harness could not record what a command changed');
|
|
111
|
+
throw new EditUnconfirmedError(`"${name}" ran, but the harness could not record what it changed; writing has stopped for this run`, { cause: error });
|
|
112
|
+
}
|
|
113
|
+
if (!result) {
|
|
114
|
+
signal.throwIfAborted();
|
|
115
|
+
throw new EditRefusedError(`"${name}" could not be started`, { cause: startFailure });
|
|
116
|
+
}
|
|
117
|
+
const report = describeRun(name, definition, result, observed);
|
|
118
|
+
if (!complete) {
|
|
119
|
+
session.stop('Writing stopped: a command could not be stopped');
|
|
120
|
+
throw new EditUnconfirmedError(`${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.`);
|
|
121
|
+
}
|
|
122
|
+
// After its changes are recorded: a cancelled command is never reported as finished.
|
|
123
|
+
if (result.outcome === 'cancelled')
|
|
124
|
+
signal.throwIfAborted();
|
|
125
|
+
if (result.outcome !== 'exited')
|
|
126
|
+
throw new Error(report);
|
|
127
|
+
return report;
|
|
128
|
+
}
|
|
129
|
+
function describeRun(name, definition, result, { changes, unrecorded }) {
|
|
130
|
+
const seconds = (result.durationMs / 1000).toFixed(1);
|
|
131
|
+
const lines = [];
|
|
132
|
+
if (result.outcome === 'timedOut') {
|
|
133
|
+
const limit = (definition.timeoutMs ?? defaultCommandTimeoutMs) / 1000;
|
|
134
|
+
lines.push(`"${name}" ran past its limit of ${String(limit)} s and was stopped.`);
|
|
135
|
+
}
|
|
136
|
+
else if (result.outcome === 'cancelled') {
|
|
137
|
+
lines.push(`"${name}" was stopped: the run was cancelled.`);
|
|
138
|
+
}
|
|
139
|
+
else if (result.exitCode !== null) {
|
|
140
|
+
lines.push(`"${name}" exited with code ${String(result.exitCode)} after ${seconds} s.`);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
lines.push(`"${name}" was killed by ${result.signal ?? 'a signal'} after ${seconds} s.`);
|
|
144
|
+
}
|
|
145
|
+
const shown = (path) => displayPath(base, path);
|
|
146
|
+
lines.push(changes.length === 0
|
|
147
|
+
? 'It changed no file the history covers.'
|
|
148
|
+
: `Files it changed, recorded in the history: ${changes.map(change => shown(change.path)).join(', ')}.`);
|
|
149
|
+
if (unrecorded.length > 0) {
|
|
150
|
+
lines.push(`Not recorded, because the history cannot copy them (a folder, a link, or a file over 64 MB): ${unrecorded.map(shown).join(', ')}.`);
|
|
151
|
+
}
|
|
152
|
+
const limit = definition.outputBytes ?? defaultCommandOutputBytes;
|
|
153
|
+
if (result.printedBytes === 0) {
|
|
154
|
+
lines.push('It printed nothing.');
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
lines.push(result.printedBytes > limit
|
|
158
|
+
? `Output, the last ${kilobytes(limit)} of ${kilobytes(result.printedBytes)}:`
|
|
159
|
+
: 'Output:', result.output);
|
|
160
|
+
}
|
|
161
|
+
return lines.join('\n');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function kilobytes(bytes) {
|
|
165
|
+
return `${String(Math.ceil(bytes / 1024))} KB`;
|
|
166
|
+
}
|
|
167
|
+
/** Its working directory, as a real path inside a `write` root. */
|
|
168
|
+
async function resolveCwd(name, definition, policy, base) {
|
|
169
|
+
const named = resolve(base, definition.cwd ?? '.');
|
|
170
|
+
let real;
|
|
171
|
+
try {
|
|
172
|
+
real = await realpath(named);
|
|
173
|
+
await policy.check(real, 'write', AbortSignal.timeout(cwdCheckMs));
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
throw new InvalidAgentConfigError(`Command "${name}" needs a cwd inside a write root of the workspace`, { cause: error });
|
|
177
|
+
}
|
|
178
|
+
return real;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Each path, once the policy lets the agent read it, relative to where the command runs. One
|
|
182
|
+
* that would read as a flag is given as `./` and its name.
|
|
183
|
+
*/
|
|
184
|
+
async function resolvePaths(paths, cwd, policy, signal) {
|
|
185
|
+
const resolved = [];
|
|
186
|
+
for (const path of paths) {
|
|
187
|
+
const { absolute } = await policy.check(path, 'read', signal);
|
|
188
|
+
const fromCwd = relative(cwd, absolute) || '.';
|
|
189
|
+
resolved.push(fromCwd.startsWith('-') && !isAbsolute(fromCwd) ? `./${fromCwd}` : fromCwd);
|
|
190
|
+
}
|
|
191
|
+
return resolved;
|
|
192
|
+
}
|
|
193
|
+
/** A small environment of its own: the inherited few, then what the consumer set. */
|
|
194
|
+
export function environmentFor(definition, env = process.env) {
|
|
195
|
+
const inherited = {};
|
|
196
|
+
for (const [variable, value] of Object.entries(env)) {
|
|
197
|
+
if (value === undefined)
|
|
198
|
+
continue;
|
|
199
|
+
if (inheritedVariables.includes(variable) || variable.startsWith('LC_')) {
|
|
200
|
+
inherited[variable] = value;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return { ...inherited, ...definition.env };
|
|
204
|
+
}
|
|
205
|
+
function describeTool(commands) {
|
|
206
|
+
const list = Object.entries(commands)
|
|
207
|
+
.map(([name, { description }]) => `- ${name}: ${description}`)
|
|
208
|
+
.join('\n');
|
|
209
|
+
return [
|
|
210
|
+
"Runs one of the project's configured commands, by name, such as its tests or its formatter.",
|
|
211
|
+
'Files it changes are recorded in the history, so they can be undone. Its output is cut to its end.',
|
|
212
|
+
'Commands:',
|
|
213
|
+
list,
|
|
214
|
+
].join('\n');
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* One object per command, told apart by `name`, each with the parameters that command takes.
|
|
218
|
+
* Strict mode needs every key present, so a parameter that may be left out takes `null`.
|
|
219
|
+
*/
|
|
220
|
+
function inputSchema(commands) {
|
|
221
|
+
const variants = Object.entries(commands).map(([name, definition]) => z.object({
|
|
222
|
+
name: z.enum([name]),
|
|
223
|
+
...Object.fromEntries(parametersOf(definition).map(parameter => [parameter.name, parameterSchema(parameter)])),
|
|
224
|
+
}));
|
|
225
|
+
const [first, ...rest] = variants;
|
|
226
|
+
// `validateCommands` refuses an empty configuration.
|
|
227
|
+
if (!first)
|
|
228
|
+
throw new InvalidAgentConfigError('No command is configured');
|
|
229
|
+
return z.object({
|
|
230
|
+
command: (rest.length === 0 ? first : z.union([first, ...rest])).describe('The command to run, and its parameters.'),
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
function parameterSchema(parameter) {
|
|
234
|
+
switch (parameter.type) {
|
|
235
|
+
case 'paths':
|
|
236
|
+
return z
|
|
237
|
+
.array(z.string())
|
|
238
|
+
.describe(`${parameter.description} Paths inside the workspace; [] for none.`);
|
|
239
|
+
case 'choice': {
|
|
240
|
+
const values = z.enum(parameter.values);
|
|
241
|
+
return parameter.required === true
|
|
242
|
+
? values.describe(parameter.description)
|
|
243
|
+
: values.nullable().describe(`${parameter.description} null to leave it out.`);
|
|
244
|
+
}
|
|
245
|
+
case 'text': {
|
|
246
|
+
const text = z.string();
|
|
247
|
+
const description = `${parameter.description} Must match the regular expression ${parameter.pattern}, and not start with "-".`;
|
|
248
|
+
return parameter.required === true
|
|
249
|
+
? text.describe(description)
|
|
250
|
+
: text.nullable().describe(`${description} null to leave it out.`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=commandTool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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 type { PreparingTool } from '../domain/preparedCall.ts';\nimport { CommandCheckpoint, type CoveredFiles } from './commandCheckpoint.ts';\nimport { type CommandProcessResult, runCommandProcess } from './commandProcess.ts';\nimport { definePreparingTool } from './defineTool.ts';\nimport type { WorkspaceWrites } from './workspaceWrites.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, { definition, cwd: await resolveCwd(name, definition, policy, base) });\n\t}\n\n\treturn definePreparingTool({\n\t\tname: 'runCommand',\n\t\tdescription: describeTool(commands),\n\t\tinput: inputSchema(commands),\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\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\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}),\n\t\t\t};\n\t\t},\n\t});\n\n\tasync function runCommand({\n\t\tname,\n\t\tline,\n\t\tdefinition,\n\t\tcontext,\n\t\tsignal,\n\t}: {\n\t\tname: string;\n\t\tline: CommandLine;\n\t\tdefinition: CommandDefinition;\n\t\tcontext: RunContext;\n\t\tsignal: AbortSignal;\n\t}): Promise<string> {\n\t\tsignal.throwIfAborted();\n\t\tconst session = writes.sessionFor(context);\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\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});\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\tif (result.outcome !== 'exited') throw new Error(report);\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): ${unrecorded.map(shown).join(', ')}.`,\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 kilobytes(bytes: number): string {\n\treturn `${String(Math.ceil(bytes / 1024))} KB`;\n}\n\n/** Its working directory, as a real path inside a `write` root. */\nasync function resolveCwd(\n\tname: string,\n\tdefinition: CommandDefinition,\n\tpolicy: AccessPolicy,\n\tbase: string,\n): Promise<string> {\n\tconst named = resolve(base, definition.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`Command \"${name}\" 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: CommandDefinition,\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"]}
|
|
@@ -13,3 +13,13 @@ export declare function createWorkspace({ root, logger, }: {
|
|
|
13
13
|
/** Defaults to warnings on stderr. */
|
|
14
14
|
logger?: ILogger;
|
|
15
15
|
}): Promise<Workspace>;
|
|
16
|
+
/**
|
|
17
|
+
* `createWorkspace`, with `hidden`: files to treat as absent, by their path relative to the
|
|
18
|
+
* root. Internal: the file tools hide the secrets their access policy closes, before anything
|
|
19
|
+
* is counted or stored.
|
|
20
|
+
*/
|
|
21
|
+
export declare function chooseWorkspace({ root, logger, hidden, }: {
|
|
22
|
+
root: string;
|
|
23
|
+
logger: ILogger;
|
|
24
|
+
hidden?: (path: string) => boolean;
|
|
25
|
+
}): Promise<Workspace>;
|
|
@@ -37,16 +37,24 @@ async function isGitWorkTree(root) {
|
|
|
37
37
|
*
|
|
38
38
|
* @throws {UnrecoverableError} when neither ripgrep nor a git repository is available.
|
|
39
39
|
*/
|
|
40
|
-
export
|
|
40
|
+
export function createWorkspace({ root, logger = new Logger(), }) {
|
|
41
|
+
return chooseWorkspace({ root, logger });
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* `createWorkspace`, with `hidden`: files to treat as absent, by their path relative to the
|
|
45
|
+
* root. Internal: the file tools hide the secrets their access policy closes, before anything
|
|
46
|
+
* is counted or stored.
|
|
47
|
+
*/
|
|
48
|
+
export async function chooseWorkspace({ root, logger, hidden, }) {
|
|
41
49
|
const ripgrepPath = await loadRipgrep();
|
|
42
50
|
if (ripgrepPath !== undefined) {
|
|
43
|
-
return new RipgrepWorkspace({ root, ripgrepPath });
|
|
51
|
+
return new RipgrepWorkspace({ root, ripgrepPath, ...(hidden ? { hidden } : {}) });
|
|
44
52
|
}
|
|
45
53
|
if (await isGitWorkTree(root)) {
|
|
46
54
|
treatErrors(() => {
|
|
47
55
|
logger.warn('ripgrep is not available on this platform; reading the repository through git');
|
|
48
56
|
}, classifyHostFailure, 'Workspace logger failed while choosing an implementation');
|
|
49
|
-
return new GitWorkspace({ root });
|
|
57
|
+
return new GitWorkspace({ root, ...(hidden ? { hidden } : {}) });
|
|
50
58
|
}
|
|
51
59
|
throw new UnrecoverableError('No program can read the workspace', {
|
|
52
60
|
cause: `ripgrep is not available on this platform, and ${root} is not a git repository.`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createWorkspace.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/createWorkspace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AAEtF,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,gGAAgG;AAChG,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAEhC,KAAK,UAAU,WAAW;IACzB,IAAI,CAAC;QACJ,yEAAyE;QACzE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;QACnD,OAAO,MAAM,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAC;IAClB,CAAC;AACF,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,CAAC;QACJ,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,uBAAuB,CAAC,EAAE;YAC5F,GAAG,EAAE,IAAI;YACT,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,iBAAiB,CAAC;SAC9C,CAAC,CAAC;QACH,OAAO,QAAQ,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,MAAM,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACR,6CAA6C;QAC7C,OAAO,KAAK,CAAC;IACd,CAAC;AACF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,
|
|
1
|
+
{"version":3,"file":"createWorkspace.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/createWorkspace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AAEtF,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,gGAAgG;AAChG,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAEhC,KAAK,UAAU,WAAW;IACzB,IAAI,CAAC;QACJ,yEAAyE;QACzE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;QACnD,OAAO,MAAM,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAC;IAClB,CAAC;AACF,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,CAAC;QACJ,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,uBAAuB,CAAC,EAAE;YAC5F,GAAG,EAAE,IAAI;YACT,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,iBAAiB,CAAC;SAC9C,CAAC,CAAC;QACH,OAAO,QAAQ,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,MAAM,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACR,6CAA6C;QAC7C,OAAO,KAAK,CAAC;IACd,CAAC;AACF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,EAC/B,IAAI,EACJ,MAAM,GAAG,IAAI,MAAM,EAAE,GAKrB;IACA,OAAO,eAAe,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;AAC1C,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,EACrC,IAAI,EACJ,MAAM,EACN,MAAM,GAKN;IACA,MAAM,WAAW,GAAG,MAAM,WAAW,EAAE,CAAC;IACxC,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC/B,OAAO,IAAI,gBAAgB,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACnF,CAAC;IAED,IAAI,MAAM,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,WAAW,CACV,GAAG,EAAE;YACJ,MAAM,CAAC,IAAI,CACV,+EAA+E,CAC/E,CAAC;QACH,CAAC,EACD,mBAAmB,EACnB,0DAA0D,CAC1D,CAAC;QACF,OAAO,IAAI,YAAY,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,MAAM,IAAI,kBAAkB,CAAC,mCAAmC,EAAE;QACjE,KAAK,EAAE,kDAAkD,IAAI,2BAA2B;KACxF,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { UnrecoverableError } from '#src/shared/domain/errors';\nimport { classifyHostFailure, treatErrors } from '#src/shared/domain/providerFailure';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport { Logger } from '#src/shared/infrastructure/logger';\nimport type { Workspace } from '../domain/workspace.ts';\nimport { GitWorkspace } from './gitWorkspace.ts';\nimport { RipgrepWorkspace } from './ripgrepWorkspace.ts';\nimport { runProcess } from './runProcess.ts';\n\n// Long enough for a slow disk; short enough that a hung git does not hold up building an agent.\nconst gitCheckTimeoutMs = 5_000;\n\nasync function loadRipgrep(): Promise<string | undefined> {\n\ttry {\n\t\t// Dynamic: the package throws on import when the platform has no binary.\n\t\tconst { rgPath } = await import('@vscode/ripgrep');\n\t\treturn rgPath;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nasync function isGitWorkTree(root: string): Promise<boolean> {\n\ttry {\n\t\tconst { stdout, exitCode } = await runProcess('git', ['rev-parse', '--is-inside-work-tree'], {\n\t\t\tcwd: root,\n\t\t\tsignal: AbortSignal.timeout(gitCheckTimeoutMs),\n\t\t});\n\t\treturn exitCode === 0 && stdout.trim() === 'true';\n\t} catch {\n\t\t// No git at all, or one that never answered.\n\t\treturn false;\n\t}\n}\n\n/**\n * Picks how to read `root`: the ripgrep that ships with the package, or git when this platform\n * has no ripgrep binary, which is logged because it is a degraded path. Plain `grep` is never a\n * fallback: it knows nothing of `.gitignore`. See \"`.gitignore` is the boundary of what an agent\n * reads\" in decisions.md.\n *\n * @throws {UnrecoverableError} when neither ripgrep nor a git repository is available.\n */\nexport function createWorkspace({\n\troot,\n\tlogger = new Logger(),\n}: {\n\troot: string;\n\t/** Defaults to warnings on stderr. */\n\tlogger?: ILogger;\n}): Promise<Workspace> {\n\treturn chooseWorkspace({ root, logger });\n}\n\n/**\n * `createWorkspace`, with `hidden`: files to treat as absent, by their path relative to the\n * root. Internal: the file tools hide the secrets their access policy closes, before anything\n * is counted or stored.\n */\nexport async function chooseWorkspace({\n\troot,\n\tlogger,\n\thidden,\n}: {\n\troot: string;\n\tlogger: ILogger;\n\thidden?: (path: string) => boolean;\n}): Promise<Workspace> {\n\tconst ripgrepPath = await loadRipgrep();\n\tif (ripgrepPath !== undefined) {\n\t\treturn new RipgrepWorkspace({ root, ripgrepPath, ...(hidden ? { hidden } : {}) });\n\t}\n\n\tif (await isGitWorkTree(root)) {\n\t\ttreatErrors(\n\t\t\t() => {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t'ripgrep is not available on this platform; reading the repository through git',\n\t\t\t\t);\n\t\t\t},\n\t\t\tclassifyHostFailure,\n\t\t\t'Workspace logger failed while choosing an implementation',\n\t\t);\n\t\treturn new GitWorkspace({ root, ...(hidden ? { hidden } : {}) });\n\t}\n\n\tthrow new UnrecoverableError('No program can read the workspace', {\n\t\tcause: `ripgrep is not available on this platform, and ${root} is not a git repository.`,\n\t});\n}\n"]}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { ToolRisk } from '#src/agent/domain/approval';
|
|
3
|
+
import type { PreparedCall, PreparingTool } from '../domain/preparedCall.ts';
|
|
4
|
+
import type { ToolCallContext } from '../domain/readRegistry.ts';
|
|
3
5
|
import type { Tool } from '../domain/tool.ts';
|
|
4
6
|
/**
|
|
5
7
|
* Builds a `Tool` from one Zod schema, which gives the model its JSON Schema, validates what the
|
|
@@ -19,3 +21,15 @@ export declare function defineTool<Schema extends z.ZodObject>({ name, descripti
|
|
|
19
21
|
risk: ToolRisk | ((input: z.infer<Schema>) => ToolRisk);
|
|
20
22
|
execute: (input: z.infer<Schema>, signal: AbortSignal) => Promise<string>;
|
|
21
23
|
}): Tool;
|
|
24
|
+
/**
|
|
25
|
+
* Builds a harness tool that prepares each call, from a Zod schema, as `defineTool` builds a
|
|
26
|
+
* consumer's: input that fails validation is refused before anything is prepared. Internal.
|
|
27
|
+
*
|
|
28
|
+
* @throws {InvalidAgentConfigError} when the schema falls outside what strict mode supports.
|
|
29
|
+
*/
|
|
30
|
+
export declare function definePreparingTool<Schema extends z.ZodObject>({ name, description, input, prepare, }: {
|
|
31
|
+
name: string;
|
|
32
|
+
description: string;
|
|
33
|
+
input: Schema;
|
|
34
|
+
prepare: (input: z.infer<Schema>, signal: AbortSignal, call: ToolCallContext) => Promise<PreparedCall>;
|
|
35
|
+
}): PreparingTool;
|
|
@@ -71,14 +71,10 @@ function assertStrict(schema, toolName) {
|
|
|
71
71
|
* @throws {InvalidAgentConfigError} when the schema falls outside what strict mode supports.
|
|
72
72
|
*/
|
|
73
73
|
export function defineTool({ name, description, input, risk, execute, }) {
|
|
74
|
-
const inputSchema = { ...z.toJSONSchema(input) };
|
|
75
|
-
// The dialect marker is noise to both APIs.
|
|
76
|
-
delete inputSchema.$schema;
|
|
77
|
-
assertStrict(inputSchema, name);
|
|
78
74
|
return {
|
|
79
75
|
name,
|
|
80
76
|
description,
|
|
81
|
-
inputSchema:
|
|
77
|
+
inputSchema: strictSchema(input, name),
|
|
82
78
|
risk: raw => {
|
|
83
79
|
if (typeof risk === 'string')
|
|
84
80
|
return risk;
|
|
@@ -94,4 +90,31 @@ export function defineTool({ name, description, input, risk, execute, }) {
|
|
|
94
90
|
},
|
|
95
91
|
};
|
|
96
92
|
}
|
|
93
|
+
/** The JSON schema of `input`, checked against what strict mode supports. */
|
|
94
|
+
function strictSchema(input, toolName) {
|
|
95
|
+
const inputSchema = { ...z.toJSONSchema(input) };
|
|
96
|
+
// The dialect marker is noise to both APIs.
|
|
97
|
+
delete inputSchema.$schema;
|
|
98
|
+
assertStrict(inputSchema, toolName);
|
|
99
|
+
return inputSchema;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Builds a harness tool that prepares each call, from a Zod schema, as `defineTool` builds a
|
|
103
|
+
* consumer's: input that fails validation is refused before anything is prepared. Internal.
|
|
104
|
+
*
|
|
105
|
+
* @throws {InvalidAgentConfigError} when the schema falls outside what strict mode supports.
|
|
106
|
+
*/
|
|
107
|
+
export function definePreparingTool({ name, description, input, prepare, }) {
|
|
108
|
+
return {
|
|
109
|
+
name,
|
|
110
|
+
description,
|
|
111
|
+
inputSchema: strictSchema(input, name),
|
|
112
|
+
prepare: (raw, signal, call) => {
|
|
113
|
+
const parsed = input.safeParse(raw);
|
|
114
|
+
if (!parsed.success)
|
|
115
|
+
return Promise.reject(new Error(z.prettifyError(parsed.error)));
|
|
116
|
+
return prepare(parsed.data, signal, call);
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
97
120
|
//# sourceMappingURL=defineTool.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defineTool.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/defineTool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"defineTool.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/defineTool.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod';\nimport type { ToolRisk } from '#src/agent/domain/approval';\nimport { InvalidAgentConfigError } from '#src/shared/domain/errors';\nimport type { JSONSchema } from '#src/llm/domain/tool';\nimport type { PreparedCall, PreparingTool } from '../domain/preparedCall.ts';\nimport type { ToolCallContext } from '../domain/readRegistry.ts';\nimport type { Tool } from '../domain/tool.ts';\n\n// Both providers take tools in strict mode, which supports only part of JSON Schema. Claude\n// rejects these bounds; a tool that needs one checks it in its own code instead.\nconst unsupportedKeywords = [\n\t'minimum',\n\t'maximum',\n\t'exclusiveMinimum',\n\t'exclusiveMaximum',\n\t'multipleOf',\n\t'minLength',\n\t'maxLength',\n];\n\n// Keywords whose value is one schema, or a list or map of them. Anything else, such as the\n// keys of `properties`, is a name or a value, never a schema to check.\nconst schemaKeywords = ['items', 'not', 'additionalProperties'];\nconst schemaListKeywords = ['anyOf', 'oneOf', 'allOf', 'prefixItems'];\nconst schemaMapKeywords = ['properties', '$defs', 'definitions'];\n\nfunction isSchema(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Walks the whole schema, nested objects included, and visits only schemas: a property named\n * `maxLength` is a name, not the keyword. A schema the providers reject would fail every\n * request with a 400, so it fails here, when the tool is defined.\n */\nfunction assertStrict(schema: unknown, toolName: string): void {\n\tif (!isSchema(schema)) {\n\t\treturn;\n\t}\n\n\tconst keyword = unsupportedKeywords.find(candidate => candidate in schema);\n\tif (keyword) {\n\t\tthrow new InvalidAgentConfigError(\n\t\t\t`Tool \"${toolName}\" uses \"${keyword}\", which strict mode does not support; check it in the tool's code`,\n\t\t);\n\t}\n\n\tif (schema.type === 'object') {\n\t\t// A record or a loose object takes keys strict mode cannot know in advance.\n\t\tif (schema.additionalProperties !== false) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`Tool \"${toolName}\" has an object open to any key; strict mode needs every key declared`,\n\t\t\t);\n\t\t}\n\n\t\tconst required = (schema.required ?? []) as string[];\n\t\t// OpenAI requires every property: an optional one is a union with `null`.\n\t\tconst optional = Object.keys(schema.properties ?? {}).filter(\n\t\t\tproperty => !required.includes(property),\n\t\t);\n\t\tif (optional.length > 0) {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t`Tool \"${toolName}\" has optional fields (${optional.join(', ')}); make them .nullable() instead`,\n\t\t\t);\n\t\t}\n\t}\n\n\tfor (const key of schemaKeywords) assertStrict(schema[key], toolName);\n\tfor (const key of schemaListKeywords) {\n\t\tconst list = schema[key];\n\t\tif (Array.isArray(list)) for (const item of list) assertStrict(item, toolName);\n\t}\n\tfor (const key of schemaMapKeywords) {\n\t\tconst map = schema[key];\n\t\tif (isSchema(map)) for (const item of Object.values(map)) assertStrict(item, toolName);\n\t}\n}\n\n/**\n * Builds a `Tool` from one Zod schema, which gives the model its JSON Schema, validates what the\n * model sends, and types the input `execute` receives. Invalid input is rejected with a message\n * naming what is wrong, which the agent hands back to the model as an error result.\n *\n * `risk` is one level for every call, or a function that judges each call from its validated\n * input, such as `destructive` only when a file already exists. Input that fails validation\n * counts as `safe`, because `execute` rejects it before doing anything.\n *\n * @throws {InvalidAgentConfigError} when the schema falls outside what strict mode supports.\n */\nexport function defineTool<Schema extends z.ZodObject>({\n\tname,\n\tdescription,\n\tinput,\n\trisk,\n\texecute,\n}: {\n\tname: string;\n\tdescription: string;\n\tinput: Schema;\n\trisk: ToolRisk | ((input: z.infer<Schema>) => ToolRisk);\n\texecute: (input: z.infer<Schema>, signal: AbortSignal) => Promise<string>;\n}): Tool {\n\treturn {\n\t\tname,\n\t\tdescription,\n\t\tinputSchema: strictSchema(input, name),\n\t\trisk: raw => {\n\t\t\tif (typeof risk === 'string') return risk;\n\t\t\tconst parsed = input.safeParse(raw);\n\t\t\treturn parsed.success ? risk(parsed.data) : 'safe';\n\t\t},\n\t\texecute: (raw, signal) => {\n\t\t\tconst parsed = input.safeParse(raw);\n\t\t\tif (!parsed.success) {\n\t\t\t\treturn Promise.reject(new Error(z.prettifyError(parsed.error)));\n\t\t\t}\n\n\t\t\treturn execute(parsed.data, signal);\n\t\t},\n\t};\n}\n\n/** The JSON schema of `input`, checked against what strict mode supports. */\nfunction strictSchema(input: z.ZodObject, toolName: string): JSONSchema {\n\tconst inputSchema: Record<string, unknown> = { ...z.toJSONSchema(input) };\n\t// The dialect marker is noise to both APIs.\n\tdelete inputSchema.$schema;\n\tassertStrict(inputSchema, toolName);\n\treturn inputSchema as JSONSchema;\n}\n\n/**\n * Builds a harness tool that prepares each call, from a Zod schema, as `defineTool` builds a\n * consumer's: input that fails validation is refused before anything is prepared. Internal.\n *\n * @throws {InvalidAgentConfigError} when the schema falls outside what strict mode supports.\n */\nexport function definePreparingTool<Schema extends z.ZodObject>({\n\tname,\n\tdescription,\n\tinput,\n\tprepare,\n}: {\n\tname: string;\n\tdescription: string;\n\tinput: Schema;\n\tprepare: (\n\t\tinput: z.infer<Schema>,\n\t\tsignal: AbortSignal,\n\t\tcall: ToolCallContext,\n\t) => Promise<PreparedCall>;\n}): PreparingTool {\n\treturn {\n\t\tname,\n\t\tdescription,\n\t\tinputSchema: strictSchema(input, name),\n\t\tprepare: (raw, signal, call) => {\n\t\t\tconst parsed = input.safeParse(raw);\n\t\t\tif (!parsed.success) return Promise.reject(new Error(z.prettifyError(parsed.error)));\n\t\t\treturn prepare(parsed.data, signal, call);\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AccessPolicy } from '../domain/accessPolicy.ts';
|
|
2
|
+
import type { PreparingTool } from '../domain/preparedCall.ts';
|
|
3
|
+
import type { Workspace } from '../domain/workspace.ts';
|
|
4
|
+
import type { WorkspaceWrites } from './workspaceWrites.ts';
|
|
5
|
+
/**
|
|
6
|
+
* OpenAI's native `apply_patch`: each call is one operation on one file, `create_file` and
|
|
7
|
+
* `update_file` with a V4A diff. It cannot read, so the agent also has `readFile`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createApplyPatchTool(writes: WorkspaceWrites): PreparingTool;
|
|
10
|
+
/**
|
|
11
|
+
* Claude's native text editor, `str_replace_based_edit_tool`. `view` reads through the access
|
|
12
|
+
* policy and counts as a read, so it replaces `readFile`; on a folder it lists the files in it.
|
|
13
|
+
* `create` makes only a file that does not exist, and the editor has no command to delete:
|
|
14
|
+
* Claude gets `delete_file` for that.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createTextEditorTool({ writes, policy, workspace, }: {
|
|
17
|
+
writes: WorkspaceWrites;
|
|
18
|
+
policy: AccessPolicy;
|
|
19
|
+
workspace: Workspace;
|
|
20
|
+
}): PreparingTool;
|
|
21
|
+
/** The delete Claude's text editor has no command for. */
|
|
22
|
+
export declare function createDeleteFileTool(writes: WorkspaceWrites): PreparingTool;
|