@mikode13/harness 9.3.1 → 10.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 +63 -7
- package/dist/agent/domain/agent.d.ts +11 -5
- package/dist/agent/domain/agent.js.map +1 -1
- package/dist/agent/domain/approval.d.ts +2 -1
- package/dist/agent/domain/approval.js +2 -1
- package/dist/agent/domain/approval.js.map +1 -1
- package/dist/agent/domain/question.d.ts +5 -0
- package/dist/agent/domain/question.js +11 -0
- package/dist/agent/domain/question.js.map +1 -1
- package/dist/agent/domain/runContext.d.ts +17 -0
- package/dist/agent/domain/runContext.js +9 -0
- package/dist/agent/domain/runContext.js.map +1 -1
- package/dist/agent/domain/transcript.d.ts +10 -0
- package/dist/agent/domain/transcript.js.map +1 -1
- package/dist/engines/domain/model/askUser.d.ts +0 -5
- package/dist/engines/domain/model/askUser.js +0 -9
- package/dist/engines/domain/model/askUser.js.map +1 -1
- package/dist/engines/domain/model/llmAgent.d.ts +19 -3
- package/dist/engines/domain/model/llmAgent.js +141 -51
- package/dist/engines/domain/model/llmAgent.js.map +1 -1
- package/dist/factory/infrastructure/agentLLMFactory.d.ts +6 -2
- package/dist/factory/infrastructure/agentLLMFactory.js +1 -1
- package/dist/factory/infrastructure/agentLLMFactory.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/tools/domain/preparedCall.d.ts +14 -0
- package/dist/tools/domain/preparedCall.js +12 -0
- package/dist/tools/domain/preparedCall.js.map +1 -1
- package/dist/tools/domain/readRegistry.d.ts +7 -0
- package/dist/tools/domain/readRegistry.js.map +1 -1
- package/dist/tools/infrastructure/agentAsTool.d.ts +61 -0
- package/dist/tools/infrastructure/agentAsTool.js +144 -0
- package/dist/tools/infrastructure/agentAsTool.js.map +1 -0
- package/dist/tools/infrastructure/commandTool.js +6 -2
- package/dist/tools/infrastructure/commandTool.js.map +1 -1
- package/dist/tools/infrastructure/exploreTool.js +9 -22
- package/dist/tools/infrastructure/exploreTool.js.map +1 -1
- package/dist/tools/infrastructure/fileEditor.d.ts +2 -0
- package/dist/tools/infrastructure/fileEditor.js +5 -1
- package/dist/tools/infrastructure/fileEditor.js.map +1 -1
- package/dist/tools/infrastructure/fileFormatter.d.ts +1 -0
- package/dist/tools/infrastructure/fileFormatter.js +5 -1
- package/dist/tools/infrastructure/fileFormatter.js.map +1 -1
- package/dist/tools/infrastructure/subagents.d.ts +19 -0
- package/dist/tools/infrastructure/subagents.js +29 -0
- package/dist/tools/infrastructure/subagents.js.map +1 -0
- package/dist/tools/infrastructure/writeSession.d.ts +8 -0
- package/dist/tools/infrastructure/writeSession.js +37 -0
- package/dist/tools/infrastructure/writeSession.js.map +1 -1
- package/package.json +1 -1
|
@@ -91,9 +91,13 @@ export async function createCommandTool({ commands, policy, writes, covered, bas
|
|
|
91
91
|
};
|
|
92
92
|
},
|
|
93
93
|
});
|
|
94
|
-
|
|
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,19 +1,9 @@
|
|
|
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
3
|
import { definePreparingTool } 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
8
|
* Runs one explorer per question, all at once, each a new agent that starts from its question
|
|
19
9
|
* alone. Every explorer's usage counts in the run's, whether it answered or failed; a failure
|
|
@@ -23,21 +13,18 @@ async function explore(questions, explorer, signal, call) {
|
|
|
23
13
|
const answers = await Promise.allSettled(questions.map(async (question, index) => {
|
|
24
14
|
const subagent = index + 1;
|
|
25
15
|
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
16
|
// Within the run that made the call, so the explorers' reads belong to it.
|
|
36
|
-
const options =
|
|
17
|
+
const options = subagentOptions(call, {
|
|
18
|
+
tool: 'explore',
|
|
19
|
+
name: 'explorer',
|
|
20
|
+
subagent,
|
|
21
|
+
task: question,
|
|
22
|
+
options: { signal },
|
|
23
|
+
});
|
|
37
24
|
try {
|
|
38
25
|
const { response, tokens } = await explorer().run(question, options);
|
|
39
26
|
call.spend(tokens, tokens === undefined);
|
|
40
|
-
return `${heading}\n${
|
|
27
|
+
return `${heading}\n${cutAnswer(response || 'It found nothing to report.')}`;
|
|
41
28
|
}
|
|
42
29
|
catch (error) {
|
|
43
30
|
if (!(error instanceof RecoverableError || error instanceof UnrecoverableError)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exploreTool.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/exploreTool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"exploreTool.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/exploreTool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAGjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEvE,wFAAwF;AACxF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC;AAE9B;;;;GAIG;AACH,KAAK,UAAU,OAAO,CACrB,SAA4B,EAC5B,QAAqB,EACrB,MAAmB,EACnB,IAAqB;IAErB,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CACvC,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;QACvC,MAAM,QAAQ,GAAG,KAAK,GAAG,CAAC,CAAC;QAC3B,MAAM,OAAO,GAAG,YAAY,MAAM,CAAC,QAAQ,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC5D,2EAA2E;QAC3E,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,EAAE;YACrC,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,UAAU;YAChB,QAAQ;YACR,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,EAAE,MAAM,EAAE;SACnB,CAAC,CAAC;QACH,IAAI,CAAC;YACJ,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,QAAQ,EAAE,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACrE,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC;YACzC,OAAO,GAAG,OAAO,KAAK,SAAS,CAAC,QAAQ,IAAI,6BAA6B,CAAC,EAAE,CAAC;QAC9E,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,CAAC,KAAK,YAAY,gBAAgB,IAAI,KAAK,YAAY,kBAAkB,CAAC,EAAE,CAAC;gBACjF,MAAM,KAAK,CAAC;YACb,CAAC;YACD,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;YAChD,OAAO,GAAG,OAAO,0BAA0B,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/D,CAAC;IACF,CAAC,CAAC,CACF,CAAC;IACF,uFAAuF;IACvF,MAAM,CAAC,cAAc,EAAE,CAAC;IACxB,OAAO,OAAO;SACZ,GAAG,CAAC,MAAM,CAAC,EAAE;QACb,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU;YAAE,MAAM,MAAM,CAAC,MAAM,CAAC;QACtD,OAAO,MAAM,CAAC,KAAK,CAAC;IACrB,CAAC,CAAC;SACD,IAAI,CAAC,MAAM,CAAC,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAqB;IACtD,OAAO,mBAAmB,CAAC;QAC1B,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,0SAA0S,MAAM,CAAC,YAAY,CAAC,4IAA4I;QACvd,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC;YACf,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,kDAAkD,CAAC;SAC3F,CAAC;QACF,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,CAC5B,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,YAAY;QACxF,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;YACnC,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;YACzD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,YAAY,EAAE,CAAC;gBACvD,OAAO,OAAO,CAAC,MAAM,CACpB,IAAI,KAAK,CAAC,qBAAqB,MAAM,CAAC,YAAY,CAAC,yBAAyB,CAAC,CAC7E,CAAC;YACH,CAAC;YACD,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,KAAK,EAAE,CAAC,EAAE,CAAC;gBAC7C,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC,CAAC;YACrE,CAAC;YACD,OAAO,OAAO,CAAC,OAAO,CAAC;gBACtB,IAAI,EAAE,MAAM;gBACZ,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC;aACrD,CAAC,CAAC;QACJ,CAAC;KACD,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { z } from 'zod';\nimport type { Agent } from '#src/agent/domain/agent';\nimport { RecoverableError, UnrecoverableError } from '#src/shared/domain/errors';\nimport type { PreparingTool } from '../domain/preparedCall.ts';\nimport type { ToolCallContext } from '../domain/readRegistry.ts';\nimport { definePreparingTool } from './defineTool.ts';\nimport { cutAnswer, failureOf, subagentOptions } from './subagents.ts';\n\n/** The most explorers one call sends, so a single call cannot fan out without bound. */\nexport const maxExplorers = 4;\n\n/**\n * Runs one explorer per question, all at once, each a new agent that starts from its question\n * alone. Every explorer's usage counts in the run's, whether it answered or failed; a failure\n * becomes that explorer's answer, so the others' are not lost. A cancellation escapes.\n */\nasync function explore(\n\tquestions: readonly string[],\n\texplorer: () => Agent,\n\tsignal: AbortSignal,\n\tcall: ToolCallContext,\n): Promise<string> {\n\tconst answers = await Promise.allSettled(\n\t\tquestions.map(async (question, index) => {\n\t\t\tconst subagent = index + 1;\n\t\t\tconst heading = `Explorer ${String(subagent)}: ${question}`;\n\t\t\t// Within the run that made the call, so the explorers' reads belong to it.\n\t\t\tconst options = subagentOptions(call, {\n\t\t\t\ttool: 'explore',\n\t\t\t\tname: 'explorer',\n\t\t\t\tsubagent,\n\t\t\t\ttask: question,\n\t\t\t\toptions: { signal },\n\t\t\t});\n\t\t\ttry {\n\t\t\t\tconst { response, tokens } = await explorer().run(question, options);\n\t\t\t\tcall.spend(tokens, tokens === undefined);\n\t\t\t\treturn `${heading}\\n${cutAnswer(response || 'It found nothing to report.')}`;\n\t\t\t} catch (error) {\n\t\t\t\tif (!(error instanceof RecoverableError || error instanceof UnrecoverableError)) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tcall.spend(error.tokens, error.usageUnreported);\n\t\t\t\treturn `${heading}\\nThe explorer failed: ${failureOf(error)}`;\n\t\t\t}\n\t\t}),\n\t);\n\t// Whatever each explorer threw once the run was cancelled, the call ends as cancelled.\n\tsignal.throwIfAborted();\n\treturn answers\n\t\t.map(answer => {\n\t\t\tif (answer.status === 'rejected') throw answer.reason;\n\t\t\treturn answer.value;\n\t\t})\n\t\t.join('\\n\\n');\n}\n\n/**\n * `explore` for the planner and the reviewer: it sends cheaper, read-only explorers, created by\n * `explorer`, to find what an agent needs in the repository, so the agent that called it reads\n * their reports instead of filling its own context with the files.\n */\nexport function createExploreTool(explorer: () => Agent): PreparingTool {\n\treturn definePreparingTool({\n\t\tname: 'explore',\n\t\tdescription: `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.`,\n\t\tinput: z.object({\n\t\t\tquestions: z.array(z.string()).describe('One complete, independent question per explorer.'),\n\t\t}),\n\t\tsummarize: ({ questions }) =>\n\t\t\tquestions.length === 1 ? (questions[0] ?? '') : `${String(questions.length)} questions`,\n\t\tprepare: ({ questions }, _, call) => {\n\t\t\tconst asked = questions.map(question => question.trim());\n\t\t\tif (asked.length === 0 || asked.length > maxExplorers) {\n\t\t\t\treturn Promise.reject(\n\t\t\t\t\tnew Error(`Ask between 1 and ${String(maxExplorers)} questions in one call.`),\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (asked.some(question => question === '')) {\n\t\t\t\treturn Promise.reject(new Error('Every question needs some text.'));\n\t\t\t}\n\t\t\treturn Promise.resolve({\n\t\t\t\trisk: 'safe',\n\t\t\t\trun: signal => explore(asked, explorer, signal, call),\n\t\t\t});\n\t\t},\n\t});\n}\n"]}
|
|
@@ -43,7 +43,9 @@ export declare class FileEditor {
|
|
|
43
43
|
content?: Buffer;
|
|
44
44
|
}>;
|
|
45
45
|
prepare(change: FileChange, signal: AbortSignal): Promise<PreparedEdit>;
|
|
46
|
+
/** Makes the change once no other write of the run is under way, so none can come between. */
|
|
46
47
|
apply(prepared: PreparedEdit, signal: AbortSignal): Promise<void>;
|
|
48
|
+
private applyAlone;
|
|
47
49
|
/**
|
|
48
50
|
* Records a change that was made. If that fails, its prepared line still holds both states, so
|
|
49
51
|
* an undo can tell; but the record cannot be trusted to continue, and the caller must know.
|
|
@@ -133,7 +133,11 @@ export class FileEditor {
|
|
|
133
133
|
widensIgnoreRules: isIgnoreFile(target.absolute) && widensIgnoreRules(previous, content),
|
|
134
134
|
});
|
|
135
135
|
}
|
|
136
|
-
|
|
136
|
+
/** Makes the change once no other write of the run is under way, so none can come between. */
|
|
137
|
+
apply(prepared, signal) {
|
|
138
|
+
return this.session.exclusive(() => this.applyAlone(prepared, signal), signal);
|
|
139
|
+
}
|
|
140
|
+
async applyAlone(prepared, signal) {
|
|
137
141
|
const { path } = prepared;
|
|
138
142
|
signal.throwIfAborted();
|
|
139
143
|
// A Buffer's bytes cannot be frozen: copied, then checked, so what is written is what was
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileEditor.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/fileEditor.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomBytes } from 'node:crypto';\nimport {\n\tchmod,\n\tlink,\n\tlstat,\n\tmkdir,\n\tmkdtemp,\n\topen,\n\treadFile,\n\trename,\n\trm,\n\trmdir,\n\tstat,\n\tunlink,\n} from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { basename, dirname, join } from 'node:path';\nimport type { FileState, RunJournal } from '#src/recovery/domain/recoveryStore';\nimport type { ToolRisk } from '#src/agent/domain/approval';\nimport type { AccessPolicy, AllowedPath } from '../domain/accessPolicy.ts';\nimport {\n\tEditRefusedError,\n\tEditUnconfirmedError,\n\ttype FileChange,\n\ttype PreparedEdit,\n} from '../domain/fileEdits.ts';\nimport { isIgnoreFile, widensIgnoreRules } from './ignoreFileChanges.ts';\nimport type { WriteSession } from './writeSession.ts';\n\n// Larger than any source file a model should rewrite whole, and small enough to keep a copy.\nconst defaultMaxFileBytes = 5 * 1024 * 1024;\n\nfunction hashOf(content: Buffer): string {\n\treturn createHash('sha256').update(content).digest('hex');\n}\n\nfunction code(error: unknown): string {\n\treturn (error as NodeJS.ErrnoException).code ?? 'an unknown error';\n}\n\nfunction sameState(a: FileState, b: FileState): boolean {\n\tif (!a.exists || !b.exists) return a.exists === b.exists;\n\treturn a.hash === b.hash && a.mode === b.mode;\n}\n\nasync function syncDirectory(path: string): Promise<void> {\n\tconst directory = await open(path, 'r');\n\ttry {\n\t\tawait directory.sync();\n\t} finally {\n\t\tawait directory.close();\n\t}\n}\n\nlet newFileMode: Promise<number> | undefined;\n\n/**\n * The mode a new file gets in this process: 0o666 less the umask. Measured once by creating\n * one, because reading the umask directly briefly changes it for every thread.\n */\nfunction getNewFileMode(): Promise<number> {\n\tnewFileMode ??= (async () => {\n\t\tconst folder = await mkdtemp(join(tmpdir(), 'mikode-harness-mode-'));\n\t\ttry {\n\t\t\tconst probe = join(folder, 'probe');\n\t\t\tawait (await open(probe, 'wx', 0o666)).close();\n\t\t\treturn (await stat(probe)).mode & 0o7777;\n\t\t} finally {\n\t\t\tawait rm(folder, { recursive: true, force: true });\n\t\t}\n\t})();\n\treturn newFileMode;\n}\n\n/** The folders between the nearest existing one and `folder`, outermost first. */\nasync function missingFolders(folder: string): Promise<string[]> {\n\tconst missing: string[] = [];\n\tfor (let current = folder; ; current = dirname(current)) {\n\t\tconst exists = await lstat(current).then(\n\t\t\t() => true,\n\t\t\t(error: unknown) => {\n\t\t\t\tif (code(error) === 'ENOENT') return false;\n\t\t\t\tthrow error;\n\t\t\t},\n\t\t);\n\t\tif (exists || dirname(current) === current) return missing;\n\t\tmissing.unshift(current);\n\t}\n}\n\n/**\n * The risk of a prepared change. A change git or the run's record can undo is `mutating`; one\n * that widens `.gitignore` is `destructive`, so it waits for the user, because it would open\n * paths that every tool keeps closed.\n */\nexport function editRisk(prepared: PreparedEdit): ToolRisk {\n\treturn prepared.widensIgnoreRules ? 'destructive' : 'mutating';\n}\n\n/**\n * Makes the changes the edit tools ask for, under the access policy and the run's recovery\n * journal. `prepare` checks a change and fixes what it will do; `apply` makes it, in this order:\n *\n * 1. Check the policy again, and that the file still holds what was prepared.\n * 2. Store the content it replaces and the content it writes, and record the change as\n * prepared. If either fails, nothing is touched.\n * 3. Write: a new file appears whole and only if nothing took its place meanwhile; a replaced\n * one is swapped for its new version in one rename; a deleted one is unlinked.\n * 4. Record the change as applied, or as abandoned when the write failed and left the file as\n * it was. A change the record cannot confirm stops the session and throws\n * `EditUnconfirmedError`, never a success or a refusal.\n *\n * Every message names the file as the model wrote it, never by its place on the host.\n */\nexport class FileEditor {\n\tprivate readonly policy: AccessPolicy;\n\tprivate readonly session: WriteSession;\n\tprivate readonly maxFileBytes: number;\n\n\tconstructor({\n\t\tpolicy,\n\t\tsession,\n\t\tmaxFileBytes = defaultMaxFileBytes,\n\t}: {\n\t\tpolicy: AccessPolicy;\n\t\tsession: WriteSession;\n\t\tmaxFileBytes?: number;\n\t}) {\n\t\tthis.policy = policy;\n\t\tthis.session = session;\n\t\tthis.maxFileBytes = maxFileBytes;\n\t}\n\n\t/**\n\t * What `path` holds now, checked as a change to it would be: an edit format works its new\n\t * content out from this, and an edit tool compares it with the version the model read.\n\t */\n\tasync current(\n\t\tpath: string,\n\t\tsignal: AbortSignal,\n\t): Promise<{ target: AllowedPath; state: FileState; content?: Buffer }> {\n\t\tconst target = await this.policy.check(path, 'write', signal);\n\t\treturn { target, ...(await this.read(target.absolute, path)) };\n\t}\n\n\tasync prepare(change: FileChange, signal: AbortSignal): Promise<PreparedEdit> {\n\t\tconst { path } = change;\n\t\tconst target = await this.policy.check(path, 'write', signal);\n\t\tconst { state, content: previous } = await this.read(target.absolute, path);\n\n\t\tif (change.kind === 'create') {\n\t\t\tif (state.exists) {\n\t\t\t\tthrow new EditRefusedError(`\"${path}\" already exists; read it and change it instead`);\n\t\t\t}\n\t\t} else {\n\t\t\tif (!state.exists) throw new EditRefusedError(`\"${path}\" does not exist`);\n\t\t\tif (change.expected !== undefined && change.expected !== state.hash) {\n\t\t\t\tthrow new EditRefusedError(`\"${path}\" changed since it was read; read it again`);\n\t\t\t}\n\t\t}\n\n\t\tconst content = change.kind === 'delete' ? undefined : change.content;\n\t\tif (content && content.length > this.maxFileBytes) {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`The new content of \"${path}\" is larger than ${String(this.maxFileBytes / 1024 / 1024)} MB`,\n\t\t\t);\n\t\t}\n\t\tif (content && state.exists && hashOf(content) === state.hash) {\n\t\t\tthrow new EditRefusedError(`\"${path}\" already holds that content; nothing to change`);\n\t\t}\n\n\t\t// Copies all the way down: what is approved must not change while the approval waits.\n\t\treturn Object.freeze({\n\t\t\tkind: change.kind,\n\t\t\tpath,\n\t\t\ttarget: Object.freeze({ ...target }),\n\t\t\tbefore: Object.freeze({ ...state }),\n\t\t\tcontent: content && Buffer.from(content),\n\t\t\tcontentHash: content && hashOf(content),\n\t\t\twidensIgnoreRules: isIgnoreFile(target.absolute) && widensIgnoreRules(previous, content),\n\t\t});\n\t}\n\n\tasync apply(prepared: PreparedEdit, signal: AbortSignal): Promise<void> {\n\t\tconst { path } = prepared;\n\t\tsignal.throwIfAborted();\n\t\t// A Buffer's bytes cannot be frozen: copied, then checked, so what is written is what was\n\t\t// approved, whatever happens to `prepared.content` from here on.\n\t\tconst content = prepared.content && Buffer.from(prepared.content);\n\t\tif ((content && hashOf(content)) !== prepared.contentHash) {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`The content prepared for \"${path}\" changed after it was prepared; prepare the change again`,\n\t\t\t);\n\t\t}\n\t\tconst target = await this.policy.check(path, 'write', signal);\n\t\tif (target.absolute !== prepared.target.absolute) {\n\t\t\tthrow new EditRefusedError(`\"${path}\" now leads somewhere else; prepare the change again`);\n\t\t}\n\t\tconst current = await this.read(target.absolute, path);\n\t\tif (!sameState(current.state, prepared.before)) {\n\t\t\tthrow new EditRefusedError(`\"${path}\" changed after this change was prepared; read it again`);\n\t\t}\n\n\t\t// Checked again before the first effect: starting the journal takes the workspace.\n\t\tsignal.throwIfAborted();\n\t\tconst journal = await this.session.journalForChange();\n\t\tconst after = await this.record(journal, path, current.content, content, prepared.before);\n\t\tconst createdFolders =\n\t\t\tprepared.kind === 'create' ? await missingFolders(dirname(target.absolute)) : [];\n\t\tconst sequence = await this.journal(path, () =>\n\t\t\tjournal.prepare({\n\t\t\t\tpath: target.absolute,\n\t\t\t\tbefore: current.state,\n\t\t\t\tafter,\n\t\t\t\t...(createdFolders.length > 0 ? { createdFolders } : {}),\n\t\t\t}),\n\t\t);\n\n\t\t// The last moment to stop: past here the file changes.\n\t\tif (signal.aborted) {\n\t\t\tawait journal.abandoned(sequence);\n\t\t\tsignal.throwIfAborted();\n\t\t}\n\n\t\ttry {\n\t\t\tawait this.write(prepared, content, target.absolute);\n\t\t} catch (error) {\n\t\t\tawait this.settleFailedWrite({\n\t\t\t\tjournal,\n\t\t\t\tsequence,\n\t\t\t\tkind: prepared.kind,\n\t\t\t\tpath,\n\t\t\t\ttarget: target.absolute,\n\t\t\t\tcurrent,\n\t\t\t\tafter,\n\t\t\t\tcreatedFolders,\n\t\t\t\terror,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tawait this.markApplied(journal, sequence, path);\n\t}\n\n\t/**\n\t * Records a change that was made. If that fails, its prepared line still holds both states, so\n\t * an undo can tell; but the record cannot be trusted to continue, and the caller must know.\n\t */\n\tprivate async markApplied(journal: RunJournal, sequence: number, path: string): Promise<void> {\n\t\ttry {\n\t\t\tawait journal.applied(sequence);\n\t\t} catch (error) {\n\t\t\tthis.session.stop('Writing stopped: the harness could not record a change it made');\n\t\t\tthrow new EditUnconfirmedError(\n\t\t\t\t`\"${path}\" was changed, but the harness could not record it; writing has stopped for this run`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\t}\n\n\t/** What `path` holds now. Only a regular file within the size limit can be changed. */\n\tprivate async read(\n\t\tabsolute: string,\n\t\tpath: string,\n\t): Promise<{ state: FileState; content?: Buffer }> {\n\t\tlet stats;\n\t\ttry {\n\t\t\tstats = await lstat(absolute);\n\t\t} catch (error) {\n\t\t\tif (code(error) === 'ENOENT') return { state: { exists: false } };\n\t\t\tthrow new EditRefusedError(`Could not read \"${path}\" (${code(error)})`, { cause: error });\n\t\t}\n\t\tif (!stats.isFile()) throw new EditRefusedError(`\"${path}\" is not a regular file`);\n\t\tif (stats.size > this.maxFileBytes) {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`\"${path}\" is larger than ${String(this.maxFileBytes / 1024 / 1024)} MB, too large to change`,\n\t\t\t);\n\t\t}\n\n\t\tlet content: Buffer;\n\t\ttry {\n\t\t\tcontent = await readFile(absolute);\n\t\t} catch (error) {\n\t\t\tthrow new EditRefusedError(`Could not read \"${path}\" (${code(error)})`, { cause: error });\n\t\t}\n\t\treturn {\n\t\t\tstate: { exists: true, hash: hashOf(content), mode: stats.mode & 0o7777 },\n\t\t\tcontent,\n\t\t};\n\t}\n\n\t/** Stores what the change replaces and what it writes, and returns the state it leaves. */\n\tprivate async record(\n\t\tjournal: RunJournal,\n\t\tpath: string,\n\t\tprevious: Buffer | undefined,\n\t\tcontent: Buffer | undefined,\n\t\tbefore: FileState,\n\t): Promise<FileState> {\n\t\tif (previous) await this.journal(path, () => journal.saveContent(previous));\n\t\tif (!content) return { exists: false };\n\n\t\tconst hash = await this.journal(path, () => journal.saveContent(content));\n\t\treturn { exists: true, hash, mode: before.exists ? before.mode : await getNewFileMode() };\n\t}\n\n\tprivate async journal<Result>(path: string, step: () => Promise<Result>): Promise<Result> {\n\t\ttry {\n\t\t\treturn await step();\n\t\t} catch (error) {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`The harness could not record how to undo the change to \"${path}\", so it was not changed`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate async write(\n\t\t{ kind, before }: PreparedEdit,\n\t\tcontent: Buffer | undefined,\n\t\tabsolute: string,\n\t): Promise<void> {\n\t\tif (kind === 'delete') {\n\t\t\tawait unlink(absolute);\n\t\t\tawait syncDirectory(dirname(absolute));\n\t\t\treturn;\n\t\t}\n\n\t\tawait mkdir(dirname(absolute), { recursive: true });\n\t\t// Written beside the file, so the final step is one rename or link on the same disk.\n\t\tconst temporary = join(\n\t\t\tdirname(absolute),\n\t\t\t`.${basename(absolute)}.${randomBytes(6).toString('hex')}.mikode-tmp`,\n\t\t);\n\t\ttry {\n\t\t\tconst handle = await open(temporary, 'wx');\n\t\t\ttry {\n\t\t\t\tawait handle.writeFile(content ?? Buffer.alloc(0));\n\t\t\t\tawait handle.sync();\n\t\t\t} finally {\n\t\t\t\tawait handle.close();\n\t\t\t}\n\t\t\tif (before.exists) await chmod(temporary, before.mode);\n\n\t\t\tif (kind === 'create') {\n\t\t\t\t// A link fails if something appeared at the path meanwhile; a rename would replace it.\n\t\t\t\tawait link(temporary, absolute);\n\t\t\t} else {\n\t\t\t\tawait rename(temporary, absolute);\n\t\t\t}\n\t\t} finally {\n\t\t\tawait rm(temporary, { force: true });\n\t\t}\n\t\tawait syncDirectory(dirname(absolute));\n\t}\n\n\t/**\n\t * A write threw. If the file still holds what it held, nothing happened and the change is\n\t * abandoned; if it holds the new state, the change happened anyway. Anything else cannot be\n\t * explained, so the session stops writing.\n\t */\n\tprivate async settleFailedWrite({\n\t\tjournal,\n\t\tsequence,\n\t\tkind,\n\t\tpath,\n\t\ttarget,\n\t\tcurrent,\n\t\tafter,\n\t\tcreatedFolders,\n\t\terror,\n\t}: {\n\t\tjournal: RunJournal;\n\t\tsequence: number;\n\t\tkind: PreparedEdit['kind'];\n\t\tpath: string;\n\t\ttarget: string;\n\t\tcurrent: { state: FileState };\n\t\tafter: FileState;\n\t\tcreatedFolders: string[];\n\t\terror: unknown;\n\t}): Promise<void> {\n\t\tconst now = await this.read(target, path).catch(() => undefined);\n\t\tif (now && sameState(now.state, after)) {\n\t\t\tawait this.markApplied(journal, sequence, path);\n\t\t\treturn;\n\t\t}\n\t\t// A link that found the path taken made no change: what is there now is someone else's.\n\t\tconst appeared = kind === 'create' && code(error) === 'EEXIST';\n\t\tif (appeared || (now && sameState(now.state, current.state))) {\n\t\t\t// Innermost first, and only while empty: anything else in them is not this run's.\n\t\t\tfor (const folder of [...createdFolders].reverse()) {\n\t\t\t\tawait rmdir(folder).catch(() => undefined);\n\t\t\t}\n\t\t\tawait journal.abandoned(sequence).catch(() => undefined);\n\t\t\tthrow new EditRefusedError(\n\t\t\t\tappeared\n\t\t\t\t\t? `\"${path}\" appeared while it was being created; read it before changing it`\n\t\t\t\t\t: `Could not write \"${path}\" (${code(error)}); it was not changed`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\n\t\tthis.session.stop(\n\t\t\t`Writing stopped: the harness could not tell whether \"${path}\" was changed; ask the user to check it`,\n\t\t);\n\t\tthrow new EditUnconfirmedError(\n\t\t\t`Could not tell whether \"${path}\" was changed (${code(error)}); writing has stopped for this run`,\n\t\t\t{ cause: error },\n\t\t);\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fileEditor.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/fileEditor.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomBytes } from 'node:crypto';\nimport {\n\tchmod,\n\tlink,\n\tlstat,\n\tmkdir,\n\tmkdtemp,\n\topen,\n\treadFile,\n\trename,\n\trm,\n\trmdir,\n\tstat,\n\tunlink,\n} from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { basename, dirname, join } from 'node:path';\nimport type { FileState, RunJournal } from '#src/recovery/domain/recoveryStore';\nimport type { ToolRisk } from '#src/agent/domain/approval';\nimport type { AccessPolicy, AllowedPath } from '../domain/accessPolicy.ts';\nimport {\n\tEditRefusedError,\n\tEditUnconfirmedError,\n\ttype FileChange,\n\ttype PreparedEdit,\n} from '../domain/fileEdits.ts';\nimport { isIgnoreFile, widensIgnoreRules } from './ignoreFileChanges.ts';\nimport type { WriteSession } from './writeSession.ts';\n\n// Larger than any source file a model should rewrite whole, and small enough to keep a copy.\nconst defaultMaxFileBytes = 5 * 1024 * 1024;\n\nfunction hashOf(content: Buffer): string {\n\treturn createHash('sha256').update(content).digest('hex');\n}\n\nfunction code(error: unknown): string {\n\treturn (error as NodeJS.ErrnoException).code ?? 'an unknown error';\n}\n\nfunction sameState(a: FileState, b: FileState): boolean {\n\tif (!a.exists || !b.exists) return a.exists === b.exists;\n\treturn a.hash === b.hash && a.mode === b.mode;\n}\n\nasync function syncDirectory(path: string): Promise<void> {\n\tconst directory = await open(path, 'r');\n\ttry {\n\t\tawait directory.sync();\n\t} finally {\n\t\tawait directory.close();\n\t}\n}\n\nlet newFileMode: Promise<number> | undefined;\n\n/**\n * The mode a new file gets in this process: 0o666 less the umask. Measured once by creating\n * one, because reading the umask directly briefly changes it for every thread.\n */\nfunction getNewFileMode(): Promise<number> {\n\tnewFileMode ??= (async () => {\n\t\tconst folder = await mkdtemp(join(tmpdir(), 'mikode-harness-mode-'));\n\t\ttry {\n\t\t\tconst probe = join(folder, 'probe');\n\t\t\tawait (await open(probe, 'wx', 0o666)).close();\n\t\t\treturn (await stat(probe)).mode & 0o7777;\n\t\t} finally {\n\t\t\tawait rm(folder, { recursive: true, force: true });\n\t\t}\n\t})();\n\treturn newFileMode;\n}\n\n/** The folders between the nearest existing one and `folder`, outermost first. */\nasync function missingFolders(folder: string): Promise<string[]> {\n\tconst missing: string[] = [];\n\tfor (let current = folder; ; current = dirname(current)) {\n\t\tconst exists = await lstat(current).then(\n\t\t\t() => true,\n\t\t\t(error: unknown) => {\n\t\t\t\tif (code(error) === 'ENOENT') return false;\n\t\t\t\tthrow error;\n\t\t\t},\n\t\t);\n\t\tif (exists || dirname(current) === current) return missing;\n\t\tmissing.unshift(current);\n\t}\n}\n\n/**\n * The risk of a prepared change. A change git or the run's record can undo is `mutating`; one\n * that widens `.gitignore` is `destructive`, so it waits for the user, because it would open\n * paths that every tool keeps closed.\n */\nexport function editRisk(prepared: PreparedEdit): ToolRisk {\n\treturn prepared.widensIgnoreRules ? 'destructive' : 'mutating';\n}\n\n/**\n * Makes the changes the edit tools ask for, under the access policy and the run's recovery\n * journal. `prepare` checks a change and fixes what it will do; `apply` makes it, in this order:\n *\n * 1. Check the policy again, and that the file still holds what was prepared.\n * 2. Store the content it replaces and the content it writes, and record the change as\n * prepared. If either fails, nothing is touched.\n * 3. Write: a new file appears whole and only if nothing took its place meanwhile; a replaced\n * one is swapped for its new version in one rename; a deleted one is unlinked.\n * 4. Record the change as applied, or as abandoned when the write failed and left the file as\n * it was. A change the record cannot confirm stops the session and throws\n * `EditUnconfirmedError`, never a success or a refusal.\n *\n * Every message names the file as the model wrote it, never by its place on the host.\n */\nexport class FileEditor {\n\tprivate readonly policy: AccessPolicy;\n\tprivate readonly session: WriteSession;\n\tprivate readonly maxFileBytes: number;\n\n\tconstructor({\n\t\tpolicy,\n\t\tsession,\n\t\tmaxFileBytes = defaultMaxFileBytes,\n\t}: {\n\t\tpolicy: AccessPolicy;\n\t\tsession: WriteSession;\n\t\tmaxFileBytes?: number;\n\t}) {\n\t\tthis.policy = policy;\n\t\tthis.session = session;\n\t\tthis.maxFileBytes = maxFileBytes;\n\t}\n\n\t/**\n\t * What `path` holds now, checked as a change to it would be: an edit format works its new\n\t * content out from this, and an edit tool compares it with the version the model read.\n\t */\n\tasync current(\n\t\tpath: string,\n\t\tsignal: AbortSignal,\n\t): Promise<{ target: AllowedPath; state: FileState; content?: Buffer }> {\n\t\tconst target = await this.policy.check(path, 'write', signal);\n\t\treturn { target, ...(await this.read(target.absolute, path)) };\n\t}\n\n\tasync prepare(change: FileChange, signal: AbortSignal): Promise<PreparedEdit> {\n\t\tconst { path } = change;\n\t\tconst target = await this.policy.check(path, 'write', signal);\n\t\tconst { state, content: previous } = await this.read(target.absolute, path);\n\n\t\tif (change.kind === 'create') {\n\t\t\tif (state.exists) {\n\t\t\t\tthrow new EditRefusedError(`\"${path}\" already exists; read it and change it instead`);\n\t\t\t}\n\t\t} else {\n\t\t\tif (!state.exists) throw new EditRefusedError(`\"${path}\" does not exist`);\n\t\t\tif (change.expected !== undefined && change.expected !== state.hash) {\n\t\t\t\tthrow new EditRefusedError(`\"${path}\" changed since it was read; read it again`);\n\t\t\t}\n\t\t}\n\n\t\tconst content = change.kind === 'delete' ? undefined : change.content;\n\t\tif (content && content.length > this.maxFileBytes) {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`The new content of \"${path}\" is larger than ${String(this.maxFileBytes / 1024 / 1024)} MB`,\n\t\t\t);\n\t\t}\n\t\tif (content && state.exists && hashOf(content) === state.hash) {\n\t\t\tthrow new EditRefusedError(`\"${path}\" already holds that content; nothing to change`);\n\t\t}\n\n\t\t// Copies all the way down: what is approved must not change while the approval waits.\n\t\treturn Object.freeze({\n\t\t\tkind: change.kind,\n\t\t\tpath,\n\t\t\ttarget: Object.freeze({ ...target }),\n\t\t\tbefore: Object.freeze({ ...state }),\n\t\t\tcontent: content && Buffer.from(content),\n\t\t\tcontentHash: content && hashOf(content),\n\t\t\twidensIgnoreRules: isIgnoreFile(target.absolute) && widensIgnoreRules(previous, content),\n\t\t});\n\t}\n\n\t/** Makes the change once no other write of the run is under way, so none can come between. */\n\tapply(prepared: PreparedEdit, signal: AbortSignal): Promise<void> {\n\t\treturn this.session.exclusive(() => this.applyAlone(prepared, signal), signal);\n\t}\n\n\tprivate async applyAlone(prepared: PreparedEdit, signal: AbortSignal): Promise<void> {\n\t\tconst { path } = prepared;\n\t\tsignal.throwIfAborted();\n\t\t// A Buffer's bytes cannot be frozen: copied, then checked, so what is written is what was\n\t\t// approved, whatever happens to `prepared.content` from here on.\n\t\tconst content = prepared.content && Buffer.from(prepared.content);\n\t\tif ((content && hashOf(content)) !== prepared.contentHash) {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`The content prepared for \"${path}\" changed after it was prepared; prepare the change again`,\n\t\t\t);\n\t\t}\n\t\tconst target = await this.policy.check(path, 'write', signal);\n\t\tif (target.absolute !== prepared.target.absolute) {\n\t\t\tthrow new EditRefusedError(`\"${path}\" now leads somewhere else; prepare the change again`);\n\t\t}\n\t\tconst current = await this.read(target.absolute, path);\n\t\tif (!sameState(current.state, prepared.before)) {\n\t\t\tthrow new EditRefusedError(`\"${path}\" changed after this change was prepared; read it again`);\n\t\t}\n\n\t\t// Checked again before the first effect: starting the journal takes the workspace.\n\t\tsignal.throwIfAborted();\n\t\tconst journal = await this.session.journalForChange();\n\t\tconst after = await this.record(journal, path, current.content, content, prepared.before);\n\t\tconst createdFolders =\n\t\t\tprepared.kind === 'create' ? await missingFolders(dirname(target.absolute)) : [];\n\t\tconst sequence = await this.journal(path, () =>\n\t\t\tjournal.prepare({\n\t\t\t\tpath: target.absolute,\n\t\t\t\tbefore: current.state,\n\t\t\t\tafter,\n\t\t\t\t...(createdFolders.length > 0 ? { createdFolders } : {}),\n\t\t\t}),\n\t\t);\n\n\t\t// The last moment to stop: past here the file changes.\n\t\tif (signal.aborted) {\n\t\t\tawait journal.abandoned(sequence);\n\t\t\tsignal.throwIfAborted();\n\t\t}\n\n\t\ttry {\n\t\t\tawait this.write(prepared, content, target.absolute);\n\t\t} catch (error) {\n\t\t\tawait this.settleFailedWrite({\n\t\t\t\tjournal,\n\t\t\t\tsequence,\n\t\t\t\tkind: prepared.kind,\n\t\t\t\tpath,\n\t\t\t\ttarget: target.absolute,\n\t\t\t\tcurrent,\n\t\t\t\tafter,\n\t\t\t\tcreatedFolders,\n\t\t\t\terror,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tawait this.markApplied(journal, sequence, path);\n\t}\n\n\t/**\n\t * Records a change that was made. If that fails, its prepared line still holds both states, so\n\t * an undo can tell; but the record cannot be trusted to continue, and the caller must know.\n\t */\n\tprivate async markApplied(journal: RunJournal, sequence: number, path: string): Promise<void> {\n\t\ttry {\n\t\t\tawait journal.applied(sequence);\n\t\t} catch (error) {\n\t\t\tthis.session.stop('Writing stopped: the harness could not record a change it made');\n\t\t\tthrow new EditUnconfirmedError(\n\t\t\t\t`\"${path}\" was changed, but the harness could not record it; writing has stopped for this run`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\t}\n\n\t/** What `path` holds now. Only a regular file within the size limit can be changed. */\n\tprivate async read(\n\t\tabsolute: string,\n\t\tpath: string,\n\t): Promise<{ state: FileState; content?: Buffer }> {\n\t\tlet stats;\n\t\ttry {\n\t\t\tstats = await lstat(absolute);\n\t\t} catch (error) {\n\t\t\tif (code(error) === 'ENOENT') return { state: { exists: false } };\n\t\t\tthrow new EditRefusedError(`Could not read \"${path}\" (${code(error)})`, { cause: error });\n\t\t}\n\t\tif (!stats.isFile()) throw new EditRefusedError(`\"${path}\" is not a regular file`);\n\t\tif (stats.size > this.maxFileBytes) {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`\"${path}\" is larger than ${String(this.maxFileBytes / 1024 / 1024)} MB, too large to change`,\n\t\t\t);\n\t\t}\n\n\t\tlet content: Buffer;\n\t\ttry {\n\t\t\tcontent = await readFile(absolute);\n\t\t} catch (error) {\n\t\t\tthrow new EditRefusedError(`Could not read \"${path}\" (${code(error)})`, { cause: error });\n\t\t}\n\t\treturn {\n\t\t\tstate: { exists: true, hash: hashOf(content), mode: stats.mode & 0o7777 },\n\t\t\tcontent,\n\t\t};\n\t}\n\n\t/** Stores what the change replaces and what it writes, and returns the state it leaves. */\n\tprivate async record(\n\t\tjournal: RunJournal,\n\t\tpath: string,\n\t\tprevious: Buffer | undefined,\n\t\tcontent: Buffer | undefined,\n\t\tbefore: FileState,\n\t): Promise<FileState> {\n\t\tif (previous) await this.journal(path, () => journal.saveContent(previous));\n\t\tif (!content) return { exists: false };\n\n\t\tconst hash = await this.journal(path, () => journal.saveContent(content));\n\t\treturn { exists: true, hash, mode: before.exists ? before.mode : await getNewFileMode() };\n\t}\n\n\tprivate async journal<Result>(path: string, step: () => Promise<Result>): Promise<Result> {\n\t\ttry {\n\t\t\treturn await step();\n\t\t} catch (error) {\n\t\t\tthrow new EditRefusedError(\n\t\t\t\t`The harness could not record how to undo the change to \"${path}\", so it was not changed`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate async write(\n\t\t{ kind, before }: PreparedEdit,\n\t\tcontent: Buffer | undefined,\n\t\tabsolute: string,\n\t): Promise<void> {\n\t\tif (kind === 'delete') {\n\t\t\tawait unlink(absolute);\n\t\t\tawait syncDirectory(dirname(absolute));\n\t\t\treturn;\n\t\t}\n\n\t\tawait mkdir(dirname(absolute), { recursive: true });\n\t\t// Written beside the file, so the final step is one rename or link on the same disk.\n\t\tconst temporary = join(\n\t\t\tdirname(absolute),\n\t\t\t`.${basename(absolute)}.${randomBytes(6).toString('hex')}.mikode-tmp`,\n\t\t);\n\t\ttry {\n\t\t\tconst handle = await open(temporary, 'wx');\n\t\t\ttry {\n\t\t\t\tawait handle.writeFile(content ?? Buffer.alloc(0));\n\t\t\t\tawait handle.sync();\n\t\t\t} finally {\n\t\t\t\tawait handle.close();\n\t\t\t}\n\t\t\tif (before.exists) await chmod(temporary, before.mode);\n\n\t\t\tif (kind === 'create') {\n\t\t\t\t// A link fails if something appeared at the path meanwhile; a rename would replace it.\n\t\t\t\tawait link(temporary, absolute);\n\t\t\t} else {\n\t\t\t\tawait rename(temporary, absolute);\n\t\t\t}\n\t\t} finally {\n\t\t\tawait rm(temporary, { force: true });\n\t\t}\n\t\tawait syncDirectory(dirname(absolute));\n\t}\n\n\t/**\n\t * A write threw. If the file still holds what it held, nothing happened and the change is\n\t * abandoned; if it holds the new state, the change happened anyway. Anything else cannot be\n\t * explained, so the session stops writing.\n\t */\n\tprivate async settleFailedWrite({\n\t\tjournal,\n\t\tsequence,\n\t\tkind,\n\t\tpath,\n\t\ttarget,\n\t\tcurrent,\n\t\tafter,\n\t\tcreatedFolders,\n\t\terror,\n\t}: {\n\t\tjournal: RunJournal;\n\t\tsequence: number;\n\t\tkind: PreparedEdit['kind'];\n\t\tpath: string;\n\t\ttarget: string;\n\t\tcurrent: { state: FileState };\n\t\tafter: FileState;\n\t\tcreatedFolders: string[];\n\t\terror: unknown;\n\t}): Promise<void> {\n\t\tconst now = await this.read(target, path).catch(() => undefined);\n\t\tif (now && sameState(now.state, after)) {\n\t\t\tawait this.markApplied(journal, sequence, path);\n\t\t\treturn;\n\t\t}\n\t\t// A link that found the path taken made no change: what is there now is someone else's.\n\t\tconst appeared = kind === 'create' && code(error) === 'EEXIST';\n\t\tif (appeared || (now && sameState(now.state, current.state))) {\n\t\t\t// Innermost first, and only while empty: anything else in them is not this run's.\n\t\t\tfor (const folder of [...createdFolders].reverse()) {\n\t\t\t\tawait rmdir(folder).catch(() => undefined);\n\t\t\t}\n\t\t\tawait journal.abandoned(sequence).catch(() => undefined);\n\t\t\tthrow new EditRefusedError(\n\t\t\t\tappeared\n\t\t\t\t\t? `\"${path}\" appeared while it was being created; read it before changing it`\n\t\t\t\t\t: `Could not write \"${path}\" (${code(error)}); it was not changed`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\n\t\tthis.session.stop(\n\t\t\t`Writing stopped: the harness could not tell whether \"${path}\" was changed; ask the user to check it`,\n\t\t);\n\t\tthrow new EditUnconfirmedError(\n\t\t\t`Could not tell whether \"${path}\" was changed (${code(error)}); writing has stopped for this run`,\n\t\t\t{ cause: error },\n\t\t);\n\t}\n}\n"]}
|
|
@@ -34,6 +34,7 @@ export declare class FileFormatter {
|
|
|
34
34
|
* stopped. Writing stops for the run then, as after such a command.
|
|
35
35
|
*/
|
|
36
36
|
format(absolute: string, session: WriteSession, signal: AbortSignal): Promise<string | undefined>;
|
|
37
|
+
private formatAlone;
|
|
37
38
|
/** The file as the formatter's last argument: relative to where it runs, never read as a flag. */
|
|
38
39
|
private argumentFor;
|
|
39
40
|
}
|
|
@@ -45,7 +45,11 @@ export class FileFormatter {
|
|
|
45
45
|
* @throws {EditUnconfirmedError} when what it changed could not be recorded, or it could not be
|
|
46
46
|
* stopped. Writing stops for the run then, as after such a command.
|
|
47
47
|
*/
|
|
48
|
-
|
|
48
|
+
format(absolute, session, signal) {
|
|
49
|
+
// Alone, as a command runs, so no other write of the run comes between its captures.
|
|
50
|
+
return session.exclusive(() => this.formatAlone(absolute, session, signal), signal);
|
|
51
|
+
}
|
|
52
|
+
async formatAlone(absolute, session, signal) {
|
|
49
53
|
signal.throwIfAborted();
|
|
50
54
|
let checkpoint;
|
|
51
55
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileFormatter.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/fileFormatter.ts"],"names":[],"mappings":"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","sourcesContent":["import { isAbsolute, relative } from 'node:path';\nimport { InvalidAgentConfigError } from '#src/shared/domain/errors';\nimport { describeFailure } from '#src/shared/domain/providerFailure';\nimport type { AccessPolicy } from '../domain/accessPolicy.ts';\nimport {\n\tdefaultFormatterTimeoutMs,\n\ttype Formatter,\n\tvalidateFormatter,\n} from '../domain/commands.ts';\nimport { EditUnconfirmedError } from '../domain/fileEdits.ts';\nimport { CommandCheckpoint } from './commandCheckpoint.ts';\nimport { type CommandProcessResult, runCommandProcess } from './commandProcess.ts';\nimport { environmentFor, resolveCwd } from './commandTool.ts';\nimport type { WriteSession } from './writeSession.ts';\n\n// What the model reads of a formatter that failed: enough for a syntax error, and short, since\n// it comes with an edit.\nconst failureOutputBytes = 4 * 1024;\n// How the history names what the formatter changed.\nconst observer = 'formatter';\n\n/**\n * Runs the consumer's formatter on a file an edit has just written. Only that file is captured\n * around it, so what it changed there is in the run's history and can be undone; a formatter\n * that changes other files leaves those changes out of the history.\n */\nexport class FileFormatter {\n\tprivate readonly formatter: Formatter;\n\tprivate readonly cwd: string;\n\tprivate readonly runProcess: typeof runCommandProcess;\n\n\tprivate constructor(formatter: Formatter, cwd: string, runProcess: typeof runCommandProcess) {\n\t\tthis.formatter = formatter;\n\t\tthis.cwd = cwd;\n\t\tthis.runProcess = runProcess;\n\t}\n\n\t/**\n\t * @throws {InvalidAgentConfigError} for an invalid formatter, a working directory outside every\n\t * `write` root, or on Windows, where a process group cannot be stopped.\n\t */\n\tstatic async create({\n\t\tformatter,\n\t\tpolicy,\n\t\tbase,\n\t\tplatform = process.platform,\n\t\trunProcess = runCommandProcess,\n\t}: {\n\t\tformatter: Formatter;\n\t\tpolicy: AccessPolicy;\n\t\t/** The real path of the first root, where a relative `cwd` starts. */\n\t\tbase: string;\n\t\tplatform?: NodeJS.Platform;\n\t\t/** Runs the program. Replaced in tests, to reach what a real process rarely does. */\n\t\trunProcess?: typeof runCommandProcess;\n\t}): Promise<FileFormatter> {\n\t\tif (platform === 'win32') {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t'A formatter is not supported on Windows yet: the harness cannot stop it and everything it started there',\n\t\t\t);\n\t\t}\n\t\tvalidateFormatter(formatter);\n\t\tconst cwd = await resolveCwd('The formatter', formatter.cwd, policy, base);\n\t\treturn new FileFormatter(formatter, cwd, runProcess);\n\t}\n\n\t/**\n\t * Formats the file at `absolute` and records in `session` what it changed.\n\t *\n\t * @returns what the model needs to know when the file may not be formatted, or `undefined`\n\t * once the formatter exited with 0.\n\t * @throws {EditUnconfirmedError} when what it changed could not be recorded, or it could not be\n\t * stopped. Writing stops for the run then, as after such a command.\n\t */\n\tasync format(\n\t\tabsolute: string,\n\t\tsession: WriteSession,\n\t\tsignal: AbortSignal,\n\t): Promise<string | undefined> {\n\t\tsignal.throwIfAborted();\n\t\tlet checkpoint: CommandCheckpoint;\n\t\ttry {\n\t\t\tconst journal = await session.journalForChange();\n\t\t\tcheckpoint = await CommandCheckpoint.begin({\n\t\t\t\tjournal,\n\t\t\t\tcovered: () => Promise.resolve([absolute]),\n\t\t\t\tby: observer,\n\t\t\t\tsignal,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tsignal.throwIfAborted();\n\t\t\treturn `The formatter did not run, so the file is as you wrote it: ${describeFailure(error)}.`;\n\t\t}\n\n\t\tconst { executable, args = [], timeoutMs = defaultFormatterTimeoutMs } = this.formatter;\n\t\tlet result: CommandProcessResult | undefined;\n\t\tlet startFailure: unknown;\n\t\ttry {\n\t\t\tresult = await this.runProcess(executable, [...args, this.argumentFor(absolute)], {\n\t\t\t\tcwd: this.cwd,\n\t\t\t\tenv: environmentFor(this.formatter),\n\t\t\t\ttimeoutMs,\n\t\t\t\toutputBytes: failureOutputBytes,\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\ttry {\n\t\t\tawait checkpoint.end({ complete });\n\t\t} catch (error) {\n\t\t\tsession.stop('Writing stopped: the harness could not record what the formatter changed');\n\t\t\tthrow new EditUnconfirmedError(\n\t\t\t\t'The formatter 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\t\tif (!complete) {\n\t\t\tsession.stop('Writing stopped: the formatter could not be stopped');\n\t\t\tthrow new EditUnconfirmedError(\n\t\t\t\t'The formatter 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\n\t\tif (!result) {\n\t\t\tsignal.throwIfAborted();\n\t\t\treturn `The formatter could not be started, so the file is as you wrote it: ${describeFailure(startFailure)}.`;\n\t\t}\n\t\t// After its changes are recorded: a cancelled formatter is never reported as finished.\n\t\tif (result.outcome === 'cancelled') signal.throwIfAborted();\n\t\tif (result.outcome === 'exited' && result.exitCode === 0) return undefined;\n\t\tconst ending =\n\t\t\tresult.outcome === 'timedOut'\n\t\t\t\t? `ran past its limit of ${String(timeoutMs / 1000)} s and was stopped`\n\t\t\t\t: result.exitCode !== null\n\t\t\t\t\t? `exited with code ${String(result.exitCode)}`\n\t\t\t\t\t: `was killed by ${result.signal ?? 'a signal'}`;\n\t\tconst output = result.printedBytes === 0 ? '' : `\\nIts output:\\n${result.output}`;\n\t\treturn `The formatter ${ending}, so the file may not be formatted.${output}`;\n\t}\n\n\t/** The file as the formatter's last argument: relative to where it runs, never read as a flag. */\n\tprivate argumentFor(absolute: string): string {\n\t\tconst fromCwd = relative(this.cwd, absolute);\n\t\treturn fromCwd.startsWith('-') && !isAbsolute(fromCwd) ? `./${fromCwd}` : fromCwd;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fileFormatter.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/fileFormatter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAErE,OAAO,EACN,yBAAyB,EAEzB,iBAAiB,GACjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAA6B,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9D,+FAA+F;AAC/F,yBAAyB;AACzB,MAAM,kBAAkB,GAAG,CAAC,GAAG,IAAI,CAAC;AACpC,oDAAoD;AACpD,MAAM,QAAQ,GAAG,WAAW,CAAC;AAE7B;;;;GAIG;AACH,MAAM,OAAO,aAAa;IACR,SAAS,CAAY;IACrB,GAAG,CAAS;IACZ,UAAU,CAA2B;IAEtD,YAAoB,SAAoB,EAAE,GAAW,EAAE,UAAoC;QAC1F,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC9B,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EACnB,SAAS,EACT,MAAM,EACN,IAAI,EACJ,QAAQ,GAAG,OAAO,CAAC,QAAQ,EAC3B,UAAU,GAAG,iBAAiB,GAS9B;QACA,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;YAC1B,MAAM,IAAI,uBAAuB,CAChC,yGAAyG,CACzG,CAAC;QACH,CAAC;QACD,iBAAiB,CAAC,SAAS,CAAC,CAAC;QAC7B,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,eAAe,EAAE,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;QAC3E,OAAO,IAAI,aAAa,CAAC,SAAS,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC;IACtD,CAAC;IAED;;;;;;;OAOG;IACH,MAAM,CACL,QAAgB,EAChB,OAAqB,EACrB,MAAmB;QAEnB,qFAAqF;QACrF,OAAO,OAAO,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IACrF,CAAC;IAEO,KAAK,CAAC,WAAW,CACxB,QAAgB,EAChB,OAAqB,EACrB,MAAmB;QAEnB,MAAM,CAAC,cAAc,EAAE,CAAC;QACxB,IAAI,UAA6B,CAAC;QAClC,IAAI,CAAC;YACJ,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,gBAAgB,EAAE,CAAC;YACjD,UAAU,GAAG,MAAM,iBAAiB,CAAC,KAAK,CAAC;gBAC1C,OAAO;gBACP,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC;gBAC1C,EAAE,EAAE,QAAQ;gBACZ,MAAM;aACN,CAAC,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,CAAC,cAAc,EAAE,CAAC;YACxB,OAAO,8DAA8D,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC;QAChG,CAAC;QAED,MAAM,EAAE,UAAU,EAAE,IAAI,GAAG,EAAE,EAAE,SAAS,GAAG,yBAAyB,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC;QACxF,IAAI,MAAwC,CAAC;QAC7C,IAAI,YAAqB,CAAC;QAC1B,IAAI,CAAC;YACJ,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC,GAAG,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,EAAE;gBACjF,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,GAAG,EAAE,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC;gBACnC,SAAS;gBACT,WAAW,EAAE,kBAAkB;gBAC/B,MAAM;aACN,CAAC,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,YAAY,GAAG,KAAK,CAAC;QACtB,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,EAAE,OAAO,IAAI,IAAI,CAAC;QACzC,IAAI,CAAC;YACJ,MAAM,UAAU,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;QACpC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,OAAO,CAAC,IAAI,CAAC,0EAA0E,CAAC,CAAC;YACzF,MAAM,IAAI,oBAAoB,CAC7B,uGAAuG,EACvG,EAAE,KAAK,EAAE,KAAK,EAAE,CAChB,CAAC;QACH,CAAC;QACD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAC;YACpE,MAAM,IAAI,oBAAoB,CAC7B,iKAAiK,CACjK,CAAC;QACH,CAAC;QAED,IAAI,CAAC,MAAM,EAAE,CAAC;YACb,MAAM,CAAC,cAAc,EAAE,CAAC;YACxB,OAAO,uEAAuE,eAAe,CAAC,YAAY,CAAC,GAAG,CAAC;QAChH,CAAC;QACD,uFAAuF;QACvF,IAAI,MAAM,CAAC,OAAO,KAAK,WAAW;YAAE,MAAM,CAAC,cAAc,EAAE,CAAC;QAC5D,IAAI,MAAM,CAAC,OAAO,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;QAC3E,MAAM,MAAM,GACX,MAAM,CAAC,OAAO,KAAK,UAAU;YAC5B,CAAC,CAAC,yBAAyB,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC,oBAAoB;YACvE,CAAC,CAAC,MAAM,CAAC,QAAQ,KAAK,IAAI;gBACzB,CAAC,CAAC,oBAAoB,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE;gBAC/C,CAAC,CAAC,iBAAiB,MAAM,CAAC,MAAM,IAAI,UAAU,EAAE,CAAC;QACpD,MAAM,MAAM,GAAG,MAAM,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,kBAAkB,MAAM,CAAC,MAAM,EAAE,CAAC;QAClF,OAAO,iBAAiB,MAAM,sCAAsC,MAAM,EAAE,CAAC;IAC9E,CAAC;IAED,kGAAkG;IAC1F,WAAW,CAAC,QAAgB;QACnC,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAC7C,OAAO,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IACnF,CAAC;CACD","sourcesContent":["import { isAbsolute, relative } from 'node:path';\nimport { InvalidAgentConfigError } from '#src/shared/domain/errors';\nimport { describeFailure } from '#src/shared/domain/providerFailure';\nimport type { AccessPolicy } from '../domain/accessPolicy.ts';\nimport {\n\tdefaultFormatterTimeoutMs,\n\ttype Formatter,\n\tvalidateFormatter,\n} from '../domain/commands.ts';\nimport { EditUnconfirmedError } from '../domain/fileEdits.ts';\nimport { CommandCheckpoint } from './commandCheckpoint.ts';\nimport { type CommandProcessResult, runCommandProcess } from './commandProcess.ts';\nimport { environmentFor, resolveCwd } from './commandTool.ts';\nimport type { WriteSession } from './writeSession.ts';\n\n// What the model reads of a formatter that failed: enough for a syntax error, and short, since\n// it comes with an edit.\nconst failureOutputBytes = 4 * 1024;\n// How the history names what the formatter changed.\nconst observer = 'formatter';\n\n/**\n * Runs the consumer's formatter on a file an edit has just written. Only that file is captured\n * around it, so what it changed there is in the run's history and can be undone; a formatter\n * that changes other files leaves those changes out of the history.\n */\nexport class FileFormatter {\n\tprivate readonly formatter: Formatter;\n\tprivate readonly cwd: string;\n\tprivate readonly runProcess: typeof runCommandProcess;\n\n\tprivate constructor(formatter: Formatter, cwd: string, runProcess: typeof runCommandProcess) {\n\t\tthis.formatter = formatter;\n\t\tthis.cwd = cwd;\n\t\tthis.runProcess = runProcess;\n\t}\n\n\t/**\n\t * @throws {InvalidAgentConfigError} for an invalid formatter, a working directory outside every\n\t * `write` root, or on Windows, where a process group cannot be stopped.\n\t */\n\tstatic async create({\n\t\tformatter,\n\t\tpolicy,\n\t\tbase,\n\t\tplatform = process.platform,\n\t\trunProcess = runCommandProcess,\n\t}: {\n\t\tformatter: Formatter;\n\t\tpolicy: AccessPolicy;\n\t\t/** The real path of the first root, where a relative `cwd` starts. */\n\t\tbase: string;\n\t\tplatform?: NodeJS.Platform;\n\t\t/** Runs the program. Replaced in tests, to reach what a real process rarely does. */\n\t\trunProcess?: typeof runCommandProcess;\n\t}): Promise<FileFormatter> {\n\t\tif (platform === 'win32') {\n\t\t\tthrow new InvalidAgentConfigError(\n\t\t\t\t'A formatter is not supported on Windows yet: the harness cannot stop it and everything it started there',\n\t\t\t);\n\t\t}\n\t\tvalidateFormatter(formatter);\n\t\tconst cwd = await resolveCwd('The formatter', formatter.cwd, policy, base);\n\t\treturn new FileFormatter(formatter, cwd, runProcess);\n\t}\n\n\t/**\n\t * Formats the file at `absolute` and records in `session` what it changed.\n\t *\n\t * @returns what the model needs to know when the file may not be formatted, or `undefined`\n\t * once the formatter exited with 0.\n\t * @throws {EditUnconfirmedError} when what it changed could not be recorded, or it could not be\n\t * stopped. Writing stops for the run then, as after such a command.\n\t */\n\tformat(\n\t\tabsolute: string,\n\t\tsession: WriteSession,\n\t\tsignal: AbortSignal,\n\t): Promise<string | undefined> {\n\t\t// Alone, as a command runs, so no other write of the run comes between its captures.\n\t\treturn session.exclusive(() => this.formatAlone(absolute, session, signal), signal);\n\t}\n\n\tprivate async formatAlone(\n\t\tabsolute: string,\n\t\tsession: WriteSession,\n\t\tsignal: AbortSignal,\n\t): Promise<string | undefined> {\n\t\tsignal.throwIfAborted();\n\t\tlet checkpoint: CommandCheckpoint;\n\t\ttry {\n\t\t\tconst journal = await session.journalForChange();\n\t\t\tcheckpoint = await CommandCheckpoint.begin({\n\t\t\t\tjournal,\n\t\t\t\tcovered: () => Promise.resolve([absolute]),\n\t\t\t\tby: observer,\n\t\t\t\tsignal,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tsignal.throwIfAborted();\n\t\t\treturn `The formatter did not run, so the file is as you wrote it: ${describeFailure(error)}.`;\n\t\t}\n\n\t\tconst { executable, args = [], timeoutMs = defaultFormatterTimeoutMs } = this.formatter;\n\t\tlet result: CommandProcessResult | undefined;\n\t\tlet startFailure: unknown;\n\t\ttry {\n\t\t\tresult = await this.runProcess(executable, [...args, this.argumentFor(absolute)], {\n\t\t\t\tcwd: this.cwd,\n\t\t\t\tenv: environmentFor(this.formatter),\n\t\t\t\ttimeoutMs,\n\t\t\t\toutputBytes: failureOutputBytes,\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\ttry {\n\t\t\tawait checkpoint.end({ complete });\n\t\t} catch (error) {\n\t\t\tsession.stop('Writing stopped: the harness could not record what the formatter changed');\n\t\t\tthrow new EditUnconfirmedError(\n\t\t\t\t'The formatter 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\t\tif (!complete) {\n\t\t\tsession.stop('Writing stopped: the formatter could not be stopped');\n\t\t\tthrow new EditUnconfirmedError(\n\t\t\t\t'The formatter 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\n\t\tif (!result) {\n\t\t\tsignal.throwIfAborted();\n\t\t\treturn `The formatter could not be started, so the file is as you wrote it: ${describeFailure(startFailure)}.`;\n\t\t}\n\t\t// After its changes are recorded: a cancelled formatter is never reported as finished.\n\t\tif (result.outcome === 'cancelled') signal.throwIfAborted();\n\t\tif (result.outcome === 'exited' && result.exitCode === 0) return undefined;\n\t\tconst ending =\n\t\t\tresult.outcome === 'timedOut'\n\t\t\t\t? `ran past its limit of ${String(timeoutMs / 1000)} s and was stopped`\n\t\t\t\t: result.exitCode !== null\n\t\t\t\t\t? `exited with code ${String(result.exitCode)}`\n\t\t\t\t\t: `was killed by ${result.signal ?? 'a signal'}`;\n\t\tconst output = result.printedBytes === 0 ? '' : `\\nIts output:\\n${result.output}`;\n\t\treturn `The formatter ${ending}, so the file may not be formatted.${output}`;\n\t}\n\n\t/** The file as the formatter's last argument: relative to where it runs, never read as a flag. */\n\tprivate argumentFor(absolute: string): string {\n\t\tconst fromCwd = relative(this.cwd, absolute);\n\t\treturn fromCwd.startsWith('-') && !isAbsolute(fromCwd) ? `./${fromCwd}` : fromCwd;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RunOptions } from '#src/agent/domain/agent';
|
|
2
|
+
import type { RecoverableError, UnrecoverableError } from '#src/shared/domain/errors';
|
|
3
|
+
import type { ToolCallContext } from '../domain/readRegistry.ts';
|
|
4
|
+
/** A subagent's answer as the agent that called it reads it, cut where a read would be. */
|
|
5
|
+
export declare function cutAnswer(answer: string): string;
|
|
6
|
+
/** Why a subagent failed, for the agent that called it. */
|
|
7
|
+
export declare function failureOf(error: RecoverableError | UnrecoverableError): string;
|
|
8
|
+
/**
|
|
9
|
+
* The options of a subagent a call starts: within the run that made the call, one delegation
|
|
10
|
+
* deeper, and narrated as that call's `subagent`, so the person watching can tell it apart.
|
|
11
|
+
*/
|
|
12
|
+
export declare function subagentOptions(call: ToolCallContext, { tool, name, subagent, task, options, }: {
|
|
13
|
+
tool: string;
|
|
14
|
+
name: string;
|
|
15
|
+
subagent: number;
|
|
16
|
+
task: string;
|
|
17
|
+
/** What else it runs with, such as its signal. */
|
|
18
|
+
options: RunOptions;
|
|
19
|
+
}): RunOptions;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { withDelegation, withRunContext } from '#src/agent/domain/runContext';
|
|
2
|
+
// An answer is read whole, like a read, which returns at most 16 KB.
|
|
3
|
+
const maxAnswerCharacters = 16_000;
|
|
4
|
+
/** A subagent's answer as the agent that called it reads it, cut where a read would be. */
|
|
5
|
+
export function cutAnswer(answer) {
|
|
6
|
+
if (answer.length <= maxAnswerCharacters)
|
|
7
|
+
return answer;
|
|
8
|
+
return `${answer.slice(0, maxAnswerCharacters)}\n[The answer was cut at ${String(maxAnswerCharacters)} characters.]`;
|
|
9
|
+
}
|
|
10
|
+
/** Why a subagent failed, for the agent that called it. */
|
|
11
|
+
export function failureOf(error) {
|
|
12
|
+
return typeof error.cause === 'string' ? `${error.message}: ${error.cause}` : error.message;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The options of a subagent a call starts: within the run that made the call, one delegation
|
|
16
|
+
* deeper, and narrated as that call's `subagent`, so the person watching can tell it apart.
|
|
17
|
+
*/
|
|
18
|
+
export function subagentOptions(call, { tool, name, subagent, task, options, }) {
|
|
19
|
+
const onProgress = (event) => {
|
|
20
|
+
call.onProgress({ type: 'subagent', callId: call.callId, subagent, name, task, event });
|
|
21
|
+
};
|
|
22
|
+
return withDelegation(withRunContext({ ...options, onProgress }, call.run), {
|
|
23
|
+
tool,
|
|
24
|
+
callId: call.callId,
|
|
25
|
+
subagent,
|
|
26
|
+
depth: call.depth + 1,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=subagents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subagents.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/subagents.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAI9E,qEAAqE;AACrE,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAEnC,2FAA2F;AAC3F,MAAM,UAAU,SAAS,CAAC,MAAc;IACvC,IAAI,MAAM,CAAC,MAAM,IAAI,mBAAmB;QAAE,OAAO,MAAM,CAAC;IACxD,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,mBAAmB,CAAC,4BAA4B,MAAM,CAAC,mBAAmB,CAAC,eAAe,CAAC;AACtH,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,SAAS,CAAC,KAA4C;IACrE,OAAO,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,OAAO,KAAK,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC;AAC7F,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC9B,IAAqB,EACrB,EACC,IAAI,EACJ,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,OAAO,GAQP;IAED,MAAM,UAAU,GAAG,CAAC,KAAoB,EAAE,EAAE;QAC3C,IAAI,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACzF,CAAC,CAAC;IACF,OAAO,cAAc,CAAC,cAAc,CAAC,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE;QAC3E,IAAI;QACJ,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,QAAQ;QACR,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG,CAAC;KACrB,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import type { ProgressEvent, RunOptions } from '#src/agent/domain/agent';\nimport { withDelegation, withRunContext } from '#src/agent/domain/runContext';\nimport type { RecoverableError, UnrecoverableError } from '#src/shared/domain/errors';\nimport type { ToolCallContext } from '../domain/readRegistry.ts';\n\n// An answer is read whole, like a read, which returns at most 16 KB.\nconst maxAnswerCharacters = 16_000;\n\n/** A subagent's answer as the agent that called it reads it, cut where a read would be. */\nexport function cutAnswer(answer: string): string {\n\tif (answer.length <= maxAnswerCharacters) return answer;\n\treturn `${answer.slice(0, maxAnswerCharacters)}\\n[The answer was cut at ${String(maxAnswerCharacters)} characters.]`;\n}\n\n/** Why a subagent failed, for the agent that called it. */\nexport function failureOf(error: RecoverableError | UnrecoverableError): string {\n\treturn typeof error.cause === 'string' ? `${error.message}: ${error.cause}` : error.message;\n}\n\n/**\n * The options of a subagent a call starts: within the run that made the call, one delegation\n * deeper, and narrated as that call's `subagent`, so the person watching can tell it apart.\n */\nexport function subagentOptions(\n\tcall: ToolCallContext,\n\t{\n\t\ttool,\n\t\tname,\n\t\tsubagent,\n\t\ttask,\n\t\toptions,\n\t}: {\n\t\ttool: string;\n\t\tname: string;\n\t\tsubagent: number;\n\t\ttask: string;\n\t\t/** What else it runs with, such as its signal. */\n\t\toptions: RunOptions;\n\t},\n): RunOptions {\n\tconst onProgress = (event: ProgressEvent) => {\n\t\tcall.onProgress({ type: 'subagent', callId: call.callId, subagent, name, task, event });\n\t};\n\treturn withDelegation(withRunContext({ ...options, onProgress }, call.run), {\n\t\ttool,\n\t\tcallId: call.callId,\n\t\tsubagent,\n\t\tdepth: call.depth + 1,\n\t});\n}\n"]}
|
|
@@ -13,6 +13,7 @@ export declare class WriteSession {
|
|
|
13
13
|
private journal;
|
|
14
14
|
private changes;
|
|
15
15
|
private stopped;
|
|
16
|
+
private writing;
|
|
16
17
|
constructor({ store, maxChanges, onStart, onDiscard, }: {
|
|
17
18
|
store: RecoveryStore;
|
|
18
19
|
maxChanges?: number;
|
|
@@ -23,6 +24,13 @@ export declare class WriteSession {
|
|
|
23
24
|
});
|
|
24
25
|
/** The journal for one more change, counted against the run's limit. */
|
|
25
26
|
journalForChange(): Promise<RunJournal>;
|
|
27
|
+
/**
|
|
28
|
+
* Runs `write` once every write before it has ended, so agents of one run that work at once
|
|
29
|
+
* never change files together: each change is read, recorded and made alone, and a command
|
|
30
|
+
* runs with no other write while the files are captured around it. A cancellation stops the
|
|
31
|
+
* wait.
|
|
32
|
+
*/
|
|
33
|
+
exclusive<Result>(write: () => Promise<Result>, signal: AbortSignal): Promise<Result>;
|
|
26
34
|
/** Refuses every further write, for this reason. The first reason given is kept. */
|
|
27
35
|
stop(reason: string): void;
|
|
28
36
|
/** The run's id once it has written, which is what an undo needs. */
|