@fitchmultz/pi-subagents 0.0.0-stage → 0.44.4

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 (342) hide show
  1. package/CHANGELOG.md +1645 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1529 -2
  4. package/agents/context-builder.md +97 -0
  5. package/agents/debugger.md +51 -0
  6. package/agents/delegate.md +13 -0
  7. package/agents/fixer.md +37 -0
  8. package/agents/oracle.md +71 -0
  9. package/agents/planner.md +72 -0
  10. package/agents/researcher.md +51 -0
  11. package/agents/reviewer-claude.md +69 -0
  12. package/agents/reviewer-gpt.md +68 -0
  13. package/agents/reviewer-ponytail.md +82 -0
  14. package/agents/reviewer-security.md +81 -0
  15. package/agents/reviewer.md +69 -0
  16. package/agents/scout.md +66 -0
  17. package/agents/ui-designer.md +51 -0
  18. package/agents/watcher.md +26 -0
  19. package/agents/worker.md +46 -0
  20. package/agents/writer.md +36 -0
  21. package/dist/agents/agent-management.js +923 -0
  22. package/dist/agents/agent-scope.js +7 -0
  23. package/dist/agents/agent-selection.js +20 -0
  24. package/dist/agents/agent-serializer.js +100 -0
  25. package/dist/agents/agents.js +752 -0
  26. package/dist/agents/chain-serializer.js +367 -0
  27. package/dist/agents/frontmatter.js +21 -0
  28. package/dist/agents/identity.js +29 -0
  29. package/dist/agents/skills.js +570 -0
  30. package/dist/extension/build-info.js +1 -0
  31. package/dist/extension/compact-tools.js +112 -0
  32. package/dist/extension/config.js +22 -0
  33. package/dist/extension/control-notices.js +29 -0
  34. package/dist/extension/doctor.js +143 -0
  35. package/dist/extension/fanout-child.js +326 -0
  36. package/dist/extension/index.js +577 -0
  37. package/dist/extension/schemas.js +416 -0
  38. package/dist/extension/tool-input.js +20 -0
  39. package/dist/extension/tool-result.js +14 -0
  40. package/dist/history/canonical-result.js +81 -0
  41. package/dist/history/database.js +72 -0
  42. package/dist/history/index.js +194 -0
  43. package/dist/history/ingest.js +447 -0
  44. package/dist/history/queries.js +350 -0
  45. package/dist/history/store.js +216 -0
  46. package/dist/history/types.js +4 -0
  47. package/dist/history/worker.js +461 -0
  48. package/dist/intercom/intercom-bridge.js +112 -0
  49. package/dist/intercom/live-intercom.js +94 -0
  50. package/dist/intercom/result-intercom.js +373 -0
  51. package/dist/pi-intercom/agent-dir.js +10 -0
  52. package/dist/pi-intercom/broker/broker.js +426 -0
  53. package/dist/pi-intercom/broker/client.js +492 -0
  54. package/dist/pi-intercom/broker/framing.js +65 -0
  55. package/dist/pi-intercom/broker/paths.js +42 -0
  56. package/dist/pi-intercom/broker/pid.js +66 -0
  57. package/dist/pi-intercom/broker/spawn.js +221 -0
  58. package/dist/pi-intercom/config.js +102 -0
  59. package/dist/pi-intercom/index.js +2942 -0
  60. package/dist/pi-intercom/reply-tracker.js +182 -0
  61. package/dist/pi-intercom/session-targets.js +128 -0
  62. package/dist/pi-intercom/subagent-live-events.js +132 -0
  63. package/dist/pi-intercom/topics.js +157 -0
  64. package/dist/pi-intercom/types.js +163 -0
  65. package/dist/pi-intercom/ui/compose.js +269 -0
  66. package/dist/pi-intercom/ui/inline-message.js +80 -0
  67. package/dist/pi-intercom/ui/session-list.js +124 -0
  68. package/dist/runs/background/async-control.js +95 -0
  69. package/dist/runs/background/async-execution.js +676 -0
  70. package/dist/runs/background/async-job-tracker.js +383 -0
  71. package/dist/runs/background/async-result-file.js +58 -0
  72. package/dist/runs/background/async-resume.js +305 -0
  73. package/dist/runs/background/async-status.js +442 -0
  74. package/dist/runs/background/completion-dedupe.js +69 -0
  75. package/dist/runs/background/completion-delivery.js +245 -0
  76. package/dist/runs/background/notify.js +71 -0
  77. package/dist/runs/background/parallel-groups.js +55 -0
  78. package/dist/runs/background/result-watcher.js +426 -0
  79. package/dist/runs/background/run-id-resolver.js +64 -0
  80. package/dist/runs/background/run-status.js +382 -0
  81. package/dist/runs/background/stale-run-reconciler.js +335 -0
  82. package/dist/runs/background/subagent-runner-launcher.js +55 -0
  83. package/dist/runs/background/subagent-runner.js +1872 -0
  84. package/dist/runs/background/top-level-async.js +5 -0
  85. package/dist/runs/foreground/chain-clarify.js +1152 -0
  86. package/dist/runs/foreground/clarify-invocation.js +72 -0
  87. package/dist/runs/foreground/execution-input.js +423 -0
  88. package/dist/runs/foreground/foreground-control.js +817 -0
  89. package/dist/runs/foreground/question-control.js +77 -0
  90. package/dist/runs/foreground/run-async-path.js +205 -0
  91. package/dist/runs/foreground/subagent-executor.js +442 -0
  92. package/dist/runs/foreground/subagent-params.js +165 -0
  93. package/dist/runs/foreground/wait-run.js +139 -0
  94. package/dist/runs/shared/acceptance-contract.js +253 -0
  95. package/dist/runs/shared/acceptance-evaluation.js +256 -0
  96. package/dist/runs/shared/acceptance-finalization.js +280 -0
  97. package/dist/runs/shared/acceptance-reports.js +147 -0
  98. package/dist/runs/shared/acceptance.js +4 -0
  99. package/dist/runs/shared/chain-outputs.js +110 -0
  100. package/dist/runs/shared/child-attempt.js +693 -0
  101. package/dist/runs/shared/child-execution-cwd.js +96 -0
  102. package/dist/runs/shared/child-observations.js +114 -0
  103. package/dist/runs/shared/claude-code.js +199 -0
  104. package/dist/runs/shared/completion-guard.js +31 -0
  105. package/dist/runs/shared/dynamic-fanout.js +274 -0
  106. package/dist/runs/shared/history-index.js +72 -0
  107. package/dist/runs/shared/mcp-direct-tool-allowlist.js +334 -0
  108. package/dist/runs/shared/model-fallback.js +185 -0
  109. package/dist/runs/shared/mutating-tool-guard.js +170 -0
  110. package/dist/runs/shared/native-finalization.js +101 -0
  111. package/dist/runs/shared/native-usage.js +153 -0
  112. package/dist/runs/shared/nested-events.js +700 -0
  113. package/dist/runs/shared/nested-path.js +48 -0
  114. package/dist/runs/shared/nested-render.js +108 -0
  115. package/dist/runs/shared/parallel-utils.js +61 -0
  116. package/dist/runs/shared/parent-receipts.js +68 -0
  117. package/dist/runs/shared/parent-usage.js +121 -0
  118. package/dist/runs/shared/pi-args.js +310 -0
  119. package/dist/runs/shared/pi-spawn.js +52 -0
  120. package/dist/runs/shared/run-history-queries.js +45 -0
  121. package/dist/runs/shared/run-history.js +103 -0
  122. package/dist/runs/shared/run-id-ambiguity.js +4 -0
  123. package/dist/runs/shared/run-records.js +679 -0
  124. package/dist/runs/shared/session-cwd-preload.js +33 -0
  125. package/dist/runs/shared/single-output.js +222 -0
  126. package/dist/runs/shared/streaming-text.js +15 -0
  127. package/dist/runs/shared/structured-output.js +55 -0
  128. package/dist/runs/shared/subagent-control.js +179 -0
  129. package/dist/runs/shared/subagent-prompt-runtime.js +197 -0
  130. package/dist/runs/shared/subagent-tool-loop-guard.js +66 -0
  131. package/dist/runs/shared/supervisor-questions.js +320 -0
  132. package/dist/runs/shared/workflow-graph.js +214 -0
  133. package/dist/runs/shared/workflow-policy.js +84 -0
  134. package/dist/runs/shared/worktree.js +525 -0
  135. package/dist/shared/agent-context-policy.js +126 -0
  136. package/dist/shared/artifacts.js +96 -0
  137. package/dist/shared/atomic-json.js +13 -0
  138. package/dist/shared/cooperative.js +20 -0
  139. package/dist/shared/execution-cwd.js +14 -0
  140. package/dist/shared/file-coalescer.js +21 -0
  141. package/dist/shared/fork-context.js +49 -0
  142. package/dist/shared/formatters.js +85 -0
  143. package/dist/shared/journal-reader.js +407 -0
  144. package/dist/shared/lazy-tools.js +32 -0
  145. package/dist/shared/model-info.js +68 -0
  146. package/dist/shared/native-session.js +15 -0
  147. package/dist/shared/native-tui.js +18 -0
  148. package/dist/shared/native-typebox.js +20 -0
  149. package/dist/shared/post-exit-stdio-guard.js +197 -0
  150. package/dist/shared/prompt-sections.js +8 -0
  151. package/dist/shared/session-entries.js +52 -0
  152. package/dist/shared/session-identity.js +14 -0
  153. package/dist/shared/settings.js +254 -0
  154. package/dist/shared/status-format.js +111 -0
  155. package/dist/shared/temp-root.js +114 -0
  156. package/dist/shared/types.js +207 -0
  157. package/dist/shared/ui-mode.js +3 -0
  158. package/dist/shared/utils.js +334 -0
  159. package/dist/slash/prompt-template-bridge.js +298 -0
  160. package/dist/slash/slash-bridge.js +116 -0
  161. package/dist/slash/slash-commands.js +607 -0
  162. package/dist/slash/slash-live-state.js +246 -0
  163. package/dist/tui/action-hints.js +75 -0
  164. package/dist/tui/agent-history.js +197 -0
  165. package/dist/tui/agent-view.js +1656 -0
  166. package/dist/tui/render.js +1515 -0
  167. package/docs/intercom.md +579 -0
  168. package/docs/performance.md +121 -0
  169. package/package.json +101 -4
  170. package/prompts/gather-context-and-clarify.md +14 -0
  171. package/prompts/parallel-cleanup.md +62 -0
  172. package/prompts/parallel-context-build.md +56 -0
  173. package/prompts/parallel-handoff-plan.md +62 -0
  174. package/prompts/parallel-research.md +51 -0
  175. package/prompts/parallel-review.md +59 -0
  176. package/prompts/review-loop.md +42 -0
  177. package/scripts/build.mjs +134 -0
  178. package/scripts/check-compat.mjs +48 -0
  179. package/scripts/compat-host.mjs +19 -0
  180. package/scripts/linux-smoke.sh +51 -0
  181. package/scripts/local-install-smoke.mjs +66 -0
  182. package/scripts/native-package-smoke.mjs +82 -0
  183. package/scripts/package-smoke.mjs +205 -0
  184. package/scripts/prepare.mjs +67 -0
  185. package/scripts/real-pi-smoke.mjs +415 -0
  186. package/scripts/run-tests.mjs +149 -0
  187. package/skills/pi-intercom/SKILL.md +113 -0
  188. package/skills/pi-intercom/evals/evals.json +29 -0
  189. package/skills/pi-intercom/evals/trigger-evals.json +47 -0
  190. package/skills/pi-intercom/references/peer-sessions.md +69 -0
  191. package/skills/pi-subagents/SKILL.md +76 -0
  192. package/skills/pi-subagents/evals/evals.json +29 -0
  193. package/skills/pi-subagents/evals/trigger-evals.json +42 -0
  194. package/skills/pi-subagents/references/full-orchestration-guide.md +863 -0
  195. package/src/agents/agent-management.ts +813 -0
  196. package/src/agents/agent-scope.ts +7 -0
  197. package/src/agents/agent-selection.ts +23 -0
  198. package/src/agents/agent-serializer.ts +99 -0
  199. package/src/agents/agents.ts +916 -0
  200. package/src/agents/chain-serializer.ts +329 -0
  201. package/src/agents/frontmatter.ts +21 -0
  202. package/src/agents/identity.ts +30 -0
  203. package/src/agents/skills.ts +637 -0
  204. package/src/extension/build-info.ts +2 -0
  205. package/src/extension/compact-tools.ts +114 -0
  206. package/src/extension/config.ts +23 -0
  207. package/src/extension/control-notices.ts +47 -0
  208. package/src/extension/doctor.ts +171 -0
  209. package/src/extension/fanout-child.ts +312 -0
  210. package/src/extension/index.ts +649 -0
  211. package/src/extension/schemas.ts +444 -0
  212. package/src/extension/tool-input.ts +19 -0
  213. package/src/extension/tool-result.ts +16 -0
  214. package/src/history/canonical-result.ts +62 -0
  215. package/src/history/database.ts +59 -0
  216. package/src/history/index.ts +152 -0
  217. package/src/history/ingest.ts +332 -0
  218. package/src/history/queries.ts +241 -0
  219. package/src/history/store.ts +203 -0
  220. package/src/history/types.ts +166 -0
  221. package/src/history/worker.ts +317 -0
  222. package/src/intercom/intercom-bridge.ts +123 -0
  223. package/src/intercom/live-intercom.ts +104 -0
  224. package/src/intercom/result-intercom.ts +429 -0
  225. package/src/pi-intercom/agent-dir.ts +9 -0
  226. package/src/pi-intercom/broker/broker.ts +463 -0
  227. package/src/pi-intercom/broker/client.ts +558 -0
  228. package/src/pi-intercom/broker/framing.ts +77 -0
  229. package/src/pi-intercom/broker/paths.ts +47 -0
  230. package/src/pi-intercom/broker/pid.ts +59 -0
  231. package/src/pi-intercom/broker/spawn.ts +248 -0
  232. package/src/pi-intercom/config.ts +142 -0
  233. package/src/pi-intercom/index.ts +2929 -0
  234. package/src/pi-intercom/reply-tracker.ts +211 -0
  235. package/src/pi-intercom/session-targets.ts +172 -0
  236. package/src/pi-intercom/subagent-live-events.ts +133 -0
  237. package/src/pi-intercom/topics.ts +125 -0
  238. package/src/pi-intercom/types.ts +271 -0
  239. package/src/pi-intercom/ui/compose.ts +289 -0
  240. package/src/pi-intercom/ui/inline-message.ts +92 -0
  241. package/src/pi-intercom/ui/session-list.ts +139 -0
  242. package/src/runs/background/async-control.ts +89 -0
  243. package/src/runs/background/async-execution.ts +850 -0
  244. package/src/runs/background/async-job-tracker.ts +385 -0
  245. package/src/runs/background/async-result-file.ts +62 -0
  246. package/src/runs/background/async-resume.ts +333 -0
  247. package/src/runs/background/async-status.ts +481 -0
  248. package/src/runs/background/completion-dedupe.ts +79 -0
  249. package/src/runs/background/completion-delivery.ts +195 -0
  250. package/src/runs/background/notify.ts +123 -0
  251. package/src/runs/background/parallel-groups.ts +53 -0
  252. package/src/runs/background/result-watcher.ts +423 -0
  253. package/src/runs/background/run-id-resolver.ts +69 -0
  254. package/src/runs/background/run-status.ts +384 -0
  255. package/src/runs/background/stale-run-reconciler.ts +369 -0
  256. package/src/runs/background/subagent-runner-launcher.ts +56 -0
  257. package/src/runs/background/subagent-runner.ts +2053 -0
  258. package/src/runs/background/top-level-async.ts +13 -0
  259. package/src/runs/foreground/chain-clarify.ts +1340 -0
  260. package/src/runs/foreground/clarify-invocation.ts +77 -0
  261. package/src/runs/foreground/execution-input.ts +467 -0
  262. package/src/runs/foreground/foreground-control.ts +821 -0
  263. package/src/runs/foreground/question-control.ts +70 -0
  264. package/src/runs/foreground/run-async-path.ts +243 -0
  265. package/src/runs/foreground/subagent-executor.ts +501 -0
  266. package/src/runs/foreground/subagent-params.ts +320 -0
  267. package/src/runs/foreground/wait-run.ts +121 -0
  268. package/src/runs/shared/acceptance-contract.ts +257 -0
  269. package/src/runs/shared/acceptance-evaluation.ts +275 -0
  270. package/src/runs/shared/acceptance-finalization.ts +353 -0
  271. package/src/runs/shared/acceptance-reports.ts +152 -0
  272. package/src/runs/shared/acceptance.ts +33 -0
  273. package/src/runs/shared/chain-outputs.ts +114 -0
  274. package/src/runs/shared/child-attempt.ts +623 -0
  275. package/src/runs/shared/child-execution-cwd.ts +91 -0
  276. package/src/runs/shared/child-observations.ts +99 -0
  277. package/src/runs/shared/claude-code.ts +243 -0
  278. package/src/runs/shared/completion-guard.ts +35 -0
  279. package/src/runs/shared/dynamic-fanout.ts +295 -0
  280. package/src/runs/shared/history-index.ts +69 -0
  281. package/src/runs/shared/mcp-direct-tool-allowlist.ts +376 -0
  282. package/src/runs/shared/model-fallback.ts +219 -0
  283. package/src/runs/shared/mutating-tool-guard.ts +194 -0
  284. package/src/runs/shared/native-finalization.ts +128 -0
  285. package/src/runs/shared/native-usage.ts +113 -0
  286. package/src/runs/shared/nested-events.ts +752 -0
  287. package/src/runs/shared/nested-path.ts +52 -0
  288. package/src/runs/shared/nested-render.ts +119 -0
  289. package/src/runs/shared/parallel-utils.ts +153 -0
  290. package/src/runs/shared/parent-receipts.ts +59 -0
  291. package/src/runs/shared/parent-usage.ts +121 -0
  292. package/src/runs/shared/pi-args.ts +350 -0
  293. package/src/runs/shared/pi-spawn.ts +52 -0
  294. package/src/runs/shared/run-history-queries.ts +44 -0
  295. package/src/runs/shared/run-history.ts +93 -0
  296. package/src/runs/shared/run-id-ambiguity.ts +4 -0
  297. package/src/runs/shared/run-records.ts +607 -0
  298. package/src/runs/shared/session-cwd-preload.ts +27 -0
  299. package/src/runs/shared/single-output.ts +267 -0
  300. package/src/runs/shared/streaming-text.ts +18 -0
  301. package/src/runs/shared/structured-output.ts +68 -0
  302. package/src/runs/shared/subagent-control.ts +219 -0
  303. package/src/runs/shared/subagent-prompt-runtime.ts +201 -0
  304. package/src/runs/shared/subagent-tool-loop-guard.ts +89 -0
  305. package/src/runs/shared/supervisor-questions.ts +372 -0
  306. package/src/runs/shared/workflow-graph.ts +220 -0
  307. package/src/runs/shared/workflow-policy.ts +125 -0
  308. package/src/runs/shared/worktree.ts +635 -0
  309. package/src/shared/agent-context-policy.ts +184 -0
  310. package/src/shared/artifacts.ts +95 -0
  311. package/src/shared/atomic-json.ts +16 -0
  312. package/src/shared/cooperative.ts +21 -0
  313. package/src/shared/execution-cwd.ts +14 -0
  314. package/src/shared/file-coalescer.ts +29 -0
  315. package/src/shared/fork-context.ts +68 -0
  316. package/src/shared/formatters.ts +84 -0
  317. package/src/shared/journal-reader.ts +333 -0
  318. package/src/shared/lazy-tools.ts +36 -0
  319. package/src/shared/model-info.ts +81 -0
  320. package/src/shared/native-session.ts +8 -0
  321. package/src/shared/native-tui.ts +10 -0
  322. package/src/shared/native-typebox.ts +14 -0
  323. package/src/shared/post-exit-stdio-guard.ts +204 -0
  324. package/src/shared/prompt-sections.ts +10 -0
  325. package/src/shared/session-entries.ts +49 -0
  326. package/src/shared/session-identity.ts +18 -0
  327. package/src/shared/settings.ts +384 -0
  328. package/src/shared/status-format.ts +123 -0
  329. package/src/shared/temp-root.ts +107 -0
  330. package/src/shared/types.ts +1259 -0
  331. package/src/shared/ui-mode.ts +5 -0
  332. package/src/shared/utils.ts +342 -0
  333. package/src/slash/prompt-template-bridge.ts +408 -0
  334. package/src/slash/slash-bridge.ts +178 -0
  335. package/src/slash/slash-commands.ts +617 -0
  336. package/src/slash/slash-live-state.ts +292 -0
  337. package/src/tui/action-hints.ts +69 -0
  338. package/src/tui/agent-history.ts +210 -0
  339. package/src/tui/agent-view.ts +1323 -0
  340. package/src/tui/render.ts +1550 -0
  341. package/tsconfig.build.json +12 -0
  342. package/tsconfig.json +19 -0
