pi-subagents 0.80.0 → 0.82.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 (184) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/bundle/agents.js +18 -0
  3. package/bundle/background-work.js +16 -0
  4. package/bundle/capability-ceiling.js +22 -0
  5. package/bundle/child-tool-plan.js +11 -0
  6. package/bundle/chunk-43SCHIMD.js +57 -0
  7. package/bundle/chunk-4QFZZV3O.js +57 -0
  8. package/bundle/chunk-4SDU2BZX.js +1190 -0
  9. package/bundle/chunk-52UBALJL.js +753 -0
  10. package/bundle/chunk-5LFLVSTM.js +260 -0
  11. package/bundle/chunk-5TYMSZX3.js +235 -0
  12. package/bundle/chunk-62OCY2S4.js +14 -0
  13. package/bundle/chunk-7F6RKT6W.js +132 -0
  14. package/bundle/chunk-7IVSUMQQ.js +1337 -0
  15. package/bundle/chunk-7J4ZXMY7.js +18 -0
  16. package/bundle/chunk-7PCKZHR5.js +1234 -0
  17. package/bundle/chunk-7YMXOYEU.js +1373 -0
  18. package/bundle/chunk-B5EKXDHE.js +16870 -0
  19. package/bundle/chunk-CIZQXJ5Q.js +8619 -0
  20. package/bundle/chunk-CTPAE36Y.js +94 -0
  21. package/bundle/chunk-D4EHT5SJ.js +14 -0
  22. package/bundle/chunk-GC4DSPMH.js +946 -0
  23. package/bundle/chunk-GJECHSAR.js +179 -0
  24. package/bundle/chunk-M2CTFYT3.js +4164 -0
  25. package/bundle/chunk-MHZ4G23A.js +1296 -0
  26. package/bundle/chunk-N2BN7BF5.js +159 -0
  27. package/bundle/chunk-NF7IEKBH.js +490 -0
  28. package/bundle/chunk-NGGAFDL6.js +742 -0
  29. package/bundle/chunk-NIXWDZZV.js +9 -0
  30. package/bundle/chunk-NRQ75MGH.js +8327 -0
  31. package/bundle/chunk-OYF6AFDY.js +458 -0
  32. package/bundle/chunk-PLCCBLPY.js +526 -0
  33. package/bundle/chunk-RMQFMMAT.js +416 -0
  34. package/bundle/chunk-SIJ4S3EN.js +276 -0
  35. package/bundle/chunk-UKG4V45O.js +228 -0
  36. package/bundle/chunk-VH5OAWVG.js +4096 -0
  37. package/bundle/chunk-W2SZ22EM.js +5462 -0
  38. package/bundle/chunk-WVD4UEL4.js +9367 -0
  39. package/bundle/chunk-XSWOTIA3.js +175 -0
  40. package/bundle/chunk-YFCK7H2S.js +129 -0
  41. package/bundle/chunk-YKEWIB4M.js +380 -0
  42. package/bundle/chunk-YYREO77V.js +137 -0
  43. package/bundle/chunk-Z7KVHQHL.js +173 -0
  44. package/bundle/chunk-ZLINIYTP.js +8613 -0
  45. package/bundle/chunk-ZQGSDHL6.js +128 -0
  46. package/bundle/control-channel.js +11 -0
  47. package/bundle/delegation.js +14 -0
  48. package/bundle/external-job-provider.js +20 -0
  49. package/bundle/external-runs.js +21 -0
  50. package/bundle/index.js +30 -0
  51. package/bundle/inspectors.js +8 -0
  52. package/bundle/intercom-bridge.js +11 -0
  53. package/bundle/preflight.js +425 -0
  54. package/bundle/project-panes.js +28 -0
  55. package/bundle/required-child-extensions.js +6 -0
  56. package/bundle/shared-types.js +8 -0
  57. package/bundle/workflow-resources.js +7 -0
  58. package/docs/agents.md +31 -0
  59. package/docs/configuration.md +9 -1
  60. package/docs/extension-api.md +27 -0
  61. package/docs/observability.md +6 -2
  62. package/docs/tool-reference.md +3 -1
  63. package/docs/workflows.md +16 -0
  64. package/package.json +19 -19
  65. package/skills/pi-subagents/references/execution-controls.md +5 -4
  66. package/src/agents/agent-management.js +3 -3
  67. package/src/agents/agent-memory.js +3 -1
  68. package/src/agents/agents.js +6 -6
  69. package/src/agents/frontmatter.d.ts +4 -0
  70. package/src/agents/frontmatter.js +24 -3
  71. package/src/agents/skills.js +2 -1
  72. package/src/api/delegation.d.ts +20 -0
  73. package/src/extension/config.js +3 -0
  74. package/src/extension/herdr-pi-bridge.js +17 -10
  75. package/src/extension/index.js +109 -9
  76. package/src/extension/schemas.js +3 -3
  77. package/src/intercom/native-supervisor-channel.js +20 -0
  78. package/src/missions/lifecycle.js +7 -1
  79. package/src/runs/background/active-async-capacity.d.ts +1 -0
  80. package/src/runs/background/active-async-capacity.js +9 -3
  81. package/src/runs/background/async-execution.js +20 -10
  82. package/src/runs/background/async-job-tracker.d.ts +6 -1
  83. package/src/runs/background/async-job-tracker.js +9 -4
  84. package/src/runs/background/async-status.d.ts +2 -0
  85. package/src/runs/background/async-status.js +21 -2
  86. package/src/runs/background/await-async-run.js +3 -7
  87. package/src/runs/background/boot-id.d.ts +12 -0
  88. package/src/runs/background/boot-id.js +47 -0
  89. package/src/runs/background/interrupted-elsewhere.d.ts +22 -0
  90. package/src/runs/background/interrupted-elsewhere.js +68 -0
  91. package/src/runs/background/notify.d.ts +2 -0
  92. package/src/runs/background/notify.js +13 -0
  93. package/src/runs/background/result-delivery-ownership.d.ts +6 -0
  94. package/src/runs/background/result-delivery-ownership.js +19 -1
  95. package/src/runs/background/result-watcher.d.ts +3 -1
  96. package/src/runs/background/result-watcher.js +24 -8
  97. package/src/runs/background/run-child-session.d.ts +1 -6
  98. package/src/runs/background/run-child-session.js +19 -1
  99. package/src/runs/background/run-status.js +2 -1
  100. package/src/runs/background/stale-run-reconciler.d.ts +25 -0
  101. package/src/runs/background/stale-run-reconciler.js +54 -13
  102. package/src/runs/background/subagent-runner-bootstrap.js +3 -0
  103. package/src/runs/background/subagent-runner.d.ts +4 -0
  104. package/src/runs/background/subagent-runner.js +13 -5
  105. package/src/runs/background/subagent-wait.d.ts +1 -1
  106. package/src/runs/background/subagent-wait.js +6 -1
  107. package/src/runs/background/terminal-run-index.d.ts +1 -0
  108. package/src/runs/background/terminal-run-index.js +4 -2
  109. package/src/runs/foreground/async-stop-action.js +3 -1
  110. package/src/runs/foreground/execution.js +45 -5
  111. package/src/runs/foreground/foreground-history.d.ts +16 -2
  112. package/src/runs/foreground/foreground-history.js +133 -31
  113. package/src/runs/foreground/subagent-executor.d.ts +11 -0
  114. package/src/runs/foreground/subagent-executor.js +277 -74
  115. package/src/runs/shared/child-launch.d.ts +2 -2
  116. package/src/runs/shared/child-session.d.ts +3 -2
  117. package/src/runs/shared/child-session.js +51 -8
  118. package/src/runs/shared/child-tool-plan.d.ts +7 -0
  119. package/src/runs/shared/child-tool-plan.js +21 -6
  120. package/src/runs/shared/herdr-connection.d.ts +17 -8
  121. package/src/runs/shared/herdr-connection.js +19 -7
  122. package/src/runs/shared/herdr-external-adapters.d.ts +3 -2
  123. package/src/runs/shared/herdr-external-adapters.js +1 -1
  124. package/src/runs/shared/herdr-machine.d.ts +12 -2
  125. package/src/runs/shared/herdr-machine.js +55 -9
  126. package/src/runs/shared/herdr-pane-host.d.ts +88 -0
  127. package/src/runs/shared/herdr-pane-host.js +155 -0
  128. package/src/runs/shared/herdr-placed-run.d.ts +27 -150
  129. package/src/runs/shared/herdr-placed-run.js +48 -395
  130. package/src/runs/shared/local-machine-access.d.ts +5 -0
  131. package/src/runs/shared/local-machine-access.js +28 -0
  132. package/src/runs/shared/orca-progress-tabs.d.ts +0 -1
  133. package/src/runs/shared/orca-progress-tabs.js +1 -24
  134. package/src/runs/shared/parallel-utils.d.ts +1 -1
  135. package/src/runs/shared/placed-run-owner.d.ts +49 -0
  136. package/src/runs/shared/placed-run-owner.js +93 -0
  137. package/src/runs/shared/placement-host.d.ts +99 -0
  138. package/src/runs/shared/placement-host.js +157 -0
  139. package/src/runs/shared/rex-machine.d.ts +25 -0
  140. package/src/runs/shared/rex-machine.js +83 -0
  141. package/src/runs/shared/rex-pane-host.d.ts +62 -0
  142. package/src/runs/shared/rex-pane-host.js +327 -0
  143. package/src/runs/shared/structured-output.d.ts +1 -0
  144. package/src/runs/shared/structured-output.js +1 -0
  145. package/src/shared/accessible-dir.js +4 -1
  146. package/src/shared/completion-owner.d.ts +5 -0
  147. package/src/shared/completion-owner.js +5 -0
  148. package/src/shared/provider-diagnostics.d.ts +9 -0
  149. package/src/shared/provider-diagnostics.js +75 -0
  150. package/src/shared/session-identity.d.ts +2 -0
  151. package/src/shared/session-identity.js +26 -0
  152. package/src/shared/types.d.ts +48 -6
  153. package/src/slash/delegation-adapters.d.ts +3 -1
  154. package/src/slash/delegation-adapters.js +7 -0
  155. package/src/slash/prompt-template-bridge.js +3 -1
  156. package/src/slash/subagents-admin.d.ts +1 -0
  157. package/src/slash/subagents-admin.js +1 -1
  158. package/src/tui/fleet-layout.d.ts +20 -0
  159. package/src/tui/fleet-layout.js +17 -0
  160. package/src/tui/fleet-viewport.d.ts +12 -0
  161. package/src/tui/fleet-viewport.js +35 -0
  162. package/src/tui/fleet.d.ts +6 -6
  163. package/src/tui/fleet.js +93 -66
  164. package/src/tui/render.d.ts +6 -0
  165. package/src/tui/render.js +3 -3
  166. package/src/tui/subagent-messages.js +1 -0
  167. package/src/workflows/scripted-workflow.d.ts +1 -1
  168. package/src/workflows/structured-workflow-scripts.d.ts +1 -0
  169. package/src/workflows/structured-workflow-scripts.js +5 -4
  170. package/src/workflows/workflow-auto-resume.d.ts +32 -0
  171. package/src/workflows/workflow-auto-resume.js +102 -0
  172. package/src/workflows/workflow-launch-record.d.ts +27 -0
  173. package/src/workflows/workflow-launch-record.js +48 -0
  174. package/src/workflows/workflow-resources.js +2 -2
  175. package/src/workflows/workflow-resume-claim.d.ts +82 -0
  176. package/src/workflows/workflow-resume-claim.js +210 -0
  177. package/src/workflows/workflow-resume.d.ts +56 -0
  178. package/src/workflows/workflow-resume.js +106 -0
  179. package/src/workflows/workflow-reuse.d.ts +15 -9
  180. package/src/workflows/workflow-reuse.js +77 -14
  181. package/src/workflows/workflow-revival.d.ts +2 -0
  182. package/src/workflows/workflow-revival.js +2 -1
  183. package/src/workflows/workflow-stop-cause.d.ts +6 -0
  184. package/src/workflows/workflow-stop-cause.js +8 -0
