pi-subagents 0.65.1 → 0.67.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 +123 -0
- package/README.md +5 -4
- package/agents/evidence-auditor.md +34 -0
- package/agents/researcher.md +23 -13
- package/agents/reviewer.md +3 -2
- package/docs/agents.md +20 -3
- package/docs/configuration.md +25 -5
- package/docs/extension-api.md +124 -18
- package/docs/missions.md +8 -0
- package/docs/models.md +59 -2
- package/docs/observability.md +46 -6
- package/docs/standalone-background.md +49 -0
- package/docs/tool-reference.md +20 -10
- package/docs/watchdog.md +35 -4
- package/docs/workflows.md +40 -19
- package/inspector-runner.mjs +2 -2
- package/package.json +2 -1
- package/prompts/parallel-review.md +1 -1
- package/{runner-server-preload.mjs → runner-peer-preload.mjs} +8 -3
- package/skills/pi-subagents/SKILL.md +14 -0
- package/skills/pi-subagents/references/execution-controls.md +20 -5
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
- package/src/agents/advertised-agent-prompt.ts +94 -0
- package/src/agents/agent-management.ts +14 -1
- package/src/agents/agent-serializer.ts +2 -0
- package/src/agents/agents.ts +14 -0
- package/src/agents/builtin-names.ts +1 -0
- package/src/api/delegation.ts +4 -0
- package/src/api/preflight.ts +76 -45
- package/src/api/shared-types.ts +3 -1
- package/src/api/workflow-resources.ts +6 -0
- package/src/extension/fanout-child.ts +63 -4
- package/src/extension/index.ts +58 -8
- package/src/extension/public-execution.ts +4 -3
- package/src/extension/rpc.ts +8 -21
- package/src/extension/schemas.ts +71 -80
- package/src/extension/tool-description.ts +29 -81
- package/src/inspectors/actions.ts +148 -0
- package/src/inspectors/ghostty/actions.ts +74 -0
- package/src/inspectors/ghostty/plugin.ts +17 -0
- package/src/inspectors/herdr/actions.ts +99 -179
- package/src/inspectors/herdr/plugin.ts +20 -0
- package/src/inspectors/herdr/project-panes.ts +1 -1
- package/src/inspectors/{herdr/inspector-runner.ts → inspector-runner.ts} +12 -12
- package/src/inspectors/plugins.ts +8 -0
- package/src/inspectors/{herdr/session-roots-codec.ts → session-roots-codec.ts} +3 -14
- package/src/inspectors/types.ts +51 -0
- package/src/intercom/intercom-bridge.ts +50 -8
- package/src/intercom/native-supervisor-channel.ts +104 -67
- package/src/runs/background/active-async-capacity.ts +22 -18
- package/src/runs/background/async-execution.ts +45 -56
- package/src/runs/background/async-job-tracker.ts +35 -3
- package/src/runs/background/async-resume.ts +5 -9
- package/src/runs/background/async-status-snapshot.ts +10 -12
- package/src/runs/background/async-status.ts +17 -9
- package/src/runs/background/auto-drain.ts +44 -30
- package/src/runs/background/binary-bootstrap.ts +33 -0
- package/src/runs/background/chain-root-attachment.ts +8 -0
- package/src/runs/background/control-channel.ts +78 -44
- package/src/runs/background/fleet-view.ts +30 -2
- package/src/runs/background/notify.ts +117 -13
- package/src/runs/background/owned-process-tree.ts +35 -8
- package/src/runs/background/process-terminal.ts +23 -23
- package/src/runs/background/run-child-session.ts +121 -36
- package/src/runs/background/run-status.ts +78 -5
- package/src/runs/background/runner-aliases.ts +28 -9
- package/src/runs/background/runner-child-launch.ts +88 -0
- package/src/runs/background/runner-child-sessions.ts +5 -4
- package/src/runs/background/scheduled-runs.ts +40 -13
- package/src/runs/background/stale-run-reconciler.ts +3 -1
- package/src/runs/background/steering.ts +20 -2
- package/src/runs/background/subagent-runner.ts +458 -239
- package/src/runs/background/subagent-wait.ts +54 -8
- package/src/runs/background/wait-completions.ts +4 -0
- package/src/runs/background/wait-tool.ts +1 -1
- package/src/runs/foreground/async-steering-action.ts +37 -7
- package/src/runs/foreground/execution.ts +145 -56
- package/src/runs/foreground/prompt-audit.ts +3 -1
- package/src/runs/foreground/subagent-executor.ts +584 -297
- package/src/runs/foreground/workflow-detach-reconcile.ts +10 -5
- package/src/runs/foreground/workflow-foreground-steering.ts +57 -2
- package/src/runs/shared/acceptance.ts +7 -4
- package/src/runs/shared/agent-contract.ts +1 -1
- package/src/runs/shared/async-status-projection.ts +51 -47
- package/src/runs/shared/capability-ceiling.ts +2 -0
- package/src/runs/shared/child-hooks.ts +167 -3
- package/src/runs/shared/child-launch.ts +28 -13
- package/src/runs/shared/child-lifecycle.ts +6 -3
- package/src/runs/shared/child-runtime-config.ts +3 -1
- package/src/runs/shared/child-session.ts +75 -8
- package/src/runs/shared/child-tool-plan.ts +124 -5
- package/src/runs/shared/completion-evidence.ts +2 -2
- package/src/runs/shared/completion-guard.ts +6 -3
- package/src/runs/shared/effective-system-prompt.ts +33 -0
- package/src/runs/shared/external-cli-runner.ts +9 -7
- package/src/runs/shared/host-step-status.ts +11 -11
- package/src/runs/shared/llm-intent-arbiter.ts +21 -11
- package/src/runs/shared/model-fallback.ts +12 -6
- package/src/runs/shared/nested-events.ts +5 -5
- package/src/runs/shared/orca-progress-tabs.ts +7 -1
- package/src/runs/shared/parallel-handoff.ts +57 -12
- package/src/runs/shared/parallel-utils.ts +2 -2
- package/src/runs/shared/pi-spawn.ts +10 -0
- package/src/runs/shared/readonly-drain-observation.ts +42 -0
- package/src/runs/shared/readonly-model-continuation.ts +69 -0
- package/src/runs/shared/readonly-session-evidence.ts +307 -0
- package/src/runs/shared/run-fanout-budget.ts +8 -8
- package/src/runs/shared/runtime-acknowledged-extensions.ts +3 -3
- package/src/runs/shared/subagent-prompt-runtime.ts +20 -4
- package/src/runs/shared/task-intent.ts +46 -13
- package/src/runs/shared/workflow-async-child-guidance.ts +18 -0
- package/src/runs/shared/worktree-setup-command.ts +190 -0
- package/src/runs/shared/worktree.ts +366 -208
- package/src/shared/fork-context.ts +15 -72
- package/src/shared/launch-contract.ts +65 -2
- package/src/shared/opencode-session-headers.ts +30 -0
- package/src/shared/types.ts +85 -61
- package/src/shared/utils.ts +7 -2
- package/src/shared/workflow-child-permit.ts +18 -13
- package/src/slash/delegation-adapters.ts +3 -1
- package/src/slash/delegation-request.ts +14 -0
- package/src/slash/slash-commands.ts +2 -1
- package/src/slash/subagents-admin.ts +11 -4
- package/src/tui/fleet-status.ts +164 -19
- package/src/tui/fleet.ts +27 -19
- package/src/tui/render.ts +172 -33
- package/src/watchdog/child-status.ts +8 -0
- package/src/watchdog/model-selection.ts +20 -0
- package/src/watchdog/permission-arbiter.ts +3 -1
- package/src/watchdog/register-child.ts +1 -0
- package/src/watchdog/register-main.ts +31 -27
- package/src/watchdog/review.ts +132 -67
- package/src/watchdog/runtime.ts +82 -20
- package/src/watchdog/scope.ts +1 -1
- package/src/watchdog/settings.ts +9 -3
- package/src/watchdog/tool-actions.ts +13 -12
- package/src/watchdog/turn-delta.ts +23 -0
- package/src/watchdog/types.ts +4 -0
- package/src/workflows/chat-progress.ts +3 -3
- package/src/workflows/scripted-workflow.ts +275 -17
- package/src/workflows/workflow-checklist.ts +13 -17
- package/src/workflows/workflow-child-summary.ts +57 -8
- package/src/workflows/workflow-preflight.ts +19 -19
- package/src/workflows/workflow-receipt.ts +3 -3
- package/src/workflows/workflow-resources.ts +96 -21
- package/src/workflows/workflow-settlement.ts +3 -0
- /package/src/inspectors/{herdr/shell-command.ts → shell-command.ts} +0 -0
|
@@ -214,14 +214,13 @@ function matchesId(run: AsyncRunSummary, id: string): boolean {
|
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
function activeDetachedForegroundRuns(params: SubagentWaitParams, deps: SubagentWaitDeps): ForegroundResumeRun[] {
|
|
217
|
-
if (!params.id || !deps.state.foregroundRuns) return [];
|
|
218
217
|
const sessionId = deps.state.currentSessionId;
|
|
219
|
-
if (!sessionId) return [];
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
&& run.sessionId === sessionId
|
|
223
|
-
&& run.children.some((child) => child.status === "detached")
|
|
218
|
+
if (!sessionId || !deps.state.foregroundRuns) return [];
|
|
219
|
+
const runs = [...deps.state.foregroundRuns.values()].filter((run) =>
|
|
220
|
+
run.sessionId === sessionId && run.children.some((child) => child.status === "detached")
|
|
224
221
|
);
|
|
222
|
+
if (params.id) return runs.filter((run) => run.runId === params.id || run.runId.startsWith(params.id!));
|
|
223
|
+
return params.all === true ? runs : [];
|
|
225
224
|
}
|
|
226
225
|
|
|
227
226
|
function summarizeForegroundChildren(run: ForegroundResumeRun, indices: Set<number>): string {
|
|
@@ -337,6 +336,9 @@ function completionUsage(completions: WaitCompletion[] | undefined): Usage | und
|
|
|
337
336
|
|
|
338
337
|
function result(text: string, isError = false, completions?: WaitCompletion[]): AgentToolResult<Details> {
|
|
339
338
|
const usage = completionUsage(completions);
|
|
339
|
+
for (const completion of completions ?? []) {
|
|
340
|
+
if (completion.workflowReceiptPath) text += `\nWorkflow receipt [${completion.runId}]: ${completion.workflowReceiptPath}`;
|
|
341
|
+
}
|
|
340
342
|
return {
|
|
341
343
|
content: [{ type: "text", text }],
|
|
342
344
|
...(isError ? { isError: true } : {}),
|
|
@@ -531,6 +533,36 @@ async function waitForDetachedForegroundRun(
|
|
|
531
533
|
}
|
|
532
534
|
}
|
|
533
535
|
|
|
536
|
+
async function waitForSessionDetachedForegroundRuns(
|
|
537
|
+
runs: ForegroundResumeRun[],
|
|
538
|
+
signal: AbortSignal | undefined,
|
|
539
|
+
deps: SubagentWaitDeps,
|
|
540
|
+
startedAt: number,
|
|
541
|
+
now: () => number,
|
|
542
|
+
pollIntervalMs: number,
|
|
543
|
+
timeoutMs: number,
|
|
544
|
+
): Promise<AgentToolResult<Details>> {
|
|
545
|
+
const texts: string[] = [];
|
|
546
|
+
for (const run of runs) {
|
|
547
|
+
const one = await waitForDetachedForegroundRun(run, signal, deps, startedAt, now, pollIntervalMs, timeoutMs);
|
|
548
|
+
if (one.isError) return one;
|
|
549
|
+
if (one.details.wait?.reason === "window_elapsed") {
|
|
550
|
+
const activeRunIds = runs.filter((initial) => {
|
|
551
|
+
const current = deps.state.foregroundRuns?.get(initial.runId);
|
|
552
|
+
if (!current || current.sessionId !== initial.sessionId) return false;
|
|
553
|
+
return initial.children.some((child) => child.status === "detached"
|
|
554
|
+
&& current.children.some((candidate) => candidate.index === child.index && candidate.status === "detached"));
|
|
555
|
+
}).map((activeRun) => activeRun.runId);
|
|
556
|
+
return windowElapsedResult(
|
|
557
|
+
one.content.map((part) => part.type === "text" ? part.text : "").join("\n"),
|
|
558
|
+
activeRunIds,
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
texts.push(one.content.map((part) => part.type === "text" ? part.text : "").join("\n").trim());
|
|
562
|
+
}
|
|
563
|
+
return result(texts.filter(Boolean).join("\n") || `Waited ${formatDuration(now() - startedAt)} for remembered detached foreground run(s); done.`);
|
|
564
|
+
}
|
|
565
|
+
|
|
534
566
|
/**
|
|
535
567
|
* Block until the targeted async or remembered detached foreground run finishes,
|
|
536
568
|
* the timeout elapses, or the turn is aborted. Resolves with a short
|
|
@@ -567,7 +599,10 @@ export async function waitForSubagents(
|
|
|
567
599
|
let providerSnapshot: BackgroundWorkSnapshot;
|
|
568
600
|
try {
|
|
569
601
|
active = activeRunsForSession(params, deps);
|
|
570
|
-
foreground = activeDetachedForegroundRuns(params, deps)
|
|
602
|
+
foreground = activeDetachedForegroundRuns(params, deps).map((run) => ({
|
|
603
|
+
...run,
|
|
604
|
+
children: run.children.map((child) => ({ ...child })),
|
|
605
|
+
}));
|
|
571
606
|
providerSnapshot = params.id ? { providers: [], items: [] } : backgroundWorkForSession(deps, startedAt);
|
|
572
607
|
} catch (error) {
|
|
573
608
|
return result(error instanceof Error ? error.message : String(error), true);
|
|
@@ -603,6 +638,9 @@ export async function waitForSubagents(
|
|
|
603
638
|
|
|
604
639
|
let providerActive = providerSnapshot.items;
|
|
605
640
|
if (active.length === 0 && providerActive.length === 0) {
|
|
641
|
+
if (waitForAll && !params.id && foreground.length > 0) {
|
|
642
|
+
return waitForSessionDetachedForegroundRuns(foreground, signal, deps, startedAt, now, pollIntervalMs, timeoutMs);
|
|
643
|
+
}
|
|
606
644
|
return result(params.id
|
|
607
645
|
? `No active run matched "${params.id}". Nothing to wait for.`
|
|
608
646
|
: "No active async runs or registered provider work in this session. Nothing to wait for.");
|
|
@@ -694,6 +732,14 @@ export async function waitForSubagents(
|
|
|
694
732
|
const recoveryNote = formatCompletionRecovery(completions);
|
|
695
733
|
|
|
696
734
|
if (waitForAll) {
|
|
735
|
+
const foregroundResult = !params.id && foreground.length > 0 && relevantAttention.length === 0
|
|
736
|
+
? await waitForSessionDetachedForegroundRuns(foreground, signal, deps, startedAt, now, pollIntervalMs, timeoutMs)
|
|
737
|
+
: undefined;
|
|
738
|
+
if (foregroundResult?.isError) return foregroundResult;
|
|
739
|
+
if (foregroundResult?.details.wait?.reason === "window_elapsed") return foregroundResult;
|
|
740
|
+
const foregroundNote = foregroundResult
|
|
741
|
+
? `\n${foregroundResult.content.map((part) => part.type === "text" ? part.text : "").join("\n")}`
|
|
742
|
+
: "";
|
|
697
743
|
const scope = params.id
|
|
698
744
|
? `run "${params.id}"`
|
|
699
745
|
: initialProviderIds.size === 0
|
|
@@ -701,7 +747,7 @@ export async function waitForSubagents(
|
|
|
701
747
|
: `${initialAsyncIds.size} async run(s) and ${initialProviderIds.size} provider item(s)`;
|
|
702
748
|
const status = relevantAttention.length > 0 ? "attention required" : "done";
|
|
703
749
|
return result(
|
|
704
|
-
`Waited ${elapsed} for ${scope}; ${status}.${outcome}${recoveryNote}${resumeGuidance}${attentionNote} Completion/control events have been observed; inspect status if a notification is not visible yet.`,
|
|
750
|
+
`Waited ${elapsed} for ${scope}; ${status}.${outcome}${recoveryNote}${resumeGuidance}${attentionNote}${foregroundNote} Completion/control events have been observed; inspect status if a notification is not visible yet.`,
|
|
705
751
|
(deps.failOnFailedRuns === true && failedAsyncCount > 0) || (deps.failOnAttention === true && relevantAttention.length > 0),
|
|
706
752
|
completions,
|
|
707
753
|
);
|
|
@@ -96,6 +96,9 @@ export function toWaitCompletion(data: Record<string, unknown>, runId: string):
|
|
|
96
96
|
})
|
|
97
97
|
: undefined;
|
|
98
98
|
const agent = asNonEmptyString(data.agent);
|
|
99
|
+
const receipt = data.workflowReceipt;
|
|
100
|
+
const workflowReceiptPath = receipt && typeof receipt === "object" && !Array.isArray(receipt)
|
|
101
|
+
? asNonEmptyString((receipt as Record<string, unknown>).path) : undefined;
|
|
99
102
|
const mode = asNonEmptyString(data.mode);
|
|
100
103
|
const state = asNonEmptyString(data.state);
|
|
101
104
|
const workflowChildren = parseWorkflowChildSummary(data.workflowChildren);
|
|
@@ -104,6 +107,7 @@ export function toWaitCompletion(data: Record<string, unknown>, runId: string):
|
|
|
104
107
|
runId,
|
|
105
108
|
...(agent ? { agent } : {}),
|
|
106
109
|
...(mode ? { mode } : {}),
|
|
110
|
+
...(workflowReceiptPath ? { workflowReceiptPath } : {}),
|
|
107
111
|
...(state ? { state } : {}),
|
|
108
112
|
...(typeof data.success === "boolean" ? { success: data.success } : {}),
|
|
109
113
|
...(results && results.length > 0 ? { results } : {}),
|
|
@@ -17,7 +17,7 @@ export function registerWaitTool(
|
|
|
17
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.
|
|
18
18
|
|
|
19
19
|
• { } — return when the first initially active async run or registered provider item finishes, or when a subagent needs attention.
|
|
20
|
-
• { all: true } — wait for every async run
|
|
20
|
+
• { all: true } — wait for every async run, provider item, and remembered detached foreground descendant that was active when the call began.
|
|
21
21
|
• { id: "..." } — wait for one async or remembered detached foreground subagent run (id or prefix).
|
|
22
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.
|
|
23
23
|
• { stopOnAttention: false } — for blocking waits only, keep waiting through idle or long-thinking attention; supervisor/contact requests still stop the wait.
|
|
@@ -3,13 +3,18 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
5
5
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
6
|
-
import type { AsyncStatus, Details, SubagentState, ToolBudgetConfig } from "../../shared/types.ts";
|
|
6
|
+
import type { AsyncStatus, Details, SteerActionResult, SubagentState, ToolBudgetConfig } from "../../shared/types.ts";
|
|
7
7
|
import { readStatus } from "../../shared/utils.ts";
|
|
8
8
|
import { deliverInterruptRequest, queueRevivalBrief, requestAsyncSteer, type SteerDeliveryMode, type SteerRequest } from "../background/control-channel.ts";
|
|
9
9
|
import { resolveAsyncResumeTarget } from "../background/async-resume.ts";
|
|
10
10
|
import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
|
|
11
11
|
import { actionResultFromSteeringStatus, claimSteeringRecovery, createSteeringStatus, recordSteeringRequest, remainingSteeringRecoveryLimits, steeringReceipt, updateSteeringTarget, waitForSteeringAction } from "../background/steering.ts";
|
|
12
12
|
|
|
13
|
+
function steerTargetsAccepted(result: Pick<SteerActionResult, "targets"> | undefined): boolean {
|
|
14
|
+
const targets = result?.targets ?? [];
|
|
15
|
+
return targets.length > 0 && targets.every((target) => target.state === "queued" || target.state === "delivered");
|
|
16
|
+
}
|
|
17
|
+
|
|
13
18
|
export function canQueueRetainedAsyncFollowUp(status: AsyncStatus, index?: number): boolean {
|
|
14
19
|
const steps = status.steps ?? [];
|
|
15
20
|
return status.state === "complete"
|
|
@@ -26,6 +31,7 @@ export async function steerAsyncRun(input: {
|
|
|
26
31
|
message: string;
|
|
27
32
|
mode?: SteerDeliveryMode;
|
|
28
33
|
index?: number;
|
|
34
|
+
findPendingAsks?: (target: { runId: string; agent: string; childIndex: number }) => string[];
|
|
29
35
|
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
|
|
30
36
|
location: { asyncDir: string | null };
|
|
31
37
|
signal?: AbortSignal;
|
|
@@ -44,6 +50,24 @@ export async function steerAsyncRun(input: {
|
|
|
44
50
|
};
|
|
45
51
|
}
|
|
46
52
|
const asyncDir = input.location.asyncDir;
|
|
53
|
+
const snapshot = input.findPendingAsks ? readStatus(asyncDir) : null;
|
|
54
|
+
if (snapshot?.mode === "single"
|
|
55
|
+
&& (!input.state.currentSessionId || snapshot.sessionId === input.state.currentSessionId)
|
|
56
|
+
&& (snapshot.state === "running" || snapshot.state === "queued")
|
|
57
|
+
&& snapshot.steps?.length === 1 && (input.index === undefined || input.index === 0)
|
|
58
|
+
&& (snapshot.steps[0]?.status === "running" || snapshot.steps[0]?.status === "pending")) {
|
|
59
|
+
const asks = input.findPendingAsks?.({ runId: snapshot.runId, agent: snapshot.steps[0].agent, childIndex: 0 }) ?? [];
|
|
60
|
+
if (asks.length > 0) {
|
|
61
|
+
const replies = asks.map(replyTo => `subagent_supervisor(${JSON.stringify({ action: "reply", replyTo, message: "<explicit answer>" })})`).join("\n");
|
|
62
|
+
return {
|
|
63
|
+
content: [{ type: "text", text: `Steering not delivered or queued: async run ${snapshot.runId} is blocked on ${asks.length === 1 ? "a pending supervisor ask" : "ambiguous pending supervisor asks"}. No reply or recovery was attempted. Reply explicitly${asks.length > 1 ? " to the intended request ID" : ""}:\n${replies}` }],
|
|
64
|
+
isError: true,
|
|
65
|
+
details: { mode: "management", results: [] },
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// The read-only snapshot precedes even stale-run reconciliation. An ask arriving
|
|
70
|
+
// later does not make a runner acknowledgement proof of consumption or unblocking.
|
|
47
71
|
const status = reconcileAsyncRun(asyncDir, { kill: input.kill }).status;
|
|
48
72
|
if (input.state.currentSessionId && status?.sessionId !== input.state.currentSessionId) {
|
|
49
73
|
return {
|
|
@@ -151,9 +175,11 @@ export async function steerAsyncRun(input: {
|
|
|
151
175
|
}
|
|
152
176
|
const finalStatus = readStatus(asyncDir);
|
|
153
177
|
const finalResult = finalStatus?.steering ? actionResultFromSteeringStatus(finalStatus.steering, status.runId, requestId) : undefined;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
178
|
+
const acceptedReply = (accepted: SteerActionResult): AgentToolResult<Details> => {
|
|
179
|
+
const acceptedState = accepted.state === "delivered" ? "delivered" : "queued";
|
|
180
|
+
return { content: [{ type: "text", text: steeringReceipt(input.message, `Steering ${acceptedState} for async run ${status.runId} (request ${requestId}).`) }], details: { mode: "management", results: [], steering: accepted } };
|
|
181
|
+
};
|
|
182
|
+
if (finalResult && steerTargetsAccepted(finalResult)) return acceptedReply(finalResult);
|
|
157
183
|
const running = (finalStatus?.steps ?? status.steps ?? []).filter((step) => step.status === "running");
|
|
158
184
|
const recoveryAllowed = (input.mode ?? "steer") === "steer" && status.mode === "single" && status.isNested !== true && running.length === 1 && Boolean(finalStatus?.steering) && (input.index === undefined || input.index === 0);
|
|
159
185
|
if (recoveryAllowed && finalResult?.state !== "scheduled" && input.recover) {
|
|
@@ -167,17 +193,21 @@ export async function steerAsyncRun(input: {
|
|
|
167
193
|
try {
|
|
168
194
|
const latest = readStatus(asyncDir);
|
|
169
195
|
const latestResult = latest?.steering ? actionResultFromSteeringStatus(latest.steering, status.runId, requestId) : undefined;
|
|
170
|
-
if (latestResult
|
|
196
|
+
if (latestResult && steerTargetsAccepted(latestResult)) return acceptedReply(latestResult);
|
|
171
197
|
const committedAt = Date.now();
|
|
172
198
|
input.onBeforeRecoveryClaim?.(requestId, committedAt);
|
|
173
199
|
const { claimPath, markerPath } = claimSteeringRecovery(asyncDir, { requestId, sourceRunId: status.runId, committedAt });
|
|
174
200
|
input.onRecoveryCommitted?.(requestId, committedAt);
|
|
175
201
|
const preCommitStatus = readStatus(asyncDir);
|
|
176
202
|
const preCommitResult = preCommitStatus?.steering ? actionResultFromSteeringStatus(preCommitStatus.steering, status.runId, requestId) : undefined;
|
|
177
|
-
if (
|
|
203
|
+
if (
|
|
204
|
+
preCommitResult
|
|
205
|
+
&& preCommitResult.targets.length > 0
|
|
206
|
+
&& preCommitResult.targets.every((target) => target.state === "queued" || (target.deliveredAt !== undefined && target.deliveredAt <= committedAt))
|
|
207
|
+
) {
|
|
178
208
|
fs.rmSync(markerPath, { force: true });
|
|
179
209
|
fs.rmSync(claimPath, { force: true });
|
|
180
|
-
return
|
|
210
|
+
return acceptedReply(preCommitResult);
|
|
181
211
|
}
|
|
182
212
|
try {
|
|
183
213
|
deliverInterruptRequest({ asyncDir, source: "steering-recovery" });
|