@mikode13/harness 5.5.0 → 6.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 +112 -0
- package/dist/agent/domain/agent.d.ts +26 -2
- package/dist/agent/domain/agent.js.map +1 -1
- package/dist/factory/infrastructure/workflowFactory.d.ts +9 -0
- package/dist/factory/infrastructure/workflowFactory.js +42 -0
- package/dist/factory/infrastructure/workflowFactory.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/orchestration/domain/model/orchestratorAgent.d.ts +1 -1
- package/dist/orchestration/domain/model/orchestratorAgent.js.map +1 -1
- package/dist/orchestration/infrastructure/model/reviewerDecisionValidator.d.ts +1 -1
- package/dist/orchestration/infrastructure/model/reviewerDecisionValidator.js.map +1 -1
- package/dist/shared/domain/isAbortError.d.ts +1 -0
- package/dist/shared/domain/isAbortError.js +2 -1
- package/dist/shared/domain/isAbortError.js.map +1 -1
- package/dist/shared/domain/validator.d.ts +4 -0
- package/dist/shared/domain/validator.js.map +1 -0
- package/dist/workflow/domain/workflow.d.ts +100 -0
- package/dist/workflow/domain/workflow.js +12 -0
- package/dist/workflow/domain/workflow.js.map +1 -0
- package/dist/workflow/domain/workflowRunner.d.ts +54 -0
- package/dist/workflow/domain/workflowRunner.js +271 -0
- package/dist/workflow/domain/workflowRunner.js.map +1 -0
- package/package.json +1 -1
- package/dist/orchestration/domain/interface/validator.d.ts +0 -3
- package/dist/orchestration/domain/interface/validator.js.map +0 -1
- /package/dist/{orchestration/domain/interface → shared/domain}/validator.js +0 -0
package/README.md
CHANGED
|
@@ -52,6 +52,10 @@ chat loop itself never knows the difference.
|
|
|
52
52
|
malformed reviewer response retries only the reviewer call, not the whole
|
|
53
53
|
cycle. Because it implements `Agent` itself, the chat loop drives it exactly
|
|
54
54
|
like it drives a bare engine.
|
|
55
|
+
- **Workflows** (`createWorkflow`): stages you define, each with an agent and a context
|
|
56
|
+
of its own. A stage's JSON answer is validated before its transition picks the next
|
|
57
|
+
stage, stops the run on purpose, or completes it. Only the data a transition hands
|
|
58
|
+
over crosses to the next stage, and the types check it against what that stage takes.
|
|
55
59
|
- **Live progress streaming**: every engine reports ongoing activity (tool
|
|
56
60
|
calls, searches, file changes, reasoning) through a typed `ProgressEvent`
|
|
57
61
|
callback, separate from the final response — so an agent's own words never
|
|
@@ -166,6 +170,114 @@ optional fields.
|
|
|
166
170
|
a role left out keeps the harness's own, and the reviewer must still answer with
|
|
167
171
|
the JSON decision described on the option.
|
|
168
172
|
|
|
173
|
+
### Composing agents into a workflow
|
|
174
|
+
|
|
175
|
+
`createWorkflow` runs stages you define, each with an agent of its own, so each can use a
|
|
176
|
+
different provider, model and instructions. It works like a middleware chain: a stage's agent
|
|
177
|
+
starts with an empty context, builds its prompt from what the previous stage handed it, and
|
|
178
|
+
answers in JSON. The stage's `validator` turns the answer into a typed value, and its
|
|
179
|
+
`transition` picks what happens next. A stage's conversation stays with it; only the
|
|
180
|
+
transition's `input` reaches the next stage.
|
|
181
|
+
|
|
182
|
+
You declare once what each stage receives, and every transition is checked against it: a `go`
|
|
183
|
+
to a stage that does not exist, or with the wrong input, does not compile, and neither does a
|
|
184
|
+
workflow that leaves a stage out.
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
import { z } from 'zod';
|
|
188
|
+
import { createLLMAgent, createWorkflow, defineWorkflowStages } from '@mikode13/harness';
|
|
189
|
+
|
|
190
|
+
const Triage = z.object({ worthAnswering: z.boolean(), topic: z.string() });
|
|
191
|
+
const Draft = z.object({ text: z.string() });
|
|
192
|
+
const Review = z.object({ approved: z.boolean(), feedback: z.string() });
|
|
193
|
+
|
|
194
|
+
/** What each stage receives. */
|
|
195
|
+
interface Inputs {
|
|
196
|
+
triage: string;
|
|
197
|
+
draft: { topic: string; feedback: string };
|
|
198
|
+
review: { topic: string; text: string };
|
|
199
|
+
}
|
|
200
|
+
const stage = defineWorkflowStages<Inputs, string>();
|
|
201
|
+
|
|
202
|
+
const workflow = createWorkflow({
|
|
203
|
+
initialStage: 'triage',
|
|
204
|
+
stages: {
|
|
205
|
+
triage: stage('triage', {
|
|
206
|
+
agent: () =>
|
|
207
|
+
createLLMAgent('openai', {
|
|
208
|
+
model: 'gpt-5.6-luna',
|
|
209
|
+
systemPrompt:
|
|
210
|
+
'Decide whether a question deserves an answer. JSON {worthAnswering, topic}.',
|
|
211
|
+
}),
|
|
212
|
+
prompt: question => question,
|
|
213
|
+
validator: { validate: value => Triage.safeParse(value).data },
|
|
214
|
+
transition: ({ worthAnswering, topic }) =>
|
|
215
|
+
worthAnswering
|
|
216
|
+
? { type: 'go', stage: 'draft', input: { topic, feedback: '' } }
|
|
217
|
+
: { type: 'stop', result: 'Not worth an answer.' },
|
|
218
|
+
}),
|
|
219
|
+
draft: stage('draft', {
|
|
220
|
+
agent: () =>
|
|
221
|
+
createLLMAgent('anthropic', {
|
|
222
|
+
systemPrompt: 'Write a short, accurate answer. JSON {text}.',
|
|
223
|
+
}),
|
|
224
|
+
prompt: ({ topic, feedback }) => (feedback ? `Revise it: ${feedback}` : `Topic: ${topic}`),
|
|
225
|
+
validator: { validate: value => Draft.safeParse(value).data },
|
|
226
|
+
// The stage's own input is the second argument, so the topic need not go through the model.
|
|
227
|
+
transition: ({ text }, { topic }) => ({
|
|
228
|
+
type: 'go',
|
|
229
|
+
stage: 'review',
|
|
230
|
+
input: { topic, text },
|
|
231
|
+
}),
|
|
232
|
+
}),
|
|
233
|
+
review: stage('review', {
|
|
234
|
+
agent: () =>
|
|
235
|
+
createLLMAgent('openai', { systemPrompt: 'Review an answer. JSON {approved, feedback}.' }),
|
|
236
|
+
prompt: ({ text }) => text,
|
|
237
|
+
validator: { validate: value => Review.safeParse(value).data },
|
|
238
|
+
transition: ({ approved, feedback }, { topic, text }) =>
|
|
239
|
+
approved
|
|
240
|
+
? { type: 'complete', result: text }
|
|
241
|
+
: { type: 'go', stage: 'draft', input: { topic, feedback } },
|
|
242
|
+
}),
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const result = await workflow.run('Why is the sky blue?', {
|
|
247
|
+
signal: AbortSignal.timeout(10 * 60_000),
|
|
248
|
+
});
|
|
249
|
+
// 'completed' or 'stopped', and what each stage received, answered and chose.
|
|
250
|
+
console.log(result.status, result.result, result.stages, result.tokens);
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
- **Agents:** `agent` creates the stage's agent the first time the stage runs in a run.
|
|
254
|
+
When the run comes back to a stage, as `draft` does after a rejected review, the same
|
|
255
|
+
agent goes on with its conversation, so it remembers its first draft. The next run
|
|
256
|
+
creates new agents, so two runs, even at once, never see each other's conversations.
|
|
257
|
+
`agent` may return a promise, so a stage can create an agent that is built async, such as
|
|
258
|
+
one from `createLLMOrchestrator`.
|
|
259
|
+
- **Transitions:**
|
|
260
|
+
- `go` runs the stage it names, with `input`.
|
|
261
|
+
- `complete` ends the run finished.
|
|
262
|
+
- `stop` ends it early on purpose, and the result's `status` is then `'stopped'`.
|
|
263
|
+
- A failure rejects the run, so a stop is never mistaken for one.
|
|
264
|
+
- **An unusable answer:** an answer that is not JSON, or that the validator rejects with
|
|
265
|
+
`undefined`, is asked for again with the reason. The stage's agent answers at most
|
|
266
|
+
`maxAttempts` times, 3 by default and settable per stage. Then the run fails with an
|
|
267
|
+
`UnrecoverableError` carrying every answer's usage.
|
|
268
|
+
- **What the run knows:** `result.stages` records, for each stage run in order, its input,
|
|
269
|
+
its validated output, its attempts, usage and duration, and the transition it chose.
|
|
270
|
+
- **Bounds:** `maxTransitions`, 100 by default, ends a loop of stages. Without the types, from
|
|
271
|
+
JavaScript, `createWorkflow` checks the stage names and the initial stage; a `go` to a stage
|
|
272
|
+
that does not exist fails the run when it is taken.
|
|
273
|
+
- **Narration:** `onProgress` receives a `workflowStage` event before each answer and a
|
|
274
|
+
`workflowTransition` event after each choice. Stages run one at a time, so the agent
|
|
275
|
+
events between them belong to that stage.
|
|
276
|
+
- **Cancelling and timing out:** the signal cancels the whole run. `AbortSignal.timeout`
|
|
277
|
+
times it out, and `isAbortError` recognizes both.
|
|
278
|
+
- **Routing stays outside:** a workflow runs the flow you picked for a request. Choosing
|
|
279
|
+
that flow is [#17](https://github.com/Mikode13/harness/issues/17).
|
|
280
|
+
|
|
169
281
|
### Validating factory options
|
|
170
282
|
|
|
171
283
|
The provider-aware guards validate command-line strings before a factory is built. Use the
|
|
@@ -30,7 +30,10 @@ export type Callback = (item: ProgressEvent) => void;
|
|
|
30
30
|
* enumerable one to reach every agent.
|
|
31
31
|
*/
|
|
32
32
|
export interface RunOptions {
|
|
33
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Cancels the run, which then rejects with the signal's reason: an `AbortError`, or a
|
|
35
|
+
* `TimeoutError` from `AbortSignal.timeout`. `isAbortError` recognizes both.
|
|
36
|
+
*/
|
|
34
37
|
signal: AbortSignal;
|
|
35
38
|
/** Receives the run's live activity. Without it, the run is silent. */
|
|
36
39
|
onProgress?: Callback | undefined;
|
|
@@ -133,6 +136,26 @@ export type ProgressEvent = {
|
|
|
133
136
|
tokens?: Tokens;
|
|
134
137
|
context: StepContext;
|
|
135
138
|
agent?: ProgressAgent;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* A workflow's stage asks its agent for an answer; `attempt` is 2 or more when the previous
|
|
142
|
+
* answer was invalid. Stages run one at a time, so the events that follow, until the next
|
|
143
|
+
* workflow event, are this stage's agent's.
|
|
144
|
+
*/
|
|
145
|
+
| {
|
|
146
|
+
type: 'workflowStage';
|
|
147
|
+
stage: string;
|
|
148
|
+
attempt: number;
|
|
149
|
+
agent?: ProgressAgent;
|
|
150
|
+
}
|
|
151
|
+
/** The transition a stage's valid answer selected, and the stage it leads to. */
|
|
152
|
+
| {
|
|
153
|
+
type: 'workflowTransition';
|
|
154
|
+
stage: string;
|
|
155
|
+
transition: 'go' | 'stop' | 'complete';
|
|
156
|
+
/** Absent after `stop` or `complete`. */
|
|
157
|
+
nextStage?: string;
|
|
158
|
+
agent?: ProgressAgent;
|
|
136
159
|
};
|
|
137
160
|
/**
|
|
138
161
|
* What one call to the model sent, in characters by where it came from: an estimate of size
|
|
@@ -181,7 +204,8 @@ export type ProgressAgent = 'planner' | 'executor' | 'reviewer';
|
|
|
181
204
|
* unclassified, including failures the SDK itself doesn't model as a domain error
|
|
182
205
|
* (network errors, malformed responses, etc.); `classifyProviderFailure` does this.
|
|
183
206
|
*
|
|
184
|
-
* Cancellation is the one exception: an `AbortError
|
|
207
|
+
* Cancellation is the one exception: an `AbortError`, or the `TimeoutError` of
|
|
208
|
+
* `AbortSignal.timeout`, must propagate unchanged, because
|
|
185
209
|
* consumers check for it before either error type.
|
|
186
210
|
*/
|
|
187
211
|
export interface Agent {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../src/agent/domain/agent.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../src/agent/domain/agent.ts"],"names":[],"mappings":"AAwDA,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.\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.\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 * `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\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,9 @@
|
|
|
1
|
+
import type { CreateWorkflowOptions, Workflow, WorkflowStageName } from '#src/workflow/domain/workflow';
|
|
2
|
+
/**
|
|
3
|
+
* Builds a workflow from the consumer's stages. The types check every `go` against the stage it
|
|
4
|
+
* names and the input it hands over. For JavaScript callers, construction checks only what it
|
|
5
|
+
* can see before a run: each stage's name, the initial stage, `maxAttempts` and `maxTransitions`.
|
|
6
|
+
* A `go` is a value a stage returns, so the run checks its target when it takes it, and nothing
|
|
7
|
+
* checks its input: the next stage's `prompt` receives it as it is.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createWorkflow<Inputs, Result, Initial extends WorkflowStageName<Inputs>>({ initialStage, stages, maxAttempts, maxTransitions, logger, }: CreateWorkflowOptions<Inputs, Result, Initial>): Workflow<Inputs[Initial], Result>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Logger } from '#src/shared/infrastructure/logger';
|
|
2
|
+
import { WorkflowRunner } from '#src/workflow/domain/workflowRunner';
|
|
3
|
+
function positiveInteger(value, name, fallback) {
|
|
4
|
+
const actual = value ?? fallback;
|
|
5
|
+
if (!Number.isInteger(actual) || actual < 1) {
|
|
6
|
+
throw new RangeError(`${name} must be a positive integer`);
|
|
7
|
+
}
|
|
8
|
+
return actual;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Builds a workflow from the consumer's stages. The types check every `go` against the stage it
|
|
12
|
+
* names and the input it hands over. For JavaScript callers, construction checks only what it
|
|
13
|
+
* can see before a run: each stage's name, the initial stage, `maxAttempts` and `maxTransitions`.
|
|
14
|
+
* A `go` is a value a stage returns, so the run checks its target when it takes it, and nothing
|
|
15
|
+
* checks its input: the next stage's `prompt` receives it as it is.
|
|
16
|
+
*/
|
|
17
|
+
export function createWorkflow({ initialStage, stages, maxAttempts, maxTransitions, logger = new Logger(), }) {
|
|
18
|
+
const runnable = new Map();
|
|
19
|
+
for (const [name, stage] of Object.entries(stages)) {
|
|
20
|
+
if (name.trim() === '')
|
|
21
|
+
throw new RangeError('workflow stage names must be non-empty');
|
|
22
|
+
if (stage.name !== name) {
|
|
23
|
+
throw new RangeError(`workflow stage "${stage.name}" is listed as "${name}"`);
|
|
24
|
+
}
|
|
25
|
+
if (stage.maxAttempts !== undefined)
|
|
26
|
+
positiveInteger(stage.maxAttempts, 'maxAttempts', 1);
|
|
27
|
+
runnable.set(name, stage);
|
|
28
|
+
}
|
|
29
|
+
if (runnable.size === 0)
|
|
30
|
+
throw new RangeError('stages must not be empty');
|
|
31
|
+
if (!runnable.has(initialStage)) {
|
|
32
|
+
throw new RangeError(`initial workflow stage is not among the stages: "${initialStage}"`);
|
|
33
|
+
}
|
|
34
|
+
return new WorkflowRunner({
|
|
35
|
+
initialStage,
|
|
36
|
+
stages: runnable,
|
|
37
|
+
maxAttempts: positiveInteger(maxAttempts, 'maxAttempts', 3),
|
|
38
|
+
maxTransitions: positiveInteger(maxTransitions, 'maxTransitions', 100),
|
|
39
|
+
logger,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=workflowFactory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflowFactory.js","sourceRoot":"","sources":["../../../src/factory/infrastructure/workflowFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAsB,MAAM,qCAAqC,CAAC;AAOzF,SAAS,eAAe,CAAC,KAAyB,EAAE,IAAY,EAAE,QAAgB;IACjF,MAAM,MAAM,GAAG,KAAK,IAAI,QAAQ,CAAC;IACjC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,UAAU,CAAC,GAAG,IAAI,6BAA6B,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAA4D,EACzF,YAAY,EACZ,MAAM,EACN,WAAW,EACX,cAAc,EACd,MAAM,GAAG,IAAI,MAAM,EAAE,GAC2B;IAChD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAyB,CAAC;IAClD,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAgB,MAAM,CAAC,EAAE,CAAC;QACnE,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,MAAM,IAAI,UAAU,CAAC,wCAAwC,CAAC,CAAC;QACvF,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YACzB,MAAM,IAAI,UAAU,CAAC,mBAAmB,KAAK,CAAC,IAAI,mBAAmB,IAAI,GAAG,CAAC,CAAC;QAC/E,CAAC;QACD,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS;YAAE,eAAe,CAAC,KAAK,CAAC,WAAW,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC;QAC1F,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC3B,CAAC;IACD,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC;QAAE,MAAM,IAAI,UAAU,CAAC,0BAA0B,CAAC,CAAC;IAC1E,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,UAAU,CAAC,oDAAoD,YAAY,GAAG,CAAC,CAAC;IAC3F,CAAC;IAED,OAAO,IAAI,cAAc,CAA0B;QAClD,YAAY;QACZ,MAAM,EAAE,QAAQ;QAChB,WAAW,EAAE,eAAe,CAAC,WAAW,EAAE,aAAa,EAAE,CAAC,CAAC;QAC3D,cAAc,EAAE,eAAe,CAAC,cAAc,EAAE,gBAAgB,EAAE,GAAG,CAAC;QACtE,MAAM;KACN,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { Logger } from '#src/shared/infrastructure/logger';\nimport { WorkflowRunner, type RunnableStage } from '#src/workflow/domain/workflowRunner';\nimport type {\n\tCreateWorkflowOptions,\n\tWorkflow,\n\tWorkflowStageName,\n} from '#src/workflow/domain/workflow';\n\nfunction positiveInteger(value: number | undefined, name: string, fallback: number): number {\n\tconst actual = value ?? fallback;\n\tif (!Number.isInteger(actual) || actual < 1) {\n\t\tthrow new RangeError(`${name} must be a positive integer`);\n\t}\n\treturn actual;\n}\n\n/**\n * Builds a workflow from the consumer's stages. The types check every `go` against the stage it\n * names and the input it hands over. For JavaScript callers, construction checks only what it\n * can see before a run: each stage's name, the initial stage, `maxAttempts` and `maxTransitions`.\n * A `go` is a value a stage returns, so the run checks its target when it takes it, and nothing\n * checks its input: the next stage's `prompt` receives it as it is.\n */\nexport function createWorkflow<Inputs, Result, Initial extends WorkflowStageName<Inputs>>({\n\tinitialStage,\n\tstages,\n\tmaxAttempts,\n\tmaxTransitions,\n\tlogger = new Logger(),\n}: CreateWorkflowOptions<Inputs, Result, Initial>): Workflow<Inputs[Initial], Result> {\n\tconst runnable = new Map<string, RunnableStage>();\n\tfor (const [name, stage] of Object.entries<RunnableStage>(stages)) {\n\t\tif (name.trim() === '') throw new RangeError('workflow stage names must be non-empty');\n\t\tif (stage.name !== name) {\n\t\t\tthrow new RangeError(`workflow stage \"${stage.name}\" is listed as \"${name}\"`);\n\t\t}\n\t\tif (stage.maxAttempts !== undefined) positiveInteger(stage.maxAttempts, 'maxAttempts', 1);\n\t\trunnable.set(name, stage);\n\t}\n\tif (runnable.size === 0) throw new RangeError('stages must not be empty');\n\tif (!runnable.has(initialStage)) {\n\t\tthrow new RangeError(`initial workflow stage is not among the stages: \"${initialStage}\"`);\n\t}\n\n\treturn new WorkflowRunner<Inputs[Initial], Result>({\n\t\tinitialStage,\n\t\tstages: runnable,\n\t\tmaxAttempts: positiveInteger(maxAttempts, 'maxAttempts', 3),\n\t\tmaxTransitions: positiveInteger(maxTransitions, 'maxTransitions', 100),\n\t\tlogger,\n\t});\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -22,3 +22,6 @@ export { createHistory } from './recovery/infrastructure/createHistory.ts';
|
|
|
22
22
|
export type { History, HistoryRun, Move } from './recovery/domain/history.ts';
|
|
23
23
|
export { historyStart, NothingToMoveError, WorkspaceBusyError, WorkspaceMovedError, type MoveOptions, type RunStatus, } from './recovery/domain/recoveryStore.ts';
|
|
24
24
|
export { HistoryExpiredError, UnknownRunError } from './recovery/domain/runTree.ts';
|
|
25
|
+
export { createWorkflow } from './factory/infrastructure/workflowFactory.ts';
|
|
26
|
+
export { defineWorkflowStages, type CreateWorkflowOptions, type Workflow, type WorkflowResult, type WorkflowStage, type WorkflowStageDefinition, type WorkflowStageName, type WorkflowStageReport, type WorkflowTransition, } from './workflow/domain/workflow.ts';
|
|
27
|
+
export type { Validator } from './shared/domain/validator.ts';
|
package/dist/index.js
CHANGED
|
@@ -11,4 +11,6 @@ export { createWorkspaceTools } from "./tools/infrastructure/workspaceTools.js";
|
|
|
11
11
|
export { createHistory } from "./recovery/infrastructure/createHistory.js";
|
|
12
12
|
export { historyStart, NothingToMoveError, WorkspaceBusyError, WorkspaceMovedError, } from "./recovery/domain/recoveryStore.js";
|
|
13
13
|
export { HistoryExpiredError, UnknownRunError } from "./recovery/domain/runTree.js";
|
|
14
|
+
export { createWorkflow } from "./factory/infrastructure/workflowFactory.js";
|
|
15
|
+
export { defineWorkflowStages, } from "./workflow/domain/workflow.js";
|
|
14
16
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACN,WAAW,EACX,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,eAAe,EACf,SAAS,EACT,mBAAmB,GAEnB,MAAM,0CAA0C,CAAC;AAClD,OAAO,EACN,eAAe,EACf,cAAc,EACd,qBAAqB,EACrB,YAAY,EACZ,sBAAsB,EACtB,SAAS,EACT,mBAAmB,GAKnB,MAAM,6CAA6C,CAAC;AAWrD,OAAO,EACN,cAAc,GAKd,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAQN,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACN,uBAAuB,EACvB,gBAAgB,EAChB,kBAAkB,GAClB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAE/D,OAAO,EACN,iBAAiB,GAOjB,MAAM,4BAA4B,CAAC;AAKpC,OAAO,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AAChF,OAAO,EAAE,aAAa,EAAE,MAAM,4CAA4C,CAAC;AAE3E,OAAO,EACN,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,GAGnB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC","sourcesContent":["export type { ILogger } from './shared/domain/logger.ts';\nexport {\n\tcreateAgent,\n\tcreateOrchestrator,\n\tagentModels,\n\tagentReasoningEfforts,\n\tisAgentProvider,\n\tisModelOf,\n\tisReasoningEffortOf,\n\ttype CreateAgentOptions,\n} from './factory/infrastructure/agentFactory.ts';\nexport {\n\tcreateFileTools,\n\tcreateLLMAgent,\n\tcreateLLMOrchestrator,\n\tisLLMModelOf,\n\tisLLMReasoningEffortOf,\n\tllmModels,\n\tllmReasoningEfforts,\n\ttype CreateLLMAgentOptions,\n\ttype CreateLLMOrchestratorOptions,\n\ttype RoleSteps,\n\ttype RoleTokens,\n} from './factory/infrastructure/agentLLMFactory.ts';\nexport type { FileTool, WorkspaceOptions } from './tools/infrastructure/fileTools.ts';\nexport type { WorkspaceRoot } from './tools/domain/accessPolicy.ts';\nexport type {\n\tCommandArgument,\n\tCommandDefinition,\n\tCommandParameter,\n\tCommandRole,\n\tCommands,\n} from './tools/domain/commands.ts';\nexport type { SecretRules } from './tools/infrastructure/rootsAccessPolicy.ts';\nexport {\n\tagentProviders,\n\ttype AgentModel,\n\ttype AgentProvider,\n\ttype CreateOrchestratorOptions,\n\ttype ReasoningEffort,\n} from './factory/infrastructure/types.ts';\nexport {\n\ttype Agent,\n\ttype AgentResponse,\n\ttype Callback,\n\ttype ProgressAgent,\n\ttype ProgressEvent,\n\ttype StepContext,\n\ttype RunOptions,\n} from './agent/domain/agent.ts';\nexport {\n\tInvalidAgentConfigError,\n\tRecoverableError,\n\tUnrecoverableError,\n} from './shared/domain/errors.ts';\nexport { isAbortError } from './shared/domain/isAbortError.ts';\nexport type { Tokens } from './shared/domain/tokens.ts';\nexport {\n\trememberApprovals,\n\ttype ApprovalDecision,\n\ttype ApprovalRequest,\n\ttype Approver,\n\ttype CommandLine,\n\ttype RememberableDecision,\n\ttype ToolRisk,\n} from './agent/domain/approval.ts';\nexport type { Asker, QuestionOption, UserQuestion } from './agent/domain/question.ts';\nexport type { JSONSchema, ToolDefinition } from './llm/domain/tool.ts';\nexport type { Tool, ToolSummarizer } from './tools/domain/tool.ts';\nexport type { TextMatch, Workspace } from './tools/domain/workspace.ts';\nexport { defineTool } from './tools/infrastructure/defineTool.ts';\nexport { createWorkspace } from './tools/infrastructure/createWorkspace.ts';\nexport { createWorkspaceTools } from './tools/infrastructure/workspaceTools.ts';\nexport { createHistory } from './recovery/infrastructure/createHistory.ts';\nexport type { History, HistoryRun, Move } from './recovery/domain/history.ts';\nexport {\n\thistoryStart,\n\tNothingToMoveError,\n\tWorkspaceBusyError,\n\tWorkspaceMovedError,\n\ttype MoveOptions,\n\ttype RunStatus,\n} from './recovery/domain/recoveryStore.ts';\nexport { HistoryExpiredError, UnknownRunError } from './recovery/domain/runTree.ts';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACN,WAAW,EACX,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,eAAe,EACf,SAAS,EACT,mBAAmB,GAEnB,MAAM,0CAA0C,CAAC;AAClD,OAAO,EACN,eAAe,EACf,cAAc,EACd,qBAAqB,EACrB,YAAY,EACZ,sBAAsB,EACtB,SAAS,EACT,mBAAmB,GAKnB,MAAM,6CAA6C,CAAC;AAWrD,OAAO,EACN,cAAc,GAKd,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAQN,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACN,uBAAuB,EACvB,gBAAgB,EAChB,kBAAkB,GAClB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAE/D,OAAO,EACN,iBAAiB,GAOjB,MAAM,4BAA4B,CAAC;AAKpC,OAAO,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AAChF,OAAO,EAAE,aAAa,EAAE,MAAM,4CAA4C,CAAC;AAE3E,OAAO,EACN,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,GAGnB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,cAAc,EAAE,MAAM,6CAA6C,CAAC;AAC7E,OAAO,EACN,oBAAoB,GASpB,MAAM,+BAA+B,CAAC","sourcesContent":["export type { ILogger } from './shared/domain/logger.ts';\nexport {\n\tcreateAgent,\n\tcreateOrchestrator,\n\tagentModels,\n\tagentReasoningEfforts,\n\tisAgentProvider,\n\tisModelOf,\n\tisReasoningEffortOf,\n\ttype CreateAgentOptions,\n} from './factory/infrastructure/agentFactory.ts';\nexport {\n\tcreateFileTools,\n\tcreateLLMAgent,\n\tcreateLLMOrchestrator,\n\tisLLMModelOf,\n\tisLLMReasoningEffortOf,\n\tllmModels,\n\tllmReasoningEfforts,\n\ttype CreateLLMAgentOptions,\n\ttype CreateLLMOrchestratorOptions,\n\ttype RoleSteps,\n\ttype RoleTokens,\n} from './factory/infrastructure/agentLLMFactory.ts';\nexport type { FileTool, WorkspaceOptions } from './tools/infrastructure/fileTools.ts';\nexport type { WorkspaceRoot } from './tools/domain/accessPolicy.ts';\nexport type {\n\tCommandArgument,\n\tCommandDefinition,\n\tCommandParameter,\n\tCommandRole,\n\tCommands,\n} from './tools/domain/commands.ts';\nexport type { SecretRules } from './tools/infrastructure/rootsAccessPolicy.ts';\nexport {\n\tagentProviders,\n\ttype AgentModel,\n\ttype AgentProvider,\n\ttype CreateOrchestratorOptions,\n\ttype ReasoningEffort,\n} from './factory/infrastructure/types.ts';\nexport {\n\ttype Agent,\n\ttype AgentResponse,\n\ttype Callback,\n\ttype ProgressAgent,\n\ttype ProgressEvent,\n\ttype StepContext,\n\ttype RunOptions,\n} from './agent/domain/agent.ts';\nexport {\n\tInvalidAgentConfigError,\n\tRecoverableError,\n\tUnrecoverableError,\n} from './shared/domain/errors.ts';\nexport { isAbortError } from './shared/domain/isAbortError.ts';\nexport type { Tokens } from './shared/domain/tokens.ts';\nexport {\n\trememberApprovals,\n\ttype ApprovalDecision,\n\ttype ApprovalRequest,\n\ttype Approver,\n\ttype CommandLine,\n\ttype RememberableDecision,\n\ttype ToolRisk,\n} from './agent/domain/approval.ts';\nexport type { Asker, QuestionOption, UserQuestion } from './agent/domain/question.ts';\nexport type { JSONSchema, ToolDefinition } from './llm/domain/tool.ts';\nexport type { Tool, ToolSummarizer } from './tools/domain/tool.ts';\nexport type { TextMatch, Workspace } from './tools/domain/workspace.ts';\nexport { defineTool } from './tools/infrastructure/defineTool.ts';\nexport { createWorkspace } from './tools/infrastructure/createWorkspace.ts';\nexport { createWorkspaceTools } from './tools/infrastructure/workspaceTools.ts';\nexport { createHistory } from './recovery/infrastructure/createHistory.ts';\nexport type { History, HistoryRun, Move } from './recovery/domain/history.ts';\nexport {\n\thistoryStart,\n\tNothingToMoveError,\n\tWorkspaceBusyError,\n\tWorkspaceMovedError,\n\ttype MoveOptions,\n\ttype RunStatus,\n} from './recovery/domain/recoveryStore.ts';\nexport { HistoryExpiredError, UnknownRunError } from './recovery/domain/runTree.ts';\nexport { createWorkflow } from './factory/infrastructure/workflowFactory.ts';\nexport {\n\tdefineWorkflowStages,\n\ttype CreateWorkflowOptions,\n\ttype Workflow,\n\ttype WorkflowResult,\n\ttype WorkflowStage,\n\ttype WorkflowStageDefinition,\n\ttype WorkflowStageName,\n\ttype WorkflowStageReport,\n\ttype WorkflowTransition,\n} from './workflow/domain/workflow.ts';\nexport type { Validator } from './shared/domain/validator.ts';\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Agent, AgentResponse, RunOptions } from '#src/agent/domain/agent';
|
|
2
2
|
import type { ReviewerDecision } from './reviewerDecision.ts';
|
|
3
|
-
import type { Validator } from '
|
|
3
|
+
import type { Validator } from '#src/shared/domain/validator';
|
|
4
4
|
import type { ILogger } from '#src/shared/domain/logger';
|
|
5
5
|
export declare const plannerInstructions = "You are the planner agent. Read the relevant repository code and create a concise, engineering-grade plan for the executor. Cover the requested behaviour, structural issues, affected files or symbols, API contracts and boundaries, important failure paths, and meaningful tests. Keep the plan focused on the current request and do not require an architecture redesign yet. Communicate tersely and professionally. In reasoning updates, state only the next concrete action or a verified finding, in one short sentence. Do not narrate routine intentions, uncertainty, self-reflection, encouragement, or a desire to be thorough. Do not restate tool results. Once you decide what to do, act immediately. Use only the detail required by your role in the final response.";
|
|
6
6
|
export declare const executorInstructions = "You are the executor agent. Read the relevant repository code and implement the original user request according to the planner's current plan. Preserve contracts and boundaries, handle important failure paths, keep the change focused, and add or update meaningful tests. Inspect and change the code; do not merely describe what should be done. Communicate tersely and professionally. In reasoning updates, state only the next concrete action or a verified finding, in one short sentence. Do not narrate routine intentions, uncertainty, self-reflection, encouragement, or a desire to be thorough. Do not restate tool results. Once you decide what to do, act immediately. Use only the detail required by your role in the final response. Before you finish, run the project's tests and checks, and finish only when they pass. Once they pass and the plan is carried out, finish at once: do not add work beyond the plan, because the reviewer asks for what is missing. If you cannot make them pass, finish by saying which ones fail and why.";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestratorAgent.js","sourceRoot":"","sources":["../../../../src/orchestration/domain/model/orchestratorAgent.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tAgent,\n\tAgentResponse,\n\tProgressAgent,\n\tProgressEvent,\n\tRunOptions,\n} from '#src/agent/domain/agent';\nimport { addTokens, type Tokens } from '#src/shared/domain/tokens';\nimport { RecoverableError, UnrecoverableError, withSpentTokens } from '#src/shared/domain/errors';\nimport type { ReviewerDecision } from './reviewerDecision.ts';\nimport type { Validator } from '../interface/validator.ts';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport {\n\tclassifyHostFailure,\n\tdescribeFailure,\n\ttreatErrors,\n} from '#src/shared/domain/providerFailure';\nimport {\n\tRunContext,\n\ttype RunEnd,\n\trunContextOf,\n\twithRunContext,\n} from '#src/agent/domain/runContext';\nimport { isAbortError } from '#src/shared/domain/isAbortError';\n\n// Each role's standing instructions, kept apart from the data of a round so an agent with a\n// system prompt can hold them there instead of receiving them again in every prompt. An agent\n// without one gets them at the head of each prompt; see `InstructedAgent`.\nconst conciseCommunication =\n\t'Communicate tersely and professionally. In reasoning updates, state only the next concrete action or a verified finding, in one short sentence. Do not narrate routine intentions, uncertainty, self-reflection, encouragement, or a desire to be thorough. Do not restate tool results. Once you decide what to do, act immediately. Use only the detail required by your role in the final response.';\n\nexport const plannerInstructions = `You are the planner agent. Read the relevant repository code and create a concise, engineering-grade plan for the executor. Cover the requested behaviour, structural issues, affected files or symbols, API contracts and boundaries, important failure paths, and meaningful tests. Keep the plan focused on the current request and do not require an architecture redesign yet. ${conciseCommunication}`;\n\nconst executorTask = `You are the executor agent. Read the relevant repository code and implement the original user request according to the planner's current plan. Preserve contracts and boundaries, handle important failure paths, keep the change focused, and add or update meaningful tests. Inspect and change the code; do not merely describe what should be done. ${conciseCommunication}`;\n\n// For an executor that can run the project's tests and checks: an Agent SDK one, which has a\n// shell, or a model-API one given commands. The harness cannot tell which command runs the\n// tests, so any command counts.\nexport const executorInstructions = `${executorTask} Before you finish, run the project's tests and checks, and finish only when they pass. Once they pass and the plan is carried out, finish at once: do not add work beyond the plan, because the reviewer asks for what is missing. If you cannot make them pass, finish by saying which ones fail and why.`;\n\n// For a model-API executor with no command, which cannot run anything: told so, it neither\n// spends its steps looking for a way to test nor claims a result it could not have.\nexport const executorInstructionsWithoutCommands = `${executorTask} You have no command that runs the project's tests or checks, so you cannot run them. Once the plan is carried out, finish at once: do not add work beyond the plan, because the reviewer asks for what is missing. Say in your summary that the tests and checks were not run.`;\n\nconst reviewerTask = `Review the code. Independently inspect the repository, the current diff, and relevant surrounding code; do not rely on the executor's narrative. Judge it against the original request, and any issue it names, first: check that the plan is right for it, and that the change fulfills it. Check correctness and logic errors, important failure paths, unused or artificial abstractions, races or shared state, API contract breaks, boundary violations, regressions, scope, and test quality. If it is correct, respond with JSON only: {\"decision\":\"approved\"}. If it is not correct, respond with JSON only: {\"decision\":\"rejected\",\"feedback\":\"list concrete, prioritized findings and the required direction for each\"}. The feedback must contain actionable engineering findings, not a general summary. ${conciseCommunication}`;\n\n// Each goes with the executor instructions of the same name.\nexport const reviewerInstructions = `You are the reviewer agent. The executor has already run the tests and checks, and finished only when they passed: do not run them again. ${reviewerTask}`;\n\nexport const reviewerInstructionsWithoutCommands = `You are the reviewer agent. Nobody has run the tests or checks: the executor had no command for them. Judge from the code whether the tests it added or changed would pass, and do not approve a change whose tests could not. ${reviewerTask}`;\n\nconst getPlannerPrompt = (userPrompt: string, previousFailureReason?: string) => {\n\tconst feedback = previousFailureReason\n\t\t? `\\n\\nFeedback from the previous attempt:\\n---\\n${previousFailureReason}\\n---`\n\t\t: '';\n\n\treturn `Original user request:\n---\n${userPrompt}\n---${feedback}`;\n};\n\nconst getExecutorPrompt = (userPrompt: string, plannerPrompt: string) =>\n\t`Original user request:\n---\n${userPrompt}\n---\n\nCurrent implementation plan:\n---\n${plannerPrompt}\n---`;\n\nconst getReviewerPrompt = (\n\tuserPrompt: string,\n\tplannerPrompt: string,\n\texecutorResult: string,\n\tparseFailureReason?: string,\n) => {\n\tconst retryNotice = parseFailureReason\n\t\t? `\\n\\nYour previous response could not be used: ${parseFailureReason} Respond with JSON only, matching the schema exactly, with no surrounding text and no markdown code fences.`\n\t\t: '';\n\n\treturn `Original user request:\n---\n${userPrompt}\n---\n\nPlanner's current plan:\n---\n${plannerPrompt}\n---\n\nExecutor response (context only; verify it independently):\n---\n${executorResult}\n---${retryNotice}`;\n};\n\ninterface RunTotals {\n\ttokens: Tokens | undefined;\n\t/** A role answered without usage, so the run's total cannot be known. */\n\tunreported: boolean;\n}\n\n// Every role's response counts, even an empty one that starts another round: it was billed.\nfunction addToTotals(totals: RunTotals, response: AgentResponse): void {\n\tif (!response.tokens) totals.unreported = true;\n\ttotals.tokens = addTokens(totals.tokens, response.tokens);\n}\n\n/** Unknown rather than partial: a sum missing a billed call must not look complete. */\nfunction runTokens(totals: RunTotals): Tokens | undefined {\n\treturn totals.unreported ? undefined : totals.tokens;\n}\n\nfunction stripCodeFence(text: string): string {\n\tconst match = /^```(?:json)?\\s*([\\s\\S]*?)\\s*```$/.exec(text.trim());\n\treturn match?.[1] ?? text;\n}\n\nfunction parseReviewerDecision(\n\tresponse: AgentResponse,\n\treviewerDecisionValidator: Validator<ReviewerDecision>,\n): ReviewerDecision {\n\tif (!response.response) {\n\t\tthrow new RecoverableError(\"There's no decision from the reviewer, something went wrong\", {\n\t\t\tcause: 'Reviewer decision is missing.',\n\t\t});\n\t}\n\n\tlet parsedResponse: unknown;\n\ttry {\n\t\tparsedResponse = JSON.parse(stripCodeFence(response.response));\n\t} catch {\n\t\tthrow new RecoverableError('Reviewer returned an invalid decision', {\n\t\t\tcause: 'Reviewer response must be valid JSON.',\n\t\t});\n\t}\n\n\tconst decision = reviewerDecisionValidator.validate(parsedResponse);\n\tif (!decision) {\n\t\tthrow new RecoverableError('Reviewer returned an invalid decision', {\n\t\t\tcause: 'Reviewer response did not match the decision schema.',\n\t\t});\n\t}\n\n\treturn decision;\n}\n\nexport class OrchestratorAgent implements Agent {\n\tprivate plannerAgent: Agent;\n\tprivate executorAgent: Agent;\n\tprivate reviewerAgent: Agent;\n\tprivate reviewerDecisionValidator: Validator<ReviewerDecision>;\n\tprivate maxAttempts: number;\n\tprivate logger: ILogger;\n\n\tconstructor({\n\t\tplannerAgent,\n\t\texecutorAgent,\n\t\treviewerAgent,\n\t\treviewerDecisionValidator,\n\t\tmaxAttempts = 3,\n\t\tlogger,\n\t}: {\n\t\tplannerAgent: Agent;\n\t\texecutorAgent: Agent;\n\t\treviewerAgent: Agent;\n\t\treviewerDecisionValidator: Validator<ReviewerDecision>;\n\t\tmaxAttempts?: number;\n\t\tlogger: ILogger;\n\t}) {\n\t\tif (!Number.isInteger(maxAttempts) || maxAttempts < 1) {\n\t\t\tthrow new RangeError('maxAttempts must be a positive integer');\n\t\t}\n\n\t\tthis.plannerAgent = plannerAgent;\n\t\tthis.executorAgent = executorAgent;\n\t\tthis.reviewerAgent = reviewerAgent;\n\t\tthis.reviewerDecisionValidator = reviewerDecisionValidator;\n\t\tthis.maxAttempts = maxAttempts;\n\t\tthis.logger = logger;\n\t}\n\n\t/**\n\t * Every role, round and retry of one run shares one run context, so what the executor writes\n\t * is one run of the workspace's history, and the planner's and reviewer's turns are tied to\n\t * it. The orchestrator ends that context with the run, unless an outer agent owns it.\n\t */\n\tasync run(prompt: string, options: RunOptions): Promise<AgentResponse> {\n\t\tconst start = Date.now();\n\t\t// Per invocation, not per instance: the CLI keeps one orchestrator for a whole session.\n\t\tconst totals: RunTotals = { tokens: undefined, unreported: false };\n\t\tconst inherited = runContextOf(options);\n\t\tconst context = inherited ?? new RunContext();\n\t\tlet end: RunEnd = 'failed';\n\n\t\ttry {\n\t\t\tawait this.runRounds(prompt, inherited ? options : withRunContext(options, context), totals);\n\t\t\tend = 'completed';\n\t\t} catch (error) {\n\t\t\tif (isAbortError(error)) end = 'cancelled';\n\t\t\t// A failing role carries its own tokens; the roles before it are in the totals.\n\t\t\tthrow withSpentTokens(error, totals.tokens, totals.unreported);\n\t\t} finally {\n\t\t\tif (!inherited) await this.finishContext(context, end);\n\t\t}\n\n\t\t// Read once the run ended: a run that changed nothing left the history then.\n\t\tconst { runId } = context.historyRun;\n\t\treturn {\n\t\t\tresponse: 'All job has finished',\n\t\t\ttokens: runTokens(totals),\n\t\t\t// The whole run's wall clock, so it matches what the consumer waited.\n\t\t\tduration: (Date.now() - start) / 1000,\n\t\t\t...(!inherited && runId ? { runId } : {}),\n\t\t};\n\t}\n\n\t/** The run's records end here, but its result stands: what it changed is already on disk. */\n\tprivate async finishContext(context: RunContext, end: RunEnd): Promise<void> {\n\t\ttry {\n\t\t\tawait context.finish(end);\n\t\t} catch (error) {\n\t\t\tthis.warn(`The run could not close its records: ${describeFailure(error)}`);\n\t\t}\n\t}\n\n\tprivate async runRounds(prompt: string, options: RunOptions, totals: RunTotals): Promise<void> {\n\t\tlet lastFailureReason: string | undefined;\n\n\t\tfor (let attempt = 1; attempt <= this.maxAttempts; attempt++) {\n\t\t\tconst isLastAttempt = attempt === this.maxAttempts;\n\n\t\t\tconst plannerResponse = await this.plannerAgent.run(\n\t\t\t\tgetPlannerPrompt(prompt, lastFailureReason),\n\t\t\t\tthis.optionsForRole(options, 'planner'),\n\t\t\t);\n\t\t\taddToTotals(totals, plannerResponse);\n\n\t\t\tif (!plannerResponse.response) {\n\t\t\t\tlastFailureReason = 'The planner produced no response.';\n\t\t\t\tif (isLastAttempt) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t\t}\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the planner produced no response; starting another round`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst executorResponse = await this.executorAgent.run(\n\t\t\t\tgetExecutorPrompt(prompt, plannerResponse.response),\n\t\t\t\tthis.optionsForRole(options, 'executor'),\n\t\t\t);\n\t\t\taddToTotals(totals, executorResponse);\n\n\t\t\tif (!executorResponse.response) {\n\t\t\t\tlastFailureReason = 'The executor produced no response.';\n\t\t\t\tif (isLastAttempt) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t\t}\n\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the executor produced no response; starting another round`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst reviewerDecision = await this.getReviewerDecision(\n\t\t\t\tprompt,\n\t\t\t\tplannerResponse.response,\n\t\t\t\texecutorResponse.response,\n\t\t\t\toptions,\n\t\t\t\ttotals,\n\t\t\t);\n\n\t\t\tif (reviewerDecision.decision === 'approved') return;\n\n\t\t\tlastFailureReason = reviewerDecision.feedback;\n\t\t\tif (isLastAttempt) {\n\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t}\n\n\t\t\tthis.warn(\n\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the reviewer rejected the round; starting another with its feedback`,\n\t\t\t\tlastFailureReason,\n\t\t\t);\n\t\t}\n\n\t\tthrow new UnrecoverableError('Max attempts exhausted', {\n\t\t\tcause: lastFailureReason ?? 'Unknown failure.',\n\t\t});\n\t}\n\n\t// Retries only the reviewer call on a malformed decision, instead of redoing\n\t// planning/execution — a bad or unparsable reviewer response is not evidence\n\t// the plan or the implementation were wrong.\n\tprivate async getReviewerDecision(\n\t\tuserPrompt: string,\n\t\tplannerPrompt: string,\n\t\texecutorResult: string,\n\t\toptions: RunOptions,\n\t\ttotals: RunTotals,\n\t): Promise<ReviewerDecision> {\n\t\tlet parseFailureReason: string | undefined;\n\n\t\tfor (let attempt = 1; attempt <= this.maxAttempts; attempt++) {\n\t\t\tconst reviewerResponse = await this.reviewerAgent.run(\n\t\t\t\tgetReviewerPrompt(userPrompt, plannerPrompt, executorResult, parseFailureReason),\n\t\t\t\tthis.optionsForRole(options, 'reviewer'),\n\t\t\t);\n\n\t\t\taddToTotals(totals, reviewerResponse);\n\n\t\t\ttry {\n\t\t\t\treturn parseReviewerDecision(reviewerResponse, this.reviewerDecisionValidator);\n\t\t\t} catch (error) {\n\t\t\t\tif (!(error instanceof RecoverableError)) throw error;\n\n\t\t\t\tparseFailureReason = error.cause;\n\t\t\t\tif (attempt === this.maxAttempts) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: parseFailureReason });\n\t\t\t\t}\n\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Reviewer decision ${String(attempt)}/${String(this.maxAttempts)} was unusable; asking the reviewer again`,\n\t\t\t\t\tparseFailureReason,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tthrow new UnrecoverableError('Max attempts exhausted', {\n\t\t\tcause: parseFailureReason ?? 'Unknown failure.',\n\t\t});\n\t}\n\n\tprivate warn(...args: unknown[]): void {\n\t\ttreatErrors(\n\t\t\t() => {\n\t\t\t\tthis.logger.warn(...args);\n\t\t\t},\n\t\t\tclassifyHostFailure,\n\t\t\t'Orchestrator logger failed while reporting a retry',\n\t\t);\n\t}\n\n\t/**\n\t * Keep the options object extensible and the private run context intact while making the\n\t * orchestrator's attribution authoritative over anything a nested agent supplied.\n\t */\n\tprivate optionsForRole(options: RunOptions, agent: ProgressAgent): RunOptions {\n\t\tconst onProgress = options.onProgress;\n\t\treturn {\n\t\t\t...options,\n\t\t\tonProgress: onProgress\n\t\t\t\t? (event: ProgressEvent) => {\n\t\t\t\t\t\ttreatErrors(\n\t\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\t\tonProgress({ ...event, agent });\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tclassifyHostFailure,\n\t\t\t\t\t\t\t'Orchestrator progress callback failed',\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t: undefined,\n\t\t};\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"orchestratorAgent.js","sourceRoot":"","sources":["../../../../src/orchestration/domain/model/orchestratorAgent.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,SAAS,EAAe,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAIlG,OAAO,EACN,mBAAmB,EACnB,eAAe,EACf,WAAW,GACX,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACN,UAAU,EAEV,YAAY,EACZ,cAAc,GACd,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAE/D,4FAA4F;AAC5F,8FAA8F;AAC9F,2EAA2E;AAC3E,MAAM,oBAAoB,GACzB,wYAAwY,CAAC;AAE1Y,MAAM,CAAC,MAAM,mBAAmB,GAAG,uXAAuX,oBAAoB,EAAE,CAAC;AAEjb,MAAM,YAAY,GAAG,2VAA2V,oBAAoB,EAAE,CAAC;AAEvY,6FAA6F;AAC7F,2FAA2F;AAC3F,gCAAgC;AAChC,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,YAAY,6SAA6S,CAAC;AAEjW,2FAA2F;AAC3F,oFAAoF;AACpF,MAAM,CAAC,MAAM,mCAAmC,GAAG,GAAG,YAAY,iRAAiR,CAAC;AAEpV,MAAM,YAAY,GAAG,wxBAAwxB,oBAAoB,EAAE,CAAC;AAEp0B,6DAA6D;AAC7D,MAAM,CAAC,MAAM,oBAAoB,GAAG,6IAA6I,YAAY,EAAE,CAAC;AAEhM,MAAM,CAAC,MAAM,mCAAmC,GAAG,kOAAkO,YAAY,EAAE,CAAC;AAEpS,MAAM,gBAAgB,GAAG,CAAC,UAAkB,EAAE,qBAA8B,EAAE,EAAE;IAC/E,MAAM,QAAQ,GAAG,qBAAqB;QACrC,CAAC,CAAC,iDAAiD,qBAAqB,OAAO;QAC/E,CAAC,CAAC,EAAE,CAAC;IAEN,OAAO;;EAEN,UAAU;KACP,QAAQ,EAAE,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAG,CAAC,UAAkB,EAAE,aAAqB,EAAE,EAAE,CACvE;;EAEC,UAAU;;;;;EAKV,aAAa;IACX,CAAC;AAEL,MAAM,iBAAiB,GAAG,CACzB,UAAkB,EAClB,aAAqB,EACrB,cAAsB,EACtB,kBAA2B,EAC1B,EAAE;IACH,MAAM,WAAW,GAAG,kBAAkB;QACrC,CAAC,CAAC,iDAAiD,kBAAkB,6GAA6G;QAClL,CAAC,CAAC,EAAE,CAAC;IAEN,OAAO;;EAEN,UAAU;;;;;EAKV,aAAa;;;;;EAKb,cAAc;KACX,WAAW,EAAE,CAAC;AACnB,CAAC,CAAC;AAQF,4FAA4F;AAC5F,SAAS,WAAW,CAAC,MAAiB,EAAE,QAAuB;IAC9D,IAAI,CAAC,QAAQ,CAAC,MAAM;QAAE,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC;IAC/C,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC3D,CAAC;AAED,uFAAuF;AACvF,SAAS,SAAS,CAAC,MAAiB;IACnC,OAAO,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;AACtD,CAAC;AAED,SAAS,cAAc,CAAC,IAAY;IACnC,MAAM,KAAK,GAAG,mCAAmC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IACpE,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;AAC3B,CAAC;AAED,SAAS,qBAAqB,CAC7B,QAAuB,EACvB,yBAAsD;IAEtD,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACxB,MAAM,IAAI,gBAAgB,CAAC,6DAA6D,EAAE;YACzF,KAAK,EAAE,+BAA+B;SACtC,CAAC,CAAC;IACJ,CAAC;IAED,IAAI,cAAuB,CAAC;IAC5B,IAAI,CAAC;QACJ,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACR,MAAM,IAAI,gBAAgB,CAAC,uCAAuC,EAAE;YACnE,KAAK,EAAE,uCAAuC;SAC9C,CAAC,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,yBAAyB,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;IACpE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACf,MAAM,IAAI,gBAAgB,CAAC,uCAAuC,EAAE;YACnE,KAAK,EAAE,sDAAsD;SAC7D,CAAC,CAAC;IACJ,CAAC;IAED,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED,MAAM,OAAO,iBAAiB;IACrB,YAAY,CAAQ;IACpB,aAAa,CAAQ;IACrB,aAAa,CAAQ;IACrB,yBAAyB,CAA8B;IACvD,WAAW,CAAS;IACpB,MAAM,CAAU;IAExB,YAAY,EACX,YAAY,EACZ,aAAa,EACb,aAAa,EACb,yBAAyB,EACzB,WAAW,GAAG,CAAC,EACf,MAAM,GAQN;QACA,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;YACvD,MAAM,IAAI,UAAU,CAAC,wCAAwC,CAAC,CAAC;QAChE,CAAC;QAED,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,yBAAyB,GAAG,yBAAyB,CAAC;QAC3D,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACtB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,GAAG,CAAC,MAAc,EAAE,OAAmB;QAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,wFAAwF;QACxF,MAAM,MAAM,GAAc,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;QACnE,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;QACxC,MAAM,OAAO,GAAG,SAAS,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9C,IAAI,GAAG,GAAW,QAAQ,CAAC;QAE3B,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;YAC7F,GAAG,GAAG,WAAW,CAAC;QACnB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,YAAY,CAAC,KAAK,CAAC;gBAAE,GAAG,GAAG,WAAW,CAAC;YAC3C,gFAAgF;YAChF,MAAM,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;QAChE,CAAC;gBAAS,CAAC;YACV,IAAI,CAAC,SAAS;gBAAE,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QACxD,CAAC;QAED,6EAA6E;QAC7E,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC;QACrC,OAAO;YACN,QAAQ,EAAE,sBAAsB;YAChC,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC;YACzB,sEAAsE;YACtE,QAAQ,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,IAAI;YACrC,GAAG,CAAC,CAAC,SAAS,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACzC,CAAC;IACH,CAAC;IAED,6FAA6F;IACrF,KAAK,CAAC,aAAa,CAAC,OAAmB,EAAE,GAAW;QAC3D,IAAI,CAAC;YACJ,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,IAAI,CAAC,wCAAwC,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC7E,CAAC;IACF,CAAC;IAEO,KAAK,CAAC,SAAS,CAAC,MAAc,EAAE,OAAmB,EAAE,MAAiB;QAC7E,IAAI,iBAAqC,CAAC;QAE1C,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,IAAI,CAAC,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;YAC9D,MAAM,aAAa,GAAG,OAAO,KAAK,IAAI,CAAC,WAAW,CAAC;YAEnD,MAAM,eAAe,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,CAClD,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,CAAC,EAC3C,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,SAAS,CAAC,CACvC,CAAC;YACF,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;YAErC,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,CAAC;gBAC/B,iBAAiB,GAAG,mCAAmC,CAAC;gBACxD,IAAI,aAAa,EAAE,CAAC;oBACnB,MAAM,IAAI,kBAAkB,CAAC,wBAAwB,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;gBACtF,CAAC;gBACD,IAAI,CAAC,IAAI,CACR,WAAW,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,4DAA4D,CAClH,CAAC;gBACF,SAAS;YACV,CAAC;YAED,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,GAAG,CACpD,iBAAiB,CAAC,MAAM,EAAE,eAAe,CAAC,QAAQ,CAAC,EACnD,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,UAAU,CAAC,CACxC,CAAC;YACF,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;YAEtC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC;gBAChC,iBAAiB,GAAG,oCAAoC,CAAC;gBACzD,IAAI,aAAa,EAAE,CAAC;oBACnB,MAAM,IAAI,kBAAkB,CAAC,wBAAwB,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;gBACtF,CAAC;gBAED,IAAI,CAAC,IAAI,CACR,WAAW,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,6DAA6D,CACnH,CAAC;gBACF,SAAS;YACV,CAAC;YAED,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,mBAAmB,CACtD,MAAM,EACN,eAAe,CAAC,QAAQ,EACxB,gBAAgB,CAAC,QAAQ,EACzB,OAAO,EACP,MAAM,CACN,CAAC;YAEF,IAAI,gBAAgB,CAAC,QAAQ,KAAK,UAAU;gBAAE,OAAO;YAErD,iBAAiB,GAAG,gBAAgB,CAAC,QAAQ,CAAC;YAC9C,IAAI,aAAa,EAAE,CAAC;gBACnB,MAAM,IAAI,kBAAkB,CAAC,wBAAwB,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;YACtF,CAAC;YAED,IAAI,CAAC,IAAI,CACR,WAAW,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,uEAAuE,EAC7H,iBAAiB,CACjB,CAAC;QACH,CAAC;QAED,MAAM,IAAI,kBAAkB,CAAC,wBAAwB,EAAE;YACtD,KAAK,EAAE,iBAAiB,IAAI,kBAAkB;SAC9C,CAAC,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,6EAA6E;IAC7E,6CAA6C;IACrC,KAAK,CAAC,mBAAmB,CAChC,UAAkB,EAClB,aAAqB,EACrB,cAAsB,EACtB,OAAmB,EACnB,MAAiB;QAEjB,IAAI,kBAAsC,CAAC;QAE3C,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,IAAI,CAAC,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;YAC9D,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,GAAG,CACpD,iBAAiB,CAAC,UAAU,EAAE,aAAa,EAAE,cAAc,EAAE,kBAAkB,CAAC,EAChF,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,UAAU,CAAC,CACxC,CAAC;YAEF,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;YAEtC,IAAI,CAAC;gBACJ,OAAO,qBAAqB,CAAC,gBAAgB,EAAE,IAAI,CAAC,yBAAyB,CAAC,CAAC;YAChF,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,IAAI,CAAC,CAAC,KAAK,YAAY,gBAAgB,CAAC;oBAAE,MAAM,KAAK,CAAC;gBAEtD,kBAAkB,GAAG,KAAK,CAAC,KAAK,CAAC;gBACjC,IAAI,OAAO,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC;oBAClC,MAAM,IAAI,kBAAkB,CAAC,wBAAwB,EAAE,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;gBACvF,CAAC;gBAED,IAAI,CAAC,IAAI,CACR,qBAAqB,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,0CAA0C,EAC1G,kBAAkB,CAClB,CAAC;YACH,CAAC;QACF,CAAC;QAED,MAAM,IAAI,kBAAkB,CAAC,wBAAwB,EAAE;YACtD,KAAK,EAAE,kBAAkB,IAAI,kBAAkB;SAC/C,CAAC,CAAC;IACJ,CAAC;IAEO,IAAI,CAAC,GAAG,IAAe;QAC9B,WAAW,CACV,GAAG,EAAE;YACJ,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;QAC3B,CAAC,EACD,mBAAmB,EACnB,oDAAoD,CACpD,CAAC;IACH,CAAC;IAED;;;OAGG;IACK,cAAc,CAAC,OAAmB,EAAE,KAAoB;QAC/D,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACtC,OAAO;YACN,GAAG,OAAO;YACV,UAAU,EAAE,UAAU;gBACrB,CAAC,CAAC,CAAC,KAAoB,EAAE,EAAE;oBACzB,WAAW,CACV,GAAG,EAAE;wBACJ,UAAU,CAAC,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;oBACjC,CAAC,EACD,mBAAmB,EACnB,uCAAuC,CACvC,CAAC;gBACH,CAAC;gBACF,CAAC,CAAC,SAAS;SACZ,CAAC;IACH,CAAC;CACD","sourcesContent":["import type {\n\tAgent,\n\tAgentResponse,\n\tProgressAgent,\n\tProgressEvent,\n\tRunOptions,\n} from '#src/agent/domain/agent';\nimport { addTokens, type Tokens } from '#src/shared/domain/tokens';\nimport { RecoverableError, UnrecoverableError, withSpentTokens } from '#src/shared/domain/errors';\nimport type { ReviewerDecision } from './reviewerDecision.ts';\nimport type { Validator } from '#src/shared/domain/validator';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport {\n\tclassifyHostFailure,\n\tdescribeFailure,\n\ttreatErrors,\n} from '#src/shared/domain/providerFailure';\nimport {\n\tRunContext,\n\ttype RunEnd,\n\trunContextOf,\n\twithRunContext,\n} from '#src/agent/domain/runContext';\nimport { isAbortError } from '#src/shared/domain/isAbortError';\n\n// Each role's standing instructions, kept apart from the data of a round so an agent with a\n// system prompt can hold them there instead of receiving them again in every prompt. An agent\n// without one gets them at the head of each prompt; see `InstructedAgent`.\nconst conciseCommunication =\n\t'Communicate tersely and professionally. In reasoning updates, state only the next concrete action or a verified finding, in one short sentence. Do not narrate routine intentions, uncertainty, self-reflection, encouragement, or a desire to be thorough. Do not restate tool results. Once you decide what to do, act immediately. Use only the detail required by your role in the final response.';\n\nexport const plannerInstructions = `You are the planner agent. Read the relevant repository code and create a concise, engineering-grade plan for the executor. Cover the requested behaviour, structural issues, affected files or symbols, API contracts and boundaries, important failure paths, and meaningful tests. Keep the plan focused on the current request and do not require an architecture redesign yet. ${conciseCommunication}`;\n\nconst executorTask = `You are the executor agent. Read the relevant repository code and implement the original user request according to the planner's current plan. Preserve contracts and boundaries, handle important failure paths, keep the change focused, and add or update meaningful tests. Inspect and change the code; do not merely describe what should be done. ${conciseCommunication}`;\n\n// For an executor that can run the project's tests and checks: an Agent SDK one, which has a\n// shell, or a model-API one given commands. The harness cannot tell which command runs the\n// tests, so any command counts.\nexport const executorInstructions = `${executorTask} Before you finish, run the project's tests and checks, and finish only when they pass. Once they pass and the plan is carried out, finish at once: do not add work beyond the plan, because the reviewer asks for what is missing. If you cannot make them pass, finish by saying which ones fail and why.`;\n\n// For a model-API executor with no command, which cannot run anything: told so, it neither\n// spends its steps looking for a way to test nor claims a result it could not have.\nexport const executorInstructionsWithoutCommands = `${executorTask} You have no command that runs the project's tests or checks, so you cannot run them. Once the plan is carried out, finish at once: do not add work beyond the plan, because the reviewer asks for what is missing. Say in your summary that the tests and checks were not run.`;\n\nconst reviewerTask = `Review the code. Independently inspect the repository, the current diff, and relevant surrounding code; do not rely on the executor's narrative. Judge it against the original request, and any issue it names, first: check that the plan is right for it, and that the change fulfills it. Check correctness and logic errors, important failure paths, unused or artificial abstractions, races or shared state, API contract breaks, boundary violations, regressions, scope, and test quality. If it is correct, respond with JSON only: {\"decision\":\"approved\"}. If it is not correct, respond with JSON only: {\"decision\":\"rejected\",\"feedback\":\"list concrete, prioritized findings and the required direction for each\"}. The feedback must contain actionable engineering findings, not a general summary. ${conciseCommunication}`;\n\n// Each goes with the executor instructions of the same name.\nexport const reviewerInstructions = `You are the reviewer agent. The executor has already run the tests and checks, and finished only when they passed: do not run them again. ${reviewerTask}`;\n\nexport const reviewerInstructionsWithoutCommands = `You are the reviewer agent. Nobody has run the tests or checks: the executor had no command for them. Judge from the code whether the tests it added or changed would pass, and do not approve a change whose tests could not. ${reviewerTask}`;\n\nconst getPlannerPrompt = (userPrompt: string, previousFailureReason?: string) => {\n\tconst feedback = previousFailureReason\n\t\t? `\\n\\nFeedback from the previous attempt:\\n---\\n${previousFailureReason}\\n---`\n\t\t: '';\n\n\treturn `Original user request:\n---\n${userPrompt}\n---${feedback}`;\n};\n\nconst getExecutorPrompt = (userPrompt: string, plannerPrompt: string) =>\n\t`Original user request:\n---\n${userPrompt}\n---\n\nCurrent implementation plan:\n---\n${plannerPrompt}\n---`;\n\nconst getReviewerPrompt = (\n\tuserPrompt: string,\n\tplannerPrompt: string,\n\texecutorResult: string,\n\tparseFailureReason?: string,\n) => {\n\tconst retryNotice = parseFailureReason\n\t\t? `\\n\\nYour previous response could not be used: ${parseFailureReason} Respond with JSON only, matching the schema exactly, with no surrounding text and no markdown code fences.`\n\t\t: '';\n\n\treturn `Original user request:\n---\n${userPrompt}\n---\n\nPlanner's current plan:\n---\n${plannerPrompt}\n---\n\nExecutor response (context only; verify it independently):\n---\n${executorResult}\n---${retryNotice}`;\n};\n\ninterface RunTotals {\n\ttokens: Tokens | undefined;\n\t/** A role answered without usage, so the run's total cannot be known. */\n\tunreported: boolean;\n}\n\n// Every role's response counts, even an empty one that starts another round: it was billed.\nfunction addToTotals(totals: RunTotals, response: AgentResponse): void {\n\tif (!response.tokens) totals.unreported = true;\n\ttotals.tokens = addTokens(totals.tokens, response.tokens);\n}\n\n/** Unknown rather than partial: a sum missing a billed call must not look complete. */\nfunction runTokens(totals: RunTotals): Tokens | undefined {\n\treturn totals.unreported ? undefined : totals.tokens;\n}\n\nfunction stripCodeFence(text: string): string {\n\tconst match = /^```(?:json)?\\s*([\\s\\S]*?)\\s*```$/.exec(text.trim());\n\treturn match?.[1] ?? text;\n}\n\nfunction parseReviewerDecision(\n\tresponse: AgentResponse,\n\treviewerDecisionValidator: Validator<ReviewerDecision>,\n): ReviewerDecision {\n\tif (!response.response) {\n\t\tthrow new RecoverableError(\"There's no decision from the reviewer, something went wrong\", {\n\t\t\tcause: 'Reviewer decision is missing.',\n\t\t});\n\t}\n\n\tlet parsedResponse: unknown;\n\ttry {\n\t\tparsedResponse = JSON.parse(stripCodeFence(response.response));\n\t} catch {\n\t\tthrow new RecoverableError('Reviewer returned an invalid decision', {\n\t\t\tcause: 'Reviewer response must be valid JSON.',\n\t\t});\n\t}\n\n\tconst decision = reviewerDecisionValidator.validate(parsedResponse);\n\tif (!decision) {\n\t\tthrow new RecoverableError('Reviewer returned an invalid decision', {\n\t\t\tcause: 'Reviewer response did not match the decision schema.',\n\t\t});\n\t}\n\n\treturn decision;\n}\n\nexport class OrchestratorAgent implements Agent {\n\tprivate plannerAgent: Agent;\n\tprivate executorAgent: Agent;\n\tprivate reviewerAgent: Agent;\n\tprivate reviewerDecisionValidator: Validator<ReviewerDecision>;\n\tprivate maxAttempts: number;\n\tprivate logger: ILogger;\n\n\tconstructor({\n\t\tplannerAgent,\n\t\texecutorAgent,\n\t\treviewerAgent,\n\t\treviewerDecisionValidator,\n\t\tmaxAttempts = 3,\n\t\tlogger,\n\t}: {\n\t\tplannerAgent: Agent;\n\t\texecutorAgent: Agent;\n\t\treviewerAgent: Agent;\n\t\treviewerDecisionValidator: Validator<ReviewerDecision>;\n\t\tmaxAttempts?: number;\n\t\tlogger: ILogger;\n\t}) {\n\t\tif (!Number.isInteger(maxAttempts) || maxAttempts < 1) {\n\t\t\tthrow new RangeError('maxAttempts must be a positive integer');\n\t\t}\n\n\t\tthis.plannerAgent = plannerAgent;\n\t\tthis.executorAgent = executorAgent;\n\t\tthis.reviewerAgent = reviewerAgent;\n\t\tthis.reviewerDecisionValidator = reviewerDecisionValidator;\n\t\tthis.maxAttempts = maxAttempts;\n\t\tthis.logger = logger;\n\t}\n\n\t/**\n\t * Every role, round and retry of one run shares one run context, so what the executor writes\n\t * is one run of the workspace's history, and the planner's and reviewer's turns are tied to\n\t * it. The orchestrator ends that context with the run, unless an outer agent owns it.\n\t */\n\tasync run(prompt: string, options: RunOptions): Promise<AgentResponse> {\n\t\tconst start = Date.now();\n\t\t// Per invocation, not per instance: the CLI keeps one orchestrator for a whole session.\n\t\tconst totals: RunTotals = { tokens: undefined, unreported: false };\n\t\tconst inherited = runContextOf(options);\n\t\tconst context = inherited ?? new RunContext();\n\t\tlet end: RunEnd = 'failed';\n\n\t\ttry {\n\t\t\tawait this.runRounds(prompt, inherited ? options : withRunContext(options, context), totals);\n\t\t\tend = 'completed';\n\t\t} catch (error) {\n\t\t\tif (isAbortError(error)) end = 'cancelled';\n\t\t\t// A failing role carries its own tokens; the roles before it are in the totals.\n\t\t\tthrow withSpentTokens(error, totals.tokens, totals.unreported);\n\t\t} finally {\n\t\t\tif (!inherited) await this.finishContext(context, end);\n\t\t}\n\n\t\t// Read once the run ended: a run that changed nothing left the history then.\n\t\tconst { runId } = context.historyRun;\n\t\treturn {\n\t\t\tresponse: 'All job has finished',\n\t\t\ttokens: runTokens(totals),\n\t\t\t// The whole run's wall clock, so it matches what the consumer waited.\n\t\t\tduration: (Date.now() - start) / 1000,\n\t\t\t...(!inherited && runId ? { runId } : {}),\n\t\t};\n\t}\n\n\t/** The run's records end here, but its result stands: what it changed is already on disk. */\n\tprivate async finishContext(context: RunContext, end: RunEnd): Promise<void> {\n\t\ttry {\n\t\t\tawait context.finish(end);\n\t\t} catch (error) {\n\t\t\tthis.warn(`The run could not close its records: ${describeFailure(error)}`);\n\t\t}\n\t}\n\n\tprivate async runRounds(prompt: string, options: RunOptions, totals: RunTotals): Promise<void> {\n\t\tlet lastFailureReason: string | undefined;\n\n\t\tfor (let attempt = 1; attempt <= this.maxAttempts; attempt++) {\n\t\t\tconst isLastAttempt = attempt === this.maxAttempts;\n\n\t\t\tconst plannerResponse = await this.plannerAgent.run(\n\t\t\t\tgetPlannerPrompt(prompt, lastFailureReason),\n\t\t\t\tthis.optionsForRole(options, 'planner'),\n\t\t\t);\n\t\t\taddToTotals(totals, plannerResponse);\n\n\t\t\tif (!plannerResponse.response) {\n\t\t\t\tlastFailureReason = 'The planner produced no response.';\n\t\t\t\tif (isLastAttempt) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t\t}\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the planner produced no response; starting another round`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst executorResponse = await this.executorAgent.run(\n\t\t\t\tgetExecutorPrompt(prompt, plannerResponse.response),\n\t\t\t\tthis.optionsForRole(options, 'executor'),\n\t\t\t);\n\t\t\taddToTotals(totals, executorResponse);\n\n\t\t\tif (!executorResponse.response) {\n\t\t\t\tlastFailureReason = 'The executor produced no response.';\n\t\t\t\tif (isLastAttempt) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t\t}\n\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the executor produced no response; starting another round`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst reviewerDecision = await this.getReviewerDecision(\n\t\t\t\tprompt,\n\t\t\t\tplannerResponse.response,\n\t\t\t\texecutorResponse.response,\n\t\t\t\toptions,\n\t\t\t\ttotals,\n\t\t\t);\n\n\t\t\tif (reviewerDecision.decision === 'approved') return;\n\n\t\t\tlastFailureReason = reviewerDecision.feedback;\n\t\t\tif (isLastAttempt) {\n\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t}\n\n\t\t\tthis.warn(\n\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the reviewer rejected the round; starting another with its feedback`,\n\t\t\t\tlastFailureReason,\n\t\t\t);\n\t\t}\n\n\t\tthrow new UnrecoverableError('Max attempts exhausted', {\n\t\t\tcause: lastFailureReason ?? 'Unknown failure.',\n\t\t});\n\t}\n\n\t// Retries only the reviewer call on a malformed decision, instead of redoing\n\t// planning/execution — a bad or unparsable reviewer response is not evidence\n\t// the plan or the implementation were wrong.\n\tprivate async getReviewerDecision(\n\t\tuserPrompt: string,\n\t\tplannerPrompt: string,\n\t\texecutorResult: string,\n\t\toptions: RunOptions,\n\t\ttotals: RunTotals,\n\t): Promise<ReviewerDecision> {\n\t\tlet parseFailureReason: string | undefined;\n\n\t\tfor (let attempt = 1; attempt <= this.maxAttempts; attempt++) {\n\t\t\tconst reviewerResponse = await this.reviewerAgent.run(\n\t\t\t\tgetReviewerPrompt(userPrompt, plannerPrompt, executorResult, parseFailureReason),\n\t\t\t\tthis.optionsForRole(options, 'reviewer'),\n\t\t\t);\n\n\t\t\taddToTotals(totals, reviewerResponse);\n\n\t\t\ttry {\n\t\t\t\treturn parseReviewerDecision(reviewerResponse, this.reviewerDecisionValidator);\n\t\t\t} catch (error) {\n\t\t\t\tif (!(error instanceof RecoverableError)) throw error;\n\n\t\t\t\tparseFailureReason = error.cause;\n\t\t\t\tif (attempt === this.maxAttempts) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: parseFailureReason });\n\t\t\t\t}\n\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Reviewer decision ${String(attempt)}/${String(this.maxAttempts)} was unusable; asking the reviewer again`,\n\t\t\t\t\tparseFailureReason,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tthrow new UnrecoverableError('Max attempts exhausted', {\n\t\t\tcause: parseFailureReason ?? 'Unknown failure.',\n\t\t});\n\t}\n\n\tprivate warn(...args: unknown[]): void {\n\t\ttreatErrors(\n\t\t\t() => {\n\t\t\t\tthis.logger.warn(...args);\n\t\t\t},\n\t\t\tclassifyHostFailure,\n\t\t\t'Orchestrator logger failed while reporting a retry',\n\t\t);\n\t}\n\n\t/**\n\t * Keep the options object extensible and the private run context intact while making the\n\t * orchestrator's attribution authoritative over anything a nested agent supplied.\n\t */\n\tprivate optionsForRole(options: RunOptions, agent: ProgressAgent): RunOptions {\n\t\tconst onProgress = options.onProgress;\n\t\treturn {\n\t\t\t...options,\n\t\t\tonProgress: onProgress\n\t\t\t\t? (event: ProgressEvent) => {\n\t\t\t\t\t\ttreatErrors(\n\t\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\t\tonProgress({ ...event, agent });\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tclassifyHostFailure,\n\t\t\t\t\t\t\t'Orchestrator progress callback failed',\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t: undefined,\n\t\t};\n\t}\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Validator } from '
|
|
1
|
+
import type { Validator } from '#src/shared/domain/validator';
|
|
2
2
|
import type { ReviewerDecision } from '../../domain/model/reviewerDecision.ts';
|
|
3
3
|
export declare class ReviewerDecisionValidator implements Validator<ReviewerDecision> {
|
|
4
4
|
validate(input: unknown): ReviewerDecision | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reviewerDecisionValidator.js","sourceRoot":"","sources":["../../../../src/orchestration/infrastructure/model/reviewerDecisionValidator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,sBAAsB,GAAG,CAAC,CAAC,kBAAkB,CAAC,UAAU,EAAE;IAC/D,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;IAC7C,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;CACjF,CAAC,CAAC;AAEH,MAAM,OAAO,yBAAyB;IACrC,QAAQ,CAAC,KAAc;QACtB,MAAM,MAAM,GAAG,sBAAsB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACvD,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACjD,CAAC;CACD","sourcesContent":["import { z } from 'zod';\nimport type { Validator } from '
|
|
1
|
+
{"version":3,"file":"reviewerDecisionValidator.js","sourceRoot":"","sources":["../../../../src/orchestration/infrastructure/model/reviewerDecisionValidator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,sBAAsB,GAAG,CAAC,CAAC,kBAAkB,CAAC,UAAU,EAAE;IAC/D,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;IAC7C,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;CACjF,CAAC,CAAC;AAEH,MAAM,OAAO,yBAAyB;IACrC,QAAQ,CAAC,KAAc;QACtB,MAAM,MAAM,GAAG,sBAAsB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACvD,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACjD,CAAC;CACD","sourcesContent":["import { z } from 'zod';\nimport type { Validator } from '#src/shared/domain/validator';\nimport type { ReviewerDecision } from '../../domain/model/reviewerDecision.ts';\n\nconst reviewerDecisionSchema = z.discriminatedUnion('decision', [\n\tz.object({ decision: z.literal('approved') }),\n\tz.object({ decision: z.literal('rejected'), feedback: z.string().trim().min(1) }),\n]);\n\nexport class ReviewerDecisionValidator implements Validator<ReviewerDecision> {\n\tvalidate(input: unknown): ReviewerDecision | undefined {\n\t\tconst result = reviewerDecisionSchema.safeParse(input);\n\t\treturn result.success ? result.data : undefined;\n\t}\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
/** A deliberate stop: an `AbortError`, or the `TimeoutError` that `AbortSignal.timeout` aborts with. */
|
|
1
2
|
export function isAbortError(e) {
|
|
2
|
-
return e instanceof Error && e.name === 'AbortError';
|
|
3
|
+
return e instanceof Error && (e.name === 'AbortError' || e.name === 'TimeoutError');
|
|
3
4
|
}
|
|
4
5
|
//# sourceMappingURL=isAbortError.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"isAbortError.js","sourceRoot":"","sources":["../../../src/shared/domain/isAbortError.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,YAAY,CAAC,CAAU;IACtC,OAAO,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,IAAI,KAAK,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"isAbortError.js","sourceRoot":"","sources":["../../../src/shared/domain/isAbortError.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,MAAM,UAAU,YAAY,CAAC,CAAU;IACtC,OAAO,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,YAAY,IAAI,CAAC,CAAC,IAAI,KAAK,cAAc,CAAC,CAAC;AACrF,CAAC","sourcesContent":["/** A deliberate stop: an `AbortError`, or the `TimeoutError` that `AbortSignal.timeout` aborts with. */\nexport function isAbortError(e: unknown): e is Error {\n\treturn e instanceof Error && (e.name === 'AbortError' || e.name === 'TimeoutError');\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.js","sourceRoot":"","sources":["../../../src/shared/domain/validator.ts"],"names":[],"mappings":"","sourcesContent":["/** Turns a model's parsed answer into the typed value a caller needs, or `undefined` when it does not fit. */\nexport interface Validator<T> {\n\tvalidate(input: unknown): T | undefined;\n}\n"]}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { Agent, RunOptions } from '#src/agent/domain/agent';
|
|
2
|
+
import type { ILogger } from '#src/shared/domain/logger';
|
|
3
|
+
import type { Tokens } from '#src/shared/domain/tokens';
|
|
4
|
+
import type { Validator } from '#src/shared/domain/validator';
|
|
5
|
+
/** The names of a workflow's stages: the keys of its map of what each stage receives. */
|
|
6
|
+
export type WorkflowStageName<Inputs> = keyof Inputs & string;
|
|
7
|
+
/**
|
|
8
|
+
* What a stage chose once its answer was valid. `go` runs the stage it names, which receives
|
|
9
|
+
* `input` and nothing else; `stop` ends the run early on purpose and `complete` ends it finished,
|
|
10
|
+
* each with the run's result. `Inputs` maps each stage's name to what it receives, so a `go` that
|
|
11
|
+
* names a stage that does not exist, or hands it the wrong input, does not compile.
|
|
12
|
+
*/
|
|
13
|
+
export type WorkflowTransition<Inputs, Result> = {
|
|
14
|
+
[Name in WorkflowStageName<Inputs>]: {
|
|
15
|
+
type: 'go';
|
|
16
|
+
stage: Name;
|
|
17
|
+
input: Inputs[Name];
|
|
18
|
+
};
|
|
19
|
+
}[WorkflowStageName<Inputs>] | {
|
|
20
|
+
type: 'stop';
|
|
21
|
+
result: Result;
|
|
22
|
+
} | {
|
|
23
|
+
type: 'complete';
|
|
24
|
+
result: Result;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* One step of a workflow: how it creates its agent, the prompt that agent gets from the stage's
|
|
28
|
+
* input, how its JSON answer is validated, and the transition the valid answer selects.
|
|
29
|
+
*/
|
|
30
|
+
export interface WorkflowStageDefinition<Inputs, Name extends WorkflowStageName<Inputs>, Output, Result> {
|
|
31
|
+
/**
|
|
32
|
+
* Creates the stage's agent, with a context of its own, the first time the stage runs in a
|
|
33
|
+
* run. When the run comes back to the stage, the same agent goes on with its conversation;
|
|
34
|
+
* the next run creates another. It may be async, as `createLLMOrchestrator` is.
|
|
35
|
+
*/
|
|
36
|
+
agent(): Agent | PromiseLike<Agent>;
|
|
37
|
+
prompt(input: Inputs[Name]): string | PromiseLike<string>;
|
|
38
|
+
/** Receives the agent's answer parsed as JSON. */
|
|
39
|
+
readonly validator: Validator<Output>;
|
|
40
|
+
/** Receives the validated answer and the stage's own input, to carry forward what the next stage needs. */
|
|
41
|
+
transition(output: Output, input: Inputs[Name]): WorkflowTransition<Inputs, Result> | PromiseLike<WorkflowTransition<Inputs, Result>>;
|
|
42
|
+
/** How many answers the stage may give before an invalid one fails the run. */
|
|
43
|
+
readonly maxAttempts?: number;
|
|
44
|
+
}
|
|
45
|
+
/** A stage bound to its name, as `defineWorkflowStages` returns it. */
|
|
46
|
+
export interface WorkflowStage<Inputs, Result, Name extends WorkflowStageName<Inputs> = WorkflowStageName<Inputs>> extends WorkflowStageDefinition<Inputs, Name, unknown, Result> {
|
|
47
|
+
readonly name: Name;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Binds stage definitions to one workflow's map of stage inputs and its result. The function it
|
|
51
|
+
* returns infers each stage's output from its validator.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* const stage = defineWorkflowStages<{ triage: string; draft: Brief }, string>();
|
|
55
|
+
* const triage = stage('triage', { agent, prompt, validator, transition });
|
|
56
|
+
*/
|
|
57
|
+
export declare function defineWorkflowStages<Inputs, Result>(): <Name extends WorkflowStageName<Inputs>, Output>(name: Name, definition: WorkflowStageDefinition<Inputs, Name, Output, Result>) => WorkflowStage<Inputs, Result, Name>;
|
|
58
|
+
export interface WorkflowStageReport {
|
|
59
|
+
stage: string;
|
|
60
|
+
/** What the stage received. */
|
|
61
|
+
input: unknown;
|
|
62
|
+
/** The stage's validated answer. */
|
|
63
|
+
output: unknown;
|
|
64
|
+
/** How many answers the stage's agent gave, the last one valid. */
|
|
65
|
+
attempts: number;
|
|
66
|
+
/** Seconds. */
|
|
67
|
+
duration: number;
|
|
68
|
+
/** Absent when a provider did not report an answer's usage. */
|
|
69
|
+
tokens?: Tokens;
|
|
70
|
+
transition: 'go' | 'stop' | 'complete';
|
|
71
|
+
/** The stage that ran next, absent after `stop` or `complete`. */
|
|
72
|
+
nextStage?: string;
|
|
73
|
+
}
|
|
74
|
+
export interface WorkflowResult<Result> {
|
|
75
|
+
/** `stopped` is a deliberate early end, not a failure: a failure rejects the run. */
|
|
76
|
+
status: 'completed' | 'stopped';
|
|
77
|
+
result: Result;
|
|
78
|
+
/** Every stage's usage. Absent when any answer's usage was not reported. */
|
|
79
|
+
tokens?: Tokens;
|
|
80
|
+
/** Seconds. */
|
|
81
|
+
duration: number;
|
|
82
|
+
runId?: string;
|
|
83
|
+
/** One report per stage run, in order: what each stage received, answered and chose. */
|
|
84
|
+
stages: readonly WorkflowStageReport[];
|
|
85
|
+
}
|
|
86
|
+
export interface Workflow<Input, Result> {
|
|
87
|
+
run(input: Input, options: RunOptions): Promise<WorkflowResult<Result>>;
|
|
88
|
+
}
|
|
89
|
+
export interface CreateWorkflowOptions<Inputs, Result, Initial extends WorkflowStageName<Inputs> = WorkflowStageName<Inputs>> {
|
|
90
|
+
initialStage: Initial;
|
|
91
|
+
/** Every stage, under its own name. */
|
|
92
|
+
stages: {
|
|
93
|
+
readonly [Name in WorkflowStageName<Inputs>]: WorkflowStage<Inputs, Result, Name>;
|
|
94
|
+
} & Readonly<Record<string, WorkflowStage<Inputs, Result>>>;
|
|
95
|
+
/** The default for a stage without its own. Defaults to 3. */
|
|
96
|
+
maxAttempts?: number;
|
|
97
|
+
/** How many times a run may `go` to a stage, so a loop of stages ends. Defaults to 100. */
|
|
98
|
+
maxTransitions?: number;
|
|
99
|
+
logger?: ILogger;
|
|
100
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binds stage definitions to one workflow's map of stage inputs and its result. The function it
|
|
3
|
+
* returns infers each stage's output from its validator.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* const stage = defineWorkflowStages<{ triage: string; draft: Brief }, string>();
|
|
7
|
+
* const triage = stage('triage', { agent, prompt, validator, transition });
|
|
8
|
+
*/
|
|
9
|
+
export function defineWorkflowStages() {
|
|
10
|
+
return (name, definition) => ({ ...definition, name });
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=workflow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow.js","sourceRoot":"","sources":["../../../src/workflow/domain/workflow.ts"],"names":[],"mappings":"AA0DA;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB;IAOnC,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;AACxD,CAAC","sourcesContent":["import type { Agent, RunOptions } from '#src/agent/domain/agent';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport type { Tokens } from '#src/shared/domain/tokens';\nimport type { Validator } from '#src/shared/domain/validator';\n\n/** The names of a workflow's stages: the keys of its map of what each stage receives. */\nexport type WorkflowStageName<Inputs> = keyof Inputs & string;\n\n/**\n * What a stage chose once its answer was valid. `go` runs the stage it names, which receives\n * `input` and nothing else; `stop` ends the run early on purpose and `complete` ends it finished,\n * each with the run's result. `Inputs` maps each stage's name to what it receives, so a `go` that\n * names a stage that does not exist, or hands it the wrong input, does not compile.\n */\nexport type WorkflowTransition<Inputs, Result> =\n\t| {\n\t\t\t[Name in WorkflowStageName<Inputs>]: { type: 'go'; stage: Name; input: Inputs[Name] };\n\t }[WorkflowStageName<Inputs>]\n\t| { type: 'stop'; result: Result }\n\t| { type: 'complete'; result: Result };\n\n/**\n * One step of a workflow: how it creates its agent, the prompt that agent gets from the stage's\n * input, how its JSON answer is validated, and the transition the valid answer selects.\n */\nexport interface WorkflowStageDefinition<\n\tInputs,\n\tName extends WorkflowStageName<Inputs>,\n\tOutput,\n\tResult,\n> {\n\t/**\n\t * Creates the stage's agent, with a context of its own, the first time the stage runs in a\n\t * run. When the run comes back to the stage, the same agent goes on with its conversation;\n\t * the next run creates another. It may be async, as `createLLMOrchestrator` is.\n\t */\n\tagent(): Agent | PromiseLike<Agent>;\n\tprompt(input: Inputs[Name]): string | PromiseLike<string>;\n\t/** Receives the agent's answer parsed as JSON. */\n\treadonly validator: Validator<Output>;\n\t/** Receives the validated answer and the stage's own input, to carry forward what the next stage needs. */\n\ttransition(\n\t\toutput: Output,\n\t\tinput: Inputs[Name],\n\t): WorkflowTransition<Inputs, Result> | PromiseLike<WorkflowTransition<Inputs, Result>>;\n\t/** How many answers the stage may give before an invalid one fails the run. */\n\treadonly maxAttempts?: number;\n}\n\n/** A stage bound to its name, as `defineWorkflowStages` returns it. */\nexport interface WorkflowStage<\n\tInputs,\n\tResult,\n\tName extends WorkflowStageName<Inputs> = WorkflowStageName<Inputs>,\n> extends WorkflowStageDefinition<Inputs, Name, unknown, Result> {\n\treadonly name: Name;\n}\n\n/**\n * Binds stage definitions to one workflow's map of stage inputs and its result. The function it\n * returns infers each stage's output from its validator.\n *\n * @example\n * const stage = defineWorkflowStages<{ triage: string; draft: Brief }, string>();\n * const triage = stage('triage', { agent, prompt, validator, transition });\n */\nexport function defineWorkflowStages<Inputs, Result>(): <\n\tName extends WorkflowStageName<Inputs>,\n\tOutput,\n>(\n\tname: Name,\n\tdefinition: WorkflowStageDefinition<Inputs, Name, Output, Result>,\n) => WorkflowStage<Inputs, Result, Name> {\n\treturn (name, definition) => ({ ...definition, name });\n}\n\nexport interface WorkflowStageReport {\n\tstage: string;\n\t/** What the stage received. */\n\tinput: unknown;\n\t/** The stage's validated answer. */\n\toutput: unknown;\n\t/** How many answers the stage's agent gave, the last one valid. */\n\tattempts: number;\n\t/** Seconds. */\n\tduration: number;\n\t/** Absent when a provider did not report an answer's usage. */\n\ttokens?: Tokens;\n\ttransition: 'go' | 'stop' | 'complete';\n\t/** The stage that ran next, absent after `stop` or `complete`. */\n\tnextStage?: string;\n}\n\nexport interface WorkflowResult<Result> {\n\t/** `stopped` is a deliberate early end, not a failure: a failure rejects the run. */\n\tstatus: 'completed' | 'stopped';\n\tresult: Result;\n\t/** Every stage's usage. Absent when any answer's usage was not reported. */\n\ttokens?: Tokens;\n\t/** Seconds. */\n\tduration: number;\n\trunId?: string;\n\t/** One report per stage run, in order: what each stage received, answered and chose. */\n\tstages: readonly WorkflowStageReport[];\n}\n\nexport interface Workflow<Input, Result> {\n\trun(input: Input, options: RunOptions): Promise<WorkflowResult<Result>>;\n}\n\nexport interface CreateWorkflowOptions<\n\tInputs,\n\tResult,\n\tInitial extends WorkflowStageName<Inputs> = WorkflowStageName<Inputs>,\n> {\n\tinitialStage: Initial;\n\t/** Every stage, under its own name. */\n\tstages: {\n\t\treadonly [Name in WorkflowStageName<Inputs>]: WorkflowStage<Inputs, Result, Name>;\n\t} & Readonly<Record<string, WorkflowStage<Inputs, Result>>>;\n\t/** The default for a stage without its own. Defaults to 3. */\n\tmaxAttempts?: number;\n\t/** How many times a run may `go` to a stage, so a loop of stages ends. Defaults to 100. */\n\tmaxTransitions?: number;\n\tlogger?: ILogger;\n}\n"]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Agent, RunOptions } from '#src/agent/domain/agent';
|
|
2
|
+
import type { ILogger } from '#src/shared/domain/logger';
|
|
3
|
+
import type { Validator } from '#src/shared/domain/validator';
|
|
4
|
+
import type { Workflow, WorkflowResult } from './workflow.ts';
|
|
5
|
+
/** A stage with its types erased: the factory checked them, and the runner only passes values on. */
|
|
6
|
+
export interface RunnableStage {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
agent(): Agent | PromiseLike<Agent>;
|
|
9
|
+
prompt(input: unknown): string | PromiseLike<string>;
|
|
10
|
+
readonly validator: Validator<unknown>;
|
|
11
|
+
transition(output: unknown, input: unknown): unknown;
|
|
12
|
+
readonly maxAttempts?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Runs the stages the factory already checked: one stage at a time, each answer validated before
|
|
16
|
+
* its transition is taken, and only the transition's `input` reaching the next stage. Each stage
|
|
17
|
+
* creates its agent once per run, so a run never sees another run's conversations.
|
|
18
|
+
*/
|
|
19
|
+
export declare class WorkflowRunner<Input, Result> implements Workflow<Input, Result> {
|
|
20
|
+
private readonly stages;
|
|
21
|
+
private readonly initialStage;
|
|
22
|
+
private readonly maxAttempts;
|
|
23
|
+
private readonly maxTransitions;
|
|
24
|
+
private readonly logger;
|
|
25
|
+
constructor(options: {
|
|
26
|
+
initialStage: string;
|
|
27
|
+
stages: ReadonlyMap<string, RunnableStage>;
|
|
28
|
+
maxAttempts: number;
|
|
29
|
+
maxTransitions: number;
|
|
30
|
+
logger: ILogger;
|
|
31
|
+
});
|
|
32
|
+
/**
|
|
33
|
+
* Every stage of one run shares one run context, so what their agents write is one run of the
|
|
34
|
+
* workspace's history. The workflow ends that context with the run, unless an outer agent owns it.
|
|
35
|
+
*/
|
|
36
|
+
run(input: Input, options: RunOptions): Promise<WorkflowResult<Result>>;
|
|
37
|
+
/** `agents` holds the agent each stage created in this run, so a stage the run comes back to goes on with it. */
|
|
38
|
+
private runStages;
|
|
39
|
+
/** The stage's agent in this run: created the first time the stage runs, then reused. */
|
|
40
|
+
private agentOf;
|
|
41
|
+
/**
|
|
42
|
+
* Asks the stage's agent until an answer is valid JSON its validator accepts, or attempts run
|
|
43
|
+
* out. Each answer's usage goes to the run's and the stage's totals; a failure carries the
|
|
44
|
+
* run's when `run` rethrows it.
|
|
45
|
+
*/
|
|
46
|
+
private answer;
|
|
47
|
+
private runAgent;
|
|
48
|
+
private transition;
|
|
49
|
+
private stageNamed;
|
|
50
|
+
/** The run's records end here, but its result stands: what it changed is already on disk. */
|
|
51
|
+
private finishContext;
|
|
52
|
+
private emit;
|
|
53
|
+
private warn;
|
|
54
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { RunContext, runContextOf, withRunContext, } from '#src/agent/domain/runContext';
|
|
2
|
+
import { RecoverableError, UnrecoverableError, withSpentTokens } from '#src/shared/domain/errors';
|
|
3
|
+
import { isAbortError } from '#src/shared/domain/isAbortError';
|
|
4
|
+
import { classifyHostFailure, describeFailure, treatErrors, } from '#src/shared/domain/providerFailure';
|
|
5
|
+
import { addTokens } from '#src/shared/domain/tokens';
|
|
6
|
+
function addUsage(totals, response) {
|
|
7
|
+
if (response.tokens === undefined)
|
|
8
|
+
totals.unreported = true;
|
|
9
|
+
totals.tokens = addTokens(totals.tokens, response.tokens);
|
|
10
|
+
}
|
|
11
|
+
function knownTokens(totals) {
|
|
12
|
+
return totals.unreported ? undefined : totals.tokens;
|
|
13
|
+
}
|
|
14
|
+
function stripCodeFence(text) {
|
|
15
|
+
const match = /^```(?:json)?\s*([\s\S]*?)\s*```$/i.exec(text.trim());
|
|
16
|
+
return match?.[1] ?? text;
|
|
17
|
+
}
|
|
18
|
+
function parseJSON(text) {
|
|
19
|
+
try {
|
|
20
|
+
return { value: JSON.parse(stripCodeFence(text)) };
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function retryNotice(reason) {
|
|
27
|
+
return `\n\nYour previous response could not be used: ${reason} Respond with JSON only, matching the schema exactly, with no surrounding text and no markdown code fences.`;
|
|
28
|
+
}
|
|
29
|
+
function has(record, field) {
|
|
30
|
+
return Object.prototype.hasOwnProperty.call(record, field);
|
|
31
|
+
}
|
|
32
|
+
/** The types only bind TypeScript callers; a JavaScript callback can return anything. */
|
|
33
|
+
function isTransition(value) {
|
|
34
|
+
if (typeof value !== 'object' || value === null || !('type' in value))
|
|
35
|
+
return false;
|
|
36
|
+
switch (value.type) {
|
|
37
|
+
case 'go':
|
|
38
|
+
return 'stage' in value && typeof value.stage === 'string' && has(value, 'input');
|
|
39
|
+
case 'stop':
|
|
40
|
+
case 'complete':
|
|
41
|
+
return has(value, 'result');
|
|
42
|
+
default:
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Runs the stages the factory already checked: one stage at a time, each answer validated before
|
|
48
|
+
* its transition is taken, and only the transition's `input` reaching the next stage. Each stage
|
|
49
|
+
* creates its agent once per run, so a run never sees another run's conversations.
|
|
50
|
+
*/
|
|
51
|
+
export class WorkflowRunner {
|
|
52
|
+
stages;
|
|
53
|
+
initialStage;
|
|
54
|
+
maxAttempts;
|
|
55
|
+
maxTransitions;
|
|
56
|
+
logger;
|
|
57
|
+
constructor(options) {
|
|
58
|
+
this.initialStage = options.initialStage;
|
|
59
|
+
this.stages = options.stages;
|
|
60
|
+
this.maxAttempts = options.maxAttempts;
|
|
61
|
+
this.maxTransitions = options.maxTransitions;
|
|
62
|
+
this.logger = options.logger;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Every stage of one run shares one run context, so what their agents write is one run of the
|
|
66
|
+
* workspace's history. The workflow ends that context with the run, unless an outer agent owns it.
|
|
67
|
+
*/
|
|
68
|
+
async run(input, options) {
|
|
69
|
+
const start = Date.now();
|
|
70
|
+
// Per invocation, not per instance: one workflow can serve several runs at once.
|
|
71
|
+
const totals = { tokens: undefined, unreported: false };
|
|
72
|
+
const stages = [];
|
|
73
|
+
const inherited = runContextOf(options);
|
|
74
|
+
const context = inherited ?? new RunContext();
|
|
75
|
+
let end = 'failed';
|
|
76
|
+
let outcome;
|
|
77
|
+
try {
|
|
78
|
+
outcome = await this.runStages(input, inherited ? options : withRunContext(options, context), totals, stages, new Map());
|
|
79
|
+
end = 'completed';
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (isAbortError(error) || options.signal.aborted)
|
|
83
|
+
end = 'cancelled';
|
|
84
|
+
// A failing stage carries its own tokens; the stages before it are in the totals.
|
|
85
|
+
throw withSpentTokens(error, totals.tokens, totals.unreported);
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
if (!inherited)
|
|
89
|
+
await this.finishContext(context, end);
|
|
90
|
+
}
|
|
91
|
+
// Read once the run ended: a run that changed nothing left the history then.
|
|
92
|
+
const { runId } = context.historyRun;
|
|
93
|
+
return {
|
|
94
|
+
...outcome,
|
|
95
|
+
tokens: knownTokens(totals),
|
|
96
|
+
duration: (Date.now() - start) / 1000,
|
|
97
|
+
stages,
|
|
98
|
+
...(!inherited && runId ? { runId } : {}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** `agents` holds the agent each stage created in this run, so a stage the run comes back to goes on with it. */
|
|
102
|
+
async runStages(input, options, totals, reports, agents) {
|
|
103
|
+
let stage = this.stageNamed(this.initialStage);
|
|
104
|
+
let stageInput = input;
|
|
105
|
+
for (let transitions = 0;; transitions++) {
|
|
106
|
+
const started = Date.now();
|
|
107
|
+
const stageTotals = { tokens: undefined, unreported: false };
|
|
108
|
+
const agent = await this.agentOf(stage, agents);
|
|
109
|
+
const { output, attempts } = await this.answer(stage, agent, stageInput, options, totals, stageTotals);
|
|
110
|
+
const transition = await this.transition(stage, output, stageInput);
|
|
111
|
+
options.signal.throwIfAborted();
|
|
112
|
+
const tokens = knownTokens(stageTotals);
|
|
113
|
+
const nextStage = transition.type === 'go' ? transition.stage : undefined;
|
|
114
|
+
reports.push({
|
|
115
|
+
stage: stage.name,
|
|
116
|
+
input: stageInput,
|
|
117
|
+
output,
|
|
118
|
+
attempts,
|
|
119
|
+
duration: (Date.now() - started) / 1000,
|
|
120
|
+
...(tokens && { tokens }),
|
|
121
|
+
transition: transition.type,
|
|
122
|
+
...(nextStage !== undefined && { nextStage }),
|
|
123
|
+
});
|
|
124
|
+
this.emit(options, {
|
|
125
|
+
type: 'workflowTransition',
|
|
126
|
+
stage: stage.name,
|
|
127
|
+
transition: transition.type,
|
|
128
|
+
...(nextStage !== undefined && { nextStage }),
|
|
129
|
+
});
|
|
130
|
+
if (transition.type !== 'go') {
|
|
131
|
+
const status = transition.type === 'complete' ? 'completed' : 'stopped';
|
|
132
|
+
return { status, result: transition.result };
|
|
133
|
+
}
|
|
134
|
+
if (transitions + 1 > this.maxTransitions) {
|
|
135
|
+
throw new UnrecoverableError('Workflow transition limit exhausted', {
|
|
136
|
+
cause: `The run went to another stage more than ${String(this.maxTransitions)} times.`,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
stage = this.stageNamed(transition.stage, stage.name);
|
|
140
|
+
stageInput = transition.input;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** The stage's agent in this run: created the first time the stage runs, then reused. */
|
|
144
|
+
async agentOf(stage, agents) {
|
|
145
|
+
const existing = agents.get(stage.name);
|
|
146
|
+
if (existing)
|
|
147
|
+
return existing;
|
|
148
|
+
let agent;
|
|
149
|
+
try {
|
|
150
|
+
agent = await stage.agent();
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
throw classifyHostFailure(error, `Workflow stage "${stage.name}" could not create its agent`);
|
|
154
|
+
}
|
|
155
|
+
agents.set(stage.name, agent);
|
|
156
|
+
return agent;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Asks the stage's agent until an answer is valid JSON its validator accepts, or attempts run
|
|
160
|
+
* out. Each answer's usage goes to the run's and the stage's totals; a failure carries the
|
|
161
|
+
* run's when `run` rethrows it.
|
|
162
|
+
*/
|
|
163
|
+
async answer(stage, agent, input, options, totals, stageTotals) {
|
|
164
|
+
const maxAttempts = stage.maxAttempts ?? this.maxAttempts;
|
|
165
|
+
options.signal.throwIfAborted();
|
|
166
|
+
let prompt;
|
|
167
|
+
try {
|
|
168
|
+
prompt = await stage.prompt(input);
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
throw classifyHostFailure(error, `Workflow stage "${stage.name}" could not build its prompt`);
|
|
172
|
+
}
|
|
173
|
+
let failureReason;
|
|
174
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
175
|
+
options.signal.throwIfAborted();
|
|
176
|
+
this.emit(options, { type: 'workflowStage', stage: stage.name, attempt });
|
|
177
|
+
const response = await this.runAgent(stage, agent, failureReason ? prompt + retryNotice(failureReason) : prompt, options);
|
|
178
|
+
addUsage(totals, response);
|
|
179
|
+
// An answer that arrives after a cancellation is not used.
|
|
180
|
+
options.signal.throwIfAborted();
|
|
181
|
+
addUsage(stageTotals, response);
|
|
182
|
+
const parsed = parseJSON(response.response);
|
|
183
|
+
if (parsed) {
|
|
184
|
+
let output;
|
|
185
|
+
try {
|
|
186
|
+
output = stage.validator.validate(parsed.value);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
throw classifyHostFailure(error, `Workflow stage "${stage.name}" could not validate its answer`);
|
|
190
|
+
}
|
|
191
|
+
if (output !== undefined)
|
|
192
|
+
return { output, attempts: attempt };
|
|
193
|
+
failureReason = 'it did not match the schema.';
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
failureReason = 'it was not valid JSON.';
|
|
197
|
+
}
|
|
198
|
+
if (attempt < maxAttempts) {
|
|
199
|
+
this.warn(`Workflow stage "${stage.name}" answer ${String(attempt)}/${String(maxAttempts)} was unusable (${failureReason}); asking again`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
throw new UnrecoverableError('Workflow stage attempts exhausted', {
|
|
203
|
+
cause: `Stage "${stage.name}" gave no usable answer in ${String(maxAttempts)} attempts: the last one ${failureReason ?? ''}`,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
async runAgent(stage, agent, prompt, options) {
|
|
207
|
+
try {
|
|
208
|
+
return await agent.run(prompt, options);
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
// Whatever the agent threw once the run was cancelled, the run ends as cancelled.
|
|
212
|
+
options.signal.throwIfAborted();
|
|
213
|
+
if (isAbortError(error))
|
|
214
|
+
throw error;
|
|
215
|
+
if (error instanceof RecoverableError || error instanceof UnrecoverableError)
|
|
216
|
+
throw error;
|
|
217
|
+
throw new UnrecoverableError(`Workflow stage "${stage.name}" failed`, {
|
|
218
|
+
cause: describeFailure(error),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async transition(stage, output, input) {
|
|
223
|
+
let transition;
|
|
224
|
+
try {
|
|
225
|
+
transition = await stage.transition(output, input);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
throw classifyHostFailure(error, `Workflow stage "${stage.name}" could not choose its transition`);
|
|
229
|
+
}
|
|
230
|
+
if (!isTransition(transition)) {
|
|
231
|
+
throw new UnrecoverableError('Workflow transition is invalid', {
|
|
232
|
+
cause: `Stage "${stage.name}" returned something that is not a transition.`,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
return transition;
|
|
236
|
+
}
|
|
237
|
+
stageNamed(name, from) {
|
|
238
|
+
const stage = this.stages.get(name);
|
|
239
|
+
if (!stage) {
|
|
240
|
+
throw new UnrecoverableError('Workflow stage does not exist', {
|
|
241
|
+
cause: from
|
|
242
|
+
? `Stage "${from}" selected unknown stage "${name}".`
|
|
243
|
+
: `The workflow has no stage "${name}".`,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
return stage;
|
|
247
|
+
}
|
|
248
|
+
/** The run's records end here, but its result stands: what it changed is already on disk. */
|
|
249
|
+
async finishContext(context, end) {
|
|
250
|
+
try {
|
|
251
|
+
await context.finish(end);
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
this.warn(`The run could not close its records: ${describeFailure(error)}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
emit(options, event) {
|
|
258
|
+
const { onProgress } = options;
|
|
259
|
+
if (!onProgress)
|
|
260
|
+
return;
|
|
261
|
+
treatErrors(() => {
|
|
262
|
+
onProgress(event);
|
|
263
|
+
}, classifyHostFailure, 'Workflow progress callback failed');
|
|
264
|
+
}
|
|
265
|
+
warn(message) {
|
|
266
|
+
treatErrors(() => {
|
|
267
|
+
this.logger.warn(message);
|
|
268
|
+
}, classifyHostFailure, 'Workflow logger failed while reporting a retry');
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
//# sourceMappingURL=workflowRunner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflowRunner.js","sourceRoot":"","sources":["../../../src/workflow/domain/workflowRunner.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Agent, AgentResponse, ProgressEvent, RunOptions } from '#src/agent/domain/agent';\nimport {\n\tRunContext,\n\trunContextOf,\n\twithRunContext,\n\ttype RunEnd,\n} from '#src/agent/domain/runContext';\nimport { RecoverableError, UnrecoverableError, withSpentTokens } from '#src/shared/domain/errors';\nimport { isAbortError } from '#src/shared/domain/isAbortError';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport {\n\tclassifyHostFailure,\n\tdescribeFailure,\n\ttreatErrors,\n} from '#src/shared/domain/providerFailure';\nimport { addTokens, type Tokens } from '#src/shared/domain/tokens';\nimport type { Validator } from '#src/shared/domain/validator';\nimport type { Workflow, WorkflowResult, WorkflowStageReport } from './workflow.ts';\n\n/** A stage with its types erased: the factory checked them, and the runner only passes values on. */\nexport interface RunnableStage {\n\treadonly name: string;\n\tagent(): Agent | PromiseLike<Agent>;\n\tprompt(input: unknown): string | PromiseLike<string>;\n\treadonly validator: Validator<unknown>;\n\ttransition(output: unknown, input: unknown): unknown;\n\treadonly maxAttempts?: number;\n}\n\ntype Transition<Result> =\n\t{ type: 'go'; stage: string; input: unknown } | { type: 'stop' | 'complete'; result: Result };\n\ninterface Totals {\n\ttokens: Tokens | undefined;\n\tunreported: boolean;\n}\n\nfunction addUsage(totals: Totals, response: AgentResponse): void {\n\tif (response.tokens === undefined) totals.unreported = true;\n\ttotals.tokens = addTokens(totals.tokens, response.tokens);\n}\n\nfunction knownTokens(totals: Totals): Tokens | undefined {\n\treturn totals.unreported ? undefined : totals.tokens;\n}\n\nfunction stripCodeFence(text: string): string {\n\tconst match = /^```(?:json)?\\s*([\\s\\S]*?)\\s*```$/i.exec(text.trim());\n\treturn match?.[1] ?? text;\n}\n\nfunction parseJSON(text: string): { value: unknown } | undefined {\n\ttry {\n\t\treturn { value: JSON.parse(stripCodeFence(text)) as unknown };\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction retryNotice(reason: string): string {\n\treturn `\\n\\nYour previous response could not be used: ${reason} Respond with JSON only, matching the schema exactly, with no surrounding text and no markdown code fences.`;\n}\n\nfunction has(record: object, field: string): boolean {\n\treturn Object.prototype.hasOwnProperty.call(record, field);\n}\n\n/** The types only bind TypeScript callers; a JavaScript callback can return anything. */\nfunction isTransition<Result>(value: unknown): value is Transition<Result> {\n\tif (typeof value !== 'object' || value === null || !('type' in value)) return false;\n\tswitch (value.type) {\n\t\tcase 'go':\n\t\t\treturn 'stage' in value && typeof value.stage === 'string' && has(value, 'input');\n\t\tcase 'stop':\n\t\tcase 'complete':\n\t\t\treturn has(value, 'result');\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\n/**\n * Runs the stages the factory already checked: one stage at a time, each answer validated before\n * its transition is taken, and only the transition's `input` reaching the next stage. Each stage\n * creates its agent once per run, so a run never sees another run's conversations.\n */\nexport class WorkflowRunner<Input, Result> implements Workflow<Input, Result> {\n\tprivate readonly stages: ReadonlyMap<string, RunnableStage>;\n\tprivate readonly initialStage: string;\n\tprivate readonly maxAttempts: number;\n\tprivate readonly maxTransitions: number;\n\tprivate readonly logger: ILogger;\n\n\tconstructor(options: {\n\t\tinitialStage: string;\n\t\tstages: ReadonlyMap<string, RunnableStage>;\n\t\tmaxAttempts: number;\n\t\tmaxTransitions: number;\n\t\tlogger: ILogger;\n\t}) {\n\t\tthis.initialStage = options.initialStage;\n\t\tthis.stages = options.stages;\n\t\tthis.maxAttempts = options.maxAttempts;\n\t\tthis.maxTransitions = options.maxTransitions;\n\t\tthis.logger = options.logger;\n\t}\n\n\t/**\n\t * Every stage of one run shares one run context, so what their agents write is one run of the\n\t * workspace's history. The workflow ends that context with the run, unless an outer agent owns it.\n\t */\n\tasync run(input: Input, options: RunOptions): Promise<WorkflowResult<Result>> {\n\t\tconst start = Date.now();\n\t\t// Per invocation, not per instance: one workflow can serve several runs at once.\n\t\tconst totals: Totals = { tokens: undefined, unreported: false };\n\t\tconst stages: WorkflowStageReport[] = [];\n\t\tconst inherited = runContextOf(options);\n\t\tconst context = inherited ?? new RunContext();\n\t\tlet end: RunEnd = 'failed';\n\t\tlet outcome: { status: 'completed' | 'stopped'; result: Result };\n\n\t\ttry {\n\t\t\toutcome = await this.runStages(\n\t\t\t\tinput,\n\t\t\t\tinherited ? options : withRunContext(options, context),\n\t\t\t\ttotals,\n\t\t\t\tstages,\n\t\t\t\tnew Map(),\n\t\t\t);\n\t\t\tend = 'completed';\n\t\t} catch (error) {\n\t\t\tif (isAbortError(error) || options.signal.aborted) end = 'cancelled';\n\t\t\t// A failing stage carries its own tokens; the stages before it are in the totals.\n\t\t\tthrow withSpentTokens(error, totals.tokens, totals.unreported);\n\t\t} finally {\n\t\t\tif (!inherited) await this.finishContext(context, end);\n\t\t}\n\n\t\t// Read once the run ended: a run that changed nothing left the history then.\n\t\tconst { runId } = context.historyRun;\n\t\treturn {\n\t\t\t...outcome,\n\t\t\ttokens: knownTokens(totals),\n\t\t\tduration: (Date.now() - start) / 1000,\n\t\t\tstages,\n\t\t\t...(!inherited && runId ? { runId } : {}),\n\t\t};\n\t}\n\n\t/** `agents` holds the agent each stage created in this run, so a stage the run comes back to goes on with it. */\n\tprivate async runStages(\n\t\tinput: Input,\n\t\toptions: RunOptions,\n\t\ttotals: Totals,\n\t\treports: WorkflowStageReport[],\n\t\tagents: Map<string, Agent>,\n\t): Promise<{ status: 'completed' | 'stopped'; result: Result }> {\n\t\tlet stage = this.stageNamed(this.initialStage);\n\t\tlet stageInput: unknown = input;\n\n\t\tfor (let transitions = 0; ; transitions++) {\n\t\t\tconst started = Date.now();\n\t\t\tconst stageTotals: Totals = { tokens: undefined, unreported: false };\n\t\t\tconst agent = await this.agentOf(stage, agents);\n\t\t\tconst { output, attempts } = await this.answer(\n\t\t\t\tstage,\n\t\t\t\tagent,\n\t\t\t\tstageInput,\n\t\t\t\toptions,\n\t\t\t\ttotals,\n\t\t\t\tstageTotals,\n\t\t\t);\n\t\t\tconst transition = await this.transition(stage, output, stageInput);\n\t\t\toptions.signal.throwIfAborted();\n\n\t\t\tconst tokens = knownTokens(stageTotals);\n\t\t\tconst nextStage = transition.type === 'go' ? transition.stage : undefined;\n\t\t\treports.push({\n\t\t\t\tstage: stage.name,\n\t\t\t\tinput: stageInput,\n\t\t\t\toutput,\n\t\t\t\tattempts,\n\t\t\t\tduration: (Date.now() - started) / 1000,\n\t\t\t\t...(tokens && { tokens }),\n\t\t\t\ttransition: transition.type,\n\t\t\t\t...(nextStage !== undefined && { nextStage }),\n\t\t\t});\n\t\t\tthis.emit(options, {\n\t\t\t\ttype: 'workflowTransition',\n\t\t\t\tstage: stage.name,\n\t\t\t\ttransition: transition.type,\n\t\t\t\t...(nextStage !== undefined && { nextStage }),\n\t\t\t});\n\n\t\t\tif (transition.type !== 'go') {\n\t\t\t\tconst status = transition.type === 'complete' ? 'completed' : 'stopped';\n\t\t\t\treturn { status, result: transition.result };\n\t\t\t}\n\t\t\tif (transitions + 1 > this.maxTransitions) {\n\t\t\t\tthrow new UnrecoverableError('Workflow transition limit exhausted', {\n\t\t\t\t\tcause: `The run went to another stage more than ${String(this.maxTransitions)} times.`,\n\t\t\t\t});\n\t\t\t}\n\t\t\tstage = this.stageNamed(transition.stage, stage.name);\n\t\t\tstageInput = transition.input;\n\t\t}\n\t}\n\n\t/** The stage's agent in this run: created the first time the stage runs, then reused. */\n\tprivate async agentOf(stage: RunnableStage, agents: Map<string, Agent>): Promise<Agent> {\n\t\tconst existing = agents.get(stage.name);\n\t\tif (existing) return existing;\n\t\tlet agent: Agent;\n\t\ttry {\n\t\t\tagent = await stage.agent();\n\t\t} catch (error) {\n\t\t\tthrow classifyHostFailure(error, `Workflow stage \"${stage.name}\" could not create its agent`);\n\t\t}\n\t\tagents.set(stage.name, agent);\n\t\treturn agent;\n\t}\n\n\t/**\n\t * Asks the stage's agent until an answer is valid JSON its validator accepts, or attempts run\n\t * out. Each answer's usage goes to the run's and the stage's totals; a failure carries the\n\t * run's when `run` rethrows it.\n\t */\n\tprivate async answer(\n\t\tstage: RunnableStage,\n\t\tagent: Agent,\n\t\tinput: unknown,\n\t\toptions: RunOptions,\n\t\ttotals: Totals,\n\t\tstageTotals: Totals,\n\t): Promise<{ output: unknown; attempts: number }> {\n\t\tconst maxAttempts = stage.maxAttempts ?? this.maxAttempts;\n\t\toptions.signal.throwIfAborted();\n\t\tlet prompt: string;\n\t\ttry {\n\t\t\tprompt = await stage.prompt(input);\n\t\t} catch (error) {\n\t\t\tthrow classifyHostFailure(error, `Workflow stage \"${stage.name}\" could not build its prompt`);\n\t\t}\n\n\t\tlet failureReason: string | undefined;\n\t\tfor (let attempt = 1; attempt <= maxAttempts; attempt++) {\n\t\t\toptions.signal.throwIfAborted();\n\t\t\tthis.emit(options, { type: 'workflowStage', stage: stage.name, attempt });\n\t\t\tconst response = await this.runAgent(\n\t\t\t\tstage,\n\t\t\t\tagent,\n\t\t\t\tfailureReason ? prompt + retryNotice(failureReason) : prompt,\n\t\t\t\toptions,\n\t\t\t);\n\t\t\taddUsage(totals, response);\n\t\t\t// An answer that arrives after a cancellation is not used.\n\t\t\toptions.signal.throwIfAborted();\n\t\t\taddUsage(stageTotals, response);\n\n\t\t\tconst parsed = parseJSON(response.response);\n\t\t\tif (parsed) {\n\t\t\t\tlet output: unknown;\n\t\t\t\ttry {\n\t\t\t\t\toutput = stage.validator.validate(parsed.value);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthrow classifyHostFailure(\n\t\t\t\t\t\terror,\n\t\t\t\t\t\t`Workflow stage \"${stage.name}\" could not validate its answer`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (output !== undefined) return { output, attempts: attempt };\n\t\t\t\tfailureReason = 'it did not match the schema.';\n\t\t\t} else {\n\t\t\t\tfailureReason = 'it was not valid JSON.';\n\t\t\t}\n\n\t\t\tif (attempt < maxAttempts) {\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Workflow stage \"${stage.name}\" answer ${String(attempt)}/${String(maxAttempts)} was unusable (${failureReason}); asking again`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tthrow new UnrecoverableError('Workflow stage attempts exhausted', {\n\t\t\tcause: `Stage \"${stage.name}\" gave no usable answer in ${String(maxAttempts)} attempts: the last one ${failureReason ?? ''}`,\n\t\t});\n\t}\n\n\tprivate async runAgent(\n\t\tstage: RunnableStage,\n\t\tagent: Agent,\n\t\tprompt: string,\n\t\toptions: RunOptions,\n\t): Promise<AgentResponse> {\n\t\ttry {\n\t\t\treturn await agent.run(prompt, options);\n\t\t} catch (error) {\n\t\t\t// Whatever the agent threw once the run was cancelled, the run ends as cancelled.\n\t\t\toptions.signal.throwIfAborted();\n\t\t\tif (isAbortError(error)) throw error;\n\t\t\tif (error instanceof RecoverableError || error instanceof UnrecoverableError) throw error;\n\t\t\tthrow new UnrecoverableError(`Workflow stage \"${stage.name}\" failed`, {\n\t\t\t\tcause: describeFailure(error),\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate async transition(\n\t\tstage: RunnableStage,\n\t\toutput: unknown,\n\t\tinput: unknown,\n\t): Promise<Transition<Result>> {\n\t\tlet transition: unknown;\n\t\ttry {\n\t\t\ttransition = await stage.transition(output, input);\n\t\t} catch (error) {\n\t\t\tthrow classifyHostFailure(\n\t\t\t\terror,\n\t\t\t\t`Workflow stage \"${stage.name}\" could not choose its transition`,\n\t\t\t);\n\t\t}\n\t\tif (!isTransition<Result>(transition)) {\n\t\t\tthrow new UnrecoverableError('Workflow transition is invalid', {\n\t\t\t\tcause: `Stage \"${stage.name}\" returned something that is not a transition.`,\n\t\t\t});\n\t\t}\n\t\treturn transition;\n\t}\n\n\tprivate stageNamed(name: string, from?: string): RunnableStage {\n\t\tconst stage = this.stages.get(name);\n\t\tif (!stage) {\n\t\t\tthrow new UnrecoverableError('Workflow stage does not exist', {\n\t\t\t\tcause: from\n\t\t\t\t\t? `Stage \"${from}\" selected unknown stage \"${name}\".`\n\t\t\t\t\t: `The workflow has no stage \"${name}\".`,\n\t\t\t});\n\t\t}\n\t\treturn stage;\n\t}\n\n\t/** The run's records end here, but its result stands: what it changed is already on disk. */\n\tprivate async finishContext(context: RunContext, end: RunEnd): Promise<void> {\n\t\ttry {\n\t\t\tawait context.finish(end);\n\t\t} catch (error) {\n\t\t\tthis.warn(`The run could not close its records: ${describeFailure(error)}`);\n\t\t}\n\t}\n\n\tprivate emit(options: RunOptions, event: ProgressEvent): void {\n\t\tconst { onProgress } = options;\n\t\tif (!onProgress) return;\n\t\ttreatErrors(\n\t\t\t() => {\n\t\t\t\tonProgress(event);\n\t\t\t},\n\t\t\tclassifyHostFailure,\n\t\t\t'Workflow progress callback failed',\n\t\t);\n\t}\n\n\tprivate warn(message: string): void {\n\t\ttreatErrors(\n\t\t\t() => {\n\t\t\t\tthis.logger.warn(message);\n\t\t\t},\n\t\t\tclassifyHostFailure,\n\t\t\t'Workflow logger failed while reporting a retry',\n\t\t);\n\t}\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"validator.js","sourceRoot":"","sources":["../../../../src/orchestration/domain/interface/validator.ts"],"names":[],"mappings":"","sourcesContent":["export interface Validator<T> {\n\tvalidate(input: unknown): T | undefined;\n}\n"]}
|
|
File without changes
|