@@ -4,6 +4,7 @@ import { writeAtomicJson } from "../../shared/atomic-json.js";
4
4
  import { resultFilePath, resultPayloadPathForSessionRun, writeAsyncResultFile } from "./result-files.js";
5
5
  import { updateActiveRunIndex } from "./active-run-index.js";
6
6
  import { readProcessTerminal } from "./process-terminal.js";
7
+ import { OWNING_SESSION_COMPLETION_OWNER } from "../../shared/completion-owner.js";
7
8
  import { readStatus } from "../../shared/utils.js";
8
9
  import { DIRS } from "../../shared/types.js";
9
10
  import { resolveEffectiveThinking } from "../../shared/model-info.js";
@@ -11,6 +12,8 @@ import { normalizeParallelGroups } from "./parallel-groups.js";
11
12
  import { nestedSummaryFromAsyncStatus, projectNestedEvents, resolveNestedAsyncDir, writeNestedEvent } from "../shared/nested-events.js";
12
13
  import { assertWorkflowGraphHostSteps } from "../shared/host-step-status.js";
13
14
  import { currentPidNamespaceScope } from "./pid-namespace.js";
15
+ import { currentBootId } from "./boot-id.js";
16
+ import { WORKFLOW_STOP_CAUSE_OWNER_EXITED } from "../../workflows/workflow-stop-cause.js";
14
17
  import { isTerminalAsyncState } from "./workflow-terminal-proof.js";
