pi-subagents 0.60.0 → 0.61.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/CHANGELOG.md +32 -0
- package/docs/agents.md +2 -2
- package/docs/configuration.md +9 -5
- package/docs/extension-api.md +14 -7
- package/docs/models.md +1 -1
- package/docs/observability.md +1 -1
- package/docs/tool-reference.md +12 -3
- package/docs/workflows.md +14 -13
- package/install.mjs +2 -1
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +6 -4
- package/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
- package/skills/pi-subagents/references/execution-controls.md +42 -11
- package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +4 -4
- package/skills/pi-subagents/references/review-and-validation.md +1 -1
- package/src/agents/agent-management.ts +102 -63
- package/src/agents/agents.ts +527 -221
- package/src/api/background-work.ts +7 -2
- package/src/api/external-runs.ts +67 -4
- package/src/api/preflight.ts +13 -8
- package/src/api/shared-types.ts +1 -0
- package/src/extension/index.ts +7 -4
- package/src/extension/public-execution.ts +47 -4
- package/src/extension/rpc.ts +62 -4
- package/src/extension/schemas.ts +11 -7
- package/src/extension/tool-description.ts +14 -18
- package/src/runs/background/async-execution.ts +51 -35
- package/src/runs/background/async-job-tracker.ts +62 -3
- package/src/runs/background/async-resume.ts +3 -1
- package/src/runs/background/async-status.ts +59 -9
- package/src/runs/background/auto-drain.ts +1 -1
- package/src/runs/background/fleet-view.ts +1 -1
- package/src/runs/background/result-watcher.ts +1 -1
- package/src/runs/background/resume-guidance.ts +1 -1
- package/src/runs/background/run-status.ts +2 -2
- package/src/runs/background/subagent-runner.ts +1 -2
- package/src/runs/background/subagent-wait.ts +20 -21
- package/src/runs/background/wait-completions.ts +1 -1
- package/src/runs/background/wait-tool.ts +24 -18
- package/src/runs/foreground/execution.ts +61 -2
- package/src/runs/foreground/subagent-executor.ts +178 -58
- package/src/runs/shared/acceptance.ts +43 -18
- package/src/runs/shared/host-step-status.ts +1 -0
- package/src/runs/shared/model-fallback.ts +61 -17
- package/src/runs/shared/permissions.ts +1 -1
- package/src/runs/shared/tool-timeout.ts +1 -1
- package/src/runs/shared/workflow-graph.ts +3 -2
- package/src/shared/types.ts +40 -3
- package/src/shared/workflow-child-permit.ts +91 -0
- package/src/slash/prompt-template-bridge.ts +37 -1
- package/src/slash/slash-commands.ts +18 -26
- package/src/tui/render.ts +95 -52
- package/src/workflows/scripted-workflow.ts +153 -4
- package/src/workflows/workflow-child-summary.ts +1 -1
- package/src/workflows/workflow-receipt.ts +41 -4
- package/src/workflows/workflow-resources.ts +150 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `
|
|
2
|
+
* `bg_wait` tool: block the current turn until outstanding async runs
|
|
3
3
|
* or a named remembered detached foreground run finishes.
|
|
4
4
|
*
|
|
5
5
|
* Background subagent runs are detached. In an interactive session the parent
|
|
@@ -8,33 +8,33 @@
|
|
|
8
8
|
* cannot work at all non-interactively (`pi -p ...`), where the run is a single
|
|
9
9
|
* turn: once the turn ends there is nothing left to receive the notification.
|
|
10
10
|
*
|
|
11
|
-
* `
|
|
11
|
+
* `bg_wait` closes that gap. It keeps the turn alive until a tracked async
|
|
12
12
|
* run for this session reaches a terminal state (complete / failed / paused),
|
|
13
13
|
* the caller-supplied timeout elapses, or the turn is aborted. Because it awaits
|
|
14
14
|
* inside the turn, the completion the model was told to wait for is actually
|
|
15
15
|
* observed before the tool returns.
|
|
16
16
|
*
|
|
17
|
-
* By default `
|
|
17
|
+
* By default `bg_wait` returns as soon as ONE run finishes, so a fleet
|
|
18
18
|
* manager can use it in a rolling-replacement loop: launch N workers, wait for
|
|
19
|
-
* the next one to finish, spawn its replacement, then call `
|
|
19
|
+
* the next one to finish, spawn its replacement, then call `bg_wait`
|
|
20
20
|
* again — keeping N in flight instead of draining to zero between batches.
|
|
21
21
|
* Pass `all: true` to block until every tracked async run is terminal, or `id`
|
|
22
22
|
* to block on one specific async or remembered detached foreground run.
|
|
23
23
|
*
|
|
24
|
-
* `
|
|
24
|
+
* `bg_wait` also returns when a run needs attention — not just on
|
|
25
25
|
* completion. A child that goes idle or blocks for a decision surfaces
|
|
26
26
|
* `needs_attention` (the same signal Pi shows as a control notice and,
|
|
27
|
-
* interactively, wakes the parent with). Since `
|
|
27
|
+
* interactively, wakes the parent with). Since `bg_wait` is used exactly
|
|
28
28
|
* where there is no next turn to receive that notice, it must break on it too,
|
|
29
29
|
* or a stuck child would stall the loop until the timeout. Attention runs are
|
|
30
30
|
* reported so the caller can inspect / nudge / resume / interrupt them.
|
|
31
31
|
*
|
|
32
|
-
* Wake mechanism: when given Pi's event bus (`deps.events`), `
|
|
32
|
+
* Wake mechanism: when given Pi's event bus (`deps.events`), `bg_wait`
|
|
33
33
|
* subscribes to the subagent completion/control channels and wakes the instant
|
|
34
34
|
* any fires, rather than waiting out a fixed poll interval. A poll still runs
|
|
35
35
|
* on the interval as a reconciliation fallback (crashed runners, missed
|
|
36
36
|
* events), and the poll is the source of truth for what actually changed — the
|
|
37
|
-
* event only ends the sleep early. With no bus, `
|
|
37
|
+
* event only ends the sleep early. With no bus, `bg_wait` degrades to pure
|
|
38
38
|
* polling.
|
|
39
39
|
*/
|
|
40
40
|
|
|
@@ -82,9 +82,8 @@ export interface SubagentWaitParams {
|
|
|
82
82
|
nonBlocking?: boolean;
|
|
83
83
|
/**
|
|
84
84
|
* When true, block until EVERY active run in this session (or matching `id`)
|
|
85
|
-
* is terminal. Default false: return
|
|
86
|
-
*
|
|
87
|
-
* targets a single run.
|
|
85
|
+
* is terminal. Default false: return when the first tracked run or provider
|
|
86
|
+
* item finishes or needs attention. Ignored when `id` targets a single run.
|
|
88
87
|
*/
|
|
89
88
|
all?: boolean;
|
|
90
89
|
/** Give up after this many milliseconds. Defaults to waitTool.defaultTimeoutMs, then 30 minutes. */
|
|
@@ -241,7 +240,7 @@ function foregroundChildrenNeedingAttention(run: ForegroundResumeRun, indices: S
|
|
|
241
240
|
function formatForegroundAttention(run: ForegroundResumeRun, children: ReturnType<typeof foregroundChildrenNeedingAttention>, elapsedMs: number): AgentToolResult<Details> {
|
|
242
241
|
const childList = children.map((child) => `${child.agent}${child.index !== undefined ? `#${child.index}` : ""}`).join(", ");
|
|
243
242
|
return result(
|
|
244
|
-
`Waited ${formatDuration(elapsedMs)} for remembered detached foreground run "${run.runId}"; attention required. ${children.length} child run(s) need attention: ${childList}. Reply to any pending supervisor request, then call
|
|
243
|
+
`Waited ${formatDuration(elapsedMs)} for remembered detached foreground run "${run.runId}"; attention required. ${children.length} child run(s) need attention: ${childList}. Reply to any pending supervisor request, then call bg_wait({ id: "${run.runId}" }) again or inspect status; do not resume or launch a replacement while it remains detached.`,
|
|
245
244
|
);
|
|
246
245
|
}
|
|
247
246
|
|
|
@@ -262,7 +261,7 @@ function backgroundWorkIdentity(item: RegisteredBackgroundWorkItem): string {
|
|
|
262
261
|
|
|
263
262
|
function backgroundWorkForSession(deps: SubagentWaitDeps, nowMs: number): BackgroundWorkSnapshot {
|
|
264
263
|
const sessionId = deps.state.currentSessionId;
|
|
265
|
-
if (!sessionId) throw new Error("
|
|
264
|
+
if (!sessionId) throw new Error("bg_wait requires an active session identity to scope background work safely.");
|
|
266
265
|
return deps.backgroundWork?.snapshot(sessionId, nowMs) ?? snapshotBackgroundWork(sessionId, nowMs);
|
|
267
266
|
}
|
|
268
267
|
|
|
@@ -376,7 +375,7 @@ function windowElapsedResult(
|
|
|
376
375
|
};
|
|
377
376
|
}
|
|
378
377
|
|
|
379
|
-
/** Build the live status shown while async work keeps
|
|
378
|
+
/** Build the live status shown while async work keeps bg_wait blocked. */
|
|
380
379
|
function asyncWaitUpdate(runs: AsyncRunSummary[], providerCount: number, elapsedMs: number): AgentToolResult<Details> {
|
|
381
380
|
const activity = runs.flatMap((run) => {
|
|
382
381
|
const activeSteps = run.steps.filter((step) => step.status === "pending" || step.status === "running");
|
|
@@ -524,7 +523,7 @@ async function waitForDetachedForegroundRun(
|
|
|
524
523
|
}
|
|
525
524
|
if (now() - startedAt >= timeoutMs) {
|
|
526
525
|
return windowElapsedResult(
|
|
527
|
-
`Wait window elapsed after ${formatDuration(timeoutMs)} with remembered foreground run "${run.runId}" still detached. Reply to any pending supervisor request, then call
|
|
526
|
+
`Wait window elapsed after ${formatDuration(timeoutMs)} with remembered foreground run "${run.runId}" still detached. Reply to any pending supervisor request, then call bg_wait({ id: "${run.runId}" }) again or inspect status; do not resume or launch a replacement while it remains detached.`,
|
|
528
527
|
[run.runId],
|
|
529
528
|
);
|
|
530
529
|
}
|
|
@@ -543,10 +542,10 @@ export async function waitForSubagents(
|
|
|
543
542
|
deps: SubagentWaitDeps,
|
|
544
543
|
): Promise<AgentToolResult<Details>> {
|
|
545
544
|
if (deps.enabled === false) {
|
|
546
|
-
return result("
|
|
545
|
+
return result("bg_wait is disabled by config.waitTool or PI_SUBAGENT_WAIT_TOOL_ENABLED; returning immediately without blocking background work. Active work keeps going, and you can inspect subagents with subagent({ action: \"status\" }) or rely on completion notifications.");
|
|
547
546
|
}
|
|
548
547
|
if (!deps.state.currentSessionId) {
|
|
549
|
-
return result("
|
|
548
|
+
return result("bg_wait requires an active session identity to scope background work safely.", true);
|
|
550
549
|
}
|
|
551
550
|
|
|
552
551
|
const now = deps.now ?? Date.now;
|
|
@@ -587,7 +586,7 @@ export async function waitForSubagents(
|
|
|
587
586
|
const selected = matches[0];
|
|
588
587
|
if (selected && params.nonBlocking) {
|
|
589
588
|
if (!deps.subscribe) {
|
|
590
|
-
return result("Non-blocking wait subscriptions require a long-lived interactive subagent runtime; this runtime can only use blocking
|
|
589
|
+
return result("Non-blocking wait subscriptions require a long-lived interactive subagent runtime; this runtime can only use blocking bg_wait calls.", true);
|
|
591
590
|
}
|
|
592
591
|
try {
|
|
593
592
|
const registration = deps.subscribe({ targetKind: selected.kind, runId: selected.id, requestedId: params.id, timeoutMs });
|
|
@@ -641,7 +640,7 @@ export async function waitForSubagents(
|
|
|
641
640
|
}
|
|
642
641
|
if (now() - startedAt >= timeoutMs) {
|
|
643
642
|
return windowElapsedResult(
|
|
644
|
-
`Wait window elapsed after ${formatDuration(timeoutMs)} with ${activeInitialRuns.length} async run(s) and ${activeInitialProviderItems.length} provider item(s) still active: ${stillActive}. The work keeps going; call
|
|
643
|
+
`Wait window elapsed after ${formatDuration(timeoutMs)} with ${activeInitialRuns.length} async run(s) and ${activeInitialProviderItems.length} provider item(s) still active: ${stillActive}. The work keeps going; call bg_wait again or inspect subagent status.`,
|
|
645
644
|
activeInitialRuns.map((run) => run.id),
|
|
646
645
|
activeInitialProviderItems,
|
|
647
646
|
);
|
|
@@ -653,7 +652,7 @@ export async function waitForSubagents(
|
|
|
653
652
|
providerSnapshot = params.id ? providerSnapshot : backgroundWorkForSession(deps, now());
|
|
654
653
|
for (const provider of initialProviderNames) {
|
|
655
654
|
if (!providerSnapshot.providers.includes(provider)) {
|
|
656
|
-
return result(`Background-work provider '${provider}' disappeared while
|
|
655
|
+
return result(`Background-work provider '${provider}' disappeared while bg_wait was tracking its active work; completion cannot be confirmed.`, true);
|
|
657
656
|
}
|
|
658
657
|
}
|
|
659
658
|
providerActive = providerSnapshot.items;
|
|
@@ -711,7 +710,7 @@ export async function waitForSubagents(
|
|
|
711
710
|
const finishedCount = finishedAsyncCount + providerFinishedCount;
|
|
712
711
|
const subject = initialProviderIds.size === 0 ? "run(s)" : "item(s)";
|
|
713
712
|
const remainder = stillRunning > 0
|
|
714
|
-
? ` ${stillRunning} ${subject} still in flight — call
|
|
713
|
+
? ` ${stillRunning} ${subject} still in flight — call bg_wait again to catch the next one.`
|
|
715
714
|
: relevantAttention.length > 0
|
|
716
715
|
? " No other work is waitable until attention is handled."
|
|
717
716
|
: initialProviderIds.size === 0 ? " No runs remain in flight." : " No work remains in flight.";
|
|
@@ -99,7 +99,7 @@ export function toWaitCompletion(data: Record<string, unknown>, runId: string):
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
|
-
* Record a consumed terminal payload for later surfacing by
|
|
102
|
+
* Record a consumed terminal payload for later surfacing by bg_wait, pruning
|
|
103
103
|
* stale entries with the same TTL that dedupes completion notifications. The result
|
|
104
104
|
* file is deleted after delivery, so this record is the only in-process source once
|
|
105
105
|
* the watcher has consumed it.
|
|
@@ -12,32 +12,38 @@ export function registerWaitTool(
|
|
|
12
12
|
subscriptions?: Pick<WaitSubscriptionManager, "arm">,
|
|
13
13
|
defaultTimeoutMs?: number,
|
|
14
14
|
): void {
|
|
15
|
-
const
|
|
16
|
-
name: "subagent_wait",
|
|
17
|
-
label: "Subagent Wait",
|
|
18
|
-
description: `Block until background work owned by this session changes, then return.
|
|
15
|
+
const description = `Wait for background, provider, or detached work that has no native completion notification, then return.
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
Ordinary async subagent runs already notify this session natively when they complete or need attention. In an interactive chat, return control instead of calling this merely to wait. Use this tool for provider jobs, remembered detached foreground runs, or other background work without a native notification path. Headless runs auto-drain current-session subagent work at agent_end; use this tool only when the current turn must receive non-notifying background work results.
|
|
21
18
|
|
|
22
19
|
• { } — return when the first initially active async run or registered provider item finishes, or when a subagent needs attention.
|
|
23
20
|
• { all: true } — wait for every async run and provider item that was active when the call began.
|
|
24
21
|
• { id: "..." } — wait for one async or remembered detached foreground subagent run (id or prefix).
|
|
25
|
-
• { id: "...", nonBlocking: true } — resolve the prefix once, persist an exact-run wake subscription, and return immediately.
|
|
22
|
+
• { id: "...", nonBlocking: true } — resolve the prefix once, persist an exact-run wake subscription, and return immediately. Use this for detached work without native completion delivery; the originating interactive session wakes on completion, failure, attention, reconciliation failure, or timeout.
|
|
26
23
|
• { stopOnAttention: false } — for blocking waits only, keep waiting through idle or long-thinking attention; supervisor/contact requests still stop the wait.
|
|
27
24
|
• { timeoutMs: 600000 } — stop waiting after N ms; active work keeps running. Omitted values use waitTool.defaultTimeoutMs, then 30 minutes. Window expiry returns a non-error window_elapsed result with active work identities.
|
|
28
25
|
|
|
29
|
-
Non-blocking subscriptions are visible in subagent status and differ from disabling waitTool: waitTool.enabled=false returns immediately without registering any future wake. Provider jobs are session-scoped and identified exactly, so replacing one job with another cannot hide a completion. Provider extensions must be explicitly loaded in this process. In a child agent, keep \`subagent_wait\` in the child tool allowlist and load each provider through the agent's extensions or subagentOnlyExtensions; this tool never loads providers or grants tools itself.${enabled ? "" : "\n\nConfigured behavior: subagent_wait
|
|
26
|
+
Non-blocking subscriptions are visible in subagent status and differ from disabling waitTool: waitTool.enabled=false returns immediately without registering any future wake. Provider jobs are session-scoped and identified exactly, so replacing one job with another cannot hide a completion. Provider extensions must be explicitly loaded in this process. In a child agent, keep \`bg_wait\` (or the deprecated \`subagent_wait\` compatibility alias) in the child tool allowlist and load each provider through the agent's extensions or subagentOnlyExtensions; this tool never loads providers or grants tools itself.${enabled ? "" : "\n\nConfigured behavior: bg_wait and its subagent_wait compatibility alias are disabled by config.waitTool or PI_SUBAGENT_WAIT_TOOL_ENABLED and return immediately without blocking."}`;
|
|
27
|
+
const execute: ToolDefinition<typeof SubagentWaitParams, Details>["execute"] = async (_id, params, signal, onUpdate, ctx) => finalizeToolResult(await waitForSubagents(params, signal, {
|
|
28
|
+
state,
|
|
29
|
+
events: pi.events,
|
|
30
|
+
enabled,
|
|
31
|
+
...(defaultTimeoutMs !== undefined ? { defaultTimeoutMs } : {}),
|
|
32
|
+
onUpdate,
|
|
33
|
+
...(subscriptions && ctx?.hasUI ? { subscribe: (input) => subscriptions.arm(input) } : {}),
|
|
34
|
+
}));
|
|
35
|
+
const primaryTool: ToolDefinition<typeof SubagentWaitParams, Details> = {
|
|
36
|
+
name: "bg_wait",
|
|
37
|
+
label: "Background Wait",
|
|
38
|
+
description,
|
|
30
39
|
parameters: SubagentWaitParams,
|
|
31
|
-
|
|
32
|
-
return finalizeToolResult(await waitForSubagents(params, signal, {
|
|
33
|
-
state,
|
|
34
|
-
events: pi.events,
|
|
35
|
-
enabled,
|
|
36
|
-
...(defaultTimeoutMs !== undefined ? { defaultTimeoutMs } : {}),
|
|
37
|
-
onUpdate,
|
|
38
|
-
...(subscriptions && ctx?.hasUI ? { subscribe: (input) => subscriptions.arm(input) } : {}),
|
|
39
|
-
}));
|
|
40
|
-
},
|
|
40
|
+
execute,
|
|
41
41
|
};
|
|
42
|
-
pi.registerTool(
|
|
42
|
+
pi.registerTool(primaryTool);
|
|
43
|
+
pi.registerTool({
|
|
44
|
+
...primaryTool,
|
|
45
|
+
name: "subagent_wait",
|
|
46
|
+
label: "Subagent Wait (deprecated)",
|
|
47
|
+
description: "Deprecated compatibility alias for `bg_wait`. Use `bg_wait` for background, provider, or detached work without a native completion notification. It has the same parameters and behavior.",
|
|
48
|
+
});
|
|
43
49
|
}
|
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
formatEmptyTerminalAssistantResponseError,
|
|
50
50
|
extractToolArgsPreview,
|
|
51
51
|
extractTextFromContent,
|
|
52
|
+
MAX_STREAMED_RECENT_TOOLS,
|
|
52
53
|
boundStreamedRecentTools,
|
|
53
54
|
boundStreamedRecentOutput,
|
|
54
55
|
boundStreamedToolCalls,
|
|
@@ -297,6 +298,55 @@ function snapshotStreamResult(result: SingleResult, progress: AgentProgress): Si
|
|
|
297
298
|
return snapshot;
|
|
298
299
|
}
|
|
299
300
|
|
|
301
|
+
interface StructuredDelegationProgressState {
|
|
302
|
+
currentTool?: string;
|
|
303
|
+
currentToolArgs?: string;
|
|
304
|
+
recentOutput: string[];
|
|
305
|
+
recentTools: Array<{ tool: string; args: string }>;
|
|
306
|
+
activityState?: AgentProgress["activityState"];
|
|
307
|
+
model?: string;
|
|
308
|
+
toolCount: number;
|
|
309
|
+
tokens: number;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function captureStructuredDelegationProgressState(progress: AgentProgress, result: SingleResult): StructuredDelegationProgressState {
|
|
313
|
+
return {
|
|
314
|
+
currentTool: progress.currentTool,
|
|
315
|
+
currentToolArgs: progress.currentToolArgs,
|
|
316
|
+
recentOutput: [...progress.recentOutput],
|
|
317
|
+
recentTools: progress.recentTools.slice(-MAX_STREAMED_RECENT_TOOLS).map(({ tool, args }) => ({ tool, args })),
|
|
318
|
+
activityState: progress.activityState,
|
|
319
|
+
model: progress.model ?? result.model,
|
|
320
|
+
toolCount: progress.toolCount,
|
|
321
|
+
tokens: progress.tokens,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function structuredDelegationProgressChanged(
|
|
326
|
+
previous: StructuredDelegationProgressState,
|
|
327
|
+
progress: AgentProgress,
|
|
328
|
+
result: SingleResult,
|
|
329
|
+
): boolean {
|
|
330
|
+
if (previous.currentTool !== progress.currentTool
|
|
331
|
+
|| previous.currentToolArgs !== progress.currentToolArgs
|
|
332
|
+
|| previous.activityState !== progress.activityState
|
|
333
|
+
|| previous.model !== (progress.model ?? result.model)
|
|
334
|
+
|| previous.toolCount !== progress.toolCount
|
|
335
|
+
|| previous.tokens !== progress.tokens
|
|
336
|
+
|| previous.recentOutput.length !== progress.recentOutput.length) return true;
|
|
337
|
+
for (let index = 0; index < progress.recentOutput.length; index++) {
|
|
338
|
+
if (previous.recentOutput[index] !== progress.recentOutput[index]) return true;
|
|
339
|
+
}
|
|
340
|
+
const recentToolsStart = Math.max(0, progress.recentTools.length - MAX_STREAMED_RECENT_TOOLS);
|
|
341
|
+
if (previous.recentTools.length !== progress.recentTools.length - recentToolsStart) return true;
|
|
342
|
+
for (let index = recentToolsStart; index < progress.recentTools.length; index++) {
|
|
343
|
+
const previousTool = previous.recentTools[index - recentToolsStart];
|
|
344
|
+
const currentTool = progress.recentTools[index];
|
|
345
|
+
if (previousTool?.tool !== currentTool?.tool || previousTool?.args !== currentTool?.args) return true;
|
|
346
|
+
}
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
|
|
300
350
|
const AFTER_COMPACTION_SETTLEMENT = Symbol("afterCompactionSettlement");
|
|
301
351
|
type AbortRecoverySingleResult = SingleResult & { [AFTER_COMPACTION_SETTLEMENT]?: true };
|
|
302
352
|
|
|
@@ -936,6 +986,7 @@ async function runSingleAttempt(
|
|
|
936
986
|
};
|
|
937
987
|
|
|
938
988
|
|
|
989
|
+
let lastStructuredDelegationProgressState: StructuredDelegationProgressState | undefined;
|
|
939
990
|
const emitUpdateSnapshot = (text: string) => {
|
|
940
991
|
if (!options.onUpdate || processClosed) return;
|
|
941
992
|
const progressSnapshot = snapshotProgress(progress);
|
|
@@ -955,6 +1006,10 @@ async function runSingleAttempt(
|
|
|
955
1006
|
const fireUpdate = () => {
|
|
956
1007
|
if (!options.onUpdate || processClosed) return;
|
|
957
1008
|
progress.durationMs = Date.now() - startTime;
|
|
1009
|
+
if (options.suppressUnchangedDelegationUpdates) {
|
|
1010
|
+
if (lastStructuredDelegationProgressState && !structuredDelegationProgressChanged(lastStructuredDelegationProgressState, progress, result)) return;
|
|
1011
|
+
lastStructuredDelegationProgressState = captureStructuredDelegationProgressState(progress, result);
|
|
1012
|
+
}
|
|
958
1013
|
const output = result.timedOut && result.finalOutput ? result.finalOutput : getFinalOutput(result.messages ?? []);
|
|
959
1014
|
emitUpdateSnapshot(output || "(running...)");
|
|
960
1015
|
};
|
|
@@ -1816,7 +1871,11 @@ async function runSyncCompletionInner(
|
|
|
1816
1871
|
agent.fallbackModels,
|
|
1817
1872
|
options.availableModels,
|
|
1818
1873
|
agent.modelProvider ?? options.preferredModelProvider,
|
|
1819
|
-
{
|
|
1874
|
+
{
|
|
1875
|
+
scope: options.modelScope,
|
|
1876
|
+
primaryModelFromParent: options.modelOverrideFromParent,
|
|
1877
|
+
origin: options.modelOrigin ?? (options.modelOverrideFromParent ? "inherited" : "configured"),
|
|
1878
|
+
},
|
|
1820
1879
|
);
|
|
1821
1880
|
if (options.workflowChildPermitLaunch && candidates.length > 1) {
|
|
1822
1881
|
const error = "Workflow child permit does not support model fallback.";
|
|
@@ -2159,7 +2218,7 @@ async function runSyncCompletionInner(
|
|
|
2159
2218
|
report: (result as SingleResult & { structuredAcceptanceReport?: import("../../shared/types.ts").AcceptanceReport; structuredAcceptanceReportError?: string }).structuredAcceptanceReport,
|
|
2160
2219
|
reportError: (result as SingleResult & { structuredAcceptanceReport?: import("../../shared/types.ts").AcceptanceReport; structuredAcceptanceReportError?: string }).structuredAcceptanceReportError,
|
|
2161
2220
|
fileOutput: childWrittenOutput !== undefined && options.outputPath
|
|
2162
|
-
? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === "file-only" }
|
|
2221
|
+
? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === "file-only", durable: result.savedOutputPath !== undefined }
|
|
2163
2222
|
: undefined,
|
|
2164
2223
|
cwd: options.cwd ?? runtimeCwd,
|
|
2165
2224
|
reportOptional: isAgentContractV1(options.agentContract),
|