@tt-a1i/openpi 0.3.1 → 0.5.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 +184 -59
- package/SETUP.md +23 -7
- package/assets/openpi-launch-card-v1.webp +0 -0
- package/bin/openpi.js +145 -0
- package/extensions/ask-user/index.ts +30 -14
- package/extensions/background-terminals/index.ts +30 -2
- package/extensions/background-terminals/src/domain.ts +2 -0
- package/extensions/background-terminals/src/manager.ts +486 -106
- package/extensions/background-terminals/src/output.ts +33 -0
- package/extensions/background-terminals/src/prompt.ts +14 -6
- package/extensions/background-terminals/src/result-delivery.ts +4 -1
- package/extensions/background-terminals/src/ui/ps.ts +132 -129
- package/extensions/capabilities/index.ts +30 -42
- package/extensions/capabilities/src/ui.ts +93 -0
- package/extensions/clear-context/index.ts +83 -0
- package/extensions/context-pivot/index.ts +16 -6
- package/extensions/cron/schedule.ts +7 -1
- package/extensions/file-mutation-display/index.ts +34 -76
- package/extensions/file-mutation-display/render.ts +146 -87
- package/extensions/file-search/index.ts +8 -7
- package/extensions/file-search/src/binaries.ts +75 -59
- package/extensions/git-info/src/changed-files-view.ts +47 -14
- package/extensions/git-read/index.ts +328 -0
- package/extensions/git-read/src/args.ts +171 -0
- package/extensions/git-read/src/process.ts +81 -0
- package/extensions/git-read/src/prompt.ts +56 -0
- package/extensions/model-info/index.ts +21 -33
- package/extensions/model-info/session-metrics.ts +96 -0
- package/extensions/plan-mode/bash-policy.ts +54 -9
- package/extensions/plan-mode/index.ts +7 -2
- package/extensions/post-edit/index.ts +16 -6
- package/extensions/sessions/git-stats.ts +258 -72
- package/extensions/sessions/index.ts +222 -140
- package/extensions/sessions/preview-cache.ts +104 -0
- package/extensions/sessions/preview-loader.ts +856 -0
- package/extensions/sessions/sessions.ts +43 -4
- package/extensions/setup/index.ts +127 -131
- package/extensions/shared/activity-status.ts +36 -5
- package/extensions/shared/agent-session-page.ts +319 -0
- package/extensions/shared/agent-tool-renderer.ts +218 -0
- package/extensions/shared/agent-transcript.ts +524 -0
- package/extensions/shared/below-editor-navigation.ts +26 -0
- package/extensions/shared/capability-intent.ts +53 -0
- package/extensions/shared/child-session.ts +444 -22
- package/extensions/shared/result-budget.ts +134 -0
- package/extensions/shared/result-delivery.ts +34 -0
- package/extensions/shared/screen-chrome.ts +133 -0
- package/extensions/shared/setup-config.ts +97 -38
- package/extensions/shared/setup-episode-state.ts +1 -1
- package/extensions/shared/spinner.ts +28 -0
- package/extensions/shared/terminal-text.ts +110 -23
- package/extensions/shared/text-projection.ts +113 -0
- package/extensions/shared/tool-activity.ts +382 -0
- package/extensions/shared/tool-surface.ts +42 -8
- package/extensions/shared/transcript-viewport.ts +46 -0
- package/extensions/shared/web-observer-registry.ts +390 -0
- package/extensions/shared/worktree.ts +11 -0
- package/extensions/subagents/index.ts +461 -186
- package/extensions/subagents/navigation.ts +86 -28
- package/extensions/subagents/src/agent-types.ts +37 -15
- package/extensions/subagents/src/backend.ts +12 -1
- package/extensions/subagents/src/backends/pi.ts +375 -66
- package/extensions/subagents/src/domain.ts +5 -0
- package/extensions/subagents/src/id-sequence.ts +84 -0
- package/extensions/subagents/src/manager.ts +651 -536
- package/extensions/subagents/src/prompt.ts +185 -42
- package/extensions/subagents/src/result-artifact.ts +146 -0
- package/extensions/subagents/src/result-delivery.ts +7 -1
- package/extensions/subagents/src/runtime.ts +23 -6
- package/extensions/subagents/src/ui/takeover.ts +128 -337
- package/extensions/subagents/src/ui/transcript.ts +38 -501
- package/extensions/subagents/src/ui/wait-result.ts +103 -15
- package/extensions/suggestions/src/ui.ts +10 -4
- package/extensions/tasks/index.ts +0 -3
- package/extensions/tasks/ui.ts +79 -62
- package/extensions/ui-customization/footer.ts +7 -44
- package/extensions/ui-customization/index.ts +0 -4
- package/extensions/user-input-fold/index.ts +185 -0
- package/extensions/web/index.ts +234 -0
- package/extensions/workflows/artifacts.ts +147 -22
- package/extensions/workflows/completion-projection.ts +457 -0
- package/extensions/workflows/controller.ts +14 -2
- package/extensions/workflows/coordinator.ts +62 -0
- package/extensions/workflows/dashboard.ts +458 -339
- package/extensions/workflows/handoff.ts +121 -25
- package/extensions/workflows/index.ts +1042 -492
- package/extensions/workflows/journal.ts +148 -13
- package/extensions/workflows/model.ts +131 -19
- package/extensions/workflows/navigation.ts +61 -18
- package/extensions/workflows/progress-projection.ts +306 -0
- package/extensions/workflows/prompt.ts +166 -10
- package/extensions/workflows/replay-safety.ts +58 -27
- package/extensions/workflows/result-delivery.ts +253 -0
- package/extensions/workflows/retention.ts +593 -0
- package/extensions/workflows/runner.ts +388 -279
- package/extensions/workflows/sandbox-child.cjs +36 -3
- package/extensions/workflows/sandbox.ts +62 -8
- package/extensions/workflows/serialization.ts +325 -17
- package/extensions/workflows/tool-renderer.ts +22 -0
- package/extensions/workflows/transcript.ts +149 -0
- package/extensions/workspace-cleanup-guard/index.ts +54 -0
- package/extensions/workspace-cleanup-guard/workspace-provenance.ts +563 -0
- package/package.json +28 -8
- package/skills/subagents/REFERENCE.md +189 -0
- package/skills/subagents/SKILL.md +2 -2
- package/skills/workflows/REFERENCE.md +10 -5
- package/skills/workflows/SKILL.md +53 -10
- package/web/adapter/pi-adapter.ts +661 -0
- package/web/host/browser-launcher.ts +20 -0
- package/web/host/static-assets.ts +4 -0
- package/web/host/terminal-status.ts +38 -0
- package/web/host/web-host.ts +789 -0
- package/web/http-dispatcher.ts +125 -0
- package/web/protocol/types.ts +462 -0
- package/web/runtime/pi-runtime.ts +991 -0
- package/web/runtime/types.ts +71 -0
- package/web/runtime/web-host-lease.ts +497 -0
- package/web/trace.ts +18 -0
- package/web/ui/app.js +1398 -0
- package/web/ui/index.html +139 -0
- package/web/ui/styles.css +598 -0
- package/web/vite.config.mjs +34 -0
- package/extensions/execution-convergence/active-evidence.ts +0 -129
- package/extensions/execution-convergence/index.ts +0 -442
- package/extensions/execution-convergence/workspace-provenance.ts +0 -338
- package/extensions/setup/intercom-fs-helper.cjs +0 -130
- package/extensions/setup/intercom.ts +0 -603
- package/extensions/subagents/src/backends/stub.ts +0 -296
- package/extensions/subagents/src/format.ts +0 -48
|
@@ -11,19 +11,20 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import {
|
|
14
|
-
createAgentSession,
|
|
15
|
-
DefaultResourceLoader,
|
|
16
|
-
defineTool,
|
|
17
|
-
SessionManager,
|
|
18
|
-
SettingsManager,
|
|
19
14
|
type AgentSession,
|
|
20
15
|
type AgentSessionEvent,
|
|
21
16
|
type AgentSessionEventListener,
|
|
17
|
+
createAgentSession,
|
|
18
|
+
DefaultResourceLoader,
|
|
19
|
+
defineTool,
|
|
22
20
|
type ExtensionAPI,
|
|
23
21
|
type ExtensionContext,
|
|
22
|
+
SessionManager,
|
|
23
|
+
SettingsManager,
|
|
24
24
|
type ToolDefinition,
|
|
25
25
|
} from "@earendil-works/pi-coding-agent";
|
|
26
|
-
import {
|
|
26
|
+
import { type TSchema, Type } from "typebox";
|
|
27
|
+
import { AgentToolRenderLedger } from "../shared/agent-tool-renderer.ts";
|
|
27
28
|
import {
|
|
28
29
|
bindChildSessionExtensions,
|
|
29
30
|
childToolPolicy,
|
|
@@ -31,23 +32,26 @@ import {
|
|
|
31
32
|
shutdownAndDisposeChildSession,
|
|
32
33
|
} from "../shared/child-session.ts";
|
|
33
34
|
import { createToolCallTimeoutGuard } from "../shared/tool-call-timeout.ts";
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
createReplayFilesystemBoundary,
|
|
37
|
-
type ReplayFilesystemBoundaryOptions,
|
|
38
|
-
} from "./replay-safety.ts";
|
|
35
|
+
import { type AgentUsage, emptyUsage, type TranscriptEntry } from "./model.ts";
|
|
39
36
|
import {
|
|
40
37
|
buildWorkflowAgentPrompt,
|
|
41
38
|
STRUCTURED_OUTPUT_SYSTEM_INSTRUCTION,
|
|
42
39
|
STRUCTURED_OUTPUT_TOOL_DESCRIPTION,
|
|
43
40
|
} from "./prompt.ts";
|
|
44
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
AgentProgressProjection,
|
|
43
|
+
type ProgressAssistantMessage,
|
|
44
|
+
transcriptFromMessages,
|
|
45
|
+
} from "./progress-projection.ts";
|
|
46
|
+
import {
|
|
47
|
+
createReplayFilesystemBoundary,
|
|
48
|
+
type ReplayFilesystemBoundaryOptions,
|
|
49
|
+
} from "./replay-safety.ts";
|
|
50
|
+
import { truncateUtf8 } from "./serialization.ts";
|
|
51
|
+
import { bindWorkflowToolRenderer } from "./tool-renderer.ts";
|
|
45
52
|
|
|
46
53
|
const AGENT_OUTPUT_MAX_BYTES = 64 * 1024;
|
|
47
|
-
export const
|
|
48
|
-
const TRANSCRIPT_ENTRY_MAX_BYTES = 16 * 1024;
|
|
49
|
-
const TRANSCRIPT_TOTAL_MAX_BYTES = 256 * 1024;
|
|
50
|
-
const TRANSCRIPT_MAX_ENTRIES = 200;
|
|
54
|
+
export const MODEL_PROGRESS_TIMEOUT_MS = 45_000;
|
|
51
55
|
|
|
52
56
|
export type WorkflowModel = NonNullable<ExtensionContext["model"]>;
|
|
53
57
|
export type ThinkingLevel = ReturnType<ExtensionAPI["getThinkingLevel"]>;
|
|
@@ -108,8 +112,8 @@ export interface RunAgentOptions {
|
|
|
108
112
|
replayFilesystemBoundary?: ReplayFilesystemBoundaryOptions;
|
|
109
113
|
/** Test-only override for the per-tool execution timeout. */
|
|
110
114
|
toolCallTimeoutMs?: number;
|
|
111
|
-
/** Test-only override for the
|
|
112
|
-
|
|
115
|
+
/** Test-only override for the per-provider-turn model-progress timeout. */
|
|
116
|
+
modelProgressTimeoutMs?: number;
|
|
113
117
|
/** Test-only override for the end-to-end abort/shutdown deadline. */
|
|
114
118
|
shutdownTimeoutMs?: number;
|
|
115
119
|
/** Test seam for lifecycle races; production always uses createAgentSession. */
|
|
@@ -237,7 +241,9 @@ function makeStructuredOutputTool(
|
|
|
237
241
|
});
|
|
238
242
|
}
|
|
239
243
|
|
|
240
|
-
type AssistantMessage =
|
|
244
|
+
type AssistantMessage = ProgressAssistantMessage;
|
|
245
|
+
|
|
246
|
+
export { transcriptFromMessages };
|
|
241
247
|
|
|
242
248
|
export interface AssistantSettlement {
|
|
243
249
|
stopReason: AssistantMessage["stopReason"];
|
|
@@ -273,28 +279,6 @@ export function agentFailureMessage(
|
|
|
273
279
|
return settlement?.errorMessage ?? promptErrorMessage ?? "Agent failed";
|
|
274
280
|
}
|
|
275
281
|
|
|
276
|
-
function finalOutput(messages: AgentMessage[]): string {
|
|
277
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
278
|
-
const msg = messages[i];
|
|
279
|
-
if (msg.role !== "assistant") continue;
|
|
280
|
-
const text = msg.content
|
|
281
|
-
.filter((part) => part.type === "text")
|
|
282
|
-
.map((part) => part.text)
|
|
283
|
-
.join("\n")
|
|
284
|
-
.trim();
|
|
285
|
-
if (text) return text;
|
|
286
|
-
}
|
|
287
|
-
return "";
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function safeJson(value: unknown): string {
|
|
291
|
-
return safeStringify(value, {
|
|
292
|
-
maxBytes: TRANSCRIPT_ENTRY_MAX_BYTES,
|
|
293
|
-
maxDepth: 12,
|
|
294
|
-
maxNodes: 2_000,
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
|
|
298
282
|
/** Record lifecycle timings without inferring completion from message timestamps. */
|
|
299
283
|
export function recordToolExecutionTiming(
|
|
300
284
|
timings: Map<string, ToolExecutionTiming>,
|
|
@@ -319,133 +303,6 @@ export function recordToolExecutionTiming(
|
|
|
319
303
|
});
|
|
320
304
|
}
|
|
321
305
|
|
|
322
|
-
function toolMetadata(
|
|
323
|
-
toolCallId: string,
|
|
324
|
-
timings: ReadonlyMap<string, ToolExecutionTiming>,
|
|
325
|
-
) {
|
|
326
|
-
const timing = timings.get(toolCallId);
|
|
327
|
-
return {
|
|
328
|
-
toolCallId: truncateUtf8(toolCallId, 1024),
|
|
329
|
-
...(timing?.startedAt === undefined ? {} : { startedAt: timing.startedAt }),
|
|
330
|
-
...(timing?.finishedAt === undefined
|
|
331
|
-
? {}
|
|
332
|
-
: { finishedAt: timing.finishedAt }),
|
|
333
|
-
...(timing?.durationMs === undefined
|
|
334
|
-
? {}
|
|
335
|
-
: { durationMs: timing.durationMs }),
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/** Convert pi messages into a compact, serializable transcript for the UI. */
|
|
340
|
-
export function transcriptFromMessages(
|
|
341
|
-
messages: AgentMessage[],
|
|
342
|
-
toolTimings: ReadonlyMap<string, ToolExecutionTiming> = new Map(),
|
|
343
|
-
): TranscriptEntry[] {
|
|
344
|
-
const entries: TranscriptEntry[] = [];
|
|
345
|
-
for (const message of messages) {
|
|
346
|
-
if (message.role === "user") {
|
|
347
|
-
const text =
|
|
348
|
-
typeof message.content === "string"
|
|
349
|
-
? message.content
|
|
350
|
-
: message.content
|
|
351
|
-
.map((part) =>
|
|
352
|
-
part.type === "text" ? part.text : `[image: ${part.mimeType}]`,
|
|
353
|
-
)
|
|
354
|
-
.join("\n");
|
|
355
|
-
if (text.trim()) {
|
|
356
|
-
entries.push({ role: "user", text, timestamp: message.timestamp });
|
|
357
|
-
}
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
if (message.role === "assistant") {
|
|
362
|
-
for (const part of message.content) {
|
|
363
|
-
if (part.type === "text" && part.text.trim()) {
|
|
364
|
-
entries.push({
|
|
365
|
-
role: "assistant",
|
|
366
|
-
text: part.text,
|
|
367
|
-
timestamp: message.timestamp,
|
|
368
|
-
});
|
|
369
|
-
} else if (part.type === "thinking" && part.thinking.trim()) {
|
|
370
|
-
entries.push({
|
|
371
|
-
role: "thinking",
|
|
372
|
-
text: part.thinking,
|
|
373
|
-
timestamp: message.timestamp,
|
|
374
|
-
});
|
|
375
|
-
} else if (part.type === "toolCall") {
|
|
376
|
-
entries.push({
|
|
377
|
-
role: "tool",
|
|
378
|
-
name: part.name,
|
|
379
|
-
text: safeJson(part.arguments),
|
|
380
|
-
timestamp: message.timestamp,
|
|
381
|
-
...toolMetadata(part.id, toolTimings),
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
continue;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
if (message.role !== "toolResult") continue;
|
|
389
|
-
const text = message.content
|
|
390
|
-
.map((part) =>
|
|
391
|
-
part.type === "text" ? part.text : `[image: ${part.mimeType}]`,
|
|
392
|
-
)
|
|
393
|
-
.join("\n");
|
|
394
|
-
entries.push({
|
|
395
|
-
role: "toolResult",
|
|
396
|
-
name: message.toolName,
|
|
397
|
-
text,
|
|
398
|
-
isError: message.isError,
|
|
399
|
-
timestamp: message.timestamp,
|
|
400
|
-
...toolMetadata(message.toolCallId, toolTimings),
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
const selected =
|
|
404
|
-
entries.length <= TRANSCRIPT_MAX_ENTRIES
|
|
405
|
-
? entries
|
|
406
|
-
: [entries[0], ...entries.slice(-(TRANSCRIPT_MAX_ENTRIES - 1))];
|
|
407
|
-
const bounded: TranscriptEntry[] = [];
|
|
408
|
-
let totalBytes = 0;
|
|
409
|
-
for (const entry of selected) {
|
|
410
|
-
const remaining = TRANSCRIPT_TOTAL_MAX_BYTES - totalBytes;
|
|
411
|
-
if (remaining <= 0) break;
|
|
412
|
-
const text = truncateUtf8(
|
|
413
|
-
entry.text,
|
|
414
|
-
Math.min(TRANSCRIPT_ENTRY_MAX_BYTES, remaining),
|
|
415
|
-
);
|
|
416
|
-
totalBytes += Buffer.byteLength(text, "utf8");
|
|
417
|
-
bounded.push({
|
|
418
|
-
...entry,
|
|
419
|
-
text:
|
|
420
|
-
text === entry.text ? text : `${text}\n[transcript entry truncated]`,
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
if (bounded.length < entries.length) {
|
|
424
|
-
bounded.push({
|
|
425
|
-
role: "toolResult",
|
|
426
|
-
name: "transcript",
|
|
427
|
-
text: `[transcript truncated: retained ${bounded.length} of ${entries.length} entries]`,
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
return bounded;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
function computeUsage(messages: AgentMessage[]): AgentUsage {
|
|
434
|
-
const usage = emptyUsage();
|
|
435
|
-
for (const msg of messages) {
|
|
436
|
-
if (msg.role !== "assistant") continue;
|
|
437
|
-
usage.turns++;
|
|
438
|
-
const u = msg.usage;
|
|
439
|
-
if (!u) continue;
|
|
440
|
-
usage.input += u.input || 0;
|
|
441
|
-
usage.output += u.output || 0;
|
|
442
|
-
usage.cacheRead += u.cacheRead || 0;
|
|
443
|
-
usage.cacheWrite += u.cacheWrite || 0;
|
|
444
|
-
usage.cost += u.cost?.total || 0;
|
|
445
|
-
}
|
|
446
|
-
return usage;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
306
|
function errorText(error: unknown): string {
|
|
450
307
|
return (error instanceof Error ? error.message : String(error)).slice(
|
|
451
308
|
0,
|
|
@@ -459,35 +316,82 @@ function formatTimeout(timeoutMs: number) {
|
|
|
459
316
|
: `${timeoutMs} ms`;
|
|
460
317
|
}
|
|
461
318
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
319
|
+
export function resolveModelProgressTimeoutMs(
|
|
320
|
+
settingsManager: SettingsManager,
|
|
321
|
+
override?: number,
|
|
322
|
+
) {
|
|
323
|
+
if (override !== undefined) return override;
|
|
324
|
+
const configured =
|
|
325
|
+
settingsManager.getProjectSettings().httpIdleTimeoutMs ??
|
|
326
|
+
settingsManager.getGlobalSettings().httpIdleTimeoutMs;
|
|
327
|
+
return typeof configured === "number" && Number.isFinite(configured)
|
|
328
|
+
? Math.max(MODEL_PROGRESS_TIMEOUT_MS, Math.floor(configured))
|
|
329
|
+
: MODEL_PROGRESS_TIMEOUT_MS;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** Abort any provider turn that stops producing model-visible progress. */
|
|
333
|
+
export function createModelProgressWatchdog(
|
|
334
|
+
onTimeout: (error: Error) => Promise<unknown>,
|
|
465
335
|
options: { timeoutMs?: number; model?: string } = {},
|
|
466
336
|
) {
|
|
467
|
-
const timeoutMs = options.timeoutMs ??
|
|
337
|
+
const timeoutMs = options.timeoutMs ?? MODEL_PROGRESS_TIMEOUT_MS;
|
|
468
338
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
339
|
+
let activeTurn = false;
|
|
340
|
+
let closed = false;
|
|
341
|
+
let rejectTimeout!: (error: Error) => void;
|
|
469
342
|
const timeout = new Promise<never>((_resolve, reject) => {
|
|
343
|
+
rejectTimeout = reject;
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const clear = () => {
|
|
347
|
+
if (timer) clearTimeout(timer);
|
|
348
|
+
timer = undefined;
|
|
349
|
+
};
|
|
350
|
+
const schedule = () => {
|
|
351
|
+
clear();
|
|
352
|
+
if (!activeTurn || closed) return;
|
|
470
353
|
// This timer owns the awaited watchdog outcome. Keep it referenced so a
|
|
471
354
|
// short-lived Node 22 process cannot exit with the promise still pending.
|
|
472
355
|
timer = setTimeout(() => {
|
|
473
356
|
timer = undefined;
|
|
357
|
+
activeTurn = false;
|
|
358
|
+
closed = true;
|
|
474
359
|
const model = options.model ? ` for ${options.model}` : "";
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
`Agent received no assistant response event${model} within ${formatTimeout(timeoutMs)}; the provider request may be stalled. Retry the workflow.`,
|
|
478
|
-
),
|
|
360
|
+
const error = new Error(
|
|
361
|
+
`Agent provider turn${model} produced no model-visible progress for ${formatTimeout(timeoutMs)}; the provider request may be stalled. Retry the workflow.`,
|
|
479
362
|
);
|
|
480
|
-
|
|
363
|
+
rejectTimeout(error);
|
|
364
|
+
try {
|
|
365
|
+
void onTimeout(error).catch(() => {});
|
|
366
|
+
} catch {
|
|
367
|
+
// The timeout result remains authoritative even if abort throws before
|
|
368
|
+
// returning its promise; bounded shutdown below gets another chance.
|
|
369
|
+
}
|
|
481
370
|
}, timeoutMs);
|
|
482
|
-
}
|
|
483
|
-
|
|
371
|
+
};
|
|
372
|
+
const armTurn = () => {
|
|
373
|
+
if (closed) return;
|
|
374
|
+
activeTurn = true;
|
|
375
|
+
schedule();
|
|
376
|
+
};
|
|
377
|
+
const markProgress = () => {
|
|
378
|
+
if (!activeTurn || closed) return;
|
|
379
|
+
schedule();
|
|
380
|
+
};
|
|
381
|
+
const completeTurn = () => {
|
|
382
|
+
activeTurn = false;
|
|
383
|
+
clear();
|
|
384
|
+
};
|
|
484
385
|
const cancel = () => {
|
|
485
|
-
|
|
486
|
-
|
|
386
|
+
closed = true;
|
|
387
|
+
activeTurn = false;
|
|
388
|
+
clear();
|
|
487
389
|
};
|
|
488
390
|
|
|
489
391
|
return {
|
|
490
|
-
|
|
392
|
+
armTurn,
|
|
393
|
+
markProgress,
|
|
394
|
+
completeTurn,
|
|
491
395
|
cancel,
|
|
492
396
|
async waitFor<T>(operation: Promise<T>) {
|
|
493
397
|
try {
|
|
@@ -499,13 +403,24 @@ export function createFirstResponseWatchdog(
|
|
|
499
403
|
};
|
|
500
404
|
}
|
|
501
405
|
|
|
502
|
-
function
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
406
|
+
function isModelVisibleProgress(event: AgentSessionEvent) {
|
|
407
|
+
if (event.type !== "message_update" || event.message.role !== "assistant") {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
// Raw transport heartbeats never become AgentSession events. Empty stream,
|
|
411
|
+
// text, and thinking starts likewise cannot keep a provider turn alive.
|
|
412
|
+
const update = event.assistantMessageEvent;
|
|
413
|
+
if (
|
|
414
|
+
update.type === "text_delta" ||
|
|
415
|
+
update.type === "thinking_delta" ||
|
|
416
|
+
update.type === "toolcall_delta"
|
|
417
|
+
) {
|
|
418
|
+
return update.delta.length > 0;
|
|
419
|
+
}
|
|
420
|
+
if (update.type === "text_end" || update.type === "thinking_end") {
|
|
421
|
+
return update.content.length > 0;
|
|
422
|
+
}
|
|
423
|
+
return update.type === "toolcall_start" || update.type === "toolcall_end";
|
|
509
424
|
}
|
|
510
425
|
|
|
511
426
|
export async function runAgent(
|
|
@@ -516,6 +431,46 @@ export async function runAgent(
|
|
|
516
431
|
let customTools: ToolDefinition[] | undefined;
|
|
517
432
|
let session: AgentSession | undefined;
|
|
518
433
|
let unsubscribeToolGuards: (() => void) | undefined;
|
|
434
|
+
let aborted = false;
|
|
435
|
+
let terminalCause: "abort" | "model-progress-timeout" | undefined;
|
|
436
|
+
let modelProgressTimeoutMessage: string | undefined;
|
|
437
|
+
let abortOperation: Promise<unknown> | undefined;
|
|
438
|
+
let rejectForAbort: ((error: Error) => void) | undefined;
|
|
439
|
+
let rejectForProjectionFailure: ((error: Error) => void) | undefined;
|
|
440
|
+
const abortRace = new Promise<never>((_resolve, reject) => {
|
|
441
|
+
rejectForAbort = reject;
|
|
442
|
+
});
|
|
443
|
+
const projectionFailureRace = new Promise<never>((_resolve, reject) => {
|
|
444
|
+
rejectForProjectionFailure = reject;
|
|
445
|
+
});
|
|
446
|
+
// The same promise covers startup and prompting. Keep it observed even when
|
|
447
|
+
// a pre-aborted run returns before either race is installed.
|
|
448
|
+
void abortRace.catch(() => {});
|
|
449
|
+
void projectionFailureRace.catch(() => {});
|
|
450
|
+
const abortError = () =>
|
|
451
|
+
options.signal?.reason instanceof Error
|
|
452
|
+
? options.signal.reason
|
|
453
|
+
: new Error("Agent was aborted");
|
|
454
|
+
const onAbort = () => {
|
|
455
|
+
if (aborted) return;
|
|
456
|
+
aborted = true;
|
|
457
|
+
terminalCause ??= "abort";
|
|
458
|
+
if (session) {
|
|
459
|
+
try {
|
|
460
|
+
abortOperation ??= session.abort();
|
|
461
|
+
void abortOperation.catch(() => {});
|
|
462
|
+
} catch (error) {
|
|
463
|
+
abortOperation = Promise.reject(error);
|
|
464
|
+
void abortOperation.catch(() => {});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
rejectForAbort?.(abortError());
|
|
468
|
+
};
|
|
469
|
+
if (options.signal) {
|
|
470
|
+
if (options.signal.aborted) onAbort();
|
|
471
|
+
else options.signal.addEventListener("abort", onAbort, { once: true });
|
|
472
|
+
}
|
|
473
|
+
|
|
519
474
|
try {
|
|
520
475
|
customTools =
|
|
521
476
|
options.schema !== undefined
|
|
@@ -529,7 +484,8 @@ export async function runAgent(
|
|
|
529
484
|
options.tools,
|
|
530
485
|
customTools !== undefined,
|
|
531
486
|
);
|
|
532
|
-
(
|
|
487
|
+
if (aborted) throw abortError();
|
|
488
|
+
const sessionCreation = (options.sessionFactory ?? createAgentSession)({
|
|
533
489
|
cwd: options.cwd,
|
|
534
490
|
...(options.model ? { model: options.model } : {}),
|
|
535
491
|
...(options.thinkingLevel
|
|
@@ -541,8 +497,28 @@ export async function runAgent(
|
|
|
541
497
|
options.sessionManager ?? SessionManager.inMemory(options.cwd),
|
|
542
498
|
...(customTools ? { customTools } : {}),
|
|
543
499
|
...childToolPolicy(childTools),
|
|
544
|
-
})
|
|
545
|
-
|
|
500
|
+
});
|
|
501
|
+
// Promise.race cannot cancel the factory. If cancellation wins, retain
|
|
502
|
+
// ownership of a late session and dispose it without re-entering startup.
|
|
503
|
+
void sessionCreation
|
|
504
|
+
.then(
|
|
505
|
+
({ session: lateSession }) => {
|
|
506
|
+
if (!aborted) return;
|
|
507
|
+
return shutdownAndDisposeChildSession(lateSession, {
|
|
508
|
+
abort: true,
|
|
509
|
+
timeoutMs: options.shutdownTimeoutMs,
|
|
510
|
+
});
|
|
511
|
+
},
|
|
512
|
+
() => undefined,
|
|
513
|
+
)
|
|
514
|
+
.catch(() => {});
|
|
515
|
+
({ session } = await Promise.race([sessionCreation, abortRace]));
|
|
516
|
+
if (aborted) throw abortError();
|
|
517
|
+
await Promise.race([
|
|
518
|
+
bindChildSessionExtensions(session, childTools),
|
|
519
|
+
abortRace,
|
|
520
|
+
]);
|
|
521
|
+
if (aborted) throw abortError();
|
|
546
522
|
unsubscribeToolGuards = guardWorkflowChildTools(
|
|
547
523
|
session,
|
|
548
524
|
options.toolCallTimeoutMs,
|
|
@@ -551,13 +527,29 @@ export async function runAgent(
|
|
|
551
527
|
} catch (error) {
|
|
552
528
|
settled = true;
|
|
553
529
|
unsubscribeToolGuards?.();
|
|
530
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
554
531
|
const cleanup = session
|
|
555
532
|
? await shutdownAndDisposeChildSession(session, {
|
|
556
533
|
abort: true,
|
|
534
|
+
abortOperation,
|
|
557
535
|
timeoutMs: options.shutdownTimeoutMs,
|
|
558
536
|
})
|
|
559
537
|
: undefined;
|
|
560
538
|
const cleanupError = cleanup?.errors.join("; ");
|
|
539
|
+
if (aborted) {
|
|
540
|
+
return {
|
|
541
|
+
ok: false,
|
|
542
|
+
output: "",
|
|
543
|
+
error: cleanupError
|
|
544
|
+
? `Agent was aborted; Cleanup failed: ${cleanupError}`
|
|
545
|
+
: "Agent was aborted",
|
|
546
|
+
aborted: true,
|
|
547
|
+
usage: emptyUsage(),
|
|
548
|
+
model: options.model?.id,
|
|
549
|
+
contextWindow: options.model?.contextWindow,
|
|
550
|
+
transcript: [],
|
|
551
|
+
};
|
|
552
|
+
}
|
|
561
553
|
return {
|
|
562
554
|
ok: false,
|
|
563
555
|
output: "",
|
|
@@ -571,6 +563,7 @@ export async function runAgent(
|
|
|
571
563
|
}
|
|
572
564
|
|
|
573
565
|
const childSession = session;
|
|
566
|
+
const toolRenderer = new AgentToolRenderLedger();
|
|
574
567
|
let usage = emptyUsage();
|
|
575
568
|
let modelId = childSession.model?.id ?? options.model?.id;
|
|
576
569
|
let contextWindow = childSession.model?.contextWindow;
|
|
@@ -578,21 +571,50 @@ export async function runAgent(
|
|
|
578
571
|
let promptErrorMessage: string | undefined;
|
|
579
572
|
const toolTimings = new Map<string, ToolExecutionTiming>();
|
|
580
573
|
|
|
581
|
-
const
|
|
582
|
-
const
|
|
583
|
-
|
|
574
|
+
const captureToolRenderData = (messages: readonly AgentMessage[]) => {
|
|
575
|
+
for (const message of messages) {
|
|
576
|
+
if (message.role === "assistant") {
|
|
577
|
+
for (const part of message.content) {
|
|
578
|
+
if (part.type !== "toolCall") continue;
|
|
579
|
+
toolRenderer.start(
|
|
580
|
+
part.id,
|
|
581
|
+
part.name,
|
|
582
|
+
part.arguments,
|
|
583
|
+
childSession.getToolDefinition(part.name),
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
} else if (message.role === "toolResult") {
|
|
587
|
+
toolRenderer.end(
|
|
588
|
+
message.toolCallId,
|
|
589
|
+
message.toolName,
|
|
590
|
+
message,
|
|
591
|
+
message.isError,
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
};
|
|
584
596
|
|
|
597
|
+
const refreshModel = (latestAssistant?: AssistantMessage) => {
|
|
585
598
|
const sessionModel = childSession.model;
|
|
586
599
|
modelId = sessionModel?.id ?? modelId;
|
|
587
600
|
contextWindow = sessionModel?.contextWindow ?? contextWindow;
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
601
|
+
if (!latestAssistant) return;
|
|
602
|
+
const responseMatchesSession =
|
|
603
|
+
!sessionModel ||
|
|
604
|
+
(latestAssistant.provider === sessionModel.provider &&
|
|
605
|
+
latestAssistant.model === sessionModel.id);
|
|
606
|
+
const reportedId = latestAssistant.responseModel ?? latestAssistant.model;
|
|
607
|
+
const reportedModel = responseMatchesSession
|
|
608
|
+
? options.modelRegistry.find(latestAssistant.provider, reportedId)
|
|
609
|
+
: undefined;
|
|
610
|
+
if (reportedModel) {
|
|
611
|
+
modelId = reportedModel.id;
|
|
612
|
+
contextWindow = reportedModel.contextWindow;
|
|
595
613
|
}
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
const sampleContextTokens = () => {
|
|
617
|
+
const context = childSession.getContextUsage();
|
|
596
618
|
if (
|
|
597
619
|
typeof context?.contextWindow === "number" &&
|
|
598
620
|
Number.isFinite(context.contextWindow) &&
|
|
@@ -600,127 +622,194 @@ export async function runAgent(
|
|
|
600
622
|
) {
|
|
601
623
|
contextWindow = context.contextWindow;
|
|
602
624
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
// latest response rather than a hardcoded/configured guess.
|
|
610
|
-
const responseMatchesSession =
|
|
611
|
-
!sessionModel ||
|
|
612
|
-
(msg.provider === sessionModel.provider &&
|
|
613
|
-
msg.model === sessionModel.id);
|
|
614
|
-
const reportedId = msg.responseModel ?? msg.model;
|
|
615
|
-
const reportedModel = responseMatchesSession
|
|
616
|
-
? options.modelRegistry.find(msg.provider, reportedId)
|
|
617
|
-
: undefined;
|
|
618
|
-
if (reportedModel) {
|
|
619
|
-
modelId = reportedModel.id;
|
|
620
|
-
contextWindow = reportedModel.contextWindow;
|
|
621
|
-
}
|
|
622
|
-
break;
|
|
625
|
+
if (
|
|
626
|
+
typeof context?.tokens === "number" &&
|
|
627
|
+
Number.isFinite(context.tokens) &&
|
|
628
|
+
context.tokens >= 0
|
|
629
|
+
) {
|
|
630
|
+
return context.tokens;
|
|
623
631
|
}
|
|
632
|
+
return context?.tokens === null ? null : undefined;
|
|
624
633
|
};
|
|
625
634
|
|
|
626
|
-
|
|
627
|
-
let
|
|
635
|
+
const projection = new AgentProgressProjection();
|
|
636
|
+
let lastProjection = projection.snapshot(toolTimings);
|
|
637
|
+
|
|
638
|
+
const snapshotProjection = () => {
|
|
639
|
+
const snapshot = projection.snapshot(toolTimings);
|
|
640
|
+
usage = snapshot.usage;
|
|
641
|
+
refreshModel(snapshot.latestAssistant);
|
|
642
|
+
lastProjection = snapshot;
|
|
643
|
+
return snapshot;
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
const reconcileProjection = () => {
|
|
647
|
+
projection.replace(childSession.messages, sampleContextTokens());
|
|
648
|
+
return snapshotProjection();
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
const emitProgress = () => {
|
|
652
|
+
const snapshot = snapshotProjection();
|
|
653
|
+
options.onProgress?.({
|
|
654
|
+
preview: snapshot.preview,
|
|
655
|
+
usage,
|
|
656
|
+
model: modelId,
|
|
657
|
+
contextWindow,
|
|
658
|
+
transcript: bindWorkflowToolRenderer(snapshot.transcript, toolRenderer),
|
|
659
|
+
});
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
let armModelProgress = () => {};
|
|
663
|
+
let markModelProgress = () => {};
|
|
664
|
+
let completeModelTurn = () => {};
|
|
665
|
+
let cancelModelProgressWatchdog = () => {};
|
|
666
|
+
let compactionReconcileQueued = false;
|
|
667
|
+
const queueCompactionReconcile = () => {
|
|
668
|
+
if (compactionReconcileQueued) return;
|
|
669
|
+
compactionReconcileQueued = true;
|
|
670
|
+
queueMicrotask(() => {
|
|
671
|
+
compactionReconcileQueued = false;
|
|
672
|
+
if (settled) return;
|
|
673
|
+
// Pi may synchronously remove a restored overflow assistant immediately
|
|
674
|
+
// after compaction_end. Read canonical state after that mutation.
|
|
675
|
+
try {
|
|
676
|
+
reconcileProjection();
|
|
677
|
+
emitProgress();
|
|
678
|
+
} catch (error) {
|
|
679
|
+
promptErrorMessage ??= `Failed to reconcile agent progress after compaction: ${errorText(error)}`;
|
|
680
|
+
rejectForProjectionFailure?.(new Error(promptErrorMessage));
|
|
681
|
+
try {
|
|
682
|
+
abortOperation ??= childSession.abort();
|
|
683
|
+
void abortOperation.catch(() => {});
|
|
684
|
+
} catch {
|
|
685
|
+
// The projection failure remains authoritative; bounded cleanup gets
|
|
686
|
+
// another chance to abort and dispose the child below.
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
};
|
|
628
691
|
const unsubscribe = childSession.subscribe((event) => {
|
|
629
692
|
if (settled) return;
|
|
630
|
-
if (
|
|
693
|
+
if (event.type === "turn_start") armModelProgress();
|
|
694
|
+
if (event.type === "tool_execution_start") {
|
|
695
|
+
toolRenderer.start(
|
|
696
|
+
event.toolCallId,
|
|
697
|
+
event.toolName,
|
|
698
|
+
event.args,
|
|
699
|
+
childSession.getToolDefinition(event.toolName),
|
|
700
|
+
);
|
|
701
|
+
} else if (event.type === "tool_execution_update") {
|
|
702
|
+
toolRenderer.update(
|
|
703
|
+
event.toolCallId,
|
|
704
|
+
event.toolName,
|
|
705
|
+
event.args,
|
|
706
|
+
event.partialResult,
|
|
707
|
+
);
|
|
708
|
+
} else if (event.type === "tool_execution_end") {
|
|
709
|
+
toolRenderer.end(
|
|
710
|
+
event.toolCallId,
|
|
711
|
+
event.toolName,
|
|
712
|
+
event.result,
|
|
713
|
+
event.isError,
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
if (isModelVisibleProgress(event)) markModelProgress();
|
|
717
|
+
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
718
|
+
completeModelTurn();
|
|
719
|
+
}
|
|
631
720
|
if (event.type === "message_end") {
|
|
632
721
|
assistantSettlement = observeAssistantSettlement(
|
|
633
722
|
assistantSettlement,
|
|
634
723
|
event.message,
|
|
635
724
|
);
|
|
725
|
+
// Pi publishes the finalized message before tool lifecycle delivery.
|
|
726
|
+
// Hydrate native rendering from this one message without reading history.
|
|
727
|
+
captureToolRenderData([event.message]);
|
|
728
|
+
projection.append(event.message);
|
|
636
729
|
}
|
|
637
730
|
if (
|
|
638
731
|
event.type === "tool_execution_start" ||
|
|
639
732
|
event.type === "tool_execution_end"
|
|
640
733
|
) {
|
|
641
734
|
recordToolExecutionTiming(toolTimings, event);
|
|
642
|
-
} else if (
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
) {
|
|
735
|
+
} else if (event.type === "compaction_end") {
|
|
736
|
+
queueCompactionReconcile();
|
|
737
|
+
return;
|
|
738
|
+
} else if (event.type !== "message_end") {
|
|
646
739
|
return;
|
|
647
740
|
}
|
|
648
|
-
|
|
649
|
-
options.onProgress?.({
|
|
650
|
-
preview: finalOutput(childSession.messages),
|
|
651
|
-
usage,
|
|
652
|
-
model: modelId,
|
|
653
|
-
contextWindow,
|
|
654
|
-
transcript: transcriptFromMessages(childSession.messages, toolTimings),
|
|
655
|
-
});
|
|
741
|
+
emitProgress();
|
|
656
742
|
});
|
|
657
743
|
|
|
658
|
-
let aborted = false;
|
|
659
|
-
let abortOperation: Promise<unknown> | undefined;
|
|
660
|
-
let rejectForAbort: ((error: Error) => void) | undefined;
|
|
661
|
-
const abortRace = new Promise<never>((_resolve, reject) => {
|
|
662
|
-
rejectForAbort = reject;
|
|
663
|
-
});
|
|
664
|
-
const onAbort = () => {
|
|
665
|
-
if (aborted) return;
|
|
666
|
-
aborted = true;
|
|
667
|
-
try {
|
|
668
|
-
abortOperation ??= childSession.abort();
|
|
669
|
-
void abortOperation.catch(() => {});
|
|
670
|
-
} catch (error) {
|
|
671
|
-
abortOperation = Promise.reject(error);
|
|
672
|
-
void abortOperation.catch(() => {});
|
|
673
|
-
}
|
|
674
|
-
rejectForAbort?.(
|
|
675
|
-
options.signal?.reason instanceof Error
|
|
676
|
-
? options.signal.reason
|
|
677
|
-
: new Error("Agent was aborted"),
|
|
678
|
-
);
|
|
679
|
-
};
|
|
680
|
-
if (options.signal) {
|
|
681
|
-
if (options.signal.aborted) onAbort();
|
|
682
|
-
else options.signal.addEventListener("abort", onAbort, { once: true });
|
|
683
|
-
}
|
|
684
|
-
|
|
685
744
|
let output = "";
|
|
686
745
|
let transcript: TranscriptEntry[] = [];
|
|
687
746
|
let cleanupErrors: string[] = [];
|
|
688
747
|
try {
|
|
748
|
+
// Operator reuse may restore messages before this activation subscribes.
|
|
749
|
+
// Hydrate both bounded projections once; ordinary progress never rescans it.
|
|
750
|
+
projection.replace(childSession.messages, sampleContextTokens());
|
|
751
|
+
captureToolRenderData(childSession.messages);
|
|
752
|
+
snapshotProjection();
|
|
689
753
|
if (!aborted) {
|
|
690
|
-
const watchdog =
|
|
691
|
-
() => {
|
|
754
|
+
const watchdog = createModelProgressWatchdog(
|
|
755
|
+
(error) => {
|
|
756
|
+
terminalCause ??= "model-progress-timeout";
|
|
757
|
+
if (terminalCause === "model-progress-timeout") {
|
|
758
|
+
modelProgressTimeoutMessage ??= error.message;
|
|
759
|
+
}
|
|
692
760
|
abortOperation ??= childSession.abort();
|
|
693
761
|
void abortOperation.catch(() => {});
|
|
694
762
|
return abortOperation;
|
|
695
763
|
},
|
|
696
764
|
{
|
|
697
|
-
timeoutMs:
|
|
765
|
+
timeoutMs: resolveModelProgressTimeoutMs(
|
|
766
|
+
options.settingsManager,
|
|
767
|
+
options.modelProgressTimeoutMs,
|
|
768
|
+
),
|
|
698
769
|
model: modelId,
|
|
699
770
|
},
|
|
700
771
|
);
|
|
701
|
-
|
|
702
|
-
|
|
772
|
+
armModelProgress = watchdog.armTurn;
|
|
773
|
+
markModelProgress = watchdog.markProgress;
|
|
774
|
+
completeModelTurn = watchdog.completeTurn;
|
|
775
|
+
cancelModelProgressWatchdog = watchdog.cancel;
|
|
703
776
|
await Promise.race([
|
|
704
777
|
watchdog.waitFor(
|
|
705
778
|
childSession.prompt(buildWorkflowAgentPrompt(options.prompt)),
|
|
706
779
|
),
|
|
707
780
|
abortRace,
|
|
781
|
+
projectionFailureRace,
|
|
708
782
|
]);
|
|
709
783
|
}
|
|
710
784
|
} catch (error) {
|
|
711
|
-
promptErrorMessage
|
|
785
|
+
promptErrorMessage ??= errorText(error);
|
|
712
786
|
} finally {
|
|
713
|
-
|
|
787
|
+
cancelModelProgressWatchdog();
|
|
714
788
|
options.signal?.removeEventListener("abort", onAbort);
|
|
715
789
|
settled = true;
|
|
716
790
|
unsubscribe();
|
|
717
791
|
unsubscribeToolGuards?.();
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
792
|
+
try {
|
|
793
|
+
const finalProjection = reconcileProjection();
|
|
794
|
+
output = truncateUtf8(finalProjection.preview, AGENT_OUTPUT_MAX_BYTES);
|
|
795
|
+
transcript = bindWorkflowToolRenderer(
|
|
796
|
+
finalProjection.transcript,
|
|
797
|
+
toolRenderer,
|
|
798
|
+
);
|
|
799
|
+
} catch (error) {
|
|
800
|
+
promptErrorMessage ??= `Failed to reconcile final agent progress: ${errorText(error)}`;
|
|
801
|
+
// A broken canonical accessor must not prevent owned-session cleanup.
|
|
802
|
+
// Preserve the last projection that was fully observed instead.
|
|
803
|
+
try {
|
|
804
|
+
output = truncateUtf8(lastProjection.preview, AGENT_OUTPUT_MAX_BYTES);
|
|
805
|
+
transcript = bindWorkflowToolRenderer(
|
|
806
|
+
lastProjection.transcript,
|
|
807
|
+
toolRenderer,
|
|
808
|
+
);
|
|
809
|
+
} catch (fallbackError) {
|
|
810
|
+
promptErrorMessage += `; failed to render last progress: ${errorText(fallbackError)}`;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
724
813
|
const cleanup = await shutdownAndDisposeChildSession(childSession, {
|
|
725
814
|
abort: aborted || promptErrorMessage !== undefined,
|
|
726
815
|
abortOperation,
|
|
@@ -734,7 +823,11 @@ export async function runAgent(
|
|
|
734
823
|
? `Cleanup failed: ${cleanupErrors.join("; ")}`
|
|
735
824
|
: undefined;
|
|
736
825
|
|
|
737
|
-
if (
|
|
826
|
+
if (
|
|
827
|
+
terminalCause === "abort" ||
|
|
828
|
+
(terminalCause === undefined &&
|
|
829
|
+
assistantSettlement?.stopReason === "aborted")
|
|
830
|
+
) {
|
|
738
831
|
return {
|
|
739
832
|
ok: false,
|
|
740
833
|
output,
|
|
@@ -751,7 +844,9 @@ export async function runAgent(
|
|
|
751
844
|
}
|
|
752
845
|
|
|
753
846
|
const failureMessage =
|
|
754
|
-
|
|
847
|
+
(terminalCause === "model-progress-timeout"
|
|
848
|
+
? modelProgressTimeoutMessage
|
|
849
|
+
: agentFailureMessage(assistantSettlement, promptErrorMessage)) ??
|
|
755
850
|
cleanupError;
|
|
756
851
|
if (failureMessage !== undefined) {
|
|
757
852
|
return {
|
|
@@ -781,6 +876,20 @@ export async function runAgent(
|
|
|
781
876
|
};
|
|
782
877
|
}
|
|
783
878
|
|
|
879
|
+
if (options.schema === undefined && assistantSettlement === undefined) {
|
|
880
|
+
return {
|
|
881
|
+
ok: false,
|
|
882
|
+
output,
|
|
883
|
+
structured,
|
|
884
|
+
error: "Agent finished without an assistant response.",
|
|
885
|
+
aborted: false,
|
|
886
|
+
usage,
|
|
887
|
+
model: modelId,
|
|
888
|
+
contextWindow,
|
|
889
|
+
transcript,
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
|
|
784
893
|
return {
|
|
785
894
|
ok: true,
|
|
786
895
|
output,
|