15
18
  function getErrorMessage(error) {
16
19
  return error instanceof Error ? error.message : String(error);
@@ -215,9 +218,14 @@ function settledWorkflowChildStatus(asyncDir, workflowRunId, step, now) {
215
218
  delete settled.stopped;
216
219
  return settled;
217
220
  }
218
- function buildFailedRepair(status, asyncDir, now, reason) {
221
+ function buildFailedRepair(status, asyncDir, now, reason, ownerExited = false, adoptCompletionOwnerId) {
219
222
  const runId = status.runId || path.basename(asyncDir);
220
223
  const pid = typeof status.pid === "number" ? status.pid : "unknown";
224
+ // A dead workflow host is the Pi process itself; its journal keeps the finished children for a relaunch.
225
+ const exitedWorkflow = ownerExited && status.mode === "workflow" ? status.workflow : undefined;
226
+ // Its owner process is dead, so nothing else can deliver its notice: an owning session that repairs it
227
+ // adopts it, and a repair elsewhere leaves it to whichever session owns the run.
228
+ const completionOwnerId = exitedWorkflow ? adoptCompletionOwnerId || OWNING_SESSION_COMPLETION_OWNER : status.completionOwnerId;
221
229
  const terminal = readProcessTerminal(asyncDir, { runId }) ?? status.processTerminal;
222
230
  const runnerExit = terminal?.state === "observed" || terminal?.state === "unknown"
223
231
  ? terminal.instances?.find((instance) => instance.kind === "runner" && instance.processInstanceId === terminal.runnerProcessInstanceId)
@@ -225,7 +233,9 @@ function buildFailedRepair(status, asyncDir, now, reason) {
225
233
  const exitText = runnerExit?.kind === "runner"
226
234
  ? `exited with code ${runnerExit.exitCode ?? "none"}${runnerExit.signal ? ` (signal ${runnerExit.signal})` : ""}`
227
235
  : "exited or disappeared";
228
- const baseMessage = reason ?? `Async runner process ${pid} ${exitText} before writing a result. Marked run failed by stale-run reconciliation.`;
236
+ const baseMessage = reason ?? (exitedWorkflow
237
+ ? `The Pi process (PID ${pid}) that ran workflow ${runId} exited before the workflow finished. Marked run failed by stale-run reconciliation. Its finished children are kept.`
238
+ : `Async runner process ${pid} ${exitText} before writing a result. Marked run failed by stale-run reconciliation.`);
229
239
  const diagnostics = readRunnerStartupDiagnostics(asyncDir);
230
240
  const message = diagnostics ? `${baseMessage}\n\nRunner stderr tail:\n${diagnostics}` : baseMessage;
231
241
  const steps = status.steps?.length ? status.steps : [{ agent: "subagent", status: "running" }];
@@ -263,6 +273,9 @@ function buildFailedRepair(status, asyncDir, now, reason) {
263
273
  lastUpdate: now,
264
274
  endedAt: now,
265
275
  steps: repairedSteps,
276
+ // Read from the pre-repair status: the repair time would make an old crash look new.
277
+ ...(exitedWorkflow ? { workflow: { ...exitedWorkflow, stopCause: WORKFLOW_STOP_CAUSE_OWNER_EXITED, ownerExitedAt: status.lastUpdate ?? status.startedAt } } : {}),
278
+ ...(completionOwnerId ? { completionOwnerId } : {}),
266
279
  };
267
280
  const resultAgent = repairedSteps[status.currentStep ?? 0]?.agent ?? repairedSteps[0]?.agent ?? "subagent";
268
281
  return {
@@ -272,7 +285,11 @@ function buildFailedRepair(status, asyncDir, now, reason) {
272
285
  id: runId,
273
286
  agent: resultAgent,
274
287
  mode: status.mode,
275
- ...(status.completionOwnerId ? { completionOwnerId: status.completionOwnerId } : {}),
288
+ ...(exitedWorkflow ? { workflow: { stopCause: WORKFLOW_STOP_CAUSE_OWNER_EXITED } } : {}),
289
+ ...(completionOwnerId ? { completionOwnerId } : {}),
290
+ ...(status.completionOwnerProcess ? { completionOwnerProcess: status.completionOwnerProcess } : {}),
291
+ ...(status.parentWorkflowRunId ? { parentWorkflowRunId: status.parentWorkflowRunId } : {}),
292
+ ...(status.workflowKey ? { workflowKey: status.workflowKey } : {}),
276
293
  success: false,
277
294
  state: "failed",
278
295
  summary: message,
@@ -296,8 +313,8 @@ function buildFailedRepair(status, asyncDir, now, reason) {
296
313
  },
297
314
  };
298
315
  }
299
- function writeFailedRepair(asyncDir, status, resultPath, now, reason) {
300
- const repair = buildFailedRepair(status, asyncDir, now, reason);
316
+ function writeFailedRepair(asyncDir, status, resultPath, now, reason, ownerExited = false, adoptCompletionOwnerId) {
317
+ const repair = buildFailedRepair(status, asyncDir, now, reason, ownerExited, adoptCompletionOwnerId);
301
318
  if (repair.result.sessionId)
302
319
  writeAsyncResultFile(resultPath, repair.result);
303
320
  writeAtomicJson(path.join(asyncDir, "status.json"), repair.status);
@@ -384,6 +401,32 @@ export function checkPidLiveness(pid, kill = process.kill, probeZombie = false)
384
401
  return "unknown";
385
402
  }
386
403
  }
404
+ export function isRecordedPid(value) {
405
+ if (!value || typeof value !== "object")
406
+ return false;
407
+ const { pid, pidNamespaceScope, bootId } = value;
408
+ return typeof pid === "number" && Number.isSafeInteger(pid) && pid > 0
409
+ && (pidNamespaceScope === undefined || typeof pidNamespaceScope === "string")
410
+ && (bootId === undefined || typeof bootId === "string");
411
+ }
412
+ /**
413
+ * Liveness of a pid saved in a status file. A pid saved under another boot is dead without a
414
+ * probe, but only when both sides are in the same pid namespace or neither records one: a
415
+ * macOS host reading a Linux container's runs must not compare their boot ids.
416
+ */
417
+ export function checkRecordedPidLiveness(recorded, observation = { observedScope: currentPidNamespaceScope(), observedBootId: currentBootId() }) {
418
+ const { observedScope, observedBootId } = observation;
419
+ if (recorded.pidNamespaceScope === observedScope && recorded.bootId && observedBootId && recorded.bootId !== observedBootId)
420
+ return "dead";
421
+ // An observer without a scope (macOS/Windows host sharing a container's temp root) cannot match a recorded one.
422
+ const scopeMismatch = recorded.pidNamespaceScope !== undefined && recorded.pidNamespaceScope !== observedScope;
423
+ // A local zombie is evidence about this pid only when its namespace is known to match.
424
+ const scopeVerified = recorded.pidNamespaceScope !== undefined && recorded.pidNamespaceScope === observedScope;
425
+ const liveness = observation.probe
426
+ ? observation.probe(recorded.pid)
427
+ : checkPidLiveness(recorded.pid, observation.kill, scopeVerified);
428
+ return liveness === "dead" && scopeMismatch ? "unknown" : liveness;
429
+ }
387
430
  export function reconcileAsyncRun(asyncDir, options = {}, observeStatus) {
388
431
  const now = options.now?.() ?? Date.now();
389
432
  const status = readStatus(asyncDir);
@@ -448,13 +491,11 @@ export function reconcileAsyncRun(asyncDir, options = {}, observeStatus) {
448
491
  return { status: null, repaired: false, resultPath };
449
492
  }
450
493
  }
451
- const observedScope = options.pidNamespaceScope ? options.pidNamespaceScope() : currentPidNamespaceScope();
452
- // An observer without a scope (macOS/Windows host sharing a container's temp root) cannot match a recorded one.
453
- const pidScopeMismatch = effectiveStatus.pidNamespaceScope !== undefined && effectiveStatus.pidNamespaceScope !== observedScope;
454
- // A local zombie is evidence about this runner only when its namespace is known to match.
455
- const pidScopeVerified = effectiveStatus.pidNamespaceScope !== undefined && effectiveStatus.pidNamespaceScope === observedScope;
456
- const observedLiveness = checkPidLiveness(effectiveStatus.pid, options.kill, pidScopeVerified);
457
- const liveness = observedLiveness === "dead" && pidScopeMismatch ? "unknown" : observedLiveness;
494
+ const liveness = checkRecordedPidLiveness({ pid: effectiveStatus.pid, pidNamespaceScope: effectiveStatus.pidNamespaceScope, bootId: effectiveStatus.bootId }, {
495
+ kill: options.kill,
496
+ observedScope: options.pidNamespaceScope ? options.pidNamespaceScope() : currentPidNamespaceScope(),
497
+ observedBootId: options.bootId ? options.bootId() : currentBootId(),
498
+ });
458
499
  if (liveness !== "dead") {
459
500
  const staleAfterMs = options.staleAlivePidMs ?? 24 * 60 * 60 * 1000;
460
501
  const lastUpdate = effectiveStatus.lastUpdate ?? effectiveStatus.startedAt;
@@ -464,5 +505,5 @@ export function reconcileAsyncRun(asyncDir, options = {}, observeStatus) {
464
505
  const message = `Async runner PID ${effectiveStatus.pid} ${probe}; status has not updated for ${now - lastUpdate}ms, so stale-run reconciliation marked the run failed because PID ownership is unverified.`;
465
506
  return writeFailedRepair(asyncDir, effectiveStatus, resultPath, now, message);
466
507
  }
467
- return writeFailedRepair(asyncDir, effectiveStatus, resultPath, now);
508
+ return writeFailedRepair(asyncDir, effectiveStatus, resultPath, now, undefined, true, options.adoptCompletionOwnerId);
468
509
  }
@@ -14,6 +14,7 @@ import { acquireSessionLease } from "../shared/session-lease.js";
14
14
  import { readProcessTerminalCandidate, writeProcessTerminalCandidate, markProcessTerminalCandidateLeaseRelease } from "./process-terminal-candidate.js";
15
15
  import { persistRunnerStartupFailure } from "./runner-startup-failure.js";
16
16
  import { currentPidNamespaceScope } from "./pid-namespace.js";
17
+ import { primeBootId } from "./boot-id.js";
17
18
  function isRecord(value) {
18
19
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
19
20
  }
@@ -103,6 +104,8 @@ async function waitForStartupControl(controlPath, token, action, timeoutMs = 30_
103
104
  export async function runConfiguredSubagent(rawConfig, options = {}) {
104
105
  validateSubagentRunConfig(rawConfig);
105
106
  const config = rawConfig;
107
+ // The runner never reads the boot id from the OS (sysctl on macOS); Pi passes it to direct runners only.
108
+ primeBootId(typeof config.bootId === "string" && config.bootId ? config.bootId : undefined);
106
109
  let lease;
107
110
  let startupCommitted = config.revivalLease === undefined && config.launchBarrierToken === undefined;
108
111
  let executionLoaded = false;
@@ -4,6 +4,7 @@ import { type RunnerSubagentStep as SubagentStep, type RunnerStep } from "../sha
4
4
  import type { InheritedChildRuntime } from "../shared/child-launch.ts";
5
5
  import type { ChildSessionFactory, DefaultChildSessionFactoryOptions } from "../shared/child-session.ts";
6
6
  import { type ChildEvent, type SteerDelivery, type StepSteerHandler } from "./run-child-session.ts";
7
+ import type { RecordedPid } from "./stale-run-reconciler.ts";
7
8
  import type { SessionLeaseRequest } from "../shared/session-lease.ts";
8
9
  import { createHerdrExternalAdapter, type HerdrExternalAdapterId, type HerdrExternalResult } from "../shared/herdr-external-adapters.ts";
9
10
  import { type OrcaProgressTab } from "../shared/orca-progress-tabs.ts";
@@ -25,6 +26,7 @@ export interface SubagentRunConfig {
25
26
  asyncDir: string;
26
27
  sessionId?: string | null;
27
28
  completionOwnerId?: string;
29
+ completionOwnerProcess?: RecordedPid;
28
30
  piPackageRoot?: string;
29
31
  /** Test seam: module the runner imports its `ChildSessionFactory` from. */
30
32
  childSessionFactoryModule?: string;
@@ -76,6 +78,8 @@ export interface SubagentRunConfig {
76
78
  runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensions;
77
79
  runnerProcessInstanceId?: string;
78
80
  launchBarrierToken?: string;
81
+ /** Pi's boot id, set only for a direct (non-launcher) runner; the runner never reads its own. */
82
+ bootId?: string;
79
83
  parentWorkflowRunId?: string;
80
84
  workflowKey?: string;
81
85
  lane?: WorkflowLaneMetadata;
@@ -49,7 +49,7 @@ import { nestedSummaryFromAsyncStatus, projectNestedEvents, resolveNestedAsyncDi
49
49
  import { isContextOverflow } from "../shared/model-resolution.js";
50
50
  import { processTerminalPath, writeProcessTerminalCandidate } from "./process-terminal.js";
51
51
  import { persistRunnerStartupFailure } from "./runner-startup-failure.js";
52
- import { currentPidNamespaceScope } from "./pid-namespace.js";
52
+ import { currentRecordedPid } from "./boot-id.js";
53
53
  import { createSteeringStatus, recordSteeringRequest, steeringStatus, terminalSteeringNoticeState, unconsumedSteerReason, updateSteeringTarget } from "./steering.js";
54
54
  import { PROMPT_REDACTED, SUBAGENT_STOPPED_MESSAGE, SUBAGENT_TIMED_OUT_MESSAGE, detectSubagentError, extractTextFromContent, extractToolArgsPreview, formatEmptyTerminalAssistantResponseError, getAgentDir, hasEmptyTerminalAssistantResponse, readStatus } from "../../shared/utils.js";
55
55
  import { planAbortRecovery } from "../shared/abort-recovery.js";
@@ -77,6 +77,7 @@ import { resolveCodexExecLaunch } from "../shared/codex-exec-adapter.js";
77
77
  import { resolveCursorAgentLaunch } from "../shared/cursor-agent-adapter.js";
78
78
  import { resolveExternalCliRunnerStatus } from "../shared/external-cli-contract.js";
79
79
  import { formatHerdrMachineHint, prepareHerdrMachineExternalCliRun } from "../shared/herdr-machine.js";
80
+ import { isRexLocalMachine } from "../shared/rex-machine.js";
80
81
  import { HerdrExternalNeedsAttentionError, createHerdrExternalAdapter, prepareInternalHerdrExternalAdapter } from "../shared/herdr-external-adapters.js";
81
82
  import { runExternalJob } from "../shared/external-job-runner.js";
82
83
  import { createOrcaProgressTab } from "../shared/orca-progress-tabs.js";
@@ -522,6 +523,7 @@ export async function runSingleStepInner(step, ctx) {
522
523
  capabilityCeiling: step.capabilityCeiling ?? ctx.capabilityCeiling,
523
524
  inheritedCapabilityCeiling: ctx.inheritedChildRuntime?.capabilityCeiling,
524
525
  requiredExtensions: step.requiredExtensions ?? ctx.inheritedChildRuntime?.requiredExtensions,
526
+ agentName: step.agent,
525
527
  permissionRules: step.permissionRules,
526
528
  }));
527
529
  }
@@ -608,6 +610,8 @@ export async function runSingleStepInner(step, ctx) {
608
610
  return omitUndefinedProperties({ agent: step.agent, context: step.context, output: message, error: message, exitCode: 1, stopped: stopped || undefined, timedOut: stopped ? undefined : true });
609
611
  }
610
612
  if (step.machine) {
613
+ if (step.machine.provider !== "herdr")
614
+ throw new Error("Rex supports native Pi children only.");
611
615
  const runner = resolveExternalCliRunnerStatus({ ...step.runner, machine: step.machine });
612
616
  const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
613
617
  const placedRunId = `${ctx.id}-${ctx.flatIndex}`;
@@ -781,7 +785,8 @@ export async function runSingleStepInner(step, ctx) {
781
785
  let toolBudget = step.toolBudget ? initialToolBudgetState(step.toolBudget) : undefined;
782
786
  let toolBudgetBlocked = false;
783
787
  let actualLaunchContractDigest = step.launchContractDigest;
784
- const mutationSnapshot = step.machine ? { source: "tracked-files", trackedOnly: true, cwd: step.cwd ?? ctx.cwd, dirtyFiles: [], fingerprints: {}, unavailable: "Local Git evidence is not authoritative for a pane-native remote run." } : snapshotTrackedMutations(step.cwd ?? ctx.cwd);
788
+ // A machine 'local' child edits this disk, so its local Git evidence is authoritative, as for an unplaced run.
789
+ const mutationSnapshot = step.machine && !isRexLocalMachine(step.machine) ? { source: "tracked-files", trackedOnly: true, cwd: step.cwd ?? ctx.cwd, dirtyFiles: [], fingerprints: {}, unavailable: "Local Git evidence is not authoritative for a pane-native remote run." } : snapshotTrackedMutations(step.cwd ?? ctx.cwd);
785
790
  let finalMutationEvidence = collectTrackedMutationEvidence(mutationSnapshot, step.cwd ?? ctx.cwd);
786
791
  let contextOverflow = false;
787
792
  let launchWarningsEmitted = false;
@@ -1004,7 +1009,7 @@ export async function runSingleStepInner(step, ctx) {
1004
1009
  ? formatEmptyTerminalAssistantResponseError(run.messages)
1005
1010
  : undefined;
1006
1011
  const remoteGitChanged = run.nativeMachine?.initialGit && run.nativeMachine.finalGit ? run.nativeMachine.initialGit.head !== run.nativeMachine.finalGit.head || run.nativeMachine.initialGit.dirty !== run.nativeMachine.finalGit.dirty : undefined;
1007
- const mutationEvidence = run.nativeMachine ? { source: "tracked-files", trackedOnly: true, changedFiles: [], attemptedMutation: remoteGitChanged === true, ...(remoteGitChanged === undefined ? { unavailable: "Remote Git before/after evidence was incomplete." } : {}) } : collectTrackedMutationEvidence(mutationSnapshot, step.cwd ?? ctx.cwd);
1012
+ const mutationEvidence = run.nativeMachine && !isRexLocalMachine(step.machine) ? { source: "tracked-files", trackedOnly: true, changedFiles: [], attemptedMutation: remoteGitChanged === true, ...(remoteGitChanged === undefined ? { unavailable: "Remote Git before/after evidence was incomplete." } : {}) } : collectTrackedMutationEvidence(mutationSnapshot, step.cwd ?? ctx.cwd);
1008
1013
  finalMutationEvidence = mutationEvidence;
1009
1014
  const mutationAttemptObserved = run.observedMutationAttempt === true || mutationEvidence.attemptedMutation === true;
1010
1015
  // Validated structured output is persistable file content even when the final text is only
@@ -1669,6 +1674,7 @@ export async function runSubagent(config, childSessions) {
1669
1674
  ...(config.toolCallId ? { toolCallId: config.toolCallId } : {}),
1670
1675
  ...(config.sessionId ? { sessionId: config.sessionId } : {}),
1671
1676
  ...(config.completionOwnerId ? { completionOwnerId: config.completionOwnerId } : {}),
1677
+ ...(config.completionOwnerProcess ? { completionOwnerProcess: config.completionOwnerProcess } : {}),
1672
1678
  mode: config.resultMode ?? (flatSteps.length > 1 ? "chain" : "single"),
1673
1679
  ...(config.nestedSelf ? { isNested: true } : {}),
1674
1680
  state: "running",
@@ -1680,8 +1686,7 @@ export async function runSubagent(config, childSessions) {
1680
1686
  ...(config.deadlineAt !== undefined ? { deadlineAt: config.deadlineAt } : {}),
1681
1687
  ...(config.toolBudget ? { toolBudget: initialToolBudgetState(config.toolBudget) } : {}),
1682
1688
  ...(config.usageBudget ? { usageBudget: usageBudgetState(config.usageBudget, undefined) } : {}),
1683
- pid: process.pid,
1684
- pidNamespaceScope: currentPidNamespaceScope(),
1689
+ ...currentRecordedPid(),
1685
1690
  cwd,
1686
1691
  currentStep: 0,
1687
1692
  chainStepCount: steps.length,
@@ -1909,6 +1914,8 @@ export async function runSubagent(config, childSessions) {
1909
1914
  cwd,
1910
1915
  sessionId: config.sessionId,
1911
1916
  completionOwnerId: config.completionOwnerId,
1917
+ completionOwnerProcess: config.completionOwnerProcess,
1918
+ parentWorkflowRunId: config.parentWorkflowRunId,
1912
1919
  sessionFile: statusPayload.sessionFile ?? latestSessionFile,
1913
1920
  }), (filePath, payload) => publishRunResult(filePath, () => writePendingAsyncResultFile(filePath, payload)));
1914
1921
  };
@@ -4797,6 +4804,7 @@ export async function runSubagent(config, childSessions) {
4797
4804
  runtimeAcknowledgedExtensions: singleRuntimeAcknowledgedExtensions,
4798
4805
  sessionId: config.sessionId,
4799
4806
  completionOwnerId: config.completionOwnerId,
4807
+ completionOwnerProcess: config.completionOwnerProcess,
4800
4808
  sessionFile: effectiveSessionFile,
4801
4809
  intercomTarget: config.controlIntercomTarget,
4802
4810
  shareUrl,
@@ -54,7 +54,7 @@ export interface SubagentWaitParams {
54
54
  all?: boolean;
55
55
  /** Give up after this many milliseconds. Defaults to waitTool.defaultTimeoutMs, then 30 minutes. */
56
56
  timeoutMs?: number;
57
- /** False keeps a blocking wait open through idle attention; supervisor/contact requests still stop the wait. */
57
+ /** False keeps a blocking wait open through all attention except supervisor/contact requests, such as idle time, a tool held open, or repeated tool failures. */
58
58
  stopOnAttention?: boolean;
59
59
  }
60
60
  /** Minimal event-bus surface wait subscribes to (matches pi.events). */
@@ -707,8 +707,13 @@ export async function waitForSubagents(params, signal, deps) {
707
707
  const supervisorAttentionHint = relevantAttention.some(hasSupervisorTool)
708
708
  ? " Reply to any pending supervisor request. If subagent_supervisor({ action: \"pending\" }) is empty, check intercom({ action: \"pending\" }) because an external intercom tool may own the request."
709
709
  : "";
710
+ // Only when watchdog attention alone ended the wait: stopOnAttention: false would have kept blocking.
711
+ const keepWaitingHint = stopOnAttention && supervisorAttentionHint === ""
712
+ && (waitForAll || finishedAsyncCount + providerFinishedCount === 0)
713
+ ? " These runs are still running and none is waiting on a supervisor reply. To keep waiting through this kind of attention, call bg_wait again with stopOnAttention: false; supervisor requests still end that wait."
714
+ : "";
710
715
  const attentionNote = relevantAttention.length > 0
711
- ? ` ${relevantAttention.length} run(s) need attention: ${relevantAttention.map((run) => run.id).join(", ")} —${supervisorAttentionHint} inspect with subagent({ action: "status" }) then steer a top-level live async child, resume a paused/completed/failed child, or interrupt explicitly.`
716
+ ? ` ${relevantAttention.length} run(s) need attention: ${relevantAttention.map((run) => run.id).join(", ")} —${supervisorAttentionHint} inspect with subagent({ action: "status" }) then steer a top-level live async child, resume a paused/completed/failed child, or interrupt explicitly.${keepWaitingHint}`
712
717
  : "";
713
718
  const stillRunning = active.filter((run) => initialAsyncIds.has(run.id)).length
714
719
  + providerActive.filter((item) => initialProviderIds.has(backgroundWorkIdentity(item))).length;
@@ -5,4 +5,5 @@ export declare function readTerminalRunToolCallIndex(asyncDirRoot: string, toolC
5
5
  export declare function readRecentTerminalRunIndex(asyncDirRoot: string, options?: {
6
6
  sessionId?: string;
7
7
  limit?: number;
8
+ endedAfter?: number;
8
9
  }): string[];
@@ -99,8 +99,10 @@ function sessionDirs(asyncDirRoot, sessionId) {
99
99
  throw error;
100
100
  }
101
101
  }
102
- function recentMarkerFiles(dirs, limit) {
102
+ function recentMarkerFiles(dirs, limit, endedAfter) {
103
103
  return dirs.flatMap(markerFiles)
104
+ // The name starts with the run's end time, so an old run is skipped without reading its marker.
105
+ .filter((marker) => endedAfter === undefined || !(Number(marker.name.slice(0, TIMESTAMP_WIDTH)) < endedAfter))
104
106
  .sort((left, right) => right.name.localeCompare(left.name))
105
107
  .slice(0, limit)
106
108
  .map((marker) => path.join(marker.dir, marker.name));
@@ -135,7 +137,7 @@ export function readTerminalRunToolCallIndex(asyncDirRoot, toolCallId) {
135
137
  }
136
138
  export function readRecentTerminalRunIndex(asyncDirRoot, options = {}) {
137
139
  const limit = options.limit === undefined ? Number.POSITIVE_INFINITY : Math.max(0, Math.floor(options.limit));
138
- const candidates = recentMarkerFiles(sessionDirs(asyncDirRoot, options.sessionId), limit);
140
+ const candidates = recentMarkerFiles(sessionDirs(asyncDirRoot, options.sessionId), limit, options.endedAfter);
139
141
  const runIds = [];
140
142
  const seen = new Set();
141
143
  for (const marker of candidates) {
@@ -44,6 +44,8 @@ function sealPausedRun(asyncDir, status, resultsDir) {
44
44
  sessionId: status.sessionId,
45
45
  asyncDir,
46
46
  ...(status.completionOwnerId ? { completionOwnerId: status.completionOwnerId } : {}),
47
+ ...(status.completionOwnerProcess ? { completionOwnerProcess: status.completionOwnerProcess } : {}),
48
+ ...(status.parentWorkflowRunId ? { parentWorkflowRunId: status.parentWorkflowRunId } : {}),
47
49
  ...(status.toolCallId ? { toolCallId: status.toolCallId } : {}),
48
50
  results: (status.steps ?? []).map((step) => ({
49
51
  agent: step.agent,
@@ -146,7 +148,7 @@ export function stopAsyncRun(state, runId, kill, location, childId) {
146
148
  if (!target)
147
149
  return null;
148
150
  const status = reconcileAsyncRun(target.asyncDir, { kill }).status;
149
- // A workflow relaunched with options.reuseFrom stops the exact child run it re-attached, whichever session launched it.
151
+ // A relaunched workflow stops the exact child run it re-attached from its relaunch chain, whichever session launched it.
150
152
  if (state.currentSessionId && !ownsRun(state.currentSessionId, status?.sessionId) && location?.reattachedRunId !== target.asyncId) {
151
153
  return {
152
154
  content: [{ type: "text", text: `Async run '${target.asyncId}' was not found in the active session.` }],
@@ -24,7 +24,7 @@ import { deriveChildSessionName } from "../../shared/child-session-name.js";
24
24
  import { assertAgentAllowedByCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling } from "../shared/capability-ceiling.js";
25
25
  import { qualifyModelWithProvider, resolveEffectiveThinking } from "../../shared/model-info.js";
26
26
  import { assertThinkingWithinCeiling, intersectThinkingCeilings } from "../../shared/thinking-ceiling.js";
27
- import { formatStructuredOutputRejectionError, MISSING_STRUCTURED_ACCEPTANCE_REPORT_ERROR, MISSING_STRUCTURED_OUTPUT_CALL_ERROR } from "../shared/structured-output.js";
27
+ import { formatStructuredOutputRejectionError, MISSING_STRUCTURED_ACCEPTANCE_REPORT_ERROR, MISSING_STRUCTURED_OUTPUT_CALL_ERROR, STRUCTURED_OUTPUT_FOLLOW_UP } from "../shared/structured-output.js";
28
28
  import { formatMidToolExitError, isOrdinaryToolForMidToolExit } from "../shared/process-signal.js";
29
29
  import { formatChildToolDiagnostic } from "../shared/tool-availability.js";
30
30
  import { formatChildModelResolutionDiagnostic, formatInheritedProviderDiagnostic, isChildModelResolutionFailure } from "../shared/model-resolution-diagnostic.js";
@@ -45,6 +45,8 @@ import { acceptChildWatchdogEvent, applyChildWatchdogMessage, childWatchdogIsAct
45
45
  import { buildInProcessChildLaunch, createReportedChildSessionInput } from "../shared/child-launch.js";
46
46
  import { childSessionFactory, childSessionHasQueuedMessages, projectChildSessionEventForJson } from "../shared/child-session.js";
47
47
  import { reconcileAttemptUsage } from "../shared/usage-reconciliation.js";
48
+ import { clearProviderRetry, observeProviderDiagnostics, snapshotProviderDiagnostics } from "../../shared/provider-diagnostics.js";
49
+ import { isRexLocalMachine } from "../shared/rex-machine.js";
48
50
  const artifactOutputByResult = new WeakMap();
49
51
  const acceptanceOutputByResult = new WeakMap();
50
52
  function emptyUsage() {
@@ -165,6 +167,7 @@ function snapshotProgress(progress) {
165
167
  ...progress,
166
168
  task: PROMPT_REDACTED,
167
169
  skills: progress.skills ? [...progress.skills] : undefined,
170
+ providerDiagnostics: snapshotProviderDiagnostics(progress.providerDiagnostics),
168
171
  recentTools: boundStreamedRecentTools(progress.recentTools),
169
172
  recentOutput: boundStreamedRecentOutput(progress.recentOutput),
170
173
  };
@@ -175,6 +178,7 @@ function snapshotResult(result, progress) {
175
178
  task: PROMPT_REDACTED,
176
179
  messages: result.outputMode === "file-only" && result.savedOutputPath ? undefined : result.messages ? [...result.messages] : undefined,
177
180
  usage: { ...result.usage },
181
+ providerDiagnostics: snapshotProviderDiagnostics(result.providerDiagnostics),
178
182
  skills: result.skills ? [...result.skills] : undefined,
179
183
  controlEvents: result.controlEvents ? result.controlEvents.map((event) => ({ ...event })) : undefined,
180
184
  progress,
@@ -198,6 +202,7 @@ function snapshotStreamResult(result, progress) {
198
202
  }
199
203
  function captureStructuredDelegationProgressState(progress, result) {
200
204
  return {
205
+ providerDiagnostics: progress.providerDiagnostics,
201
206
  currentTool: progress.currentTool,
202
207
  currentToolArgs: progress.currentToolArgs,
203
208
  recentOutput: [...progress.recentOutput],
@@ -210,7 +215,9 @@ function captureStructuredDelegationProgressState(progress, result) {
210
215
  };
211
216
  }
212
217
  function structuredDelegationProgressChanged(previous, progress, result) {
213
- if (previous.currentTool !== progress.currentTool
218
+ // Observations replace internal diagnostics; no transcript walk or serialization on heartbeats.
219
+ if (previous.providerDiagnostics !== progress.providerDiagnostics
220
+ || previous.currentTool !== progress.currentTool
214
221
  || previous.currentToolArgs !== progress.currentToolArgs
215
222
  || previous.activityState !== progress.activityState
216
223
  || previous.model !== (progress.model ?? result.model)
@@ -352,6 +359,7 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
352
359
  exitCode: 0,
353
360
  outputState: "absent",
354
361
  messages: [],
362
+ providerDiagnostics: shared.providerDiagnostics,
355
363
  usage: emptyUsage(),
356
364
  model: modelArg,
357
365
  ...(resolvedThinking ? { thinking: resolvedThinking } : {}),
@@ -383,6 +391,7 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
383
391
  let cacheReadUsageComplete = true;
384
392
  let cacheWriteUsageComplete = true;
385
393
  const progress = {
394
+ providerDiagnostics: result.providerDiagnostics,
386
395
  index: options.index ?? 0,
387
396
  agent: agent.name,
388
397
  ...(childSessionName ? { sessionName: childSessionName } : {}),
@@ -433,7 +442,9 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
433
442
  };
434
443
  return result;
435
444
  }
436
- const mutationSnapshot = options.machine ? { source: "tracked-files", trackedOnly: true, cwd: options.cwd ?? runtimeCwd, dirtyFiles: [], fingerprints: {}, unavailable: "Local Git evidence is not authoritative for a pane-native remote run." } : snapshotTrackedMutations(options.cwd ?? runtimeCwd);
445
+ // A machine 'local' child edits this disk, so its local Git evidence is authoritative, as for an unplaced run.
446
+ const mutationCwd = isRexLocalMachine(options.machine) ? options.machine.cwd : options.cwd ?? runtimeCwd;
447
+ const mutationSnapshot = options.machine && !isRexLocalMachine(options.machine) ? { source: "tracked-files", trackedOnly: true, cwd: mutationCwd, dirtyFiles: [], fingerprints: {}, unavailable: "Local Git evidence is not authoritative for a pane-native remote run." } : snapshotTrackedMutations(mutationCwd);
437
448
  let structuredOutputToolInvoked = false;
438
449
  let structuredOutputMessageStartIndex;
439
450
  let toolAvailabilityError;
@@ -972,6 +983,14 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
972
983
  return;
973
984
  }
974
985
  const now = Date.now();
986
+ const providerDiagnostics = observeProviderDiagnostics(result.providerDiagnostics, evt, now);
987
+ if (providerDiagnostics !== result.providerDiagnostics) {
988
+ result.providerDiagnostics = providerDiagnostics;
989
+ progress.providerDiagnostics = providerDiagnostics;
990
+ // message_end already emits synchronously below; retry-only transitions need their own update.
991
+ if (evt.type !== "message_end")
992
+ fireUpdate();
993
+ }
975
994
  progress.durationMs = now - startTime;
976
995
  progress.lastActivityAt = now;
977
996
  updateActivityState(now);
@@ -1243,7 +1262,7 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
1243
1262
  toolAvailabilityError = toolDiagnosticError;
1244
1263
  result.runtimeAcknowledgedExtensions = capture.runtimeAcknowledgedExtensions();
1245
1264
  if (session?.machineEvidence)
1246
- result.nativeMachine = { provider: "herdr", machineId: session.machineEvidence.machineId, ...(session.machineEvidence.initial ? { initialGit: session.machineEvidence.initial } : {}), ...(session.machineEvidence.final ? { finalGit: session.machineEvidence.final } : {}) };
1265
+ result.nativeMachine = { provider: session.machineEvidence.provider, machineId: session.machineEvidence.machineId, ...(session.machineEvidence.initial ? { initialGit: session.machineEvidence.initial } : {}), ...(session.machineEvidence.final ? { finalGit: session.machineEvidence.final } : {}) };
1247
1266
  let closeError = result.error ?? toolDiagnosticError ?? assistantError;
1248
1267
  const promptErrorMessage = promptError === undefined ? undefined : promptError instanceof Error ? promptError.message : String(promptError);
1249
1268
  if (promptError !== undefined)
@@ -1396,6 +1415,24 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
1396
1415
  // A runtime replacement can stop a child that detached while it was still being created.
1397
1416
  if (!hostStopReason)
1398
1417
  await created.prompt(`Task: ${task}`);
1418
+ // A structured child that finished normally without calling structured_output gets one more turn (#2997).
1419
+ if (options.structuredOutput
1420
+ && !structuredOutputToolInvoked
1421
+ && !sessionSettled
1422
+ && !lifecycleFinished
1423
+ && !hostStopReason
1424
+ && !created.shutDown
1425
+ && !abortedBySignal
1426
+ && !interruptedByControl
1427
+ && !result.timedOut
1428
+ && !forcedTermination
1429
+ && !result.error
1430
+ && !assistantError
1431
+ && !capture.toolDiagnostic()
1432
+ && !(progress.currentTool && isOrdinaryToolForMidToolExit(progress.currentTool))) {
1433
+ applyChildLifecycle("cancel-drain");
1434
+ await created.prompt(STRUCTURED_OUTPUT_FOLLOW_UP);
1435
+ }
1399
1436
  settle(undefined);
1400
1437
  }
1401
1438
  catch (error) {
@@ -1404,6 +1441,8 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
1404
1441
  })();
1405
1442
  });
1406
1443
  result.exitCode = exitCode;
1444
+ result.providerDiagnostics = clearProviderRetry(result.providerDiagnostics);
1445
+ progress.providerDiagnostics = result.providerDiagnostics;
1407
1446
  if (afterCompactionSettlement) {
1408
1447
  result[AFTER_COMPACTION_SETTLEMENT] = true;
1409
1448
  }
@@ -1497,7 +1536,7 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
1497
1536
  durationMs: progress.durationMs,
1498
1537
  };
1499
1538
  const remoteGitChanged = result.nativeMachine?.initialGit && result.nativeMachine.finalGit ? result.nativeMachine.initialGit.head !== result.nativeMachine.finalGit.head || result.nativeMachine.initialGit.dirty !== result.nativeMachine.finalGit.dirty : undefined;
1500
- const mutationEvidence = result.nativeMachine ? { source: "tracked-files", trackedOnly: true, changedFiles: [], attemptedMutation: remoteGitChanged === true, ...(remoteGitChanged === undefined ? { unavailable: "Remote Git before/after evidence was incomplete." } : {}) } : collectTrackedMutationEvidence(mutationSnapshot, options.cwd ?? runtimeCwd);
1539
+ const mutationEvidence = result.nativeMachine && !isRexLocalMachine(options.machine) ? { source: "tracked-files", trackedOnly: true, changedFiles: [], attemptedMutation: remoteGitChanged === true, ...(remoteGitChanged === undefined ? { unavailable: "Remote Git before/after evidence was incomplete." } : {}) } : collectTrackedMutationEvidence(mutationSnapshot, mutationCwd);
1501
1540
  const acceptanceOutput = getFinalOutput(result.messages ?? []);
1502
1541
  const structuredText = result.structuredOutput === undefined ? undefined : JSON.stringify(result.structuredOutput, null, 2);
1503
1542
  let fullOutput = stripAcceptanceReport(acceptanceOutput);
@@ -1855,6 +1894,7 @@ async function runSyncCompletionInner(runtimeCwd, agents, agentName, task, optio
1855
1894
  orcaProgressTab,
1856
1895
  launchWarnings,
1857
1896
  verifyModel,
1897
+ providerDiagnostics: lastResult?.providerDiagnostics,
1858
1898
  });
1859
1899
  lastResult = attemptResult;
1860
1900
  sumUsage(aggregateUsage, attemptResult.usage);
@@ -1,11 +1,25 @@
1
- import type { SubagentState } from "../../shared/types.ts";
1
+ import type { ForegroundResumeChild, ForegroundResumeRun, SubagentState } from "../../shared/types.ts";
2
2
  export declare const MAX_REMEMBERED_FOREGROUND_RUNS = 50;
3
- export declare function persistForegroundRunHistory(state: SubagentState, options?: {
3
+ export declare const FOREGROUND_OWNER_EXITED_STOP_ERROR = "Subagent stopped because the Pi process that ran it exited before the run finished.";
4
+ /**
5
+ * Saves one foreground run to its own history file. A run with a `running` child is a launch-time
6
+ * record; the run's final save replaces it.
7
+ */
8
+ export declare function persistForegroundRun(run: ForegroundResumeRun, options?: {
4
9
  resultsDir?: string;
5
10
  limit?: number;
6
11
  }): void;
12
+ /** Drops this process's `running` record for a launch that threw, so it never loads as interrupted. */
13
+ export declare function discardRunningForegroundRun(runId: string, options?: {
14
+ resultsDir?: string;
15
+ }): void;
16
+ /** For a record with a `running` child: its owning Pi process is gone. */
17
+ export declare function ownerConfirmedDead(run: ForegroundResumeRun): boolean;
18
+ /** State text for a remembered child; a child cut off by a Pi exit names the resume target. */
19
+ export declare function foregroundChildStateLabel(run: Pick<ForegroundResumeRun, "runId">, child: Pick<ForegroundResumeChild, "status" | "error" | "sessionFile">): string;
7
20
  export declare function restoreForegroundRunHistory(state: SubagentState, options?: {
8
21
  resultsDir?: string;
9
22
  sessionId?: string | null;
10
23
  limit?: number;
24
+ observeForeign?: (run: ForegroundResumeRun) => void;
11
25
  }): number;