@mikode13/harness 9.3.1 → 11.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.
Files changed (58) hide show
  1. package/README.md +81 -10
  2. package/dist/agent/domain/agent.d.ts +31 -7
  3. package/dist/agent/domain/agent.js.map +1 -1
  4. package/dist/agent/domain/approval.d.ts +2 -1
  5. package/dist/agent/domain/approval.js +2 -1
  6. package/dist/agent/domain/approval.js.map +1 -1
  7. package/dist/agent/domain/question.d.ts +5 -0
  8. package/dist/agent/domain/question.js +11 -0
  9. package/dist/agent/domain/question.js.map +1 -1
  10. package/dist/agent/domain/runContext.d.ts +17 -0
  11. package/dist/agent/domain/runContext.js +9 -0
  12. package/dist/agent/domain/runContext.js.map +1 -1
  13. package/dist/agent/domain/transcript.d.ts +10 -0
  14. package/dist/agent/domain/transcript.js.map +1 -1
  15. package/dist/engines/domain/model/askUser.d.ts +0 -5
  16. package/dist/engines/domain/model/askUser.js +0 -9
  17. package/dist/engines/domain/model/askUser.js.map +1 -1
  18. package/dist/engines/domain/model/backgroundAgents.d.ts +60 -0
  19. package/dist/engines/domain/model/backgroundAgents.js +165 -0
  20. package/dist/engines/domain/model/backgroundAgents.js.map +1 -0
  21. package/dist/engines/domain/model/llmAgent.d.ts +12 -3
  22. package/dist/engines/domain/model/llmAgent.js +184 -74
  23. package/dist/engines/domain/model/llmAgent.js.map +1 -1
  24. package/dist/factory/infrastructure/agentLLMFactory.d.ts +7 -3
  25. package/dist/factory/infrastructure/agentLLMFactory.js +3 -3
  26. package/dist/factory/infrastructure/agentLLMFactory.js.map +1 -1
  27. package/dist/index.d.ts +1 -0
  28. package/dist/index.js +1 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/tools/domain/preparedCall.d.ts +52 -2
  31. package/dist/tools/domain/preparedCall.js +15 -0
  32. package/dist/tools/domain/preparedCall.js.map +1 -1
  33. package/dist/tools/domain/readRegistry.d.ts +7 -0
  34. package/dist/tools/domain/readRegistry.js.map +1 -1
  35. package/dist/tools/infrastructure/agentAsTool.d.ts +63 -0
  36. package/dist/tools/infrastructure/agentAsTool.js +143 -0
  37. package/dist/tools/infrastructure/agentAsTool.js.map +1 -0
  38. package/dist/tools/infrastructure/commandTool.js +6 -2
  39. package/dist/tools/infrastructure/commandTool.js.map +1 -1
  40. package/dist/tools/infrastructure/defineTool.d.ts +14 -1
  41. package/dist/tools/infrastructure/defineTool.js +24 -0
  42. package/dist/tools/infrastructure/defineTool.js.map +1 -1
  43. package/dist/tools/infrastructure/exploreTool.d.ts +2 -2
  44. package/dist/tools/infrastructure/exploreTool.js +36 -56
  45. package/dist/tools/infrastructure/exploreTool.js.map +1 -1
  46. package/dist/tools/infrastructure/fileEditor.d.ts +2 -0
  47. package/dist/tools/infrastructure/fileEditor.js +5 -1
  48. package/dist/tools/infrastructure/fileEditor.js.map +1 -1
  49. package/dist/tools/infrastructure/fileFormatter.d.ts +1 -0
  50. package/dist/tools/infrastructure/fileFormatter.js +5 -1
  51. package/dist/tools/infrastructure/fileFormatter.js.map +1 -1
  52. package/dist/tools/infrastructure/subagents.d.ts +19 -0
  53. package/dist/tools/infrastructure/subagents.js +29 -0
  54. package/dist/tools/infrastructure/subagents.js.map +1 -0
  55. package/dist/tools/infrastructure/writeSession.d.ts +8 -0
  56. package/dist/tools/infrastructure/writeSession.js +37 -0
  57. package/dist/tools/infrastructure/writeSession.js.map +1 -1
  58. package/package.json +1 -1