@@ -0,0 +1,679 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { NativeJournal, journalStamp, readOutputPage } from "../../shared/journal-reader.js";
4
+ import { snapshotNativeUsage, readNativeUsage } from "./native-usage.js";
5
+ import { runHistoryIndex, updateRunHistory } from "./history-index.js";
6
+ import { resolveCurrentSessionId } from "../../shared/session-identity.js";
7
+ import { writeAtomicJson } from "../../shared/atomic-json.js";
8
+ import { runCooperatively, runSynchronously } from "../../shared/cooperative.js";
9
+ import { compactForegroundResult, getFinalOutput, getSingleResultOutput, readStatus } from "../../shared/utils.js";
10
+ import { resolveSubagentResultStatus } from "../../intercom/result-intercom.js";
11
+ import { buildManagementControl, formatAgentProcessExit, formatRunAction } from "../../shared/status-format.js";
12
+ import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.js";
13
+ import { isDurableRun, readAsyncResultFile } from "../background/async-result-file.js";
14
+ import { exactAsyncRunLocation } from "../background/async-resume.js";
15
+ import { createAsyncRunDiscovery } from "../background/async-status.js";
16
+ import { reconcileAsyncRun } from "../background/stale-run-reconciler.js";
17
+ import { acceptanceHumanAction } from "./acceptance-evaluation.js";
18
+ import { formatRunIdAmbiguity } from "./run-id-ambiguity.js";
19
+ import { resolveFinalizationOutput } from "./acceptance-finalization.js";
20
+ import { parseAcceptanceReport, validateAcceptanceReportShape } from "./acceptance-reports.js";
21
+ import { sumAttemptUsage } from "./model-fallback.js";
22
+ import { workflowAgentNodes } from "./workflow-graph.js";
23
+ import { collectInvocationAgentNames } from "../../shared/settings.js";
24
+ import { compactOwnerResult, getRunMetadataDir, listOwnedRunQuestions, migrateSupervisorQuestionSteps, questionProcessAlive, readQuestionContract, readRunJson, saveAsyncRunResult, saveRunStatus, saveQuestionOwner, saveQuestionContract } from "./supervisor-questions.js";
25
+ import { ASYNC_DIR, DEFAULT_MAX_OUTPUT, RESULTS_DIR, SLASH_RESULT_TYPE, truncateOutput } from "../../shared/types.js";
26
+ export const OWNED_RUN_ENTRY = "subagent-run";
27
+ const failedAccountingSources = new Map();
28
+ /** Repair only recorded native billing evidence; never execute or verify work again. */
29
+ export function repairOwnedRunAccounting(run) {
30
+ const file = path.join(getRunMetadataDir(run.runId), "result.json");
31
+ if (!fs.existsSync(file))
32
+ return;
33
+ const saved = readAsyncResultFile(file);
34
+ let changed = false;
35
+ saved.results?.forEach((child, index) => {
36
+ if (child.accounting?.state !== "incomplete")
37
+ return;
38
+ const contract = readQuestionContract(run.runId, index, undefined, { readConfiguration: false });
39
+ const terminalEntryId = child.terminalEntryId ?? contract?.terminalEntryId;
40
+ if (!contract?.attemptBaseline || !terminalEntryId)
41
+ return;
42
+ const source = child.sessionFile ?? contract.sessionFile;
43
+ if (!source)
44
+ return;
45
+ const stat = fs.statSync(source, { bigint: true, throwIfNoEntry: false });
46
+ const key = `${run.runId}:${index}`, stamp = `${stat ? journalStamp(stat) : "missing"}:${terminalEntryId}:${JSON.stringify(contract.attemptBaseline)}`;
47
+ if (failedAccountingSources.get(key) === stamp)
48
+ return;
49
+ let usage;
50
+ try {
51
+ usage = readNativeUsage(source, new Set(contract.attemptBaseline), [], { terminalEntryId })?.[0];
52
+ }
53
+ catch (error) {
54
+ failedAccountingSources.set(key, stamp);
55
+ throw error;
56
+ }
57
+ if (!usage)
58
+ return;
59
+ failedAccountingSources.delete(key);
60
+ child.usage = usage;
61
+ child.accounting = { state: "complete" };
62
+ changed = true;
63
+ if (contract.result)
64
+ saveQuestionContract(run.runId, index, { accounting: child.accounting, result: { ...contract.result, usage, accounting: child.accounting } });
65
+ });
66
+ if (changed)
67
+ saveAsyncRunResult(run.runId, saved);
68
+ }
69
+ export function rememberOwnedRun(state, run) {
70
+ const previous = state.ownedRuns?.get(run.runId);
71
+ (state.ownedRuns ??= new Map()).set(run.runId, run);
72
+ if (JSON.stringify(previous) !== JSON.stringify(run)) {
73
+ state.persistOwnedRun?.(run);
74
+ updateRunHistory(state, run);
75
+ state.onRunsChanged?.();
76
+ }
77
+ }
78
+ export function resolveOwnedRun(state, requested) {
79
+ const id = requested.trim();
80
+ getRunMetadataDir(id); // Same ID boundary as the question and result files.
81
+ const exact = state.ownedRuns?.get(id);
82
+ if (exact && id !== "latest" && id !== "last")
83
+ return exact;
84
+ const runs = [...(state.ownedRuns?.values() ?? [])];
85
+ if (id === "latest" || id === "last")
86
+ return runs.sort((a, b) => b.startedAt - a.startedAt)[0];
87
+ const matches = runs.filter((run) => run.runId.startsWith(id));
88
+ if (matches.length > 1)
89
+ throw new Error(formatRunIdAmbiguity("owned", id, matches.map((run) => run.runId)));
90
+ return matches[0];
91
+ }
92
+ export function saveForegroundRun(input) {
93
+ const run = {
94
+ runId: input.runId, mode: input.mode, cwd: input.cwd, updatedAt: Date.now(),
95
+ ...(input.error ? { error: input.error } : {}),
96
+ ...(input.pausedReason ? { pausedReason: input.pausedReason } : {}),
97
+ children: input.results.map((result, index) => ({
98
+ agent: result.agent, index,
99
+ status: resolveSubagentResultStatus(result),
100
+ ...(!result.detached ? { summary: (getSingleResultOutput(result) || result.error)?.slice(-8192) } : {}),
101
+ artifactPath: result.artifactPaths?.outputPath,
102
+ sessionFile: result.sessionFile,
103
+ effectiveAcceptance: result.acceptance?.effectiveAcceptance,
104
+ result: compactOwnerResult(input.runId, index, compactForegroundResult(result)),
105
+ })),
106
+ };
107
+ writeAtomicJson(path.join(getRunMetadataDir(input.runId), "foreground.json"), run);
108
+ return run;
109
+ }
110
+ function receiptDetails(entry) {
111
+ if (entry.type === "message" && entry.message.role === "toolResult" && ["subagent", "delegate", "agent_runs"].includes(entry.message.toolName))
112
+ return entry.message.details;
113
+ if (entry.type === "custom_message" && entry.customType === SLASH_RESULT_TYPE) {
114
+ const details = entry.details;
115
+ return details?.result?.details;
116
+ }
117
+ return undefined;
118
+ }
119
+ function sessionFiles(root) {
120
+ if (!fs.existsSync(root))
121
+ return [];
122
+ return fs.readdirSync(root, { withFileTypes: true }).flatMap((entry) => entry.isDirectory()
123
+ ? sessionFiles(path.join(root, entry.name))
124
+ : entry.name.endsWith(".jsonl") ? [path.join(root, entry.name)] : []);
125
+ }
126
+ function recoverOutput(sessionFile, outputFile, endedAt) {
127
+ if (outputFile && fs.existsSync(outputFile))
128
+ return readOutputPage(outputFile).text;
129
+ if (!sessionFile || !fs.existsSync(sessionFile))
130
+ return undefined;
131
+ const journal = new NativeJournal(sessionFile);
132
+ for (const record of journal.branch(undefined, endedAt).reverse()) {
133
+ if (record.value.type !== "message" || record.value.message?.role !== "assistant")
134
+ continue;
135
+ const output = getFinalOutput([journal.body(record).message]);
136
+ if (output)
137
+ return output;
138
+ }
139
+ }
140
+ function recoverLegacyTerminalOutput(sessionFile, startedAt, endedAt, acceptance) {
141
+ const review = acceptance?.finalization;
142
+ const reviewedOutput = review?.status === "completed" ? review.turns.at(-1)?.rawOutput : undefined;
143
+ if (reviewedOutput?.trim())
144
+ return resolveFinalizationOutput(reviewedOutput, "") || undefined;
145
+ // Live and finalization logs are separate, mutable streams, not a final-answer receipt.
146
+ if (endedAt === undefined || !Number.isFinite(endedAt) || !sessionFile || !fs.existsSync(sessionFile))
147
+ return;
148
+ const journal = new NativeJournal(sessionFile);
149
+ const records = journal.branch(undefined, endedAt).filter(({ value }) => value.type === "message" && Date.parse(value.timestamp) >= startedAt);
150
+ const lastAssistant = records.findLastIndex(({ value }) => value.message?.role === "assistant");
151
+ const messages = records.map((record, index) => index === lastAssistant ? journal.body(record).message : record.value.message);
152
+ const index = messages.findLastIndex((message) => message.role === "assistant");
153
+ const last = messages[index];
154
+ if (last?.role !== "assistant" || last.errorMessage || !["stop", "toolUse"].includes(last.stopReason) || !Array.isArray(last.content))
155
+ return;
156
+ const calls = last.content.filter((part) => part.type === "toolCall");
157
+ if (!calls.length)
158
+ return index === messages.length - 1 && last.stopReason === "stop" ? resolveFinalizationOutput(getFinalOutput([last]), "") || undefined : undefined;
159
+ if (calls.length !== 1 || calls[0].name !== "structured_output")
160
+ return;
161
+ const following = messages.slice(index + 1);
162
+ const result = following.at(-1);
163
+ if (following.some((message) => message.role !== "toolResult") || result?.role !== "toolResult" || result.toolCallId !== calls[0].id || result.toolName !== "structured_output" || result.isError !== false)
164
+ return;
165
+ const value = calls[0].arguments.value;
166
+ if (!value || typeof value !== "object" || !("report" in value))
167
+ return;
168
+ if (typeof value.report === "string" && parseAcceptanceReport(value.report).report)
169
+ return resolveFinalizationOutput(value.report, "") || undefined;
170
+ if ("answer" in value && typeof value.answer === "string" && value.answer.trim() && !validateAcceptanceReportShape(value.report))
171
+ return value.answer;
172
+ }
173
+ export function workflowChildren(children, graph) {
174
+ if (!graph || (graph.mode !== "chain" && !children.some((child) => child.workflowNodeId)))
175
+ return children;
176
+ return workflowAgentNodes(graph).map((node, index) => {
177
+ const declared = children.find((child) => child.workflowNodeId === node.id);
178
+ return { ...declared, index, workflowNodeId: node.id, agent: node.agent ?? declared?.agent ?? "unknown",
179
+ ...(node.itemKey !== undefined ? { label: node.label } : {}) };
180
+ });
181
+ }
182
+ function savedWorkflowNodes(status) {
183
+ if (!status?.workflowGraph || status.workflowGraph.runId !== status.runId)
184
+ return;
185
+ const nodes = workflowAgentNodes(status.workflowGraph);
186
+ if (nodes.length !== status.steps?.length || new Set(nodes.map((node) => node.id)).size !== nodes.length)
187
+ return;
188
+ if (nodes.some((node, index) => typeof node.id !== "string" || !node.id || (node.agent && node.agent !== status.steps[index].agent)))
189
+ return;
190
+ return nodes;
191
+ }
192
+ export function restoreOwnedRuns(state, ctx) {
193
+ return runSynchronously(restoreOwnedRunSteps(state, ctx));
194
+ }
195
+ export async function restoreOwnedRunsAsync(state, ctx) {
196
+ const onRunsChanged = state.onRunsChanged;
197
+ // Publish readiness once restoration is complete, before starting browse/UI work.
198
+ state.onRunsChanged = undefined;
199
+ try {
200
+ return await runCooperatively(restoreOwnedRunSteps(state, ctx));
201
+ }
202
+ finally {
203
+ state.onRunsChanged = onRunsChanged;
204
+ }
205
+ }
206
+ function* restoreOwnedRunSteps(state, ctx) {
207
+ const startedAt = Date.now();
208
+ const ownerSessionId = ctx.sessionManager.getSessionId();
209
+ const entries = ctx.sessionManager.getEntries();
210
+ state.ownedRuns = new Map();
211
+ yield* migrateSupervisorQuestionSteps(ownerSessionId);
212
+ for (const entry of entries) {
213
+ yield;
214
+ if (entry.type !== "custom" || entry.customType !== OWNED_RUN_ENTRY)
215
+ continue;
216
+ const run = ctx.sessionManager.getEntry(entry.id)?.data;
217
+ if (run?.ownerSessionId === ownerSessionId && run.runId && Array.isArray(run.children))
218
+ state.ownedRuns.set(run.runId, run);
219
+ }
220
+ // Forks copy old entries. Their old receipts are evidence, not ownership for the new parent.
221
+ const inheritedIds = new Set();
222
+ const parentFile = ctx.sessionManager.getHeader()?.parentSession;
223
+ if (parentFile && fs.existsSync(parentFile)) {
224
+ for (const id of snapshotNativeUsage(parentFile))
225
+ inheritedIds.add(id);
226
+ }
227
+ const recoveredReceipts = [];
228
+ for (const entry of entries) {
229
+ yield;
230
+ if (!(entry.type === "message" && entry.message.role === "toolResult" && ["subagent", "delegate", "agent_runs"].includes(entry.message.toolName)
231
+ || entry.type === "custom_message" && entry.customType === SLASH_RESULT_TYPE))
232
+ continue;
233
+ const receipt = ctx.sessionManager.getEntry(entry.id);
234
+ if (receipt)
235
+ recoveredReceipts.push(receipt);
236
+ }
237
+ const calls = new Map();
238
+ const needsLegacyCalls = recoveredReceipts.some((entry) => { const details = receiptDetails(entry); return details && !state.ownedRuns.has(details.runId ?? details.asyncId ?? ""); });
239
+ for (const metadata of needsLegacyCalls ? entries : []) {
240
+ yield;
241
+ if (metadata.type !== "message" || metadata.message.role !== "assistant")
242
+ continue;
243
+ const entry = ctx.sessionManager.getEntry(metadata.id);
244
+ if (entry?.type !== "message" || entry.message.role !== "assistant" || !Array.isArray(entry.message.content))
245
+ continue;
246
+ for (const part of entry.message.content)
247
+ if (part.type === "toolCall" && ["subagent", "delegate"].includes(part.name))
248
+ calls.set(part.id, part.arguments);
249
+ }
250
+ for (const entry of recoveredReceipts) {
251
+ yield;
252
+ if (inheritedIds.has(entry.id) || (parentFile && !fs.existsSync(parentFile)))
253
+ continue;
254
+ const details = receiptDetails(entry);
255
+ const runId = details?.runId ?? details?.asyncId;
256
+ if (!runId || !details || !Array.isArray(details.results) || (details.mode !== "single" && details.mode !== "parallel" && details.mode !== "chain") || state.ownedRuns.has(runId))
257
+ continue;
258
+ const cwd = details.results.find((result) => result.sessionFile)?.sessionFile;
259
+ const request = entry.type === "message" && entry.message.role === "toolResult" ? calls.get(entry.message.toolCallId) : undefined;
260
+ const run = {
261
+ runId, ownerSessionId, rootRunId: details.managementControl?.revivedFromRunId ?? runId,
262
+ predecessorRunId: details.managementControl?.revivedFromRunId,
263
+ source: details.asyncId ? "async" : "foreground", mode: details.mode,
264
+ cwd: request?.cwd ? path.resolve(ctx.cwd, request.cwd) : ctx.cwd, task: details.results[0]?.task ?? request?.task ?? "Recovered delegated run",
265
+ startedAt: Date.parse(entry.timestamp), asyncDir: details.asyncDir, legacy: true,
266
+ children: details.results.length ? details.results.map((result, index) => ({ agent: result.agent, index, task: result.task, sessionFile: result.sessionFile })) : collectInvocationAgentNames(request ?? {}).map((agent, index) => ({ agent, index })),
267
+ };
268
+ try {
269
+ if (cwd && fs.existsSync(cwd)) {
270
+ const header = new NativeJournal(cwd).records[0]?.value;
271
+ if (header?.type === "session" && header.cwd)
272
+ run.cwd = header.cwd;
273
+ }
274
+ if (run.source === "foreground")
275
+ saveForegroundRun({ ...run, results: details.results.map((result) => ({ ...result, finalOutput: result.finalOutput ?? recoverOutput(result.sessionFile, result.artifactPaths?.outputPath, Date.parse(entry.timestamp)) })) });
276
+ let owner;
277
+ try {
278
+ owner = readRunJson(path.join(getRunMetadataDir(runId), "question-owner.json"));
279
+ }
280
+ catch { /* Unusable metadata can be repaired from the genuine receipt. */ }
281
+ if (typeof owner?.sessionId !== "string" || !owner.sessionId.trim())
282
+ saveQuestionOwner(runId, ownerSessionId);
283
+ rememberOwnedRun(state, run);
284
+ }
285
+ catch (error) {
286
+ // The genuine receipt still establishes ownership. Keep completion
287
+ // unconfirmed when its supplemental output/context cannot be recovered.
288
+ rememberOwnedRun(state, { ...run, recoveryError: `Saved child recovery remains incomplete: ${String(error)}` });
289
+ console.error(`Could not recover legacy receipt ${runId}: ${String(error)}`);
290
+ }
291
+ }
292
+ // Pre-update background runs may have no parent tool receipt (for example slash launches).
293
+ const scan = createAsyncRunDiscovery(ASYNC_DIR, {
294
+ sessionId: ctx.sessionManager.getSessionFile() ?? resolveCurrentSessionId(ctx.sessionManager), ownerSessionId,
295
+ receiptRunIds: () => state.ownedRuns.keys(), skipInvalid: true,
296
+ });
297
+ function* discoverSteps() {
298
+ const records = yield* scan.steps();
299
+ for (const { location, status, durable } of records) {
300
+ yield;
301
+ try {
302
+ const asyncDir = location.asyncDir;
303
+ if (!asyncDir || !status)
304
+ continue;
305
+ const terminal = !["running", "queued"].includes(status.state);
306
+ if (!durable && terminal)
307
+ saveRunStatus(status.runId, status);
308
+ const old = state.ownedRuns.get(status.runId);
309
+ const nodes = savedWorkflowNodes(status);
310
+ const declared = status.mode === "chain" && nodes && !old?.children.some((child) => child.workflowNodeId) ? [] : old?.children ?? [];
311
+ const children = workflowChildren(declared, nodes ? status.workflowGraph : undefined);
312
+ rememberOwnedRun(state, {
313
+ ...old, runId: status.runId, ownerSessionId, rootRunId: old?.rootRunId ?? status.runId,
314
+ source: "async", mode: status.mode, cwd: status.cwd ?? old?.cwd ?? ctx.cwd,
315
+ task: old?.task ?? "Recovered background run", startedAt: status.startedAt,
316
+ asyncDir, pid: status.pid, legacy: old?.legacy ?? !durable,
317
+ children: (status.steps ?? []).map((step, index) => ({ ...children.find((child) => child.index === index), agent: step.agent, index, ...(status.mode === "chain" && nodes?.[index] ? { workflowNodeId: nodes[index].id } : {}), label: step.label ?? children[index]?.label, sessionFile: step.sessionFile ?? (status.steps?.length === 1 ? status.sessionFile : undefined) })),
318
+ });
319
+ const resultPath = path.join(RESULTS_DIR, `${status.runId}.json`);
320
+ if (!durable && terminal && !fs.existsSync(path.join(getRunMetadataDir(status.runId), "result.json"))) {
321
+ if (fs.existsSync(resultPath))
322
+ saveAsyncRunResult(status.runId, readAsyncResultFile(resultPath));
323
+ else if (status.steps?.length && status.steps.every((step) => !["running", "pending"].includes(step.status))) {
324
+ const endedAt = status.endedAt ?? status.lastUpdate ?? status.startedAt;
325
+ const results = status.steps.map((step) => {
326
+ const sessionFile = step.sessionFile ?? (status.steps.length === 1 ? status.sessionFile : undefined);
327
+ return { agent: step.agent, sessionFile, model: step.model, acceptance: step.acceptance,
328
+ exitCode: step.exitCode, agentProcessExit: step.agentProcessExit, success: step.status === "complete" || step.status === "completed",
329
+ interrupted: step.status === "paused" || undefined, timedOut: step.status === "timed-out" || undefined, error: step.error,
330
+ output: recoverLegacyTerminalOutput(sessionFile, step.startedAt ?? status.startedAt, step.endedAt ?? status.endedAt, step.acceptance) ?? "" };
331
+ });
332
+ saveAsyncRunResult(status.runId, { id: status.runId, sessionId: status.sessionId, mode: status.mode, state: status.state,
333
+ success: status.state === "complete", error: status.error, timestamp: endedAt, cwd: status.cwd, asyncDir, sessionFile: status.sessionFile, results });
334
+ }
335
+ }
336
+ }
337
+ catch (error) {
338
+ console.error(`Could not recover owned async metadata for '${location.resolvedId}':`, error);
339
+ }
340
+ }
341
+ return records;
342
+ }
343
+ const discover = () => runSynchronously(discoverSteps());
344
+ const records = yield* discoverSteps();
345
+ for (const run of state.ownedRuns.values()) {
346
+ yield;
347
+ try {
348
+ const stored = readRunJson(path.join(getRunMetadataDir(run.runId), "foreground.json"));
349
+ if (stored)
350
+ (state.foregroundRuns ??= new Map()).set(run.runId, stored);
351
+ if (run.children.some((child) => child.sessionFile) || !run.legacy)
352
+ continue;
353
+ const file = ctx.sessionManager.getSessionFile();
354
+ if (!file)
355
+ continue;
356
+ const root = path.join(path.dirname(file), path.basename(file, ".jsonl"), run.runId);
357
+ const files = sessionFiles(root).sort();
358
+ if (files.length)
359
+ rememberOwnedRun(state, { ...run, children: files.map((sessionFile, index) => ({ agent: run.children[index]?.agent ?? "unknown", index, sessionFile })) });
360
+ }
361
+ catch (error) {
362
+ console.error(`Could not recover foreground owner ${run.runId}: ${String(error)}`);
363
+ }
364
+ }
365
+ return { startedAt, records, discover };
366
+ }
367
+ function normalizedState(value) {
368
+ if (value === "running" || value === "queued")
369
+ return "live";
370
+ if (value === "complete" || value === "completed")
371
+ return "completed";
372
+ if (value === "failed" || value === "timed-out")
373
+ return "failed";
374
+ if (value === "blocked")
375
+ return "blocked";
376
+ if (value === "paused")
377
+ return "paused";
378
+ return "unknown";
379
+ }
380
+ function processAlive(pid) {
381
+ return Boolean(pid && Number.isSafeInteger(pid) && pid > 0 && questionProcessAlive({ pid }));
382
+ }
383
+ function asyncChildResult(child, task) {
384
+ const { output, ...result } = child;
385
+ return {
386
+ ...result, agent: child.agent ?? "unknown", task: child.task ?? task, exitCode: child.exitCode ?? (child.success ? 0 : 1),
387
+ finalOutput: child.finalOutput ?? output, usage: child.usage ?? sumAttemptUsage(child.modelAttempts ?? []),
388
+ };
389
+ }
390
+ function runAttention(run, executionState, pendingInput) {
391
+ return [
392
+ ...(pendingInput ? ["awaiting_input"] : []),
393
+ ...(run.review?.decision === "needs_changes" ? ["needs_changes"] : []),
394
+ ...(run.review?.decision !== "accepted" && ["failed", "blocked", "paused", "unknown"].includes(executionState) ? [executionState] : []),
395
+ ...(executionState === "completed" && !run.review ? ["unreviewed"] : []),
396
+ ];
397
+ }
398
+ export function ownedRunView(run, state, options = {}) {
399
+ run = state.ownedRuns?.get(run.runId) ?? run;
400
+ const root = getRunMetadataDir(run.runId);
401
+ const foreground = readRunJson(path.join(root, "foreground.json")) ?? state.foregroundRuns?.get(run.runId);
402
+ const resultPath = path.join(root, "result.json");
403
+ const result = fs.existsSync(resultPath) ? readAsyncResultFile(resultPath) : undefined;
404
+ const location = exactAsyncRunLocation(run.runId, ASYNC_DIR, RESULTS_DIR);
405
+ const asyncDir = location.asyncDir ?? run.asyncDir;
406
+ const liveStatus = asyncDir ? readStatus(asyncDir) : null;
407
+ const savedStatus = liveStatus ?? readRunJson(path.join(root, "status.json"));
408
+ const durable = isDurableRun(savedStatus) || isDurableRun(readRunJson(path.join(root, "launch.json")));
409
+ const reconciliation = durable && options.reconcile !== false ? reconcileAsyncRun(asyncDir ?? root) : undefined;
410
+ const status = reconciliation?.status ?? savedStatus;
411
+ const contracts = new Map();
412
+ const contractDir = path.join(root, "contracts");
413
+ for (const name of fs.existsSync(contractDir) ? fs.readdirSync(contractDir) : []) {
414
+ if (!/^\d+\.json$/.test(name))
415
+ continue;
416
+ const index = Number(name.slice(0, -5));
417
+ const contract = readQuestionContract(run.runId, index, undefined, { endedAt: status?.steps?.[index]?.endedAt ?? result?.timestamp ?? foreground?.updatedAt, readConfiguration: options.readConfiguration });
418
+ if (contract)
419
+ contracts.set(index, contract);
420
+ }
421
+ const nodes = savedWorkflowNodes(status);
422
+ const declarations = workflowChildren(run.children, nodes ? status?.workflowGraph : undefined);
423
+ // Terminal snapshots contain materialized children; declared slots can include an empty fanout or unstarted downstream steps.
424
+ const terminalIndices = result?.results?.map((_, index) => index) ?? foreground?.children.map((child) => child.index);
425
+ const indices = new Set(terminalIndices ?? [...declarations.map((child) => child.index), ...contracts.keys(), ...(status?.steps?.map((_, index) => index) ?? [])]);
426
+ const sessions = new Map([...indices].map((index) => [index, foreground?.children.find((child) => child.index === index)?.sessionFile ?? result?.results?.[index]?.sessionFile ?? status?.steps?.[index]?.sessionFile ?? contracts.get(index)?.sessionFile]));
427
+ const sessionUses = new Map();
428
+ for (const file of sessions.values())
429
+ if (file)
430
+ sessionUses.set(file, (sessionUses.get(file) ?? 0) + 1);
431
+ const uncertainIndices = run.mode === "chain" && (!run.children.some((child) => child.workflowNodeId) || (run.source === "async" && status && !nodes));
432
+ const children = [...indices].sort((a, b) => a - b).map((index) => {
433
+ const boundSession = sessions.get(index);
434
+ const declared = uncertainIndices && !nodes
435
+ ? run.children.find((child) => boundSession && sessionUses.get(boundSession) === 1 && child.sessionFile === boundSession)
436
+ : declarations.find((child) => child.index === index);
437
+ const contract = contracts.get(index);
438
+ const fg = foreground?.children.find((child) => child.index === index);
439
+ const bg = result?.results?.[index];
440
+ const step = status?.steps?.[index];
441
+ const sessionFile = boundSession ?? declared?.sessionFile;
442
+ const live = processAlive(contract?.pid) || ((!step || step.status === "running" || step.status === "pending") && processAlive(status?.pid ?? run.pid));
443
+ const pending = !fg && !bg && !contract?.pid && !contract?.result && ((!step || step.status === "pending") && processAlive(status?.pid ?? run.pid));
444
+ const childState = bg ? normalizedState(resolveSubagentResultStatus({ success: bg.success, exitCode: bg.exitCode ?? undefined, interrupted: bg.interrupted, acceptance: bg.acceptance, state: typeof bg.success !== "boolean" && bg.exitCode == null ? result?.terminalState : undefined }))
445
+ : fg && fg.status !== "detached" ? normalizedState(fg.status)
446
+ : contract?.result ? normalizedState(resolveSubagentResultStatus(contract.result))
447
+ : live || pending ? "live"
448
+ : step && !["running", "pending"].includes(step.status) ? normalizedState(step.status) : "unknown";
449
+ const task = contract?.task ?? declared?.task ?? fg?.result?.task ?? (run.children.length === 1 ? run.task : undefined);
450
+ const selection = contract?.modelSelection ?? step ?? fg?.result?.progress;
451
+ return {
452
+ agent: fg?.agent ?? bg?.agent ?? step?.agent ?? contract?.launch?.agent.name ?? declared?.agent ?? "unknown", index, workflowNodeId: declared?.workflowNodeId, sessionFile,
453
+ task, label: contract?.label ?? step?.label ?? declared?.label,
454
+ ...(run.mode === "chain" && !declared?.workflowNodeId && (!boundSession || sessionUses.get(boundSession) !== 1) ? { identityUnavailable: true } : {}),
455
+ modelSelection: selection ? { model: selection.model, thinking: selection.thinking, modelStartedAt: selection.modelStartedAt } : undefined,
456
+ activity: childState === "live" ? (pending ? { ...step, status: "pending" } : step) : undefined,
457
+ state: childState, result: fg?.status !== "detached" && fg?.result ? fg.result : bg ? asyncChildResult(bg, task ?? "Original child assignment unavailable") : contract?.result ?? fg?.result,
458
+ launch: contract?.launch, configuration: contract?.launch ? "saved" : "legacy-partial",
459
+ ...(sessionFile && !fs.existsSync(sessionFile) ? { missingSession: true } : {}),
460
+ };
461
+ });
462
+ const live = children.some((child) => child.state === "live") || (!result && (!status || status.state === "running" || status.state === "queued") && processAlive(status?.pid ?? run.pid));
463
+ const error = result?.error ?? foreground?.error ?? run.error ?? (status && !["running", "queued"].includes(status.state) ? status.error : undefined);
464
+ const executionState = error ? "failed" : result ? normalizedState(result.terminalState)
465
+ : durable && !live ? "unknown"
466
+ : live ? "live"
467
+ : children.some((child) => child.state === "failed") ? "failed"
468
+ : children.some((child) => child.state === "blocked") ? "blocked"
469
+ : children.some((child) => child.state === "paused") ? "paused"
470
+ : children.length && children.every((child) => child.state === "completed") ? (foreground?.pausedReason ? "paused" : "completed")
471
+ : status && !["running", "queued"].includes(status.state) ? normalizedState(status.state) : "unknown";
472
+ const pendingInput = options.pendingInput ?? listOwnedRunQuestions(run.ownerSessionId, run.runId).some((question) => question.state === "awaiting_input" || question.state === "answer_pending");
473
+ return {
474
+ ...run, state: executionState, children, attention: runAttention(run, executionState, pendingInput),
475
+ canInterrupt: pendingInput || (live && savedStatus?.state === "running" && savedStatus.runId === run.runId),
476
+ updatedAt: result?.timestamp ?? status?.lastUpdate ?? foreground?.updatedAt ?? run.startedAt,
477
+ continuations: options.includeContinuations === false ? [] : [...(state.ownedRuns?.values() ?? [])].filter((candidate) => candidate.rootRunId === run.rootRunId && candidate.predecessorRunId).sort((a, b) => a.startedAt - b.startedAt).map((candidate) => ({ runId: candidate.runId, predecessorRunId: candidate.predecessorRunId, predecessorIndex: candidate.predecessorIndex })),
478
+ ...(result ? { resultPath } : foreground ? { resultPath: path.join(root, "foreground.json") } : {}),
479
+ ...(error ? { diagnosis: error } : executionState === "paused" && foreground?.pausedReason ? { diagnosis: foreground.pausedReason } : executionState === "unknown" ? { diagnosis: run.recoveryError ?? reconciliation?.message ?? "Completion is unconfirmed. Saved sessions are context, not proof of successful execution." } : {}),
480
+ };
481
+ }
482
+ function workflowDetails(graph) {
483
+ if (!graph)
484
+ return {};
485
+ if (graph.mode !== "chain")
486
+ return { workflowGraph: graph };
487
+ const current = graph.nodes.find((node) => node.id === graph.currentNodeId || node.children?.some((child) => child.id === graph.currentNodeId));
488
+ return { workflowGraph: graph, chainAgents: graph.nodes.map((node) => node.agent ?? node.label), totalSteps: graph.nodes.length,
489
+ ...(current?.stepIndex !== undefined ? { currentStepIndex: current.stepIndex } : {}) };
490
+ }
491
+ export function ownedRunExecutionResult(run, state, index, includeProgress = false) {
492
+ try {
493
+ repairOwnedRunAccounting(run);
494
+ }
495
+ catch (error) {
496
+ console.error(`Native accounting for ${run.runId} remains incomplete:`, error);
497
+ }
498
+ const view = ownedRunView(run, state);
499
+ const location = exactAsyncRunLocation(run.runId, ASYNC_DIR, RESULTS_DIR);
500
+ const saved = location.resultPath ? readAsyncResultFile(location.resultPath) : undefined;
501
+ const limits = { ...DEFAULT_MAX_OUTPUT, ...(saved?.maxOutput ?? view.children[0]?.launch?.maxOutput) };
502
+ const children = view.children.filter((child) => index === undefined || child.index === index);
503
+ const projected = view.children.map((child) => {
504
+ if (!child.result)
505
+ return { ...child, result: undefined };
506
+ const { artifactPaths, ...result } = child.result;
507
+ const finalOutput = getSingleResultOutput(result);
508
+ const truncation = result.outputMode === "file-only" && result.exitCode === 0 && result.outputReference
509
+ ? { text: result.outputReference.message, truncated: false } : truncateOutput(finalOutput, limits, artifactPaths?.outputPath);
510
+ const compacted = compactForegroundResult({ ...result, finalOutput: truncation.text,
511
+ ...(truncation.truncated ? { truncation } : {}),
512
+ ...(result.initialOutput ? { initialOutput: truncateOutput(result.initialOutput, limits, artifactPaths?.outputPath).text } : {}),
513
+ });
514
+ return { ...child, result: { ...compacted, ...(artifactPaths ? { artifactPaths } : {}) } };
515
+ });
516
+ const results = projected.filter((child) => index === undefined || child.index === index).flatMap((child) => {
517
+ if (!child.result)
518
+ return [];
519
+ const { artifactPaths, ...result } = child.result;
520
+ return [{ ...result, ...(artifactPaths?.inputPath && artifactPaths.outputPath && artifactPaths.metadataPath
521
+ ? { artifactPaths: { inputPath: artifactPaths.inputPath, outputPath: artifactPaths.outputPath, metadataPath: artifactPaths.metadataPath } } : {}) }];
522
+ });
523
+ let text = (index === undefined ? saved?.summary : undefined) || [...children.map((child) => child.result ? getSingleResultOutput(child.result) || child.result.error : undefined), view.diagnosis].filter(Boolean).join("\n\n");
524
+ const failed = index === undefined ? ["failed", "unknown"].includes(view.state) : children.length === 0 || children.some((child) => ["failed", "unknown"].includes(child.state));
525
+ if (failed && saved?.error && !text.includes(saved.error))
526
+ text = `${saved.error}\n\n${text}`;
527
+ const logPath = path.join(location.asyncDir ?? run.asyncDir ?? getRunMetadataDir(run.runId), `subagent-log-${run.runId}.md`);
528
+ const artifactPath = run.mode === "single" ? results[0]?.artifactPaths?.outputPath : fs.existsSync(logPath) ? logPath : undefined;
529
+ const referenceOnly = view.state === "completed" && run.mode === "single" && results.length === 1 && results[0]?.outputMode === "file-only" && results[0].outputReference;
530
+ const truncation = referenceOnly ? { text: referenceOnly.message, truncated: false }
531
+ : view.state === "blocked" ? { text, truncated: false } : truncateOutput(text, limits, artifactPath);
532
+ const files = results.flatMap((result) => result.artifactPaths ? [result.artifactPaths] : []);
533
+ const progressSummary = { toolCount: results.reduce((total, result) => total + (result.progressSummary?.toolCount ?? 0), 0),
534
+ tokens: results.reduce((total, result) => total + (result.progressSummary?.tokens ?? 0), 0),
535
+ durationMs: saved?.durationMs ?? Math.max(0, ...results.map((result) => result.progressSummary?.durationMs ?? 0)) };
536
+ const share = saved?.shareUrl ? `Session: ${saved.shareUrl}` : saved?.shareError ? `Session share error: ${saved.shareError}` : undefined;
537
+ const identity = run.predecessorRunId ? `Run: ${run.runId}\nPredecessor: ${run.predecessorRunId} (child ${run.predecessorIndex ?? 0})` : undefined;
538
+ return { content: [{ type: "text", text: [identity, truncation.text || `Run ${run.runId}: ${view.state}.`, share].filter(Boolean).join("\n\n") }],
539
+ ...(failed ? { isError: true } : {}),
540
+ details: { mode: run.mode, runId: run.runId, asyncId: run.runId, asyncDir: location.asyncDir ?? run.asyncDir, results,
541
+ run: { ...view, children: projected }, progressSummary,
542
+ ...(includeProgress ? { progress: ownedRunProgressResult(run, state, index).details.progress } : {}),
543
+ ...(saved?.shareUrl ? { shareUrl: saved.shareUrl } : {}),
544
+ ...(saved?.gistUrl ? { gistUrl: saved.gistUrl } : {}),
545
+ ...(saved?.shareError ? { shareError: saved.shareError } : {}),
546
+ ...(files.length ? { artifacts: { dir: saved?.artifactsDir ?? path.dirname(files[0].outputPath), files } } : {}),
547
+ ...(truncation.truncated ? { truncation } : {}),
548
+ ...(saved?.outputs ? { outputs: saved.outputs } : {}), ...workflowDetails(saved?.workflowGraph) } };
549
+ }
550
+ /** Project the run owner's native activity for waiting callers without copying its journal. */
551
+ export function ownedRunProgressResult(run, state, index, view = ownedRunView(run, state, { readConfiguration: false, includeContinuations: false })) {
552
+ const status = readStatus(run.asyncDir ?? getRunMetadataDir(run.runId));
553
+ const children = view.children.filter((child) => index === undefined || child.index === index);
554
+ const progress = children.map((child) => {
555
+ const step = status?.steps?.[child.index];
556
+ const live = child.state === "live";
557
+ return {
558
+ index: child.index, agent: child.agent, task: child.task ?? run.task,
559
+ status: step?.status ?? (child.state === "live" ? "pending" : child.state === "unknown" ? "failed" : child.state),
560
+ model: step?.model, thinking: step?.thinking, modelStartedAt: step?.modelStartedAt,
561
+ activityState: step?.activityState, lastActivityAt: step?.lastActivityAt, skills: step?.skills,
562
+ ...(live ? { currentTool: step?.currentTool, currentToolArgs: step?.currentToolArgs, currentToolStartedAt: step?.currentToolStartedAt,
563
+ currentPath: step?.currentPath, streamingText: step?.streamingText } : {}),
564
+ recentTools: live ? step?.recentTools?.slice(-10) ?? [] : [], recentOutput: live ? step?.recentOutput?.slice(-10) ?? [] : [],
565
+ toolCount: step?.toolCount ?? 0, turnCount: step?.turnCount, tokens: step?.tokens?.total ?? 0,
566
+ durationMs: step?.durationMs ?? Math.max(0, Date.now() - (step?.startedAt ?? run.startedAt)), error: step?.error,
567
+ };
568
+ });
569
+ const results = progress.map((item, position) => ({
570
+ agent: item.agent, task: item.task, exitCode: children[position]?.result?.exitCode ?? 0,
571
+ usage: children[position]?.result?.usage ?? { input: status?.steps?.[item.index]?.tokens?.input ?? 0, output: status?.steps?.[item.index]?.tokens?.output ?? 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: item.turnCount ?? 0 },
572
+ progress: item, model: item.model, sessionFile: children[position]?.sessionFile,
573
+ finalOutput: getSingleResultOutput(children[position]?.result ?? {}) || item.streamingText || item.recentOutput.at(-1),
574
+ }));
575
+ return { content: [{ type: "text", text: progress.map((item) => `${item.agent}: ${item.status}${item.currentTool ? ` — ${item.currentTool}${item.currentToolArgs ? ` ${item.currentToolArgs}` : ""}` : ""}`).join("\n") }],
576
+ details: { mode: run.mode, runId: run.runId, asyncId: run.runId, asyncDir: run.asyncDir, results, progress, ...workflowDetails(status?.workflowGraph) } };
577
+ }
578
+ function compact(value, max = 180) {
579
+ const text = value.replace(/\s+/g, " ").trim();
580
+ return text.length > max ? `${text.slice(0, max - 1)}…` : text;
581
+ }
582
+ function ownedRunControl(view) {
583
+ const active = view.children.find((child) => child.state === "live" && child.activity?.status !== "pending");
584
+ const resumable = active ?? view.children.find((child) => child.state !== "live" && child.sessionFile && !child.missingSession);
585
+ return buildManagementControl({ state: view.state, runId: view.runId, index: resumable?.index, canReview: view.state !== "live", canResume: Boolean(resumable), canNudge: Boolean(active), canInterrupt: view.canInterrupt, intercomTarget: active ? resolveSubagentIntercomTarget(view.runId, active.agent, active.index) : undefined });
586
+ }
587
+ export function ownedRunStatusResult(run, state, runtime, options = {}) {
588
+ const view = ownedRunView(run, state);
589
+ const liveControl = view.state === "live" && runtime?.details.managementControl?.state === "live" ? runtime.details.managementControl : undefined;
590
+ const control = liveControl ?? ownedRunControl(view);
591
+ const lines = [
592
+ `Run: ${run.runId}`, `State: ${view.state}`, `Mode: ${run.mode} (${run.source})`, `Task: ${options.full ? run.task : compact(run.task)}`, `Launch cwd: ${run.cwd}`,
593
+ `Root: ${run.rootRunId}`, ...(run.predecessorRunId ? [`Predecessor: ${run.predecessorRunId} (child ${run.predecessorIndex ?? 0})`] : []),
594
+ `Parent review (not sent to child): ${run.review?.decision ?? "unreviewed"}${run.review?.message ? ` — ${options.full ? run.review.message : compact(run.review.message)}` : ""}`,
595
+ `Notification: ${run.delivery ? `recorded ${new Date(run.delivery.notifiedAt).toISOString()}; intercom ${run.delivery.intercomDelivered ? "delivered" : "not confirmed"}` : "not recorded"}`,
596
+ ...(view.attention.length ? [`Attention: ${view.attention.join(", ")}`] : []),
597
+ ...(view.resultPath ? [`Result: ${view.resultPath}`] : []), ...(view.diagnosis ? [view.diagnosis] : []),
598
+ ...(runtime && !runtime.isError && (run.source === "async" || liveControl) ? runtime.content.flatMap((part) => part.type === "text" ? [part.text] : []) : []),
599
+ ];
600
+ for (const child of view.children) {
601
+ lines.push(`Child ${child.index}: ${child.agent} | ${child.state}${child.result?.acceptance ? ` | validation: ${child.result.acceptance.status}` : ""}`);
602
+ const humanAction = acceptanceHumanAction(child.result?.acceptance);
603
+ if (humanAction)
604
+ lines.push(` Needs your action — acceptance incomplete:\n${humanAction}`);
605
+ if (child.state !== "live")
606
+ lines.push(` ${formatAgentProcessExit(child.result?.agentProcessExit)}`);
607
+ if (child.sessionFile)
608
+ lines.push(` Session: ${child.sessionFile}${child.missingSession ? " (missing; continuation unavailable)" : ""}`);
609
+ if (child.result?.auditPath)
610
+ lines.push(` Child observations / uncommitted output: ${child.result.auditPath}`);
611
+ if (child.result?.fullOutputPath)
612
+ lines.push(` Full output: ${child.result.fullOutputPath}`);
613
+ if (child.result?.accounting?.state === "incomplete")
614
+ lines.push(` Accounting incomplete: ${child.result.accounting.error ?? "Native billing evidence is unavailable; no work was repeated."}`);
615
+ const artifact = child.result?.artifactPaths?.outputPath;
616
+ if (artifact)
617
+ lines.push(` Artifact: ${artifact}${fs.existsSync(artifact) ? "" : " (missing)"}`);
618
+ const metadata = child.result?.artifactPaths?.metadataPath;
619
+ if (metadata)
620
+ lines.push(` Result metadata (acceptance details when configured): ${metadata}${fs.existsSync(metadata) ? "" : " (missing)"}`);
621
+ if (child.launch) {
622
+ const launch = child.launch;
623
+ lines.push(` Effective model: ${launch.model ?? "native default"}; thinking: ${launch.thinking ?? "native default"}`, ` Output: ${launch.output || "disabled"} (${launch.outputMode}); configuration: saved launch snapshot`);
624
+ if (options.full)
625
+ lines.push(` Saved launch configuration:\n${JSON.stringify(launch, null, 2)}`);
626
+ }
627
+ else
628
+ lines.push(" Configuration: legacy-partial; original profile snapshot was not recorded.");
629
+ const output = child.result && getSingleResultOutput(child.result);
630
+ if (output)
631
+ lines.push(` Result: ${compact(output, 600)}`);
632
+ if (child.result?.error)
633
+ lines.push(` Error: ${child.result.error}`);
634
+ }
635
+ if (control.capabilities.includes("review"))
636
+ lines.push(`Review (parent-only, not sent to child): ${formatRunAction("review", run.runId, { decision: "accepted" }, options.childSafe)} or decision: "needs_changes".`);
637
+ if (view.continuations.length)
638
+ lines.push("Continuation history:", ...view.continuations.map((next) => ` ${next.predecessorRunId}:${next.predecessorIndex ?? 0} -> ${next.runId}`));
639
+ if (control.capabilities.includes("resume"))
640
+ lines.push(`Continue: ${formatRunAction("resume", run.runId, { ...(view.children.length > 1 ? { index: control.nextActions.find((action) => action.action === "resume")?.index ?? 0 } : {}), message: "..." }, options.childSafe)}`);
641
+ if (!options.full)
642
+ lines.push(`Full task/configuration: ${formatRunAction("status", run.runId, { full: true }, options.childSafe)}`);
643
+ return {
644
+ content: [{ type: "text", text: lines.join("\n") }],
645
+ details: { ...runtime?.details, mode: "management", results: [], run: view, managementControl: control },
646
+ };
647
+ }
648
+ function unavailableRunView(run, error) {
649
+ return { ...run, state: "unknown", updatedAt: run.startedAt, attention: ["unknown"], canInterrupt: false, continuations: [],
650
+ children: run.children.map((child) => ({ ...child, state: "unknown", configuration: "legacy-partial" })),
651
+ diagnosis: `Saved owner records unavailable: ${String(error)}. Completion is unconfirmed.` };
652
+ }
653
+ export async function ownedRunList(state, params) {
654
+ const limit = params.limit ?? 20, sort = params.sort;
655
+ if (sort === "relevance")
656
+ throw new Error("Run list sort must be attention, newest, or oldest.");
657
+ const indexed = await (await runHistoryIndex(state, true)).listRuns({ ...params, sort });
658
+ // The index orders observations; only the bounded selected page gets authoritative controls.
659
+ const page = indexed.rows.map((row) => {
660
+ const run = state.ownedRuns?.get(row.runId);
661
+ if (!run || run.ownerSessionId !== row.ownerSessionId)
662
+ throw new Error("Owning session changed while listing runs.");
663
+ try {
664
+ return ownedRunView(run, state, { includeContinuations: false, readConfiguration: false });
665
+ }
666
+ catch (error) {
667
+ return unavailableRunView(run, error);
668
+ }
669
+ });
670
+ const owned = [...(state.ownedRuns?.values() ?? [])];
671
+ const runs = page.map(({ runId, source, mode, cwd, task, state: runState, updatedAt, attention, review, rootRunId, predecessorRunId, predecessorIndex, children }) => ({ runId, source, mode, cwd, task: compact(task, 2048), state: runState, updatedAt, attention, review, rootRunId, predecessorRunId, predecessorIndex, continuations: owned.filter((candidate) => candidate.predecessorRunId === runId).map((candidate) => candidate.runId), summary: compact(children.map((child) => child.result ? getSingleResultOutput(child.result) || child.result.error || "" : "").filter(Boolean).join(" | ")) }));
672
+ const controls = page.map(ownedRunControl);
673
+ const { offset, nextOffset, nextCursor, total, freshness, version } = indexed;
674
+ const next = nextCursor ? { action: "list", cursor: nextCursor, limit, ...(sort ? { sort } : {}), ...(params.agent ? { agent: params.agent } : {}), ...(params.state ? { state: params.state } : {}), ...(params.text ? { text: params.text } : {}) } : undefined;
675
+ return {
676
+ content: [{ type: "text", text: total ? [`Owned runs: ${total} (showing ${page.length ? `${offset + 1}–${offset + page.length}` : "none"}; ${params.sort ?? "attention"} order)`, ...(freshness.state !== "current" ? [`Browse index: ${freshness.state}; ordering/filter observations may be incomplete. Selected controls are checked against owner records.`] : []), ...runs.map((run) => `- ${run.runId} | ${run.state}${run.attention.length ? ` | ${run.attention.join(", ")}` : ""} | ${compact(run.task)}${run.summary ? ` | ${run.summary}` : ""} | Launch cwd: ${run.cwd}${run.predecessorRunId ? ` | from ${run.predecessorRunId}:${run.predecessorIndex ?? 0}` : ""}${run.continuations.length ? ` | continued as ${run.continuations.join(", ")} (separate results/reviews)` : ""}`), ...(next ? [`Next: agent_runs(${JSON.stringify(next)})`] : [])].join("\n") : "No delegated runs match in this owning session." }],
677
+ details: { mode: "management", results: [], runs, managementControls: controls, managementControl: controls.find((control) => control.state === "live"), runList: { total, offset, limit, version, freshness, ...(nextOffset !== undefined ? { nextOffset } : {}), ...(nextCursor ? { nextCursor } : {}) } },
678
+ };
679
+ }