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.
Files changed (148) hide show
  1. package/CHANGELOG.md +123 -0
  2. package/README.md +5 -4
  3. package/agents/evidence-auditor.md +34 -0
  4. package/agents/researcher.md +23 -13
  5. package/agents/reviewer.md +3 -2
  6. package/docs/agents.md +20 -3
  7. package/docs/configuration.md +25 -5
  8. package/docs/extension-api.md +124 -18
  9. package/docs/missions.md +8 -0
  10. package/docs/models.md +59 -2
  11. package/docs/observability.md +46 -6
  12. package/docs/standalone-background.md +49 -0
  13. package/docs/tool-reference.md +20 -10
  14. package/docs/watchdog.md +35 -4
  15. package/docs/workflows.md +40 -19
  16. package/inspector-runner.mjs +2 -2
  17. package/package.json +2 -1
  18. package/prompts/parallel-review.md +1 -1
  19. package/{runner-server-preload.mjs → runner-peer-preload.mjs} +8 -3
  20. package/skills/pi-subagents/SKILL.md +14 -0
  21. package/skills/pi-subagents/references/execution-controls.md +20 -5
  22. package/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
  23. package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
  24. package/src/agents/advertised-agent-prompt.ts +94 -0
  25. package/src/agents/agent-management.ts +14 -1
  26. package/src/agents/agent-serializer.ts +2 -0
  27. package/src/agents/agents.ts +14 -0
  28. package/src/agents/builtin-names.ts +1 -0
  29. package/src/api/delegation.ts +4 -0
  30. package/src/api/preflight.ts +76 -45
  31. package/src/api/shared-types.ts +3 -1
  32. package/src/api/workflow-resources.ts +6 -0
  33. package/src/extension/fanout-child.ts +63 -4
  34. package/src/extension/index.ts +58 -8
  35. package/src/extension/public-execution.ts +4 -3
  36. package/src/extension/rpc.ts +8 -21
  37. package/src/extension/schemas.ts +71 -80
  38. package/src/extension/tool-description.ts +29 -81
  39. package/src/inspectors/actions.ts +148 -0
  40. package/src/inspectors/ghostty/actions.ts +74 -0
  41. package/src/inspectors/ghostty/plugin.ts +17 -0
  42. package/src/inspectors/herdr/actions.ts +99 -179
  43. package/src/inspectors/herdr/plugin.ts +20 -0
  44. package/src/inspectors/herdr/project-panes.ts +1 -1
  45. package/src/inspectors/{herdr/inspector-runner.ts → inspector-runner.ts} +12 -12
  46. package/src/inspectors/plugins.ts +8 -0
  47. package/src/inspectors/{herdr/session-roots-codec.ts → session-roots-codec.ts} +3 -14
  48. package/src/inspectors/types.ts +51 -0
  49. package/src/intercom/intercom-bridge.ts +50 -8
  50. package/src/intercom/native-supervisor-channel.ts +104 -67
  51. package/src/runs/background/active-async-capacity.ts +22 -18
  52. package/src/runs/background/async-execution.ts +45 -56
  53. package/src/runs/background/async-job-tracker.ts +35 -3
  54. package/src/runs/background/async-resume.ts +5 -9
  55. package/src/runs/background/async-status-snapshot.ts +10 -12
  56. package/src/runs/background/async-status.ts +17 -9
  57. package/src/runs/background/auto-drain.ts +44 -30
  58. package/src/runs/background/binary-bootstrap.ts +33 -0
  59. package/src/runs/background/chain-root-attachment.ts +8 -0
  60. package/src/runs/background/control-channel.ts +78 -44
  61. package/src/runs/background/fleet-view.ts +30 -2
  62. package/src/runs/background/notify.ts +117 -13
  63. package/src/runs/background/owned-process-tree.ts +35 -8
  64. package/src/runs/background/process-terminal.ts +23 -23
  65. package/src/runs/background/run-child-session.ts +121 -36
  66. package/src/runs/background/run-status.ts +78 -5
  67. package/src/runs/background/runner-aliases.ts +28 -9
  68. package/src/runs/background/runner-child-launch.ts +88 -0
  69. package/src/runs/background/runner-child-sessions.ts +5 -4
  70. package/src/runs/background/scheduled-runs.ts +40 -13
  71. package/src/runs/background/stale-run-reconciler.ts +3 -1
  72. package/src/runs/background/steering.ts +20 -2
  73. package/src/runs/background/subagent-runner.ts +458 -239
  74. package/src/runs/background/subagent-wait.ts +54 -8
  75. package/src/runs/background/wait-completions.ts +4 -0
  76. package/src/runs/background/wait-tool.ts +1 -1
  77. package/src/runs/foreground/async-steering-action.ts +37 -7
  78. package/src/runs/foreground/execution.ts +145 -56
  79. package/src/runs/foreground/prompt-audit.ts +3 -1
  80. package/src/runs/foreground/subagent-executor.ts +584 -297
  81. package/src/runs/foreground/workflow-detach-reconcile.ts +10 -5
  82. package/src/runs/foreground/workflow-foreground-steering.ts +57 -2
  83. package/src/runs/shared/acceptance.ts +7 -4
  84. package/src/runs/shared/agent-contract.ts +1 -1
  85. package/src/runs/shared/async-status-projection.ts +51 -47
  86. package/src/runs/shared/capability-ceiling.ts +2 -0
  87. package/src/runs/shared/child-hooks.ts +167 -3
  88. package/src/runs/shared/child-launch.ts +28 -13
  89. package/src/runs/shared/child-lifecycle.ts +6 -3
  90. package/src/runs/shared/child-runtime-config.ts +3 -1
  91. package/src/runs/shared/child-session.ts +75 -8
  92. package/src/runs/shared/child-tool-plan.ts +124 -5
  93. package/src/runs/shared/completion-evidence.ts +2 -2
  94. package/src/runs/shared/completion-guard.ts +6 -3
  95. package/src/runs/shared/effective-system-prompt.ts +33 -0
  96. package/src/runs/shared/external-cli-runner.ts +9 -7
  97. package/src/runs/shared/host-step-status.ts +11 -11
  98. package/src/runs/shared/llm-intent-arbiter.ts +21 -11
  99. package/src/runs/shared/model-fallback.ts +12 -6
  100. package/src/runs/shared/nested-events.ts +5 -5
  101. package/src/runs/shared/orca-progress-tabs.ts +7 -1
  102. package/src/runs/shared/parallel-handoff.ts +57 -12
  103. package/src/runs/shared/parallel-utils.ts +2 -2
  104. package/src/runs/shared/pi-spawn.ts +10 -0
  105. package/src/runs/shared/readonly-drain-observation.ts +42 -0
  106. package/src/runs/shared/readonly-model-continuation.ts +69 -0
  107. package/src/runs/shared/readonly-session-evidence.ts +307 -0
  108. package/src/runs/shared/run-fanout-budget.ts +8 -8
  109. package/src/runs/shared/runtime-acknowledged-extensions.ts +3 -3
  110. package/src/runs/shared/subagent-prompt-runtime.ts +20 -4
  111. package/src/runs/shared/task-intent.ts +46 -13
  112. package/src/runs/shared/workflow-async-child-guidance.ts +18 -0
  113. package/src/runs/shared/worktree-setup-command.ts +190 -0
  114. package/src/runs/shared/worktree.ts +366 -208
  115. package/src/shared/fork-context.ts +15 -72
  116. package/src/shared/launch-contract.ts +65 -2
  117. package/src/shared/opencode-session-headers.ts +30 -0
  118. package/src/shared/types.ts +85 -61
  119. package/src/shared/utils.ts +7 -2
  120. package/src/shared/workflow-child-permit.ts +18 -13
  121. package/src/slash/delegation-adapters.ts +3 -1
  122. package/src/slash/delegation-request.ts +14 -0
  123. package/src/slash/slash-commands.ts +2 -1
  124. package/src/slash/subagents-admin.ts +11 -4
  125. package/src/tui/fleet-status.ts +164 -19
  126. package/src/tui/fleet.ts +27 -19
  127. package/src/tui/render.ts +172 -33
  128. package/src/watchdog/child-status.ts +8 -0
  129. package/src/watchdog/model-selection.ts +20 -0
  130. package/src/watchdog/permission-arbiter.ts +3 -1
  131. package/src/watchdog/register-child.ts +1 -0
  132. package/src/watchdog/register-main.ts +31 -27
  133. package/src/watchdog/review.ts +132 -67
  134. package/src/watchdog/runtime.ts +82 -20
  135. package/src/watchdog/scope.ts +1 -1
  136. package/src/watchdog/settings.ts +9 -3
  137. package/src/watchdog/tool-actions.ts +13 -12
  138. package/src/watchdog/turn-delta.ts +23 -0
  139. package/src/watchdog/types.ts +4 -0
  140. package/src/workflows/chat-progress.ts +3 -3
  141. package/src/workflows/scripted-workflow.ts +275 -17
  142. package/src/workflows/workflow-checklist.ts +13 -17
  143. package/src/workflows/workflow-child-summary.ts +57 -8
  144. package/src/workflows/workflow-preflight.ts +19 -19
  145. package/src/workflows/workflow-receipt.ts +3 -3
  146. package/src/workflows/workflow-resources.ts +96 -21
  147. package/src/workflows/workflow-settlement.ts +3 -0
  148. /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
- return [...deps.state.foregroundRuns.values()].filter((run) =>
221
- (run.runId === params.id || run.runId.startsWith(params.id!))
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 and provider item that was active when the call began.
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
- if (finalResult?.state === "delivered") {
155
- return { content: [{ type: "text", text: steeringReceipt(input.message, `Steering delivered for async run ${status.runId} (request ${requestId}).`) }], details: { mode: "management", results: [], steering: finalResult } };
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?.state === "delivered") return { content: [{ type: "text", text: steeringReceipt(input.message, `Steering delivered for async run ${status.runId} (request ${requestId}).`) }], details: { mode: "management", results: [], steering: 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 (preCommitResult?.state === "delivered" && preCommitResult.targets.every((target) => target.deliveredAt !== undefined && target.deliveredAt <= committedAt)) {
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 { content: [{ type: "text", text: steeringReceipt(input.message, `Steering delivered for async run ${status.runId} (request ${requestId}).`) }], details: { mode: "management", results: [], steering: preCommitResult } };
210
+ return acceptedReply(preCommitResult);
181
211
  }
182
212
  try {
183
213
  deliverInterruptRequest({ asyncDir, source: "steering-recovery" });