@@ -0,0 +1,143 @@
1
+ import { z } from 'zod';
2
+ import { readDecision } from '#src/agent/domain/approval';
3
+ import { readAnswers } from '#src/agent/domain/question';
4
+ import { InvalidAgentConfigError, RecoverableError, UnrecoverableError, withSpentTokens, } from '#src/shared/domain/errors';
5
+ import { isAbortError } from '#src/shared/domain/isAbortError';
6
+ import { classifyHostFailure } from '#src/shared/domain/providerFailure';
7
+ import { RunEndingToolError } from "../domain/preparedCall.js";
8
+ import { defineDelegatingTool } from "./defineTool.js";
9
+ import { cutAnswer, failureOf, subagentOptions } from "./subagents.js";
10
+ /** How deep agents may delegate unless a tool says otherwise: planner → coder → committer. */
11
+ export const defaultMaxDepth = 2;
12
+ /**
13
+ * Offers an agent to another agent as a tool. The calling model writes the task, and the call
14
+ * answers at once: the agent works on it in the background, as part of the caller's run, while
15
+ * the caller keeps working. Its answer reaches the caller's model in a message of its own once it
16
+ * finishes, and nothing else of its work does:
17
+ *
18
+ * - its changes go into the same run of the workspace's history, so one undo reverts both, and
19
+ * each agent keeps its own tools' access policy and its own reads;
20
+ * - what it spends counts in the caller's `tokens`, a failed attempt included;
21
+ * - its progress reaches the consumer as `subagent` events of the call, and a transcript it gives
22
+ * names the call in `delegation`;
23
+ * - it is asked and approved through the run's `ask` and `approve`. Delegating never grants
24
+ * `autoApprove`: the agent has it only if the `createAgent` option gave it.
25
+ *
26
+ * The caller's run ends only once every agent it sent has finished. Every change and command of
27
+ * the run is made one at a time, so the history stays exact, but agents working at once can see
28
+ * each other's unfinished work. An agent that fails answers with why, for the model to act on; a
29
+ * cancellation, or a run's approver or asker that failed inside it, ends the run and stops every
30
+ * agent still working.
31
+ *
32
+ * Only for agents on the model APIs, from `createLLMAgent`: an Agent SDK agent from the
33
+ * `createAgent` factory runs as its own run, outside these guarantees.
34
+ *
35
+ * @throws {InvalidAgentConfigError} when `maxDepth` is not a positive integer.
36
+ */
37
+ export function agentAsTool({ name, description, createAgent, risk = 'mutating', maxDepth = defaultMaxDepth, }) {
38
+ if (!Number.isInteger(maxDepth) || maxDepth < 1) {
39
+ throw new InvalidAgentConfigError(`maxDepth of "${name}" must be a positive integer; got ${String(maxDepth)}`);
40
+ }
41
+ const tool = defineDelegatingTool({
42
+ name,
43
+ description,
44
+ input: z.object({
45
+ task: z
46
+ .string()
47
+ .describe('The whole task. The agent starts from it alone, so say what to do and what to report back.'),
48
+ }),
49
+ summarize: ({ task }) => task,
50
+ delegate: ({ task }, _, call) => {
51
+ const asked = task.trim();
52
+ if (asked === '')
53
+ return Promise.reject(new Error('The task needs some text.'));
54
+ if (call.depth >= maxDepth) {
55
+ return Promise.reject(new Error(`"${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.`));
56
+ }
57
+ return Promise.resolve({
58
+ risk,
59
+ // What the agent changes is in the history, but it may run tools of the consumer's
60
+ // that change what the history never sees.
61
+ unrecorded: true,
62
+ start: signal => [{ name, subagent: 1, task: asked, answer: hand(asked, signal, call) }],
63
+ });
64
+ },
65
+ });
66
+ async function hand(task, signal, call) {
67
+ // The run's approver and asker are host code: one that fails inside the agent ends the
68
+ // run, not just the agent's task.
69
+ const failed = { host: false };
70
+ const options = subagentOptions(call, {
71
+ tool: name,
72
+ name,
73
+ subagent: 1,
74
+ task,
75
+ options: {
76
+ signal,
77
+ ...(call.approve ? { approve: watchedApprover(call.approve, failed) } : {}),
78
+ ...(call.ask ? { ask: watchedAsker(call.ask, failed) } : {}),
79
+ },
80
+ });
81
+ let agent;
82
+ try {
83
+ agent = createAgent();
84
+ }
85
+ catch (error) {
86
+ throw new RunEndingToolError(classifyHostFailure(error, `The agent for "${name}" could not be created`));
87
+ }
88
+ try {
89
+ const { response, tokens } = await agent.run(task, options);
90
+ call.spend(tokens, tokens === undefined);
91
+ return { text: cutAnswer(response || 'It finished without an answer.') };
92
+ }
93
+ catch (error) {
94
+ if (isAbortError(error))
95
+ throw error;
96
+ const classified = error instanceof RecoverableError || error instanceof UnrecoverableError;
97
+ // Its failure carries what it spent, which the run's failure keeps.
98
+ if (classified && failed.host)
99
+ throw new RunEndingToolError(error);
100
+ if (!classified) {
101
+ // It broke the contract of `Agent`, so what it spent is unknown, not nothing.
102
+ throw new RunEndingToolError(withSpentTokens(classifyHostFailure(error, `The agent for "${name}" failed`), undefined, true));
103
+ }
104
+ call.spend(error.tokens, error.usageUnreported);
105
+ return { text: `The agent failed: ${failureOf(error)}`, failed: true };
106
+ }
107
+ }
108
+ // A file tool's kind of brand: the harness built it, and only `createLLMAgent` takes it.
109
+ return tool;
110
+ }
111
+ /** `approve`, noting in `failed` when it throws or answers with no decision. */
112
+ function watchedApprover(approve, failed) {
113
+ return async (request, signal) => {
114
+ try {
115
+ const answer = await approve(request, signal);
116
+ readDecision(answer);
117
+ return answer;
118
+ }
119
+ catch (error) {
120
+ // One that gives up on a cancellation did not fail.
121
+ if (!signal.aborted)
122
+ failed.host = true;
123
+ throw error;
124
+ }
125
+ };
126
+ }
127
+ /** `ask`, noting in `failed` when it throws or answers in a shape no agent accepts. */
128
+ function watchedAsker(ask, failed) {
129
+ return async (questions, signal) => {
130
+ try {
131
+ const answers = await ask(questions, signal);
132
+ if (!readAnswers(answers, questions))
133
+ failed.host = true;
134
+ return answers;
135
+ }
136
+ catch (error) {
137
+ if (!signal.aborted)
138
+ failed.host = true;
139
+ throw error;
140
+ }
141
+ };
142
+ }
143
+ //# sourceMappingURL=agentAsTool.js.map
@@ -0,0 +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"]}
@@ -91,9 +91,13 @@ export async function createCommandTool({ commands, policy, writes, covered, bas
91
91
  };
92
92
  },
