@mikode13/harness 10.0.0 → 11.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -16
- package/dist/agent/domain/agent.d.ts +24 -6
- package/dist/agent/domain/agent.js.map +1 -1
- package/dist/engines/domain/model/backgroundAgents.d.ts +60 -0
- package/dist/engines/domain/model/backgroundAgents.js +165 -0
- package/dist/engines/domain/model/backgroundAgents.js.map +1 -0
- package/dist/engines/domain/model/llmAgent.d.ts +4 -11
- package/dist/engines/domain/model/llmAgent.js +103 -83
- package/dist/engines/domain/model/llmAgent.js.map +1 -1
- package/dist/factory/infrastructure/agentLLMFactory.d.ts +1 -1
- package/dist/factory/infrastructure/agentLLMFactory.js +2 -2
- package/dist/factory/infrastructure/agentLLMFactory.js.map +1 -1
- package/dist/tools/domain/preparedCall.d.ts +43 -7
- package/dist/tools/domain/preparedCall.js +3 -0
- package/dist/tools/domain/preparedCall.js.map +1 -1
- package/dist/tools/infrastructure/agentAsTool.d.ts +11 -9
- package/dist/tools/infrastructure/agentAsTool.js +39 -40
- package/dist/tools/infrastructure/agentAsTool.js.map +1 -1
- package/dist/tools/infrastructure/defineTool.d.ts +14 -1
- package/dist/tools/infrastructure/defineTool.js +24 -0
- package/dist/tools/infrastructure/defineTool.js.map +1 -1
- package/dist/tools/infrastructure/exploreTool.d.ts +2 -2
- package/dist/tools/infrastructure/exploreTool.js +35 -42
- package/dist/tools/infrastructure/exploreTool.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -513,8 +513,10 @@ model APIs:
|
|
|
513
513
|
explorers at once, one per question, on the role's provider's cheaper model at `medium`:
|
|
514
514
|
Claude Haiku 5.5, or `gpt-5.6-luna`. Each starts from its question alone and reports where
|
|
515
515
|
the answer is, the files and line ranges, as a librarian says where a book is; the role
|
|
516
|
-
then reads those ranges itself, with `readFiles`, instead of searching the repository.
|
|
517
|
-
|
|
516
|
+
then reads those ranges itself, with `readFiles`, instead of searching the repository. They
|
|
517
|
+
work in the background, as the agents of `agentAsTool` do, and each report reaches the role
|
|
518
|
+
when its explorer finishes. Their usage counts in the run's, and their narration arrives as
|
|
519
|
+
`subagent` events;
|
|
518
520
|
- the executor gets its provider's file tools. `autoApprove` reaches only the executor;
|
|
519
521
|
- each role gets the commands whose `roles` name it; a command names only the executor unless
|
|
520
522
|
it says otherwise.
|
|
@@ -557,8 +559,9 @@ orchestrator is one run of the history, every round included.
|
|
|
557
559
|
### Agents that hand work to other agents
|
|
558
560
|
|
|
559
561
|
`agentAsTool` offers an agent to another one as a tool. The calling model writes the task, and
|
|
560
|
-
a new agent from the `createAgent` option
|
|
561
|
-
from a package you reuse in every project, or be written in one
|
|
562
|
+
a new agent from the `createAgent` option works on it in the background while the caller goes
|
|
563
|
+
on. Specialists can come from a package you reuse in every project, or be written in one
|
|
564
|
+
project:
|
|
562
565
|
|
|
563
566
|
```ts
|
|
564
567
|
import { agentAsTool, createFileTools, createLLMAgent } from '@mikode13/harness';
|
|
@@ -592,17 +595,26 @@ The agent works inside the caller's run:
|
|
|
592
595
|
`createAgent` option gave it. Handing over a task asks no one unless you set
|
|
593
596
|
`risk: 'destructive'`;
|
|
594
597
|
- its progress arrives as `subagent` events of the call, with the tool's `name`, and its
|
|
595
|
-
transcript names the call in `delegation`.
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
598
|
+
transcript names the call in `delegation`. The call's own `tool` event ends with
|
|
599
|
+
`status: 'sent'`, the agent's events keep arriving after it, and a `subagentEnded` event says
|
|
600
|
+
when it ended and how: `answered`, `failed`, or `stopped` because the run ended.
|
|
601
|
+
|
|
602
|
+
The call answers at once, telling the model the agent works in the background. When the agent
|
|
603
|
+
finishes, only its answer reaches the caller's model, in a message of its own at the caller's
|
|
604
|
+
next step; nothing else of its work does. The caller reacts to each answer as it arrives. An
|
|
605
|
+
answer the caller gives while agents still work is a wait, narrated like any other message:
|
|
606
|
+
the run makes no call to the model until the next agent answers, and it ends only when the
|
|
607
|
+
caller answers with nothing pending. Waiting counts against the caller's `maxSteps`, and each
|
|
608
|
+
agent has its own.
|
|
609
|
+
|
|
610
|
+
Agents that write work at once too. The history stays exact, because every change and command
|
|
611
|
+
of a run is made one at a time, but agents working at once can see each other's unfinished
|
|
612
|
+
work, and a long command holds the others' writes until it ends. An agent that fails answers
|
|
613
|
+
with why, for the model to act on. A cancellation, or your approver or asker failing inside an
|
|
614
|
+
agent, ends the run: every agent still working is stopped, the run ends once they have, and
|
|
615
|
+
what they had changed stays in the run's history. A call deeper than `maxDepth`, 2 by default,
|
|
616
|
+
is refused. Only agents from `createLLMAgent` work this way: an Agent SDK agent from the
|
|
617
|
+
`createAgent` factory runs as a run of its own.
|
|
606
618
|
|
|
607
619
|
### Running the project's commands
|
|
608
620
|
|
|
@@ -798,7 +810,10 @@ The CLI shows each command as the line it will run before asking, and "always" a
|
|
|
798
810
|
that command again, not every command. Tool calls finish as one dimmed line with an
|
|
799
811
|
icon, role, tool name, summary and duration; a failed configured command includes
|
|
800
812
|
`exit N` beside its duration, while a command event also includes its exit code.
|
|
801
|
-
A denied call is marked `denied`.
|
|
813
|
+
A denied call is marked `denied`. A call that sent agents to work in the background is marked
|
|
814
|
+
`↗ … | sent`, and each of its agents gets a line of its own when it ends, `✓` when it answered,
|
|
815
|
+
`✗` when it failed and `⊘` when it was stopped, with how long it worked; while no call runs,
|
|
816
|
+
the spinner names an agent still at work. Messages remain normal text under a bold role
|
|
802
817
|
heading. Reasoning is dim italic and complete by default; pass
|
|
803
818
|
`--thinking-headings` to show only Markdown bold headings as `∴ …`. A tool start
|
|
804
819
|
updates the spinner rather than printing a second line, and cancelling a turn clears
|
|
@@ -107,9 +107,11 @@ export type ProgressEvent = {
|
|
|
107
107
|
error?: string;
|
|
108
108
|
/**
|
|
109
109
|
* `in_progress` only once the call is about to run, after any approval. `denied` when
|
|
110
|
-
* it never ran because no one allowed it, with no `in_progress` before it.
|
|
110
|
+
* it never ran because no one allowed it, with no `in_progress` before it. `sent` when
|
|
111
|
+
* it sent subagents to work in the background, such as an `explore` call: they go on
|
|
112
|
+
* after it, as `subagent` events, until a `subagentEnded` for each.
|
|
111
113
|
*/
|
|
112
|
-
status: 'in_progress' | 'completed' | 'error' | 'denied';
|
|
114
|
+
status: 'in_progress' | 'completed' | 'error' | 'denied' | 'sent';
|
|
113
115
|
/**
|
|
114
116
|
* On `completed`, for a call that changed a file through the harness's write tools: the
|
|
115
117
|
* change as a unified diff, for the person watching. Narration only; the run's whole
|
|
@@ -165,11 +167,13 @@ export type ProgressEvent = {
|
|
|
165
167
|
agent?: ProgressAgent;
|
|
166
168
|
}
|
|
167
169
|
/**
|
|
168
|
-
* An event of a subagent that a tool call started: one of the explorers an `explore` call
|
|
169
|
-
*
|
|
170
|
+
* An event of a subagent that a tool call started: one of the explorers an `explore` call
|
|
171
|
+
* sends, or the agent a tool from `agentAsTool` delegates to. `callId` is the `id` of that
|
|
170
172
|
* call's `tool` events, and `subagent` numbers its subagents from 1, so the events of agents
|
|
171
|
-
* running in parallel stay apart.
|
|
172
|
-
*
|
|
173
|
+
* running in parallel stay apart. Subagents work in the background: the call's own `tool`
|
|
174
|
+
* event ends with `status: 'sent'`, and each subagent's events keep arriving after it, until
|
|
175
|
+
* its `subagentEnded`, whether it answered, failed or was stopped. A subagent's own subagents
|
|
176
|
+
* arrive as `subagent` events inside this one's `event`.
|
|
173
177
|
*/
|
|
174
178
|
| {
|
|
175
179
|
type: 'subagent';
|
|
@@ -181,6 +185,20 @@ export type ProgressEvent = {
|
|
|
181
185
|
task: string;
|
|
182
186
|
event: ProgressEvent;
|
|
183
187
|
agent?: ProgressAgent;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* A subagent that a call with `status: 'sent'` sent has ended: it `answered`, it `failed` and
|
|
191
|
+
* its answer says why, or it was `stopped` because the run ended. `callId`, `subagent` and
|
|
192
|
+
* `name` are those of its `subagent` events. Narration only: its answer reaches the model that
|
|
193
|
+
* sent it, not the consumer.
|
|
194
|
+
*/
|
|
195
|
+
| {
|
|
196
|
+
type: 'subagentEnded';
|
|
197
|
+
callId: string;
|
|
198
|
+
subagent: number;
|
|
199
|
+
name: string;
|
|
200
|
+
outcome: 'answered' | 'failed' | 'stopped';
|
|
201
|
+
agent?: ProgressAgent;
|
|
184
202
|
};
|
|
185
203
|
/**
|
|
186
204
|
* What one call to the model sent, in characters by where it came from: an estimate of size
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../src/agent/domain/agent.ts"],"names":[],"mappings":"AA0DA,gDAAgD;AAChD,MAAM,CAAC,MAAM,cAAc,GAAa,GAAG,EAAE,CAAC,SAAS,CAAC","sourcesContent":["import type { Tokens } from '#src/shared/domain/tokens';\nimport type { Approver } from './approval.ts';\nimport type { Asker } from './question.ts';\n\n/**\n * How a run ended when it did not fail. Every token a run spends travels with its end: here\n * when it finishes, in `RecoverableError.tokens` or `UnrecoverableError.tokens` when it fails.\n */\nexport interface AgentResponse {\n\t/** The final text. Empty when the run produced none, for example a turn that only edited files. */\n\tresponse: string;\n\t/**\n\t * Missing when any call in the run completed without reporting usage, which is not the same\n\t * as zero: a partial sum would look complete.\n\t */\n\ttokens?: Tokens;\n\t/** Wall-clock seconds the consumer waited for the run, retries and every role included. */\n\tduration: number;\n\t/**\n\t * The run the workspace's history recorded, when this run wrote through the harness's write\n\t * tools. Absent for a run that wrote nothing, and from the Agent SDK engines, whose changes\n\t * the history does not record.\n\t */\n\trunId?: string;\n}\n\nexport type Callback = (item: ProgressEvent) => void;\n\n/**\n * What a run needs besides its prompt. InstructedAgent passes it on whole. OrchestratorAgent\n * copies its own enumerable fields and replaces only `onProgress` per role; RetryingAgent does\n * the same for a run with `ask`, replacing only `ask`. A field must therefore be an own\n * enumerable one to reach every agent.\n */\nexport interface RunOptions {\n\t/**\n\t * Cancels the run, which then rejects with the signal's reason: an `AbortError`, or a\n\t * `TimeoutError` from `AbortSignal.timeout`. `isAbortError` recognizes both.\n\t */\n\tsignal: AbortSignal;\n\t/** Receives the run's live activity. Without it, the run is silent. */\n\tonProgress?: Callback | undefined;\n\t/**\n\t * Asked before a model-backed agent runs a `destructive` tool call. Without it, such a call\n\t * is denied. An agent built with `autoApprove` never asks, and the Agent SDK engines ignore\n\t * it: their own permission systems decide. Agents a delegation runs at once may call it before\n\t * an earlier call has its answer.\n\t */\n\tapprove?: Approver | undefined;\n\t/**\n\t * Answers what a model-backed agent asks the user, through its `askUser` tool, when a\n\t * decision is the user's to make. Without it the agent has no such tool. The Agent SDK\n\t * engines ignore it. Agents a delegation runs at once may call it before an earlier call has\n\t * its answer.\n\t */\n\task?: Asker | undefined;\n}\n\n/** The `onProgress` of a run that gave none. */\nexport const ignoreProgress: Callback = () => undefined;\n\n/**\n * Live activity during a run, for narration only: the result travels in `AgentResponse`.\n * New event types can arrive in a minor release, so render the types you know and ignore\n * the rest instead of switching over them exhaustively with a `never` check.\n */\nexport type ProgressEvent =\n\t| { type: 'command'; command: string; exitCode?: number; agent?: ProgressAgent }\n\t| { type: 'reasoning'; message: string; agent?: ProgressAgent }\n\t| { type: 'search'; query: string; agent?: ProgressAgent }\n\t| {\n\t\t\ttype: 'fileChange';\n\t\t\tchanges: { path: string; kind: 'add' | 'update' | 'delete' }[];\n\t\t\tagent?: ProgressAgent;\n\t }\n\t| { type: 'mcpTool'; server: string; tool: string; status: string; agent?: ProgressAgent }\n\t| { type: 'agentMessage'; message: string; agent?: ProgressAgent }\n\t/** `id` pairs a call's end with its start when one step calls the same tool more than once. */\n\t| {\n\t\t\ttype: 'tool';\n\t\t\tid: string;\n\t\t\tname: string;\n\t\t\tagent?: ProgressAgent;\n\t\t\t/** A short, tool-owned description of the call. Narration only. */\n\t\t\tsummary?: string;\n\t\t\t/** The exit code of a configured command that ended with an error. Narration only. */\n\t\t\texitCode?: number;\n\t\t\t/**\n\t\t\t * On `error`, for an agent on the model APIs: what the model was told went wrong, such\n\t\t\t * as a patch that did not apply or a failed command's output. Narration only.\n\t\t\t */\n\t\t\terror?: string;\n\t\t\t/**\n\t\t\t * `in_progress` only once the call is about to run, after any approval. `denied` when\n\t\t\t * it never ran because no one allowed it, with no `in_progress` before it.\n\t\t\t */\n\t\t\tstatus: 'in_progress' | 'completed' | 'error' | 'denied';\n\t\t\t/**\n\t\t\t * On `completed`, for a call that changed a file through the harness's write tools: the\n\t\t\t * change as a unified diff, for the person watching. Narration only; the run's whole\n\t\t\t * record is the history's `changes`.\n\t\t\t */\n\t\t\tdiff?: string;\n\t }\n\t| { type: 'todoList'; items: { text: string; completed: boolean }[]; agent?: ProgressAgent }\n\t| { type: 'turnStarted'; agent?: ProgressAgent }\n\t| { type: 'turnEnded'; agent?: ProgressAgent }\n\t/**\n\t * One billed call to the model by an agent on the model APIs, including one the model\n\t * stopped early or the client turned into an error: what the call sent and what it cost.\n\t * Narration only: the run's total is `AgentResponse.tokens`, or the failure's `tokens`.\n\t */\n\t| {\n\t\t\ttype: 'step';\n\t\t\t/** 1 for the first call of the run. */\n\t\t\tstep: number;\n\t\t\t/** Missing when the provider did not report this call's usage. */\n\t\t\ttokens?: Tokens;\n\t\t\tcontext: StepContext;\n\t\t\tagent?: ProgressAgent;\n\t }\n\t/**\n\t * A workflow's stage asks its agent for an answer; `attempt` is 2 or more when the previous\n\t * answer was invalid. Stages run one at a time, so the events that follow, until the next\n\t * workflow event, are this stage's agent's.\n\t */\n\t| { type: 'workflowStage'; stage: string; attempt: number; agent?: ProgressAgent }\n\t/** The transition a stage's valid answer selected, and the stage it leads to. */\n\t| {\n\t\t\ttype: 'workflowTransition';\n\t\t\tstage: string;\n\t\t\ttransition: 'go' | 'stop' | 'complete';\n\t\t\t/** Absent after `stop` or `complete`. */\n\t\t\tnextStage?: string;\n\t\t\tagent?: ProgressAgent;\n\t }\n\t/**\n\t * An event of a subagent that a tool call started: one of the explorers an `explore` call runs\n\t * at once, or the agent a tool from `agentAsTool` delegates to. `callId` is the `id` of that\n\t * call's `tool` events, and `subagent` numbers its subagents from 1, so the events of agents\n\t * running in parallel stay apart. A subagent's own subagents arrive as `subagent` events\n\t * inside this one's `event`.\n\t */\n\t| {\n\t\t\ttype: 'subagent';\n\t\t\tcallId: string;\n\t\t\tsubagent: number;\n\t\t\t/** Which agent it is: `explorer`, or the name of the tool that delegates to it. */\n\t\t\tname: string;\n\t\t\t/** What the subagent was asked. */\n\t\t\ttask: string;\n\t\t\tevent: ProgressEvent;\n\t\t\tagent?: ProgressAgent;\n\t };\n\n/**\n * What one call to the model sent, in characters by where it came from: an estimate of size\n * for finding what fills a context, not a token count.\n */\nexport interface StepContext {\n\t/** The system prompt. */\n\tinstructions: number;\n\t/** The tool definitions. */\n\ttools: number;\n\t/**\n\t * The text of the user's prompts and the model's answers, and the provider's summary of the\n\t * turns before it once it has compacted them: only what the call sent.\n\t */\n\tmessages: number;\n\t/** Reasoning the model produced earlier and is sent back: its provider's own signed or encrypted blocks. */\n\treasoning: number;\n\t/** The input of every tool call so far. */\n\ttoolCalls: number;\n\t/** Every tool result so far, by tool, largest first. */\n\ttoolResults: { tool: string; characters: number; calls: number }[];\n\t/** The largest single results, each with its call's summary when the tool gives one. */\n\tlargestResults: { tool: string; characters: number; summary?: string }[];\n}\n\n/** A role in the fixed planner → executor → reviewer workflow. */\nexport type ProgressAgent = 'planner' | 'executor' | 'reviewer';\n\n/**\n * The contract every engine (CodexAgent, ClaudeAgent, future providers) and every\n * decorator (RetryingAgent, OrchestratorAgent) is built against.\n *\n * Implementers MUST only ever reject with `RecoverableError` or `UnrecoverableError`\n * (see src/shared/domain/errors.ts) — never a raw SDK error, a plain `Error`, or anything else leaked\n * unclassified. Every consumer of `Agent` (RetryingAgent's retry decision,\n * OrchestratorAgent's failure handling) `instanceof`-checks against those two types to\n * decide what to do next; a leaked, unclassified error bypasses that decision\n * entirely — it gets retried when it shouldn't be, or crashes a run that a retry\n * would have recovered. Wrap every call into the underlying SDK so nothing escapes\n * unclassified, including failures the SDK itself doesn't model as a domain error\n * (network errors, malformed responses, etc.); `classifyProviderFailure` does this.\n *\n * Cancellation is the one exception: an `AbortError`, or the `TimeoutError` of\n * `AbortSignal.timeout`, must propagate unchanged, because\n * consumers check for it before either error type.\n */\nexport interface Agent {\n\trun(prompt: string, options: RunOptions): Promise<AgentResponse>;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../src/agent/domain/agent.ts"],"names":[],"mappings":"AA0DA,gDAAgD;AAChD,MAAM,CAAC,MAAM,cAAc,GAAa,GAAG,EAAE,CAAC,SAAS,CAAC","sourcesContent":["import type { Tokens } from '#src/shared/domain/tokens';\nimport type { Approver } from './approval.ts';\nimport type { Asker } from './question.ts';\n\n/**\n * How a run ended when it did not fail. Every token a run spends travels with its end: here\n * when it finishes, in `RecoverableError.tokens` or `UnrecoverableError.tokens` when it fails.\n */\nexport interface AgentResponse {\n\t/** The final text. Empty when the run produced none, for example a turn that only edited files. */\n\tresponse: string;\n\t/**\n\t * Missing when any call in the run completed without reporting usage, which is not the same\n\t * as zero: a partial sum would look complete.\n\t */\n\ttokens?: Tokens;\n\t/** Wall-clock seconds the consumer waited for the run, retries and every role included. */\n\tduration: number;\n\t/**\n\t * The run the workspace's history recorded, when this run wrote through the harness's write\n\t * tools. Absent for a run that wrote nothing, and from the Agent SDK engines, whose changes\n\t * the history does not record.\n\t */\n\trunId?: string;\n}\n\nexport type Callback = (item: ProgressEvent) => void;\n\n/**\n * What a run needs besides its prompt. InstructedAgent passes it on whole. OrchestratorAgent\n * copies its own enumerable fields and replaces only `onProgress` per role; RetryingAgent does\n * the same for a run with `ask`, replacing only `ask`. A field must therefore be an own\n * enumerable one to reach every agent.\n */\nexport interface RunOptions {\n\t/**\n\t * Cancels the run, which then rejects with the signal's reason: an `AbortError`, or a\n\t * `TimeoutError` from `AbortSignal.timeout`. `isAbortError` recognizes both.\n\t */\n\tsignal: AbortSignal;\n\t/** Receives the run's live activity. Without it, the run is silent. */\n\tonProgress?: Callback | undefined;\n\t/**\n\t * Asked before a model-backed agent runs a `destructive` tool call. Without it, such a call\n\t * is denied. An agent built with `autoApprove` never asks, and the Agent SDK engines ignore\n\t * it: their own permission systems decide. Agents a delegation runs at once may call it before\n\t * an earlier call has its answer.\n\t */\n\tapprove?: Approver | undefined;\n\t/**\n\t * Answers what a model-backed agent asks the user, through its `askUser` tool, when a\n\t * decision is the user's to make. Without it the agent has no such tool. The Agent SDK\n\t * engines ignore it. Agents a delegation runs at once may call it before an earlier call has\n\t * its answer.\n\t */\n\task?: Asker | undefined;\n}\n\n/** The `onProgress` of a run that gave none. */\nexport const ignoreProgress: Callback = () => undefined;\n\n/**\n * Live activity during a run, for narration only: the result travels in `AgentResponse`.\n * New event types can arrive in a minor release, so render the types you know and ignore\n * the rest instead of switching over them exhaustively with a `never` check.\n */\nexport type ProgressEvent =\n\t| { type: 'command'; command: string; exitCode?: number; agent?: ProgressAgent }\n\t| { type: 'reasoning'; message: string; agent?: ProgressAgent }\n\t| { type: 'search'; query: string; agent?: ProgressAgent }\n\t| {\n\t\t\ttype: 'fileChange';\n\t\t\tchanges: { path: string; kind: 'add' | 'update' | 'delete' }[];\n\t\t\tagent?: ProgressAgent;\n\t }\n\t| { type: 'mcpTool'; server: string; tool: string; status: string; agent?: ProgressAgent }\n\t| { type: 'agentMessage'; message: string; agent?: ProgressAgent }\n\t/** `id` pairs a call's end with its start when one step calls the same tool more than once. */\n\t| {\n\t\t\ttype: 'tool';\n\t\t\tid: string;\n\t\t\tname: string;\n\t\t\tagent?: ProgressAgent;\n\t\t\t/** A short, tool-owned description of the call. Narration only. */\n\t\t\tsummary?: string;\n\t\t\t/** The exit code of a configured command that ended with an error. Narration only. */\n\t\t\texitCode?: number;\n\t\t\t/**\n\t\t\t * On `error`, for an agent on the model APIs: what the model was told went wrong, such\n\t\t\t * as a patch that did not apply or a failed command's output. Narration only.\n\t\t\t */\n\t\t\terror?: string;\n\t\t\t/**\n\t\t\t * `in_progress` only once the call is about to run, after any approval. `denied` when\n\t\t\t * it never ran because no one allowed it, with no `in_progress` before it. `sent` when\n\t\t\t * it sent subagents to work in the background, such as an `explore` call: they go on\n\t\t\t * after it, as `subagent` events, until a `subagentEnded` for each.\n\t\t\t */\n\t\t\tstatus: 'in_progress' | 'completed' | 'error' | 'denied' | 'sent';\n\t\t\t/**\n\t\t\t * On `completed`, for a call that changed a file through the harness's write tools: the\n\t\t\t * change as a unified diff, for the person watching. Narration only; the run's whole\n\t\t\t * record is the history's `changes`.\n\t\t\t */\n\t\t\tdiff?: string;\n\t }\n\t| { type: 'todoList'; items: { text: string; completed: boolean }[]; agent?: ProgressAgent }\n\t| { type: 'turnStarted'; agent?: ProgressAgent }\n\t| { type: 'turnEnded'; agent?: ProgressAgent }\n\t/**\n\t * One billed call to the model by an agent on the model APIs, including one the model\n\t * stopped early or the client turned into an error: what the call sent and what it cost.\n\t * Narration only: the run's total is `AgentResponse.tokens`, or the failure's `tokens`.\n\t */\n\t| {\n\t\t\ttype: 'step';\n\t\t\t/** 1 for the first call of the run. */\n\t\t\tstep: number;\n\t\t\t/** Missing when the provider did not report this call's usage. */\n\t\t\ttokens?: Tokens;\n\t\t\tcontext: StepContext;\n\t\t\tagent?: ProgressAgent;\n\t }\n\t/**\n\t * A workflow's stage asks its agent for an answer; `attempt` is 2 or more when the previous\n\t * answer was invalid. Stages run one at a time, so the events that follow, until the next\n\t * workflow event, are this stage's agent's.\n\t */\n\t| { type: 'workflowStage'; stage: string; attempt: number; agent?: ProgressAgent }\n\t/** The transition a stage's valid answer selected, and the stage it leads to. */\n\t| {\n\t\t\ttype: 'workflowTransition';\n\t\t\tstage: string;\n\t\t\ttransition: 'go' | 'stop' | 'complete';\n\t\t\t/** Absent after `stop` or `complete`. */\n\t\t\tnextStage?: string;\n\t\t\tagent?: ProgressAgent;\n\t }\n\t/**\n\t * An event of a subagent that a tool call started: one of the explorers an `explore` call\n\t * sends, or the agent a tool from `agentAsTool` delegates to. `callId` is the `id` of that\n\t * call's `tool` events, and `subagent` numbers its subagents from 1, so the events of agents\n\t * running in parallel stay apart. Subagents work in the background: the call's own `tool`\n\t * event ends with `status: 'sent'`, and each subagent's events keep arriving after it, until\n\t * its `subagentEnded`, whether it answered, failed or was stopped. A subagent's own subagents\n\t * arrive as `subagent` events inside this one's `event`.\n\t */\n\t| {\n\t\t\ttype: 'subagent';\n\t\t\tcallId: string;\n\t\t\tsubagent: number;\n\t\t\t/** Which agent it is: `explorer`, or the name of the tool that delegates to it. */\n\t\t\tname: string;\n\t\t\t/** What the subagent was asked. */\n\t\t\ttask: string;\n\t\t\tevent: ProgressEvent;\n\t\t\tagent?: ProgressAgent;\n\t }\n\t/**\n\t * A subagent that a call with `status: 'sent'` sent has ended: it `answered`, it `failed` and\n\t * its answer says why, or it was `stopped` because the run ended. `callId`, `subagent` and\n\t * `name` are those of its `subagent` events. Narration only: its answer reaches the model that\n\t * sent it, not the consumer.\n\t */\n\t| {\n\t\t\ttype: 'subagentEnded';\n\t\t\tcallId: string;\n\t\t\tsubagent: number;\n\t\t\tname: string;\n\t\t\toutcome: 'answered' | 'failed' | 'stopped';\n\t\t\tagent?: ProgressAgent;\n\t };\n\n/**\n * What one call to the model sent, in characters by where it came from: an estimate of size\n * for finding what fills a context, not a token count.\n */\nexport interface StepContext {\n\t/** The system prompt. */\n\tinstructions: number;\n\t/** The tool definitions. */\n\ttools: number;\n\t/**\n\t * The text of the user's prompts and the model's answers, and the provider's summary of the\n\t * turns before it once it has compacted them: only what the call sent.\n\t */\n\tmessages: number;\n\t/** Reasoning the model produced earlier and is sent back: its provider's own signed or encrypted blocks. */\n\treasoning: number;\n\t/** The input of every tool call so far. */\n\ttoolCalls: number;\n\t/** Every tool result so far, by tool, largest first. */\n\ttoolResults: { tool: string; characters: number; calls: number }[];\n\t/** The largest single results, each with its call's summary when the tool gives one. */\n\tlargestResults: { tool: string; characters: number; summary?: string }[];\n}\n\n/** A role in the fixed planner → executor → reviewer workflow. */\nexport type ProgressAgent = 'planner' | 'executor' | 'reviewer';\n\n/**\n * The contract every engine (CodexAgent, ClaudeAgent, future providers) and every\n * decorator (RetryingAgent, OrchestratorAgent) is built against.\n *\n * Implementers MUST only ever reject with `RecoverableError` or `UnrecoverableError`\n * (see src/shared/domain/errors.ts) — never a raw SDK error, a plain `Error`, or anything else leaked\n * unclassified. Every consumer of `Agent` (RetryingAgent's retry decision,\n * OrchestratorAgent's failure handling) `instanceof`-checks against those two types to\n * decide what to do next; a leaked, unclassified error bypasses that decision\n * entirely — it gets retried when it shouldn't be, or crashes a run that a retry\n * would have recovered. Wrap every call into the underlying SDK so nothing escapes\n * unclassified, including failures the SDK itself doesn't model as a domain error\n * (network errors, malformed responses, etc.); `classifyProviderFailure` does this.\n *\n * Cancellation is the one exception: an `AbortError`, or the `TimeoutError` of\n * `AbortSignal.timeout`, must propagate unchanged, because\n * consumers check for it before either error type.\n */\nexport interface Agent {\n\trun(prompt: string, options: RunOptions): Promise<AgentResponse>;\n}\n"]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ProgressEvent } from '#src/agent/domain/agent';
|
|
2
|
+
import type { Message } from '#src/llm/domain/message';
|
|
3
|
+
import { type Delegated } from '#src/tools/domain/preparedCall';
|
|
4
|
+
/** What the model is told of the agents a call sent, as that call's result. */
|
|
5
|
+
export declare function sentNote(agents: readonly Delegated[]): string;
|
|
6
|
+
/**
|
|
7
|
+
* The agents a run's calls sent to work in the background. Each one's answer waits here, in the
|
|
8
|
+
* order they finished, until the model's next step takes it. A failure that ends the run stops
|
|
9
|
+
* every agent still working at once, and the run at its next boundary.
|
|
10
|
+
*/
|
|
11
|
+
export declare class BackgroundAgents {
|
|
12
|
+
private readonly stopping;
|
|
13
|
+
/** What the agents work under: the run's own signal, or the stop of the run's end. */
|
|
14
|
+
readonly signal: AbortSignal;
|
|
15
|
+
private readonly agents;
|
|
16
|
+
private readonly arrived;
|
|
17
|
+
private ended;
|
|
18
|
+
private unrecordedWorking;
|
|
19
|
+
private unrecordedEnded;
|
|
20
|
+
private woken;
|
|
21
|
+
/**
|
|
22
|
+
* What some agent spent is unknown: it was stopped while it worked, or failed in a way that
|
|
23
|
+
* carries no usage.
|
|
24
|
+
*/
|
|
25
|
+
private unknownUsage;
|
|
26
|
+
private readonly narrate;
|
|
27
|
+
/** `narrate` tells the person watching when each agent ended; it must never throw. */
|
|
28
|
+
constructor(runSignal: AbortSignal, narrate: (event: ProgressEvent) => void);
|
|
29
|
+
/** Some agent is still working. */
|
|
30
|
+
get working(): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Whether an agent that may change files the history does not record worked since the last
|
|
33
|
+
* time this was asked: one is working now, or one ended meanwhile.
|
|
34
|
+
*/
|
|
35
|
+
mayHaveChangedUnseen(): boolean;
|
|
36
|
+
get usageUnknown(): boolean;
|
|
37
|
+
/** Watches the agents call `callId` sent, until each one answers. */
|
|
38
|
+
add(callId: string, agents: readonly Delegated[], unrecorded: boolean): void;
|
|
39
|
+
/**
|
|
40
|
+
* The message holding every answer that arrived since the last one, if any did, and what
|
|
41
|
+
* became of each agent a stop interrupted.
|
|
42
|
+
*/
|
|
43
|
+
takeAnswers(): Message | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Waits until an answer arrives, while agents are working. Rejects as cancelled when the run
|
|
46
|
+
* is cancelled meanwhile, and with the failure that ended it.
|
|
47
|
+
*/
|
|
48
|
+
nextAnswer(): Promise<void>;
|
|
49
|
+
/** Throws the failure of an agent that ended the run, if one did. */
|
|
50
|
+
throwIfEnded(): void;
|
|
51
|
+
/**
|
|
52
|
+
* Stops every agent still working, and waits until each one has: what they changed and spent
|
|
53
|
+
* belongs to the run, which ends only after them.
|
|
54
|
+
*/
|
|
55
|
+
stop(): Promise<void>;
|
|
56
|
+
private arrive;
|
|
57
|
+
/** A failure ends the run: every agent is stopped for it. */
|
|
58
|
+
private end;
|
|
59
|
+
private wake;
|
|
60
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { describeFailure } from '#src/shared/domain/providerFailure';
|
|
2
|
+
import { RunEndingToolError } from '#src/tools/domain/preparedCall';
|
|
3
|
+
// How much of a task the answer repeats, so the model can tell which one it is.
|
|
4
|
+
const shownTaskCharacters = 200;
|
|
5
|
+
/** How the model that sent an agent is told which one it is, such as `explorer 2`. */
|
|
6
|
+
function labelOf({ name, subagent }) {
|
|
7
|
+
return `${name} ${String(subagent)}`;
|
|
8
|
+
}
|
|
9
|
+
/** What the model is told of the agents a call sent, as that call's result. */
|
|
10
|
+
export function sentNote(agents) {
|
|
11
|
+
const names = agents.map(labelOf).join(', ');
|
|
12
|
+
return `Sent to work in the background: ${names}. Each one's answer reaches you in a message of its own once it finishes. Keep working meanwhile. When you have nothing else to do until they answer, say briefly that you are waiting, without calling a tool: the run waits for them, and you go on with their answers.`;
|
|
13
|
+
}
|
|
14
|
+
// What a model is told of an agent the end of its run stopped, in a conversation kept for the next.
|
|
15
|
+
const stoppedAnswer = 'None: it was stopped when the run ended, before it answered. Anything it changed stays as it left it.';
|
|
16
|
+
function shownTask(task) {
|
|
17
|
+
return task.length > shownTaskCharacters ? `${task.slice(0, shownTaskCharacters)}…` : task;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The agents a run's calls sent to work in the background. Each one's answer waits here, in the
|
|
21
|
+
* order they finished, until the model's next step takes it. A failure that ends the run stops
|
|
22
|
+
* every agent still working at once, and the run at its next boundary.
|
|
23
|
+
*/
|
|
24
|
+
export class BackgroundAgents {
|
|
25
|
+
stopping = new AbortController();
|
|
26
|
+
/** What the agents work under: the run's own signal, or the stop of the run's end. */
|
|
27
|
+
signal;
|
|
28
|
+
agents = new Set();
|
|
29
|
+
arrived = [];
|
|
30
|
+
ended;
|
|
31
|
+
unrecordedWorking = 0;
|
|
32
|
+
// Such an agent ended since `mayHaveChangedUnseen` was last asked.
|
|
33
|
+
unrecordedEnded = false;
|
|
34
|
+
woken;
|
|
35
|
+
/**
|
|
36
|
+
* What some agent spent is unknown: it was stopped while it worked, or failed in a way that
|
|
37
|
+
* carries no usage.
|
|
38
|
+
*/
|
|
39
|
+
unknownUsage = false;
|
|
40
|
+
narrate;
|
|
41
|
+
/** `narrate` tells the person watching when each agent ended; it must never throw. */
|
|
42
|
+
constructor(runSignal, narrate) {
|
|
43
|
+
this.signal = AbortSignal.any([runSignal, this.stopping.signal]);
|
|
44
|
+
this.narrate = narrate;
|
|
45
|
+
}
|
|
46
|
+
/** Some agent is still working. */
|
|
47
|
+
get working() {
|
|
48
|
+
return this.agents.size > 0;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Whether an agent that may change files the history does not record worked since the last
|
|
52
|
+
* time this was asked: one is working now, or one ended meanwhile.
|
|
53
|
+
*/
|
|
54
|
+
mayHaveChangedUnseen() {
|
|
55
|
+
const worked = this.unrecordedWorking > 0 || this.unrecordedEnded;
|
|
56
|
+
this.unrecordedEnded = false;
|
|
57
|
+
return worked;
|
|
58
|
+
}
|
|
59
|
+
get usageUnknown() {
|
|
60
|
+
return this.unknownUsage;
|
|
61
|
+
}
|
|
62
|
+
/** Watches the agents call `callId` sent, until each one answers. */
|
|
63
|
+
add(callId, agents, unrecorded) {
|
|
64
|
+
for (const agent of agents) {
|
|
65
|
+
if (unrecorded)
|
|
66
|
+
this.unrecordedWorking++;
|
|
67
|
+
const ended = (outcome) => {
|
|
68
|
+
const { name, subagent } = agent;
|
|
69
|
+
this.narrate({ type: 'subagentEnded', callId, subagent, name, outcome });
|
|
70
|
+
};
|
|
71
|
+
const watched = agent.answer
|
|
72
|
+
.then(({ text, failed }) => {
|
|
73
|
+
ended(failed ? 'failed' : 'answered');
|
|
74
|
+
this.arrive(agent, callId, text);
|
|
75
|
+
}, (error) => {
|
|
76
|
+
if (this.signal.aborted) {
|
|
77
|
+
ended('stopped');
|
|
78
|
+
this.unknownUsage = true;
|
|
79
|
+
this.arrive(agent, callId, stoppedAnswer);
|
|
80
|
+
}
|
|
81
|
+
else if (error instanceof RunEndingToolError) {
|
|
82
|
+
ended('failed');
|
|
83
|
+
this.end(error.failure);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// It broke the promise of answering, so what it spent is unknown.
|
|
87
|
+
ended('failed');
|
|
88
|
+
this.unknownUsage = true;
|
|
89
|
+
this.arrive(agent, callId, `It failed: ${describeFailure(error)}`);
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
.finally(() => {
|
|
93
|
+
if (unrecorded) {
|
|
94
|
+
this.unrecordedWorking--;
|
|
95
|
+
this.unrecordedEnded = true;
|
|
96
|
+
}
|
|
97
|
+
this.agents.delete(watched);
|
|
98
|
+
this.wake();
|
|
99
|
+
});
|
|
100
|
+
this.agents.add(watched);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The message holding every answer that arrived since the last one, if any did, and what
|
|
105
|
+
* became of each agent a stop interrupted.
|
|
106
|
+
*/
|
|
107
|
+
takeAnswers() {
|
|
108
|
+
if (this.arrived.length === 0)
|
|
109
|
+
return undefined;
|
|
110
|
+
const answers = this.arrived.splice(0);
|
|
111
|
+
const text = [
|
|
112
|
+
'This message is from the harness, not the user: agents you sent finished.',
|
|
113
|
+
...answers,
|
|
114
|
+
].join('\n\n');
|
|
115
|
+
return { role: 'user', content: [{ type: 'text', text }] };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Waits until an answer arrives, while agents are working. Rejects as cancelled when the run
|
|
119
|
+
* is cancelled meanwhile, and with the failure that ended it.
|
|
120
|
+
*/
|
|
121
|
+
async nextAnswer() {
|
|
122
|
+
while (this.arrived.length === 0 && this.agents.size > 0) {
|
|
123
|
+
this.throwIfEnded();
|
|
124
|
+
this.signal.throwIfAborted();
|
|
125
|
+
await new Promise(resolve => {
|
|
126
|
+
const done = () => {
|
|
127
|
+
this.signal.removeEventListener('abort', done);
|
|
128
|
+
resolve();
|
|
129
|
+
};
|
|
130
|
+
this.woken = done;
|
|
131
|
+
this.signal.addEventListener('abort', done, { once: true });
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
this.throwIfEnded();
|
|
135
|
+
this.signal.throwIfAborted();
|
|
136
|
+
}
|
|
137
|
+
/** Throws the failure of an agent that ended the run, if one did. */
|
|
138
|
+
throwIfEnded() {
|
|
139
|
+
if (this.ended)
|
|
140
|
+
throw this.ended.failure;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Stops every agent still working, and waits until each one has: what they changed and spent
|
|
144
|
+
* belongs to the run, which ends only after them.
|
|
145
|
+
*/
|
|
146
|
+
async stop() {
|
|
147
|
+
if (this.agents.size > 0)
|
|
148
|
+
this.stopping.abort();
|
|
149
|
+
await Promise.all(this.agents);
|
|
150
|
+
}
|
|
151
|
+
arrive(agent, callId, answer) {
|
|
152
|
+
this.arrived.push(`${labelOf(agent)}, sent by your call ${callId} with the task: ${shownTask(agent.task)}\nIts answer:\n${answer}`);
|
|
153
|
+
}
|
|
154
|
+
/** A failure ends the run: every agent is stopped for it. */
|
|
155
|
+
end(failure) {
|
|
156
|
+
this.ended ??= { failure };
|
|
157
|
+
this.stopping.abort();
|
|
158
|
+
}
|
|
159
|
+
wake() {
|
|
160
|
+
const woken = this.woken;
|
|
161
|
+
this.woken = undefined;
|
|
162
|
+
woken?.();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=backgroundAgents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backgroundAgents.js","sourceRoot":"","sources":["../../../../src/engines/domain/model/backgroundAgents.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ProgressEvent } from '#src/agent/domain/agent';\nimport type { Message } from '#src/llm/domain/message';\nimport { describeFailure } from '#src/shared/domain/providerFailure';\nimport { type Delegated, RunEndingToolError } from '#src/tools/domain/preparedCall';\n\n// How much of a task the answer repeats, so the model can tell which one it is.\nconst shownTaskCharacters = 200;\n\n/** How the model that sent an agent is told which one it is, such as `explorer 2`. */\nfunction labelOf({ name, subagent }: Delegated): string {\n\treturn `${name} ${String(subagent)}`;\n}\n\n/** What the model is told of the agents a call sent, as that call's result. */\nexport function sentNote(agents: readonly Delegated[]): string {\n\tconst names = agents.map(labelOf).join(', ');\n\treturn `Sent to work in the background: ${names}. Each one's answer reaches you in a message of its own once it finishes. Keep working meanwhile. When you have nothing else to do until they answer, say briefly that you are waiting, without calling a tool: the run waits for them, and you go on with their answers.`;\n}\n\n// What a model is told of an agent the end of its run stopped, in a conversation kept for the next.\nconst stoppedAnswer =\n\t'None: it was stopped when the run ended, before it answered. Anything it changed stays as it left it.';\n\nfunction shownTask(task: string): string {\n\treturn task.length > shownTaskCharacters ? `${task.slice(0, shownTaskCharacters)}…` : task;\n}\n\n/**\n * The agents a run's calls sent to work in the background. Each one's answer waits here, in the\n * order they finished, until the model's next step takes it. A failure that ends the run stops\n * every agent still working at once, and the run at its next boundary.\n */\nexport class BackgroundAgents {\n\tprivate readonly stopping = new AbortController();\n\t/** What the agents work under: the run's own signal, or the stop of the run's end. */\n\treadonly signal: AbortSignal;\n\tprivate readonly agents = new Set<Promise<void>>();\n\tprivate readonly arrived: string[] = [];\n\tprivate ended: { failure: unknown } | undefined;\n\tprivate unrecordedWorking = 0;\n\t// Such an agent ended since `mayHaveChangedUnseen` was last asked.\n\tprivate unrecordedEnded = false;\n\tprivate woken: (() => void) | undefined;\n\t/**\n\t * What some agent spent is unknown: it was stopped while it worked, or failed in a way that\n\t * carries no usage.\n\t */\n\tprivate unknownUsage = false;\n\n\tprivate readonly narrate: (event: ProgressEvent) => void;\n\n\t/** `narrate` tells the person watching when each agent ended; it must never throw. */\n\tconstructor(runSignal: AbortSignal, narrate: (event: ProgressEvent) => void) {\n\t\tthis.signal = AbortSignal.any([runSignal, this.stopping.signal]);\n\t\tthis.narrate = narrate;\n\t}\n\n\t/** Some agent is still working. */\n\tget working(): boolean {\n\t\treturn this.agents.size > 0;\n\t}\n\n\t/**\n\t * Whether an agent that may change files the history does not record worked since the last\n\t * time this was asked: one is working now, or one ended meanwhile.\n\t */\n\tmayHaveChangedUnseen(): boolean {\n\t\tconst worked = this.unrecordedWorking > 0 || this.unrecordedEnded;\n\t\tthis.unrecordedEnded = false;\n\t\treturn worked;\n\t}\n\n\tget usageUnknown(): boolean {\n\t\treturn this.unknownUsage;\n\t}\n\n\t/** Watches the agents call `callId` sent, until each one answers. */\n\tadd(callId: string, agents: readonly Delegated[], unrecorded: boolean): void {\n\t\tfor (const agent of agents) {\n\t\t\tif (unrecorded) this.unrecordedWorking++;\n\t\t\tconst ended = (outcome: 'answered' | 'failed' | 'stopped') => {\n\t\t\t\tconst { name, subagent } = agent;\n\t\t\t\tthis.narrate({ type: 'subagentEnded', callId, subagent, name, outcome });\n\t\t\t};\n\t\t\tconst watched = agent.answer\n\t\t\t\t.then(\n\t\t\t\t\t({ text, failed }) => {\n\t\t\t\t\t\tended(failed ? 'failed' : 'answered');\n\t\t\t\t\t\tthis.arrive(agent, callId, text);\n\t\t\t\t\t},\n\t\t\t\t\t(error: unknown) => {\n\t\t\t\t\t\tif (this.signal.aborted) {\n\t\t\t\t\t\t\tended('stopped');\n\t\t\t\t\t\t\tthis.unknownUsage = true;\n\t\t\t\t\t\t\tthis.arrive(agent, callId, stoppedAnswer);\n\t\t\t\t\t\t} else if (error instanceof RunEndingToolError) {\n\t\t\t\t\t\t\tended('failed');\n\t\t\t\t\t\t\tthis.end(error.failure);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// It broke the promise of answering, so what it spent is unknown.\n\t\t\t\t\t\t\tended('failed');\n\t\t\t\t\t\t\tthis.unknownUsage = true;\n\t\t\t\t\t\t\tthis.arrive(agent, callId, `It failed: ${describeFailure(error)}`);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\t.finally(() => {\n\t\t\t\t\tif (unrecorded) {\n\t\t\t\t\t\tthis.unrecordedWorking--;\n\t\t\t\t\t\tthis.unrecordedEnded = true;\n\t\t\t\t\t}\n\t\t\t\t\tthis.agents.delete(watched);\n\t\t\t\t\tthis.wake();\n\t\t\t\t});\n\t\t\tthis.agents.add(watched);\n\t\t}\n\t}\n\n\t/**\n\t * The message holding every answer that arrived since the last one, if any did, and what\n\t * became of each agent a stop interrupted.\n\t */\n\ttakeAnswers(): Message | undefined {\n\t\tif (this.arrived.length === 0) return undefined;\n\t\tconst answers = this.arrived.splice(0);\n\t\tconst text = [\n\t\t\t'This message is from the harness, not the user: agents you sent finished.',\n\t\t\t...answers,\n\t\t].join('\\n\\n');\n\t\treturn { role: 'user', content: [{ type: 'text', text }] };\n\t}\n\n\t/**\n\t * Waits until an answer arrives, while agents are working. Rejects as cancelled when the run\n\t * is cancelled meanwhile, and with the failure that ended it.\n\t */\n\tasync nextAnswer(): Promise<void> {\n\t\twhile (this.arrived.length === 0 && this.agents.size > 0) {\n\t\t\tthis.throwIfEnded();\n\t\t\tthis.signal.throwIfAborted();\n\t\t\tawait new Promise<void>(resolve => {\n\t\t\t\tconst done = () => {\n\t\t\t\t\tthis.signal.removeEventListener('abort', done);\n\t\t\t\t\tresolve();\n\t\t\t\t};\n\t\t\t\tthis.woken = done;\n\t\t\t\tthis.signal.addEventListener('abort', done, { once: true });\n\t\t\t});\n\t\t}\n\t\tthis.throwIfEnded();\n\t\tthis.signal.throwIfAborted();\n\t}\n\n\t/** Throws the failure of an agent that ended the run, if one did. */\n\tthrowIfEnded(): void {\n\t\tif (this.ended) throw this.ended.failure;\n\t}\n\n\t/**\n\t * Stops every agent still working, and waits until each one has: what they changed and spent\n\t * belongs to the run, which ends only after them.\n\t */\n\tasync stop(): Promise<void> {\n\t\tif (this.agents.size > 0) this.stopping.abort();\n\t\tawait Promise.all(this.agents);\n\t}\n\n\tprivate arrive(agent: Delegated, callId: string, answer: string): void {\n\t\tthis.arrived.push(\n\t\t\t`${labelOf(agent)}, sent by your call ${callId} with the task: ${shownTask(agent.task)}\\nIts answer:\\n${answer}`,\n\t\t);\n\t}\n\n\t/** A failure ends the run: every agent is stopped for it. */\n\tprivate end(failure: unknown): void {\n\t\tthis.ended ??= { failure };\n\t\tthis.stopping.abort();\n\t}\n\n\tprivate wake(): void {\n\t\tconst woken = this.woken;\n\t\tthis.woken = undefined;\n\t\twoken?.();\n\t}\n}\n"]}
|
|
@@ -68,7 +68,8 @@ export declare class LLMAgent implements Agent {
|
|
|
68
68
|
/**
|
|
69
69
|
* Always answers the call: a missing tool, a denied call or a failing one becomes an error
|
|
70
70
|
* result, so the model can correct itself. Only a cancellation, a failing approver, or host
|
|
71
|
-
* code that failed inside the call
|
|
71
|
+
* code that failed inside the call escapes. A call that hands tasks to agents answers once
|
|
72
|
+
* it sent them, and `background` watches them from then on.
|
|
72
73
|
*/
|
|
73
74
|
private runTool;
|
|
74
75
|
/**
|
|
@@ -77,18 +78,10 @@ export declare class LLMAgent implements Agent {
|
|
|
77
78
|
*/
|
|
78
79
|
private askUser;
|
|
79
80
|
/**
|
|
80
|
-
* One at a time and in order, so their effects and their narration never interleave.
|
|
81
|
-
*
|
|
82
|
-
* to, each narrated as it goes.
|
|
81
|
+
* One at a time and in order, so their effects and their narration never interleave. Agents a
|
|
82
|
+
* call hands tasks to keep working in the background once it answered.
|
|
83
83
|
*/
|
|
84
84
|
private runTools;
|
|
85
|
-
/** The calls from `index` on whose tool is `parallel`, up to the first that is not. */
|
|
86
|
-
private parallelFrom;
|
|
87
|
-
/**
|
|
88
|
-
* Runs the calls at once, their results in the order of the calls. The first failure that
|
|
89
|
-
* ends the run stops the others, and the run ends once every one has.
|
|
90
|
-
*/
|
|
91
|
-
private runTogether;
|
|
92
85
|
/**
|
|
93
86
|
* Narrates how a call ended and notes its command's ending, with the warning for the model
|
|
94
87
|
* when that ending keeps coming back alike.
|