@howarewoo/pi-subagents 0.1.1 → 0.2.1
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/CHANGELOG.md +50 -0
- package/README.md +268 -97
- package/docs/advanced-policies.md +4 -2
- package/docs/delegation-contract.md +57 -28
- package/docs/images/subagent-hub.svg +1 -1
- package/docs/images/subagent-profiles.svg +1 -1
- package/docs/images/subagent-widget.svg +1 -1
- package/docs/releases.md +111 -23
- package/docs/verification.md +1 -1
- package/package.json +4 -4
- package/src/child-execution.ts +459 -0
- package/src/coordinator.ts +608 -664
- package/src/delegation-types.ts +111 -0
- package/src/hub.ts +80 -300
- package/src/pi-extended-subagents.ts +83 -189
- package/src/profile-viewer.ts +109 -38
- package/src/scheduling.ts +1 -1
- package/src/subagent-widget.ts +150 -0
- package/src/supported-host.ts +26 -0
- package/src/task-input.ts +3 -2
- package/src/task-presentation.ts +196 -0
- package/src/tool-schemas.ts +165 -0
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
createAgentSession,
|
|
5
|
+
createCodemodeExtension,
|
|
6
|
+
createExtensionRuntime,
|
|
7
|
+
DefaultResourceLoader,
|
|
8
|
+
SessionManager,
|
|
9
|
+
SettingsManager,
|
|
10
|
+
type AgentSession,
|
|
11
|
+
type AgentSessionEvent,
|
|
12
|
+
type ExtensionToolContext,
|
|
13
|
+
type ResourceLoader,
|
|
14
|
+
type Skill,
|
|
15
|
+
type ToolDefinition,
|
|
16
|
+
} from "@earendil-works/pi-coding-agent";
|
|
17
|
+
import type { Validator } from "@earendil-works/pi-schema";
|
|
18
|
+
import { submissionParameters, saveOutcomeArtifact } from "./outcomes.ts";
|
|
19
|
+
import { validateModelSelection, type ModelSelection } from "./model-policy.ts";
|
|
20
|
+
import { parentModelRuntime } from "./model-adapter.ts";
|
|
21
|
+
import { workspaceArguments } from "./workspace.ts";
|
|
22
|
+
import type { BuiltinTool } from "./profiles.ts";
|
|
23
|
+
import type { ChildSnapshot, StartTask } from "./delegation-types.ts";
|
|
24
|
+
|
|
25
|
+
/** Shared live execution fields; lifecycle ownership stays with the coordinator. */
|
|
26
|
+
export type ExecutionRecord = {
|
|
27
|
+
validator?: Validator;
|
|
28
|
+
input: StartTask;
|
|
29
|
+
selection: Pick<
|
|
30
|
+
ExtensionToolContext,
|
|
31
|
+
"model" | "modelRegistry" | "scopedModels" | "thinkingLevel" | "cwd"
|
|
32
|
+
>;
|
|
33
|
+
state: ChildSnapshot;
|
|
34
|
+
timer?: ReturnType<typeof setTimeout>;
|
|
35
|
+
run?: Promise<void>;
|
|
36
|
+
session?: AgentSession;
|
|
37
|
+
unsubscribe?: () => void;
|
|
38
|
+
paused?: boolean;
|
|
39
|
+
prompt?: (text: string) => Promise<void>;
|
|
40
|
+
resumeText?: string;
|
|
41
|
+
prompted?: boolean;
|
|
42
|
+
cancelled: boolean;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** Operations bound to the exact admitted entry, never a later generation with its ID. */
|
|
46
|
+
export interface ExecutionOwner {
|
|
47
|
+
readonly closing: boolean;
|
|
48
|
+
update(patch: Partial<ChildSnapshot>, event?: AgentSessionEvent): void;
|
|
49
|
+
revalidateModel(selected: ModelSelection, requirePrewalk: boolean): Promise<void>;
|
|
50
|
+
authorizeTool(name: string, builtin: boolean): Promise<void>;
|
|
51
|
+
assertTool(name: string, builtin: boolean, signal?: AbortSignal): void;
|
|
52
|
+
chargeRequest(): void;
|
|
53
|
+
chargeToolCall(): void;
|
|
54
|
+
observeUserMessage(text: string): void;
|
|
55
|
+
drainDescendants(): Promise<void>;
|
|
56
|
+
dispose(): void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function childResources(prompt: string, skills: Skill[] = []): ResourceLoader {
|
|
60
|
+
const extensions = { extensions: [], errors: [], runtime: createExtensionRuntime() };
|
|
61
|
+
return {
|
|
62
|
+
getExtensions: () => extensions,
|
|
63
|
+
getSkills: () => ({ skills, diagnostics: [] }),
|
|
64
|
+
getPrompts: () => ({ prompts: [], diagnostics: [] }),
|
|
65
|
+
getThemes: () => ({ themes: [], diagnostics: [] }),
|
|
66
|
+
getAgentsFiles: () => ({ agentsFiles: [] }),
|
|
67
|
+
getSystemPrompt: () =>
|
|
68
|
+
prompt +
|
|
69
|
+
"\nYou are a delegated child. Complete only this assignment; use only explicitly granted tools and spawn roles.",
|
|
70
|
+
getSystemPromptSource: () => undefined,
|
|
71
|
+
getAppendSystemPrompt: () => [],
|
|
72
|
+
getAppendSystemPromptSources: () => [],
|
|
73
|
+
extendResources: () => {},
|
|
74
|
+
reload: async () => {},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function createChildExecution(
|
|
79
|
+
root: string,
|
|
80
|
+
entry: ExecutionRecord,
|
|
81
|
+
owner: ExecutionOwner,
|
|
82
|
+
): Promise<void> {
|
|
83
|
+
const { input, selection } = entry;
|
|
84
|
+
try {
|
|
85
|
+
await mkdir(root, { recursive: true });
|
|
86
|
+
const taskDir = join(
|
|
87
|
+
root,
|
|
88
|
+
entry.state.generation === 1
|
|
89
|
+
? entry.state.id
|
|
90
|
+
: `${entry.state.id}-g${entry.state.generation}`,
|
|
91
|
+
);
|
|
92
|
+
await mkdir(taskDir, { mode: 0o700 }); // exclusive ownership: never reuse an existing task record
|
|
93
|
+
const agentDir = join(taskDir, "agent");
|
|
94
|
+
await mkdir(agentDir, { mode: 0o700 });
|
|
95
|
+
const transcriptDir = join(taskDir, "transcripts");
|
|
96
|
+
await mkdir(transcriptDir, { mode: 0o700 });
|
|
97
|
+
const target =
|
|
98
|
+
entry.state.prewalk?.status === "armed"
|
|
99
|
+
? validateModelSelection(input.context, entry.state.prewalk.target)
|
|
100
|
+
: undefined;
|
|
101
|
+
let policyError: string | undefined;
|
|
102
|
+
let completedMutation = false;
|
|
103
|
+
const inheritCodemode = !!input.codemodeEnabled?.();
|
|
104
|
+
if (!inheritCodemode) input.codemodeEnabled = () => false;
|
|
105
|
+
const effective = (): ModelSelection => ({
|
|
106
|
+
...entry.state.model,
|
|
107
|
+
effort: entry.state.effort as ModelSelection["effort"],
|
|
108
|
+
});
|
|
109
|
+
const runtime = await parentModelRuntime(
|
|
110
|
+
selection,
|
|
111
|
+
agentDir,
|
|
112
|
+
() => {
|
|
113
|
+
if (policyError) throw new Error(policyError);
|
|
114
|
+
owner.chargeRequest();
|
|
115
|
+
},
|
|
116
|
+
target ? [target] : [],
|
|
117
|
+
);
|
|
118
|
+
const resources = childResources(
|
|
119
|
+
input.profile.prompt +
|
|
120
|
+
(target
|
|
121
|
+
? "\nPrewalk: investigate and plan using only granted context/tools. After the first completed edit or write the same conversation switches once to the explicitly granted implementation target; all normal budgets still apply."
|
|
122
|
+
: "") +
|
|
123
|
+
`\nYour delegation identity is ${entry.state.id} generation ${entry.state.generation}; parent ${entry.state.parentId}. Coordination, if granted, is limited to family identities explicitly supplied to you.` +
|
|
124
|
+
(entry.validator
|
|
125
|
+
? "\nThis assignment requires a structured result. Call submit_result with the actual value; final prose is not a submission."
|
|
126
|
+
: ""),
|
|
127
|
+
input.skills,
|
|
128
|
+
);
|
|
129
|
+
const loader = new DefaultResourceLoader({
|
|
130
|
+
cwd: selection.cwd,
|
|
131
|
+
agentDir,
|
|
132
|
+
settingsManager: SettingsManager.inMemory(),
|
|
133
|
+
noExtensions: true,
|
|
134
|
+
noSkills: true,
|
|
135
|
+
noPromptTemplates: true,
|
|
136
|
+
noThemes: true,
|
|
137
|
+
noContextFiles: true,
|
|
138
|
+
extensionFactories: [
|
|
139
|
+
...(inheritCodemode ? [createCodemodeExtension({ models: false })] : []),
|
|
140
|
+
(pi) => {
|
|
141
|
+
pi.on("tool_call", (event, ctx) => {
|
|
142
|
+
if (event.toolName !== "codemode") return;
|
|
143
|
+
if (entry.cancelled || entry.paused || owner.closing)
|
|
144
|
+
throw new Error("Delegation cancelled");
|
|
145
|
+
ctx.signal?.throwIfAborted();
|
|
146
|
+
owner.chargeToolCall();
|
|
147
|
+
if (!input.codemodeEnabled?.())
|
|
148
|
+
return { block: true, reason: "Parent codemode is no longer enabled" };
|
|
149
|
+
});
|
|
150
|
+
pi.on("turn_start", async () => {
|
|
151
|
+
try {
|
|
152
|
+
await owner.revalidateModel(effective(), entry.state.prewalk?.status === "armed");
|
|
153
|
+
policyError = undefined;
|
|
154
|
+
} catch (error) {
|
|
155
|
+
policyError = String(error);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
pi.on("turn_end", async (event) => {
|
|
159
|
+
if (
|
|
160
|
+
!entry.cancelled &&
|
|
161
|
+
!entry.paused &&
|
|
162
|
+
!owner.closing &&
|
|
163
|
+
entry.state.prewalk?.status === "armed" &&
|
|
164
|
+
(completedMutation ||
|
|
165
|
+
event.toolResults.some(
|
|
166
|
+
(result) =>
|
|
167
|
+
!result.isError &&
|
|
168
|
+
(result.toolName === "edit" || result.toolName === "write") &&
|
|
169
|
+
input.profile.tools.includes(result.toolName),
|
|
170
|
+
))
|
|
171
|
+
) {
|
|
172
|
+
try {
|
|
173
|
+
const next = entry.state.prewalk.target;
|
|
174
|
+
await owner.revalidateModel(next, true);
|
|
175
|
+
if (entry.cancelled || entry.paused || owner.closing) return;
|
|
176
|
+
await entry.session!.setModel(target!);
|
|
177
|
+
entry.session!.setThinkingLevel(next.effort);
|
|
178
|
+
if (entry.session!.thinkingLevel !== next.effort)
|
|
179
|
+
throw new Error("SDK coerced prewalk effort");
|
|
180
|
+
owner.update({
|
|
181
|
+
model: { provider: next.provider, id: next.id },
|
|
182
|
+
effort: next.effort,
|
|
183
|
+
prewalk: { target: next, status: "handed-off" },
|
|
184
|
+
});
|
|
185
|
+
} catch (error) {
|
|
186
|
+
policyError = String(error);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
});
|
|
193
|
+
await loader.reload();
|
|
194
|
+
resources.getExtensions = () => loader.getExtensions();
|
|
195
|
+
const tools: ToolDefinition[] = [
|
|
196
|
+
...input.profile.tools,
|
|
197
|
+
...(input.profile.leafTools ?? []),
|
|
198
|
+
].map((name) => {
|
|
199
|
+
const tool = input.context.tools.find((tool) => tool.name === name);
|
|
200
|
+
if (!tool) throw new Error(`Admitted parent tool is no longer callable: ${name}`);
|
|
201
|
+
return {
|
|
202
|
+
name,
|
|
203
|
+
label: name,
|
|
204
|
+
description: tool.description,
|
|
205
|
+
parameters: tool.parameters,
|
|
206
|
+
execute: async (_id, args, signal, onUpdate) => {
|
|
207
|
+
if (entry.cancelled || entry.paused || owner.closing)
|
|
208
|
+
throw new Error("Delegation cancelled");
|
|
209
|
+
if (!input.context.tools.some((tool) => tool.name === name))
|
|
210
|
+
throw new Error(`Admitted parent tool is no longer callable: ${name}`);
|
|
211
|
+
const builtin = input.profile.tools.includes(name as BuiltinTool);
|
|
212
|
+
await owner.authorizeTool(name, builtin);
|
|
213
|
+
owner.assertTool(name, builtin, signal);
|
|
214
|
+
// The public parent dispatcher preserves validation and permission hooks. Do not
|
|
215
|
+
// call a raw create*Tool.execute here: that would bypass operator policy.
|
|
216
|
+
const outcome = await input.context.executeTool(
|
|
217
|
+
name,
|
|
218
|
+
builtin
|
|
219
|
+
? workspaceArguments(name as BuiltinTool, args, selection.cwd, input.context.cwd)
|
|
220
|
+
: args,
|
|
221
|
+
{ signal, onUpdate },
|
|
222
|
+
);
|
|
223
|
+
// SDK turn results contain only the outer codemode call, not its nested edits.
|
|
224
|
+
if (!outcome.isError && builtin && (name === "edit" || name === "write"))
|
|
225
|
+
completedMutation = true;
|
|
226
|
+
return { ...outcome.result, isError: outcome.isError };
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
});
|
|
230
|
+
if (entry.validator) {
|
|
231
|
+
const submission: ToolDefinition<typeof submissionParameters> = {
|
|
232
|
+
name: "submit_result",
|
|
233
|
+
label: "Submit result",
|
|
234
|
+
exposure: "model-only",
|
|
235
|
+
description: `Submit the assignment's required structured JSON value exactly once. Invalid values return errors and can be corrected within normal limits. Submission is provisional until prompt/tools/descendants drain; it never overrides cancellation or failure. Required schema: ${JSON.stringify(input.resultSchema)}`,
|
|
236
|
+
parameters: submissionParameters,
|
|
237
|
+
execute: async (_id, args, signal) => {
|
|
238
|
+
signal?.throwIfAborted();
|
|
239
|
+
if (
|
|
240
|
+
entry.cancelled ||
|
|
241
|
+
owner.closing ||
|
|
242
|
+
entry.state.disposed ||
|
|
243
|
+
entry.state.status !== "running"
|
|
244
|
+
)
|
|
245
|
+
throw new Error("Submission identity is no longer running");
|
|
246
|
+
const outcome = entry.state.outcome!;
|
|
247
|
+
if (outcome.validation === "submitted")
|
|
248
|
+
throw new Error("A result was already submitted; duplicate submission rejected");
|
|
249
|
+
const checked = entry.validator!.validate(args.value);
|
|
250
|
+
if (!checked.ok) {
|
|
251
|
+
const errors = checked.errors
|
|
252
|
+
.map((error) => `${error.path || "/"}: ${error.message}`)
|
|
253
|
+
.slice(0, 32);
|
|
254
|
+
owner.update({
|
|
255
|
+
outcome: {
|
|
256
|
+
validation: "invalid",
|
|
257
|
+
attempts: outcome.attempts + 1,
|
|
258
|
+
errors,
|
|
259
|
+
finalized: false,
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
throw new Error(
|
|
263
|
+
"Invalid structured result; correct and resubmit: " + errors.join("; "),
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
owner.update({
|
|
267
|
+
outcome: {
|
|
268
|
+
validation: "submitted",
|
|
269
|
+
attempts: outcome.attempts + 1,
|
|
270
|
+
errors: [],
|
|
271
|
+
value: structuredClone(args.value),
|
|
272
|
+
finalized: false,
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
return {
|
|
276
|
+
content: [
|
|
277
|
+
{
|
|
278
|
+
type: "text",
|
|
279
|
+
text: "Structured value submitted; final outcome awaits prompt/tool/descendant drain.",
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
details: {},
|
|
283
|
+
};
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
tools.push(submission);
|
|
287
|
+
}
|
|
288
|
+
if (input.profile.coordination?.length && input.coordinationTools)
|
|
289
|
+
tools.push(
|
|
290
|
+
...input.coordinationTools({ id: entry.state.id, generation: entry.state.generation }),
|
|
291
|
+
);
|
|
292
|
+
if (input.profile.spawn?.length && input.nestedTool) tools.push(input.nestedTool(entry.state));
|
|
293
|
+
for (const tool of tools) {
|
|
294
|
+
const execute = tool.execute;
|
|
295
|
+
tool.execute = async (...args) => {
|
|
296
|
+
if (entry.cancelled || entry.paused || owner.closing)
|
|
297
|
+
throw new Error("Delegation cancelled");
|
|
298
|
+
owner.chargeToolCall();
|
|
299
|
+
return execute(...args);
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
const { session, modelFallbackMessage } = await createAgentSession({
|
|
303
|
+
cwd: selection.cwd,
|
|
304
|
+
agentDir,
|
|
305
|
+
modelRuntime: runtime,
|
|
306
|
+
model: selection.model,
|
|
307
|
+
thinkingLevel: selection.thinkingLevel,
|
|
308
|
+
scopedModels: [
|
|
309
|
+
{ model: selection.model!, thinkingLevel: selection.thinkingLevel },
|
|
310
|
+
...(target ? [{ model: target, thinkingLevel: input.profile.prewalk!.effort }] : []),
|
|
311
|
+
],
|
|
312
|
+
resourceLoader: resources,
|
|
313
|
+
tools: [...tools.map((tool) => tool.name), ...(inheritCodemode ? ["codemode"] : [])],
|
|
314
|
+
customTools: tools,
|
|
315
|
+
settingsManager: SettingsManager.inMemory({
|
|
316
|
+
compaction: { enabled: false },
|
|
317
|
+
retry: { enabled: false },
|
|
318
|
+
}),
|
|
319
|
+
sessionManager: SessionManager.create(selection.cwd, transcriptDir),
|
|
320
|
+
});
|
|
321
|
+
entry.session = session;
|
|
322
|
+
if (session.thinkingLevel !== selection.thinkingLevel)
|
|
323
|
+
throw new Error("SDK did not preserve selected effort");
|
|
324
|
+
if (
|
|
325
|
+
modelFallbackMessage ||
|
|
326
|
+
session.model?.id !== selection.model?.id ||
|
|
327
|
+
session.model?.provider !== selection.model?.provider
|
|
328
|
+
)
|
|
329
|
+
throw new Error("SDK did not preserve selected model");
|
|
330
|
+
let markStarted = () => {};
|
|
331
|
+
entry.unsubscribe = session.subscribe((event) => {
|
|
332
|
+
const patch: { -readonly [K in keyof ChildSnapshot]?: ChildSnapshot[K] } = {};
|
|
333
|
+
if (event.type === "agent_start") {
|
|
334
|
+
patch.status = entry.cancelled ? "draining" : entry.paused ? "pausing" : "running";
|
|
335
|
+
markStarted();
|
|
336
|
+
}
|
|
337
|
+
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
|
|
338
|
+
patch.progress = (entry.state.progress + event.assistantMessageEvent.delta).slice(-4096);
|
|
339
|
+
patch.activity = patch.progress;
|
|
340
|
+
}
|
|
341
|
+
if (event.type === "message_end" && event.message.role === "user") {
|
|
342
|
+
const text =
|
|
343
|
+
typeof event.message.content === "string"
|
|
344
|
+
? event.message.content
|
|
345
|
+
: event.message.content
|
|
346
|
+
.filter((block) => block.type === "text")
|
|
347
|
+
.map((block) => block.text)
|
|
348
|
+
.join("");
|
|
349
|
+
owner.observeUserMessage(text);
|
|
350
|
+
}
|
|
351
|
+
if (event.type === "tool_execution_start") patch.activity = `Tool: ${event.toolName}`;
|
|
352
|
+
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
353
|
+
const stats = session.getSessionStats();
|
|
354
|
+
patch.telemetry = {
|
|
355
|
+
...entry.state.telemetry,
|
|
356
|
+
tokens: stats.tokens,
|
|
357
|
+
cost: stats.cost,
|
|
358
|
+
context: session.getContextUsage() ?? null,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
owner.update(patch, event);
|
|
362
|
+
});
|
|
363
|
+
owner.update({ transcript: session.sessionFile });
|
|
364
|
+
if (owner.closing || entry.cancelled) {
|
|
365
|
+
owner.update({ status: "cancelled" });
|
|
366
|
+
owner.dispose();
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
entry.prompt = (text) => {
|
|
370
|
+
if (!entry.prompted) {
|
|
371
|
+
text = entry.resumeText ? input.assignment + "\n\n" + entry.resumeText : input.assignment;
|
|
372
|
+
entry.prompted = true;
|
|
373
|
+
}
|
|
374
|
+
const started = new Promise<void>((resolve) => {
|
|
375
|
+
markStarted = resolve;
|
|
376
|
+
});
|
|
377
|
+
entry.run = session
|
|
378
|
+
.prompt(text, { expandPromptTemplates: false })
|
|
379
|
+
.then(async () => {
|
|
380
|
+
if (entry.paused && !entry.cancelled && !owner.closing) return;
|
|
381
|
+
await owner.drainDescendants();
|
|
382
|
+
if (entry.paused && !entry.cancelled && !owner.closing) return;
|
|
383
|
+
clearTimeout(entry.timer); // Work has drained; artifact publication is finalization.
|
|
384
|
+
const last = session.messages.filter((message) => message.role === "assistant").at(-1);
|
|
385
|
+
const failed = !last || last.stopReason === "error";
|
|
386
|
+
const stats = session.getSessionStats();
|
|
387
|
+
owner.update({ status: "draining", activity: "Finalizing outcome artifacts" });
|
|
388
|
+
const result = session.getLastAssistantText() ?? "";
|
|
389
|
+
const artifacts: NonNullable<ChildSnapshot["artifacts"]> = {};
|
|
390
|
+
try {
|
|
391
|
+
artifacts.text = await saveOutcomeArtifact(
|
|
392
|
+
taskDir,
|
|
393
|
+
"result.txt",
|
|
394
|
+
last?.content
|
|
395
|
+
.filter((block) => block.type === "text")
|
|
396
|
+
.map((block) => block.text)
|
|
397
|
+
.join("") ?? "",
|
|
398
|
+
);
|
|
399
|
+
if (entry.state.outcome?.validation === "submitted")
|
|
400
|
+
artifacts.structured = await saveOutcomeArtifact(
|
|
401
|
+
taskDir,
|
|
402
|
+
"structured.json",
|
|
403
|
+
JSON.stringify(entry.state.outcome.value),
|
|
404
|
+
);
|
|
405
|
+
} catch (error) {
|
|
406
|
+
artifacts.error = `Outcome artifact persistence failed: ${String(error)}`;
|
|
407
|
+
}
|
|
408
|
+
const missing = entry.validator && entry.state.outcome?.validation !== "submitted";
|
|
409
|
+
owner.update({
|
|
410
|
+
artifacts,
|
|
411
|
+
outcome: entry.state.outcome ? { ...entry.state.outcome, finalized: true } : undefined,
|
|
412
|
+
status:
|
|
413
|
+
entry.cancelled || last?.stopReason === "aborted"
|
|
414
|
+
? "cancelled"
|
|
415
|
+
: failed || missing || artifacts.error
|
|
416
|
+
? "failed"
|
|
417
|
+
: "settled",
|
|
418
|
+
result,
|
|
419
|
+
error: entry.state.limitReason
|
|
420
|
+
? entry.state.error
|
|
421
|
+
: failed
|
|
422
|
+
? (last?.errorMessage ?? "No final assistant outcome")
|
|
423
|
+
: missing
|
|
424
|
+
? "Required structured result was not submitted successfully"
|
|
425
|
+
: artifacts.error,
|
|
426
|
+
activity: "Prompt completed",
|
|
427
|
+
telemetry: {
|
|
428
|
+
requests: entry.state.telemetry.requests,
|
|
429
|
+
tokens: stats.assistantMessages ? stats.tokens : null,
|
|
430
|
+
cost: stats.assistantMessages ? stats.cost : null,
|
|
431
|
+
context: session.getContextUsage() ?? null,
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
})
|
|
435
|
+
.catch(async (error) => {
|
|
436
|
+
if (entry.paused && !entry.cancelled && !owner.closing) return;
|
|
437
|
+
await owner.drainDescendants();
|
|
438
|
+
owner.update({
|
|
439
|
+
status: entry.cancelled ? "cancelled" : "failed",
|
|
440
|
+
error: entry.state.limitReason ? entry.state.error : String(error),
|
|
441
|
+
});
|
|
442
|
+
})
|
|
443
|
+
.finally(() => {
|
|
444
|
+
if (!entry.paused || entry.cancelled || owner.closing) owner.dispose();
|
|
445
|
+
});
|
|
446
|
+
// Prompt preparation precedes abort-signal ownership in every execution window.
|
|
447
|
+
return Promise.race([started, entry.run]);
|
|
448
|
+
};
|
|
449
|
+
if (entry.paused) return;
|
|
450
|
+
await entry.prompt(entry.resumeText ?? input.assignment);
|
|
451
|
+
} catch (error) {
|
|
452
|
+
owner.update({
|
|
453
|
+
status: entry.cancelled || owner.closing ? "cancelled" : "failed",
|
|
454
|
+
error: String(error),
|
|
455
|
+
});
|
|
456
|
+
owner.dispose();
|
|
457
|
+
throw error;
|
|
458
|
+
}
|
|
459
|
+
}
|