93
93
  });
94
- async function runCommand({ name, line, definition, context, signal, ended, }) {
94
+ // Alone: a change another agent of the run made while it ran would be taken for its own.
95
+ function runCommand(run) {
96
+ const session = writes.sessionFor(run.context);
97
+ return session.exclusive(() => runCommandAlone(run, session), run.signal);
98
+ }
99
+ async function runCommandAlone({ name, line, definition, signal, ended }, session) {
95
100
  signal.throwIfAborted();
96
- const session = writes.sessionFor(context);
97
101
  const journal = await session.journalForChange();
98
102
  let checkpoint;
99
103
  try {
@@ -1 +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 {\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';\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\tasync function runCommand({\n\t\tname,\n\t\tline,\n\t\tdefinition,\n\t\tcontext,\n\t\tsignal,\n\t\tended,\n\t}: {\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}): 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\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, { 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,6 +1,6 @@
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';
3
+ import type { DelegatingTool, PreparedCall, PreparedDelegation, PreparingTool } from '../domain/preparedCall.ts';
4
4
  import type { ToolCallContext } from '../domain/readRegistry.ts';
5
5
  import { type Tool } from '../domain/tool.ts';
6
6
  /**
@@ -35,3 +35,16 @@ export declare function definePreparingTool<Schema extends z.ZodObject>({ name,
35
35
  summarize?: (input: z.infer<Schema>) => string;
36
36
  prepare: (input: z.infer<Schema>, signal: AbortSignal, call: ToolCallContext) => Promise<PreparedCall>;
37
37
  }): PreparingTool;
38
+ /**
39
+ * Builds a harness tool whose calls hand tasks to agents working in the background, from a Zod
40
+ * schema: input that fails validation is refused before any task is handed over. Internal.
41
+ *
42
+ * @throws {InvalidAgentConfigError} when the schema falls outside what strict mode supports.
43
+ */
44
+ export declare function defineDelegatingTool<Schema extends z.ZodObject>({ name, description, input, summarize, delegate, }: {
45
+ name: string;
46
+ description: string;
47
+ input: Schema;
48
+ summarize?: (input: z.infer<Schema>) => string;
49
+ delegate: (input: z.infer<Schema>, signal: AbortSignal, call: ToolCallContext) => Promise<PreparedDelegation>;
50
+ }): DelegatingTool;
@@ -129,4 +129,28 @@ export function definePreparingTool({ name, description, input, summarize, prepa
129
129
  },
130
130
  };
131
131
  }
132
+ /**
133
+ * Builds a harness tool whose calls hand tasks to agents working in the background, from a Zod
134
+ * schema: input that fails validation is refused before any task is handed over. Internal.
135
+ *
136
+ * @throws {InvalidAgentConfigError} when the schema falls outside what strict mode supports.
137
+ */
138
+ export function defineDelegatingTool({ name, description, input, summarize, delegate, }) {
139
+ return {
140
+ name,
141
+ description,
142
+ inputSchema: strictSchema(input, name),
143
+ ...(summarize
144
+ ? {
145
+ summarize: validatedSummarizer(input, summarize),
146
+ }
147
+ : {}),
148
+ delegate: (raw, signal, call) => {
149
+ const parsed = input.safeParse(raw);
150
+ if (!parsed.success)
151
+ return Promise.reject(new Error(z.prettifyError(parsed.error)));
152
+ return delegate(parsed.data, signal, call);
153
+ },
154
+ };
155
+ }
132
156
  //# sourceMappingURL=defineTool.js.map
@@ -1 +1 @@
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';\nimport { validatedSummarizer } from './validatedSummarizer.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\tsummarize,\n\texecute,\n}: {\n\tname: string;\n\tdescription: string;\n\tinput: Schema;\n\trisk: ToolRisk | ((input: z.infer<Schema>) => ToolRisk);\n\tsummarize?: (input: z.infer<Schema>) => string;\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\t...(summarize\n\t\t\t? {\n\t\t\t\t\tsummarize: validatedSummarizer(input, summarize),\n\t\t\t\t}\n\t\t\t: {}),\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\tsummarize,\n\tprepare,\n}: {\n\tname: string;\n\tdescription: string;\n\tinput: Schema;\n\tsummarize?: (input: z.infer<Schema>) => string;\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\t...(summarize\n\t\t\t? {\n\t\t\t\t\tsummarize: validatedSummarizer(input, summarize),\n\t\t\t\t}\n\t\t\t: {}),\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"]}
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 {\n\tDelegatingTool,\n\tPreparedCall,\n\tPreparedDelegation,\n\tPreparingTool,\n} from '../domain/preparedCall.ts';\nimport type { ToolCallContext } from '../domain/readRegistry.ts';\nimport { type Tool } from '../domain/tool.ts';\nimport { validatedSummarizer } from './validatedSummarizer.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\tsummarize,\n\texecute,\n}: {\n\tname: string;\n\tdescription: string;\n\tinput: Schema;\n\trisk: ToolRisk | ((input: z.infer<Schema>) => ToolRisk);\n\tsummarize?: (input: z.infer<Schema>) => string;\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\t...(summarize\n\t\t\t? {\n\t\t\t\t\tsummarize: validatedSummarizer(input, summarize),\n\t\t\t\t}\n\t\t\t: {}),\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\tsummarize,\n\tprepare,\n}: {\n\tname: string;\n\tdescription: string;\n\tinput: Schema;\n\tsummarize?: (input: z.infer<Schema>) => string;\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\t...(summarize\n\t\t\t? {\n\t\t\t\t\tsummarize: validatedSummarizer(input, summarize),\n\t\t\t\t}\n\t\t\t: {}),\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\n/**\n * Builds a harness tool whose calls hand tasks to agents working in the background, from a Zod\n * schema: input that fails validation is refused before any task is handed over. Internal.\n *\n * @throws {InvalidAgentConfigError} when the schema falls outside what strict mode supports.\n */\nexport function defineDelegatingTool<Schema extends z.ZodObject>({\n\tname,\n\tdescription,\n\tinput,\n\tsummarize,\n\tdelegate,\n}: {\n\tname: string;\n\tdescription: string;\n\tinput: Schema;\n\tsummarize?: (input: z.infer<Schema>) => string;\n\tdelegate: (\n\t\tinput: z.infer<Schema>,\n\t\tsignal: AbortSignal,\n\t\tcall: ToolCallContext,\n\t) => Promise<PreparedDelegation>;\n}): DelegatingTool {\n\treturn {\n\t\tname,\n\t\tdescription,\n\t\tinputSchema: strictSchema(input, name),\n\t\t...(summarize\n\t\t\t? {\n\t\t\t\t\tsummarize: validatedSummarizer(input, summarize),\n\t\t\t\t}\n\t\t\t: {}),\n\t\tdelegate: (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 delegate(parsed.data, signal, call);\n\t\t},\n\t};\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import type { Agent } from '#src/agent/domain/agent';
2
- import type { PreparingTool } from '../domain/preparedCall.ts';
2
+ import type { DelegatingTool } from '../domain/preparedCall.ts';
3
3
  /** The most explorers one call sends, so a single call cannot fan out without bound. */
4
4
  export declare const maxExplorers = 4;
5
5
  /**
@@ -7,4 +7,4 @@ export declare const maxExplorers = 4;
7
7
  * `explorer`, to find what an agent needs in the repository, so the agent that called it reads
8
8
  * their reports instead of filling its own context with the files.
9
9
  */
10
- export declare function createExploreTool(explorer: () => Agent): PreparingTool;
10
+ export declare function createExploreTool(explorer: () => Agent): DelegatingTool;
@@ -1,61 +1,36 @@
1
1
  import { z } from 'zod';
2
- import { withRunContext } from '#src/agent/domain/runContext';
3
2
  import { RecoverableError, UnrecoverableError } from '#src/shared/domain/errors';
4
- import { definePreparingTool } from "./defineTool.js";
3
+ import { defineDelegatingTool } from "./defineTool.js";
4
+ import { cutAnswer, failureOf, subagentOptions } from "./subagents.js";
5
5
  /** The most explorers one call sends, so a single call cannot fan out without bound. */
6
6
  export const maxExplorers = 4;
7
- // An answer is read whole, like a read, which returns at most 16 KB.
8
- const maxAnswerCharacters = 16_000;
9
- function cut(answer) {
10
- if (answer.length <= maxAnswerCharacters)
11
- return answer;
12
- return `${answer.slice(0, maxAnswerCharacters)}\n[The answer was cut at ${String(maxAnswerCharacters)} characters.]`;
13
- }
14
- function failureOf(error) {
15
- return typeof error.cause === 'string' ? `${error.message}: ${error.cause}` : error.message;
16
- }
17
7
  /**
18
- * Runs one explorer per question, all at once, each a new agent that starts from its question
19
- * alone. Every explorer's usage counts in the run's, whether it answered or failed; a failure
20
- * becomes that explorer's answer, so the others' are not lost. A cancellation escapes.
8
+ * Sends one explorer, a new agent that starts from its question alone. What it spends counts in
9
+ * the run's usage, whether it answered or failed; a failure becomes its answer. A cancellation
10
+ * escapes.
21
11
  */
22
- async function explore(questions, explorer, signal, call) {
23
- const answers = await Promise.allSettled(questions.map(async (question, index) => {
24
- const subagent = index + 1;
25
- const heading = `Explorer ${String(subagent)}: ${question}`;
26
- const onProgress = (event) => {
27
- call.onProgress({
28
- type: 'subagent',
29
- callId: call.callId,
30
- subagent,
31
- task: question,
32
- event,
33
- });
34
- };
35
- // Within the run that made the call, so the explorers' reads belong to it.
36
- const options = withRunContext({ signal, onProgress }, call.run);
37
- try {
38
- const { response, tokens } = await explorer().run(question, options);
39
- call.spend(tokens, tokens === undefined);
40
- return `${heading}\n${cut(response || 'It found nothing to report.')}`;
41
- }
42
- catch (error) {
43
- if (!(error instanceof RecoverableError || error instanceof UnrecoverableError)) {
44
- throw error;
45
- }
46
- call.spend(error.tokens, error.usageUnreported);
47
- return `${heading}\nThe explorer failed: ${failureOf(error)}`;
48
- }
49
- }));
50
- // Whatever each explorer threw once the run was cancelled, the call ends as cancelled.
51
- signal.throwIfAborted();
52
- return answers
53
- .map(answer => {
54
- if (answer.status === 'rejected')
55
- throw answer.reason;
56
- return answer.value;
57
- })
58
- .join('\n\n');
12
+ async function explore(question, subagent, explorer, signal, call) {
13
+ // Within the run that made the call, so the explorer's reads belong to it.
14
+ const options = subagentOptions(call, {
15
+ tool: 'explore',
16
+ name: 'explorer',
17
+ subagent,
18
+ task: question,
19
+ options: { signal },
20
+ });
21
+ try {
22
+ const { response, tokens } = await explorer().run(question, options);
23
+ call.spend(tokens, tokens === undefined);
24
+ return { text: cutAnswer(response || 'It found nothing to report.') };
25
+ }
26
+ catch (error) {
27
+ // Whatever it threw once the run was cancelled, it ends as cancelled.
28
+ signal.throwIfAborted();
29
+ if (!(error instanceof RecoverableError || error instanceof UnrecoverableError))
30
+ throw error;
31
+ call.spend(error.tokens, error.usageUnreported);
32
+ return { text: `The explorer failed: ${failureOf(error)}`, failed: true };
33
+ }
59
34
  }
60
35
  /**
61
36
  * `explore` for the planner and the reviewer: it sends cheaper, read-only explorers, created by
@@ -63,14 +38,14 @@ async function explore(questions, explorer, signal, call) {
63
38
  * their reports instead of filling its own context with the files.
64
39
  */
65
40
  export function createExploreTool(explorer) {
66
- return definePreparingTool({
41
+ return defineDelegatingTool({
67
42
  name: 'explore',
68
- description: `Sends read-only explorers to find things in the repository for you, one for each question, all at once. Each explorer starts from nothing but its question, so make each one complete: what to find, and what to report about it, such as which files and lines implement something and how. Ask up to ${String(maxExplorers)} independent questions in one call. Each answer is an explorer's report, not the files: read a file yourself when you need its exact text.`,
43
+ description: `Sends read-only explorers to find things in the repository for you, one for each question, all at once. They work in the background: keep working meanwhile, and each explorer's report reaches you in a message of its own once it finishes. Each explorer starts from nothing but its question, so make each one complete: what to find, and what to report about it, such as which files and lines implement something and how. Ask up to ${String(maxExplorers)} independent questions in one call. Each answer is an explorer's report, not the files: read a file yourself when you need its exact text.`,
69
44
  input: z.object({
70
45
  questions: z.array(z.string()).describe('One complete, independent question per explorer.'),
71
46
  }),
72
47
  summarize: ({ questions }) => questions.length === 1 ? (questions[0] ?? '') : `${String(questions.length)} questions`,
73
- prepare: ({ questions }, _, call) => {
48
+ delegate: ({ questions }, _, call) => {
74
49
  const asked = questions.map(question => question.trim());
75
50
  if (asked.length === 0 || asked.length > maxExplorers) {
76
51
  return Promise.reject(new Error(`Ask between 1 and ${String(maxExplorers)} questions in one call.`));
@@ -80,7 +55,12 @@ export function createExploreTool(explorer) {
80
55
  }
81
56
  return Promise.resolve({
82
57
  risk: 'safe',
83
- run: signal => explore(asked, explorer, signal, call),
58
+ start: signal => asked.map((question, index) => ({
59
+ name: 'explorer',
60
+ subagent: index + 1,
61
+ task: question,
62
+ answer: explore(question, index + 1, explorer, signal, call),
63
+ })),
84
64
  });
85
65
  },
86
66
  });