@fitchmultz/pi-subagents 0.44.7 → 0.46.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 (955) hide show
  1. package/CHANGELOG.md +264 -7
  2. package/LICENSE +1 -0
  3. package/README.md +58 -1483
  4. package/agents/context-builder.md +19 -1
  5. package/agents/debugger.md +10 -3
  6. package/agents/delegate.md +1 -0
  7. package/agents/fixer.md +4 -1
  8. package/agents/oracle.md +13 -2
  9. package/agents/planner.md +14 -3
  10. package/agents/researcher.md +11 -2
  11. package/agents/reviewer-claude.md +11 -2
  12. package/agents/reviewer-gpt.md +12 -2
  13. package/agents/reviewer-ponytail.md +11 -1
  14. package/agents/reviewer-security.md +13 -2
  15. package/agents/reviewer.md +12 -3
  16. package/agents/scout.md +12 -1
  17. package/agents/ui-designer.md +10 -3
  18. package/agents/watcher.md +1 -0
  19. package/agents/worker.md +8 -2
  20. package/agents/writer.md +6 -3
  21. package/dist/agents/agent-defaults.js +9 -0
  22. package/dist/agents/agent-definition.js +196 -0
  23. package/dist/agents/agent-management.js +64 -905
  24. package/dist/agents/agent-scope.js +4 -2
  25. package/dist/agents/agent-selection.js +10 -5
  26. package/dist/agents/agent-serializer.js +55 -67
  27. package/dist/agents/agents.js +75 -716
  28. package/dist/agents/builtin-overrides.js +218 -0
  29. package/dist/agents/chain-serializer.js +3 -367
  30. package/dist/agents/config-values.js +52 -0
  31. package/dist/agents/discovery-paths.js +91 -0
  32. package/dist/agents/frontmatter.js +15 -5
  33. package/dist/agents/identity.js +25 -8
  34. package/dist/agents/json-chain.js +254 -0
  35. package/dist/agents/management-config.js +332 -0
  36. package/dist/agents/management-format.js +231 -0
  37. package/dist/agents/management-persistence.js +287 -0
  38. package/dist/agents/management-targets.js +136 -0
  39. package/dist/agents/management-warnings.js +81 -0
  40. package/dist/agents/markdown-chain.js +210 -0
  41. package/dist/agents/package-agents.js +154 -0
  42. package/dist/agents/skill-files.js +157 -0
  43. package/dist/agents/skill-search-paths.js +246 -0
  44. package/dist/agents/skill-types.js +1 -0
  45. package/dist/agents/skills.js +54 -499
  46. package/dist/extension/acceptance-schema.js +55 -0
  47. package/dist/extension/build-info.js +1 -1
  48. package/dist/extension/chain-schema.js +196 -0
  49. package/dist/extension/child-runtime.js +237 -0
  50. package/dist/extension/compact-tools.js +122 -51
  51. package/dist/extension/config-schema.js +29 -0
  52. package/dist/extension/config.js +27 -11
  53. package/dist/extension/control-notice-schema.js +37 -0
  54. package/dist/extension/control-notices.js +13 -6
  55. package/dist/extension/delegation-bridges.js +43 -0
  56. package/dist/extension/doctor.js +48 -25
  57. package/dist/extension/everyday-schemas.js +207 -0
  58. package/dist/extension/fanout-child.js +18 -319
  59. package/dist/extension/index.js +8 -563
  60. package/dist/extension/message-renderers.js +225 -0
  61. package/dist/extension/nested-control-inbox.js +154 -0
  62. package/dist/extension/parent-runtime.js +339 -0
  63. package/dist/extension/primary-tool.js +69 -0
  64. package/dist/extension/result-snapshot.js +22 -0
  65. package/dist/extension/runtime-reload.js +62 -0
  66. package/dist/extension/runtime-storage.js +43 -0
  67. package/dist/extension/schema-overrides.js +75 -0
  68. package/dist/extension/schemas.js +265 -336
  69. package/dist/extension/tool-input.js +27 -12
  70. package/dist/extension/tool-result.js +19 -4
  71. package/dist/history/answer-texts.js +57 -0
  72. package/dist/history/canonical-result.js +76 -43
  73. package/dist/history/canonical-selection.js +40 -0
  74. package/dist/history/database-generation.js +82 -0
  75. package/dist/history/database.js +57 -33
  76. package/dist/history/entry-ingestion.js +61 -0
  77. package/dist/history/index.js +242 -97
  78. package/dist/history/ingest.js +168 -331
  79. package/dist/history/output-digests.js +40 -0
  80. package/dist/history/preview.js +210 -0
  81. package/dist/history/prune.js +33 -0
  82. package/dist/history/queries.js +60 -349
  83. package/dist/history/query-scope.js +190 -0
  84. package/dist/history/rows.js +77 -0
  85. package/dist/history/run-projection.js +137 -0
  86. package/dist/history/run-queries.js +153 -0
  87. package/dist/history/schema.js +31 -0
  88. package/dist/history/search-queries.js +163 -0
  89. package/dist/history/selected-record.js +108 -0
  90. package/dist/history/source-file.js +45 -0
  91. package/dist/history/source-watches.js +141 -0
  92. package/dist/history/store.js +164 -153
  93. package/dist/history/text-chunks.js +169 -0
  94. package/dist/history/text.js +35 -0
  95. package/dist/history/transcript-queries.js +177 -0
  96. package/dist/history/types.js +5 -1
  97. package/dist/history/values.js +36 -0
  98. package/dist/history/wire-inputs.js +122 -0
  99. package/dist/history/wire-models.js +38 -0
  100. package/dist/history/worker-runtime.js +441 -0
  101. package/dist/history/worker.js +66 -462
  102. package/dist/intercom/intercom-bridge.js +48 -20
  103. package/dist/intercom/live-intercom.js +80 -23
  104. package/dist/intercom/nested-result-tree.js +163 -0
  105. package/dist/intercom/result-delivery.js +40 -0
  106. package/dist/intercom/result-intercom.js +178 -261
  107. package/dist/pi-intercom/agent-dir.js +7 -3
  108. package/dist/pi-intercom/async.js +27 -0
  109. package/dist/pi-intercom/bridge-ask.js +57 -0
  110. package/dist/pi-intercom/bridge-config.js +89 -0
  111. package/dist/pi-intercom/bridge-http.js +86 -0
  112. package/dist/pi-intercom/bridge-launch-policy.js +88 -0
  113. package/dist/pi-intercom/bridge-launch.js +217 -0
  114. package/dist/pi-intercom/bridge-protocol.js +106 -0
  115. package/dist/pi-intercom/bridge-routes.js +72 -0
  116. package/dist/pi-intercom/bridge-rpc-process.js +264 -0
  117. package/dist/pi-intercom/bridge-session.js +420 -0
  118. package/dist/pi-intercom/bridge.js +221 -606
  119. package/dist/pi-intercom/broker/broker.js +236 -244
  120. package/dist/pi-intercom/broker/client.js +189 -304
  121. package/dist/pi-intercom/broker/connection.js +165 -0
  122. package/dist/pi-intercom/broker/framing.js +5 -3
  123. package/dist/pi-intercom/broker/paths.js +4 -1
  124. package/dist/pi-intercom/broker/pid.js +53 -25
  125. package/dist/pi-intercom/broker/snapshot-reader.js +78 -0
  126. package/dist/pi-intercom/broker/snapshots.js +188 -0
  127. package/dist/pi-intercom/broker/spawn.js +36 -25
  128. package/dist/pi-intercom/config.js +85 -71
  129. package/dist/pi-intercom/connection.js +297 -0
  130. package/dist/pi-intercom/identity.js +58 -0
  131. package/dist/pi-intercom/inbound-delivery.js +356 -0
  132. package/dist/pi-intercom/inbound-journal.js +303 -0
  133. package/dist/pi-intercom/inbound-record.js +113 -0
  134. package/dist/pi-intercom/inbound.js +179 -0
  135. package/dist/pi-intercom/index.js +79 -2933
  136. package/dist/pi-intercom/inspection.js +62 -0
  137. package/dist/pi-intercom/interview.js +236 -0
  138. package/dist/pi-intercom/lifecycle.js +168 -0
  139. package/dist/pi-intercom/message-format.js +69 -0
  140. package/dist/pi-intercom/outbound.js +304 -0
  141. package/dist/pi-intercom/peer-awareness.js +66 -0
  142. package/dist/pi-intercom/reply-tracker.js +62 -43
  143. package/dist/pi-intercom/reply-wait.js +201 -0
  144. package/dist/pi-intercom/runtime-types.js +3 -0
  145. package/dist/pi-intercom/session-events.js +180 -0
  146. package/dist/pi-intercom/session-targets.js +42 -34
  147. package/dist/pi-intercom/subagent-events.js +238 -0
  148. package/dist/pi-intercom/subagent-live-events.js +129 -86
  149. package/dist/pi-intercom/supervisor.js +282 -0
  150. package/dist/pi-intercom/tool-arguments.js +74 -0
  151. package/dist/pi-intercom/tool-registration.js +95 -0
  152. package/dist/pi-intercom/tool-results.js +38 -0
  153. package/dist/pi-intercom/tool-schemas.js +82 -0
  154. package/dist/pi-intercom/tools.js +51 -0
  155. package/dist/pi-intercom/topic-actions.js +177 -0
  156. package/dist/pi-intercom/topics.js +177 -92
  157. package/dist/pi-intercom/transport.js +1 -0
  158. package/dist/pi-intercom/types.js +151 -114
  159. package/dist/pi-intercom/ui/compose-paste.js +93 -0
  160. package/dist/pi-intercom/ui/compose.js +121 -143
  161. package/dist/pi-intercom/ui/inline-message.js +59 -59
  162. package/dist/pi-intercom/ui/message-renderers.js +147 -0
  163. package/dist/pi-intercom/ui/overlay.js +181 -0
  164. package/dist/pi-intercom/ui/session-format.js +124 -0
  165. package/dist/pi-intercom/ui/session-list.js +76 -43
  166. package/dist/pi-intercom/ui/tool-renderers.js +110 -0
  167. package/dist/pi-intercom/ui/topic-view.js +88 -0
  168. package/dist/pi-intercom/validation.js +59 -0
  169. package/dist/runs/background/async-chain-plan.js +265 -0
  170. package/dist/runs/background/async-control.js +134 -58
  171. package/dist/runs/background/async-execution.js +136 -633
  172. package/dist/runs/background/async-job-admission.js +40 -0
  173. package/dist/runs/background/async-job-projection.js +169 -0
  174. package/dist/runs/background/async-job-tracker.js +297 -327
  175. package/dist/runs/background/async-launch.js +216 -0
  176. package/dist/runs/background/async-plan.js +116 -0
  177. package/dist/runs/background/async-result-file.js +47 -23
  178. package/dist/runs/background/async-resume.js +195 -253
  179. package/dist/runs/background/async-run-discovery.js +213 -0
  180. package/dist/runs/background/async-run-location.js +135 -0
  181. package/dist/runs/background/async-run-record.js +1 -0
  182. package/dist/runs/background/async-run-repair.js +223 -0
  183. package/dist/runs/background/async-run-summary.js +1 -0
  184. package/dist/runs/background/async-single-plan.js +105 -0
  185. package/dist/runs/background/async-status-projection.js +155 -0
  186. package/dist/runs/background/async-status-render.js +117 -0
  187. package/dist/runs/background/async-status-validation.js +181 -0
  188. package/dist/runs/background/async-status.js +3 -442
  189. package/dist/runs/background/async-value.js +21 -0
  190. package/dist/runs/background/completion-dedupe.js +36 -12
  191. package/dist/runs/background/completion-delivery.js +211 -205
  192. package/dist/runs/background/completion-receipts.js +237 -0
  193. package/dist/runs/background/legacy-status-acceptance.js +34 -0
  194. package/dist/runs/background/notify.js +96 -48
  195. package/dist/runs/background/parallel-groups.js +36 -19
  196. package/dist/runs/background/result-payload.js +178 -0
  197. package/dist/runs/background/result-watcher.js +348 -339
  198. package/dist/runs/background/run-id-resolver.js +53 -33
  199. package/dist/runs/background/run-schemas.js +189 -0
  200. package/dist/runs/background/run-status-contracts.js +14 -0
  201. package/dist/runs/background/run-status-render.js +270 -0
  202. package/dist/runs/background/run-status.js +231 -339
  203. package/dist/runs/background/runner-attempt-output.js +155 -0
  204. package/dist/runs/background/runner-attempt.js +263 -0
  205. package/dist/runs/background/runner-child-executor.js +166 -0
  206. package/dist/runs/background/runner-child-observer.js +192 -0
  207. package/dist/runs/background/runner-completion-summary.js +71 -0
  208. package/dist/runs/background/runner-completion.js +211 -0
  209. package/dist/runs/background/runner-config.js +1 -0
  210. package/dist/runs/background/runner-contract.js +1 -0
  211. package/dist/runs/background/runner-dynamic.js +121 -0
  212. package/dist/runs/background/runner-finalization.js +149 -0
  213. package/dist/runs/background/runner-graph.js +68 -0
  214. package/dist/runs/background/runner-initial-status.js +60 -0
  215. package/dist/runs/background/runner-lifecycle.js +142 -0
  216. package/dist/runs/background/runner-monitor.js +309 -0
  217. package/dist/runs/background/runner-parallel.js +204 -0
  218. package/dist/runs/background/runner-prepare-step.js +86 -0
  219. package/dist/runs/background/runner-reporting.js +153 -0
  220. package/dist/runs/background/runner-status.js +148 -0
  221. package/dist/runs/background/runner-step-output.js +92 -0
  222. package/dist/runs/background/runner-step-record.js +45 -0
  223. package/dist/runs/background/runner-step.js +112 -0
  224. package/dist/runs/background/runner-streaming.js +173 -0
  225. package/dist/runs/background/runner-workflow.js +213 -0
  226. package/dist/runs/background/stale-run-reconciler.js +155 -292
  227. package/dist/runs/background/subagent-runner-launcher.js +33 -13
  228. package/dist/runs/background/subagent-runner.js +51 -1842
  229. package/dist/runs/background/top-level-async.js +2 -1
  230. package/dist/runs/foreground/chain-clarify.js +293 -1017
  231. package/dist/runs/foreground/clarification-draft.js +87 -0
  232. package/dist/runs/foreground/clarify-contracts.js +1 -0
  233. package/dist/runs/foreground/clarify-invocation.js +129 -38
  234. package/dist/runs/foreground/clarify-text-editor.js +210 -0
  235. package/dist/runs/foreground/clarify-view.js +167 -0
  236. package/dist/runs/foreground/execution-input.js +226 -353
  237. package/dist/runs/foreground/execution-routing.js +33 -0
  238. package/dist/runs/foreground/execution-timeout.js +81 -0
  239. package/dist/runs/foreground/foreground-control.js +222 -763
  240. package/dist/runs/foreground/foreground-memory.js +99 -0
  241. package/dist/runs/foreground/invocation-context.js +55 -0
  242. package/dist/runs/foreground/invocation-execution.js +331 -0
  243. package/dist/runs/foreground/invocation-expansion.js +48 -0
  244. package/dist/runs/foreground/management-actions.js +295 -0
  245. package/dist/runs/foreground/nested-control.js +179 -0
  246. package/dist/runs/foreground/question-continuation.js +78 -0
  247. package/dist/runs/foreground/question-control.js +155 -50
  248. package/dist/runs/foreground/resume-target.js +221 -0
  249. package/dist/runs/foreground/revival-launch-policy.js +58 -0
  250. package/dist/runs/foreground/run-async-path.js +190 -178
  251. package/dist/runs/foreground/run-interrupt.js +164 -0
  252. package/dist/runs/foreground/run-nudge.js +278 -0
  253. package/dist/runs/foreground/saved-revival.js +251 -0
  254. package/dist/runs/foreground/subagent-executor.js +135 -424
  255. package/dist/runs/foreground/subagent-params.js +67 -30
  256. package/dist/runs/foreground/wait-observation.js +104 -0
  257. package/dist/runs/foreground/wait-registration.js +17 -0
  258. package/dist/runs/foreground/wait-run.js +283 -123
  259. package/dist/runs/shared/acceptance-contract.js +193 -99
  260. package/dist/runs/shared/acceptance-defaults.js +1 -0
  261. package/dist/runs/shared/acceptance-evaluation.js +201 -181
  262. package/dist/runs/shared/acceptance-finalization.js +256 -180
  263. package/dist/runs/shared/acceptance-outcome.js +53 -0
  264. package/dist/runs/shared/acceptance-reports.js +137 -72
  265. package/dist/runs/shared/acceptance-submission.js +167 -0
  266. package/dist/runs/shared/acceptance-verification.js +100 -0
  267. package/dist/runs/shared/chain-outputs.js +80 -52
  268. package/dist/runs/shared/child-attempt-types.js +1 -0
  269. package/dist/runs/shared/child-attempt.js +314 -667
  270. package/dist/runs/shared/child-boundaries.js +173 -0
  271. package/dist/runs/shared/child-context-filter.js +44 -0
  272. package/dist/runs/shared/child-event-observer.js +390 -0
  273. package/dist/runs/shared/child-execution-cwd.js +73 -43
  274. package/dist/runs/shared/child-execution-outcome.js +72 -0
  275. package/dist/runs/shared/child-invocation.js +54 -0
  276. package/dist/runs/shared/child-message-validation.js +215 -0
  277. package/dist/runs/shared/child-native-accounting.js +106 -0
  278. package/dist/runs/shared/child-native-observation.js +69 -0
  279. package/dist/runs/shared/child-observation-spool.js +164 -0
  280. package/dist/runs/shared/child-observations.js +157 -76
  281. package/dist/runs/shared/child-presence.js +3 -0
  282. package/dist/runs/shared/child-route-env.js +119 -0
  283. package/dist/runs/shared/child-stream-observer.js +221 -0
  284. package/dist/runs/shared/child-stream-projection.js +137 -0
  285. package/dist/runs/shared/child-structured-tool.js +27 -0
  286. package/dist/runs/shared/claude-code.js +121 -158
  287. package/dist/runs/shared/claude-model.js +42 -0
  288. package/dist/runs/shared/claude-session.js +83 -0
  289. package/dist/runs/shared/claude-types.js +1 -0
  290. package/dist/runs/shared/completion-guard.js +18 -18
  291. package/dist/runs/shared/control-event-policy.js +8 -0
  292. package/dist/runs/shared/control-notification-owner.js +13 -0
  293. package/dist/runs/shared/dynamic-fanout.js +188 -173
  294. package/dist/runs/shared/dynamic-item.js +102 -0
  295. package/dist/runs/shared/history-index.js +62 -36
  296. package/dist/runs/shared/legacy-async-recovery.js +114 -0
  297. package/dist/runs/shared/legacy-output-recovery.js +148 -0
  298. package/dist/runs/shared/mcp-config-schema.js +36 -0
  299. package/dist/runs/shared/mcp-direct-tool-allowlist.js +147 -116
  300. package/dist/runs/shared/model-fallback.js +97 -38
  301. package/dist/runs/shared/mutating-tool-guard.js +128 -88
  302. package/dist/runs/shared/native-finalization-types.js +1 -0
  303. package/dist/runs/shared/native-finalization.js +155 -76
  304. package/dist/runs/shared/native-usage.js +238 -98
  305. package/dist/runs/shared/nested-control-records.js +128 -0
  306. package/dist/runs/shared/nested-events.js +8 -700
  307. package/dist/runs/shared/nested-path.js +32 -28
  308. package/dist/runs/shared/nested-projection.js +155 -0
  309. package/dist/runs/shared/nested-protocol.js +18 -0
  310. package/dist/runs/shared/nested-registry.js +191 -0
  311. package/dist/runs/shared/nested-render.js +106 -58
  312. package/dist/runs/shared/nested-route.js +220 -0
  313. package/dist/runs/shared/nested-validation.js +216 -0
  314. package/dist/runs/shared/owned-child-evidence.js +125 -0
  315. package/dist/runs/shared/owned-run-list.js +146 -0
  316. package/dist/runs/shared/owned-run-observation.js +267 -0
  317. package/dist/runs/shared/owned-run-progress.js +109 -0
  318. package/dist/runs/shared/owned-run-read-state.js +1 -0
  319. package/dist/runs/shared/owned-run-results.js +202 -0
  320. package/dist/runs/shared/owned-run-status.js +180 -0
  321. package/dist/runs/shared/owned-run-view.js +51 -0
  322. package/dist/runs/shared/owned-workflow-details.js +17 -0
  323. package/dist/runs/shared/owner-output.js +43 -0
  324. package/dist/runs/shared/parallel-utils.js +36 -28
  325. package/dist/runs/shared/parent-receipts.js +90 -32
  326. package/dist/runs/shared/parent-usage.js +169 -57
  327. package/dist/runs/shared/pi-args.js +108 -201
  328. package/dist/runs/shared/pi-launch-input.js +1 -0
  329. package/dist/runs/shared/pi-session-launch.js +86 -0
  330. package/dist/runs/shared/pi-spawn.js +35 -21
  331. package/dist/runs/shared/run-history-queries.js +126 -35
  332. package/dist/runs/shared/run-history.js +101 -35
  333. package/dist/runs/shared/run-metadata-paths.js +14 -0
  334. package/dist/runs/shared/run-metadata.js +158 -0
  335. package/dist/runs/shared/run-persistence.js +131 -0
  336. package/dist/runs/shared/run-records.js +9 -679
  337. package/dist/runs/shared/run-restoration.js +338 -0
  338. package/dist/runs/shared/run-state-owner.js +26 -0
  339. package/dist/runs/shared/session-cwd-preload.js +27 -18
  340. package/dist/runs/shared/single-output.js +148 -70
  341. package/dist/runs/shared/stable-value.js +15 -0
  342. package/dist/runs/shared/streaming-text.js +23 -12
  343. package/dist/runs/shared/structured-output.js +25 -13
  344. package/dist/runs/shared/subagent-control.js +167 -100
  345. package/dist/runs/shared/subagent-prompt-runtime.js +27 -130
  346. package/dist/runs/shared/subagent-tool-loop-guard.js +81 -52
  347. package/dist/runs/shared/supervisor-question-state.js +145 -0
  348. package/dist/runs/shared/supervisor-questions.js +185 -254
  349. package/dist/runs/shared/workflow-graph.js +259 -167
  350. package/dist/runs/shared/workflow-policy.js +120 -48
  351. package/dist/runs/shared/worktree-contract.js +1 -0
  352. package/dist/runs/shared/worktree-hook.js +138 -0
  353. package/dist/runs/shared/worktree-location.js +47 -0
  354. package/dist/runs/shared/worktree-preservation.js +8 -0
  355. package/dist/runs/shared/worktree-process.js +107 -0
  356. package/dist/runs/shared/worktree.js +183 -322
  357. package/dist/shared/agent-context-policy.js +34 -44
  358. package/dist/shared/agent-dir.js +14 -0
  359. package/dist/shared/artifacts.js +30 -12
  360. package/dist/shared/atomic-json.js +4 -1
  361. package/dist/shared/chain-files.js +72 -0
  362. package/dist/shared/concurrency.js +18 -0
  363. package/dist/shared/content-preview.js +115 -0
  364. package/dist/shared/cooperative.js +5 -2
  365. package/dist/shared/execution-cwd.js +13 -5
  366. package/dist/shared/file-coalescer.js +2 -1
  367. package/dist/shared/fork-context.js +10 -12
  368. package/dist/shared/formatters.js +45 -19
  369. package/dist/shared/journal-frames.js +164 -0
  370. package/dist/shared/journal-projections.js +121 -0
  371. package/dist/shared/journal-reader.js +6 -407
  372. package/dist/shared/json-projection.js +191 -0
  373. package/dist/shared/lazy-tools.js +19 -8
  374. package/dist/shared/message-output.js +116 -0
  375. package/dist/shared/model-info.js +31 -14
  376. package/dist/shared/native-import.js +20 -0
  377. package/dist/shared/native-journal.js +140 -0
  378. package/dist/shared/native-session-loader.js +17 -0
  379. package/dist/shared/native-session.js +2 -13
  380. package/dist/shared/native-tui.js +7 -2
  381. package/dist/shared/native-typebox.js +15 -3
  382. package/dist/shared/output-page.js +63 -0
  383. package/dist/shared/post-exit-stdio-guard.js +128 -88
  384. package/dist/shared/prompt-sections.js +2 -0
  385. package/dist/shared/session-entries.js +70 -35
  386. package/dist/shared/session-identity.js +5 -3
  387. package/dist/shared/settings.js +62 -192
  388. package/dist/shared/status-format.js +97 -37
  389. package/dist/shared/task-file-policy.js +24 -0
  390. package/dist/shared/temp-root.js +44 -15
  391. package/dist/shared/types/acceptance.js +1 -0
  392. package/dist/shared/types/async.js +1 -0
  393. package/dist/shared/types/config.js +1 -0
  394. package/dist/shared/types/details.js +1 -0
  395. package/dist/shared/types/history.js +1 -0
  396. package/dist/shared/types/inputs.js +1 -0
  397. package/dist/shared/types/intercom.js +1 -0
  398. package/dist/shared/types/launch.js +1 -0
  399. package/dist/shared/types/messages.js +1 -0
  400. package/dist/shared/types/nested.js +1 -0
  401. package/dist/shared/types/owned-runs.js +1 -0
  402. package/dist/shared/types/progress.js +1 -0
  403. package/dist/shared/types/questions.js +1 -0
  404. package/dist/shared/types/results.js +1 -0
  405. package/dist/shared/types/state.js +1 -0
  406. package/dist/shared/types/usage.js +1 -0
  407. package/dist/shared/types/workflow.js +1 -0
  408. package/dist/shared/types.js +75 -30
  409. package/dist/shared/unknown.js +52 -0
  410. package/dist/shared/utils.js +54 -260
  411. package/dist/slash/command-discovery.js +82 -0
  412. package/dist/slash/command-input.js +190 -0
  413. package/dist/slash/prompt-template-bridge.js +94 -270
  414. package/dist/slash/prompt-template-contract.js +77 -0
  415. package/dist/slash/prompt-template-results.js +125 -0
  416. package/dist/slash/request-protocol.js +68 -0
  417. package/dist/slash/result-delivery.js +237 -0
  418. package/dist/slash/saved-chain.js +78 -0
  419. package/dist/slash/slash-bridge.js +123 -89
  420. package/dist/slash/slash-commands.js +101 -572
  421. package/dist/slash/slash-live-state.js +68 -47
  422. package/dist/tui/action-hints.js +50 -22
  423. package/dist/tui/activity-lines.js +32 -0
  424. package/dist/tui/agent-browser.js +290 -0
  425. package/dist/tui/agent-controls.js +289 -0
  426. package/dist/tui/agent-conversation.js +444 -0
  427. package/dist/tui/agent-dock.js +171 -0
  428. package/dist/tui/agent-history.js +50 -183
  429. package/dist/tui/agent-model.js +62 -0
  430. package/dist/tui/agent-picker.js +262 -0
  431. package/dist/tui/agent-view.js +232 -1564
  432. package/dist/tui/compact-results.js +193 -0
  433. package/dist/tui/conversation-details.js +190 -0
  434. package/dist/tui/conversation-frame.js +135 -0
  435. package/dist/tui/conversation-history.js +201 -0
  436. package/dist/tui/conversation-menu.js +78 -0
  437. package/dist/tui/conversation-notices.js +79 -0
  438. package/dist/tui/conversation-selection.js +58 -0
  439. package/dist/tui/conversation-viewport.js +129 -0
  440. package/dist/tui/direction-message.js +27 -0
  441. package/dist/tui/display.js +282 -0
  442. package/dist/tui/editor-viewport.js +18 -0
  443. package/dist/tui/expanded-content.js +87 -0
  444. package/dist/tui/expanded-multi.js +170 -0
  445. package/dist/tui/expanded-single.js +65 -0
  446. package/dist/tui/history-cards.js +216 -0
  447. package/dist/tui/history-display.js +238 -0
  448. package/dist/tui/history-json.js +16 -0
  449. package/dist/tui/history-metadata.js +152 -0
  450. package/dist/tui/history-record.js +251 -0
  451. package/dist/tui/history-text.js +34 -0
  452. package/dist/tui/indexed-history.js +116 -0
  453. package/dist/tui/nested-widget.js +120 -0
  454. package/dist/tui/picker-page.js +59 -0
  455. package/dist/tui/render.js +58 -1501
  456. package/dist/tui/result-facts.js +64 -0
  457. package/dist/tui/task-observation.js +59 -0
  458. package/dist/tui/task-store.js +366 -0
  459. package/dist/tui/text-values.js +16 -0
  460. package/dist/tui/view-connections.js +47 -0
  461. package/dist/tui/view-model.js +94 -0
  462. package/dist/tui/view-persistence.js +51 -0
  463. package/dist/tui/view-ports.js +1 -0
  464. package/dist/tui/view-session.js +37 -0
  465. package/dist/tui/widget-details.js +144 -0
  466. package/dist/tui/widget-status.js +245 -0
  467. package/dist/tui/widget.js +145 -0
  468. package/dist/tui/workflow-labels.js +223 -0
  469. package/dist/tui/workflow-outcomes.js +124 -0
  470. package/docs/code-quality.md +38 -0
  471. package/docs/development.md +99 -0
  472. package/docs/intercom-bridge.md +77 -22
  473. package/docs/intercom.md +132 -106
  474. package/docs/performance.md +19 -17
  475. package/docs/reference.md +1472 -0
  476. package/generated/RunValidators.d.ts +28 -0
  477. package/generated/RunValidators.js +2 -0
  478. package/package.json +27 -6
  479. package/prompts/parallel-cleanup.md +3 -0
  480. package/prompts/parallel-context-build.md +3 -0
  481. package/prompts/parallel-handoff-plan.md +6 -0
  482. package/prompts/parallel-research.md +4 -0
  483. package/prompts/parallel-review.md +2 -0
  484. package/prompts/review-loop.md +2 -0
  485. package/scripts/build.mjs +129 -85
  486. package/scripts/check-compat.mjs +48 -11
  487. package/scripts/compat-host.mjs +35 -7
  488. package/scripts/generate-run-schemas.mjs +210 -0
  489. package/scripts/intercom-bridge-credentials.mjs +158 -29
  490. package/scripts/intercom-remote.mjs +213 -52
  491. package/scripts/local-install-smoke.mjs +47 -37
  492. package/scripts/native-package-smoke.mjs +135 -25
  493. package/scripts/package-smoke.mjs +360 -140
  494. package/scripts/prepare.mjs +54 -34
  495. package/scripts/real-pi-processes.mjs +261 -0
  496. package/scripts/real-pi-smoke.mjs +432 -351
  497. package/scripts/run-tests.mjs +29 -15
  498. package/skills/pi-intercom/SKILL.md +11 -11
  499. package/skills/pi-intercom/references/peer-sessions.md +7 -2
  500. package/skills/pi-subagents/SKILL.md +1 -1
  501. package/skills/pi-subagents/references/full-orchestration-guide.md +254 -112
  502. package/src/agents/agent-defaults.ts +13 -0
  503. package/src/agents/agent-definition.ts +253 -0
  504. package/src/agents/agent-management.ts +111 -808
  505. package/src/agents/agent-scope.ts +7 -3
  506. package/src/agents/agent-selection.ts +26 -16
  507. package/src/agents/agent-serializer.ts +84 -93
  508. package/src/agents/agents.ts +199 -906
  509. package/src/agents/builtin-overrides.ts +297 -0
  510. package/src/agents/chain-serializer.ts +3 -329
  511. package/src/agents/config-values.ts +57 -0
  512. package/src/agents/discovery-paths.ts +134 -0
  513. package/src/agents/frontmatter.ts +35 -17
  514. package/src/agents/identity.ts +49 -18
  515. package/src/agents/json-chain.ts +305 -0
  516. package/src/agents/management-config.ts +342 -0
  517. package/src/agents/management-format.ts +243 -0
  518. package/src/agents/management-persistence.ts +408 -0
  519. package/src/agents/management-targets.ts +260 -0
  520. package/src/agents/management-warnings.ts +115 -0
  521. package/src/agents/markdown-chain.ts +237 -0
  522. package/src/agents/package-agents.ts +231 -0
  523. package/src/agents/skill-files.ts +182 -0
  524. package/src/agents/skill-search-paths.ts +289 -0
  525. package/src/agents/skill-types.ts +31 -0
  526. package/src/agents/skills.ts +152 -610
  527. package/src/extension/acceptance-schema.ts +88 -0
  528. package/src/extension/chain-schema.ts +276 -0
  529. package/src/extension/child-runtime.ts +302 -0
  530. package/src/extension/compact-tools.ts +259 -102
  531. package/src/extension/config-schema.ts +59 -0
  532. package/src/extension/config.ts +39 -17
  533. package/src/extension/control-notice-schema.ts +55 -0
  534. package/src/extension/control-notices.ts +57 -33
  535. package/src/extension/delegation-bridges.ts +59 -0
  536. package/src/extension/doctor.ts +171 -135
  537. package/src/extension/everyday-schemas.ts +252 -0
  538. package/src/extension/fanout-child.ts +29 -308
  539. package/src/extension/index.ts +17 -642
  540. package/src/extension/message-renderers.ts +334 -0
  541. package/src/extension/nested-control-inbox.ts +196 -0
  542. package/src/extension/parent-runtime.ts +405 -0
  543. package/src/extension/primary-tool.ts +130 -0
  544. package/src/extension/result-snapshot.ts +35 -0
  545. package/src/extension/runtime-reload.ts +72 -0
  546. package/src/extension/runtime-storage.ts +49 -0
  547. package/src/extension/schema-overrides.ts +114 -0
  548. package/src/extension/schemas.ts +451 -418
  549. package/src/extension/tool-input.ts +40 -13
  550. package/src/extension/tool-result.ts +45 -14
  551. package/src/history/answer-texts.ts +74 -0
  552. package/src/history/canonical-result.ts +127 -51
  553. package/src/history/canonical-selection.ts +54 -0
  554. package/src/history/database-generation.ts +86 -0
  555. package/src/history/database.ts +86 -48
  556. package/src/history/entry-ingestion.ts +129 -0
  557. package/src/history/index.ts +426 -148
  558. package/src/history/ingest.ts +372 -342
  559. package/src/history/output-digests.ts +41 -0
  560. package/src/history/preview.ts +235 -0
  561. package/src/history/prune.ts +48 -0
  562. package/src/history/queries.ts +122 -256
  563. package/src/history/query-scope.ts +278 -0
  564. package/src/history/rows.ts +118 -0
  565. package/src/history/run-projection.ts +164 -0
  566. package/src/history/run-queries.ts +199 -0
  567. package/src/history/schema.ts +34 -0
  568. package/src/history/search-queries.ts +216 -0
  569. package/src/history/selected-record.ts +140 -0
  570. package/src/history/source-file.ts +51 -0
  571. package/src/history/source-watches.ts +156 -0
  572. package/src/history/store.ts +310 -197
  573. package/src/history/text-chunks.ts +181 -0
  574. package/src/history/text.ts +43 -0
  575. package/src/history/transcript-queries.ts +251 -0
  576. package/src/history/types.ts +27 -164
  577. package/src/history/values.ts +36 -0
  578. package/src/history/wire-inputs.ts +141 -0
  579. package/src/history/wire-models.ts +51 -0
  580. package/src/history/worker-runtime.ts +502 -0
  581. package/src/history/worker.ts +94 -336
  582. package/src/intercom/intercom-bridge.ts +136 -69
  583. package/src/intercom/live-intercom.ts +197 -92
  584. package/src/intercom/nested-result-tree.ts +213 -0
  585. package/src/intercom/result-delivery.ts +56 -0
  586. package/src/intercom/result-intercom.ts +364 -382
  587. package/src/pi-intercom/agent-dir.ts +9 -3
  588. package/src/pi-intercom/async.ts +33 -0
  589. package/src/pi-intercom/bridge-ask.ts +64 -0
  590. package/src/pi-intercom/bridge-config.ts +120 -0
  591. package/src/pi-intercom/bridge-http.ts +97 -0
  592. package/src/pi-intercom/bridge-launch-policy.ts +109 -0
  593. package/src/pi-intercom/bridge-launch.ts +317 -0
  594. package/src/pi-intercom/bridge-protocol.ts +123 -0
  595. package/src/pi-intercom/bridge-routes.ts +105 -0
  596. package/src/pi-intercom/bridge-rpc-process.ts +293 -0
  597. package/src/pi-intercom/bridge-session.ts +510 -0
  598. package/src/pi-intercom/bridge.ts +305 -549
  599. package/src/pi-intercom/broker/broker.ts +326 -263
  600. package/src/pi-intercom/broker/client.ts +273 -354
  601. package/src/pi-intercom/broker/connection.ts +192 -0
  602. package/src/pi-intercom/broker/framing.ts +11 -6
  603. package/src/pi-intercom/broker/paths.ts +28 -25
  604. package/src/pi-intercom/broker/pid.ts +68 -24
  605. package/src/pi-intercom/broker/snapshot-reader.ts +91 -0
  606. package/src/pi-intercom/broker/snapshots.ts +241 -0
  607. package/src/pi-intercom/broker/spawn.ts +58 -34
  608. package/src/pi-intercom/config.ts +113 -108
  609. package/src/pi-intercom/connection.ts +346 -0
  610. package/src/pi-intercom/identity.ts +71 -0
  611. package/src/pi-intercom/inbound-delivery.ts +450 -0
  612. package/src/pi-intercom/inbound-journal.ts +350 -0
  613. package/src/pi-intercom/inbound-record.ts +141 -0
  614. package/src/pi-intercom/inbound.ts +237 -0
  615. package/src/pi-intercom/index.ts +86 -2923
  616. package/src/pi-intercom/inspection.ts +82 -0
  617. package/src/pi-intercom/interview.ts +295 -0
  618. package/src/pi-intercom/lifecycle.ts +183 -0
  619. package/src/pi-intercom/message-format.ts +77 -0
  620. package/src/pi-intercom/outbound.ts +418 -0
  621. package/src/pi-intercom/peer-awareness.ts +84 -0
  622. package/src/pi-intercom/reply-tracker.ts +121 -59
  623. package/src/pi-intercom/reply-wait.ts +247 -0
  624. package/src/pi-intercom/runtime-types.ts +97 -0
  625. package/src/pi-intercom/session-events.ts +247 -0
  626. package/src/pi-intercom/session-targets.ts +124 -72
  627. package/src/pi-intercom/subagent-events.ts +299 -0
  628. package/src/pi-intercom/subagent-live-events.ts +212 -109
  629. package/src/pi-intercom/supervisor.ts +362 -0
  630. package/src/pi-intercom/tool-arguments.ts +120 -0
  631. package/src/pi-intercom/tool-registration.ts +121 -0
  632. package/src/pi-intercom/tool-results.ts +54 -0
  633. package/src/pi-intercom/tool-schemas.ts +134 -0
  634. package/src/pi-intercom/tools.ts +80 -0
  635. package/src/pi-intercom/topic-actions.ts +243 -0
  636. package/src/pi-intercom/topics.ts +267 -121
  637. package/src/pi-intercom/transport.ts +14 -0
  638. package/src/pi-intercom/types.ts +288 -190
  639. package/src/pi-intercom/ui/compose-paste.ts +109 -0
  640. package/src/pi-intercom/ui/compose.ts +173 -157
  641. package/src/pi-intercom/ui/inline-message.ts +80 -62
  642. package/src/pi-intercom/ui/message-renderers.ts +182 -0
  643. package/src/pi-intercom/ui/overlay.ts +238 -0
  644. package/src/pi-intercom/ui/session-format.ts +146 -0
  645. package/src/pi-intercom/ui/session-list.ts +145 -62
  646. package/src/pi-intercom/ui/tool-renderers.ts +148 -0
  647. package/src/pi-intercom/ui/topic-view.ts +107 -0
  648. package/src/pi-intercom/validation.ts +69 -0
  649. package/src/runs/background/async-chain-plan.ts +451 -0
  650. package/src/runs/background/async-control.ts +201 -71
  651. package/src/runs/background/async-execution.ts +206 -829
  652. package/src/runs/background/async-job-admission.ts +54 -0
  653. package/src/runs/background/async-job-projection.ts +225 -0
  654. package/src/runs/background/async-job-tracker.ts +416 -360
  655. package/src/runs/background/async-launch.ts +319 -0
  656. package/src/runs/background/async-plan.ts +285 -0
  657. package/src/runs/background/async-result-file.ts +79 -37
  658. package/src/runs/background/async-resume.ts +338 -285
  659. package/src/runs/background/async-run-discovery.ts +270 -0
  660. package/src/runs/background/async-run-location.ts +194 -0
  661. package/src/runs/background/async-run-record.ts +14 -0
  662. package/src/runs/background/async-run-repair.ts +307 -0
  663. package/src/runs/background/async-run-summary.ts +71 -0
  664. package/src/runs/background/async-single-plan.ts +168 -0
  665. package/src/runs/background/async-status-projection.ts +188 -0
  666. package/src/runs/background/async-status-render.ts +164 -0
  667. package/src/runs/background/async-status-validation.ts +238 -0
  668. package/src/runs/background/async-status.ts +9 -481
  669. package/src/runs/background/async-value.ts +29 -0
  670. package/src/runs/background/completion-dedupe.ts +103 -54
  671. package/src/runs/background/completion-delivery.ts +313 -187
  672. package/src/runs/background/completion-receipts.ts +309 -0
  673. package/src/runs/background/legacy-status-acceptance.ts +38 -0
  674. package/src/runs/background/notify.ts +167 -106
  675. package/src/runs/background/parallel-groups.ts +95 -46
  676. package/src/runs/background/result-payload.ts +268 -0
  677. package/src/runs/background/result-watcher.ts +484 -387
  678. package/src/runs/background/run-id-resolver.ts +126 -57
  679. package/src/runs/background/run-schemas.ts +240 -0
  680. package/src/runs/background/run-status-contracts.ts +46 -0
  681. package/src/runs/background/run-status-render.ts +364 -0
  682. package/src/runs/background/run-status.ts +338 -356
  683. package/src/runs/background/runner-attempt-output.ts +281 -0
  684. package/src/runs/background/runner-attempt.ts +354 -0
  685. package/src/runs/background/runner-child-executor.ts +232 -0
  686. package/src/runs/background/runner-child-observer.ts +270 -0
  687. package/src/runs/background/runner-completion-summary.ts +106 -0
  688. package/src/runs/background/runner-completion.ts +274 -0
  689. package/src/runs/background/runner-config.ts +52 -0
  690. package/src/runs/background/runner-contract.ts +51 -0
  691. package/src/runs/background/runner-dynamic.ts +185 -0
  692. package/src/runs/background/runner-finalization.ts +203 -0
  693. package/src/runs/background/runner-graph.ts +91 -0
  694. package/src/runs/background/runner-initial-status.ts +72 -0
  695. package/src/runs/background/runner-lifecycle.ts +159 -0
  696. package/src/runs/background/runner-monitor.ts +399 -0
  697. package/src/runs/background/runner-parallel.ts +280 -0
  698. package/src/runs/background/runner-prepare-step.ts +128 -0
  699. package/src/runs/background/runner-reporting.ts +192 -0
  700. package/src/runs/background/runner-status.ts +220 -0
  701. package/src/runs/background/runner-step-output.ts +158 -0
  702. package/src/runs/background/runner-step-record.ts +76 -0
  703. package/src/runs/background/runner-step.ts +150 -0
  704. package/src/runs/background/runner-streaming.ts +220 -0
  705. package/src/runs/background/runner-workflow.ts +284 -0
  706. package/src/runs/background/stale-run-reconciler.ts +279 -336
  707. package/src/runs/background/subagent-runner-launcher.ts +67 -35
  708. package/src/runs/background/subagent-runner.ts +80 -2048
  709. package/src/runs/background/top-level-async.ts +9 -7
  710. package/src/runs/foreground/chain-clarify.ts +499 -1334
  711. package/src/runs/foreground/clarification-draft.ts +92 -0
  712. package/src/runs/foreground/clarify-contracts.ts +33 -0
  713. package/src/runs/foreground/clarify-invocation.ts +192 -65
  714. package/src/runs/foreground/clarify-text-editor.ts +229 -0
  715. package/src/runs/foreground/clarify-view.ts +222 -0
  716. package/src/runs/foreground/execution-input.ts +358 -424
  717. package/src/runs/foreground/execution-routing.ts +41 -0
  718. package/src/runs/foreground/execution-timeout.ts +92 -0
  719. package/src/runs/foreground/foreground-control.ts +333 -812
  720. package/src/runs/foreground/foreground-memory.ts +132 -0
  721. package/src/runs/foreground/invocation-context.ts +88 -0
  722. package/src/runs/foreground/invocation-execution.ts +465 -0
  723. package/src/runs/foreground/invocation-expansion.ts +58 -0
  724. package/src/runs/foreground/management-actions.ts +360 -0
  725. package/src/runs/foreground/nested-control.ts +248 -0
  726. package/src/runs/foreground/question-continuation.ts +112 -0
  727. package/src/runs/foreground/question-control.ts +297 -70
  728. package/src/runs/foreground/resume-target.ts +330 -0
  729. package/src/runs/foreground/revival-launch-policy.ts +112 -0
  730. package/src/runs/foreground/run-async-path.ts +307 -223
  731. package/src/runs/foreground/run-interrupt.ts +202 -0
  732. package/src/runs/foreground/run-nudge.ts +355 -0
  733. package/src/runs/foreground/saved-revival.ts +356 -0
  734. package/src/runs/foreground/subagent-executor.ts +195 -493
  735. package/src/runs/foreground/subagent-params.ts +314 -242
  736. package/src/runs/foreground/wait-observation.ts +160 -0
  737. package/src/runs/foreground/wait-registration.ts +18 -0
  738. package/src/runs/foreground/wait-run.ts +361 -119
  739. package/src/runs/shared/acceptance-contract.ts +410 -209
  740. package/src/runs/shared/acceptance-defaults.ts +1 -0
  741. package/src/runs/shared/acceptance-evaluation.ts +314 -240
  742. package/src/runs/shared/acceptance-finalization.ts +447 -305
  743. package/src/runs/shared/acceptance-outcome.ts +69 -0
  744. package/src/runs/shared/acceptance-reports.ts +220 -126
  745. package/src/runs/shared/acceptance-submission.ts +251 -0
  746. package/src/runs/shared/acceptance-verification.ts +118 -0
  747. package/src/runs/shared/acceptance.ts +25 -25
  748. package/src/runs/shared/chain-outputs.ts +190 -98
  749. package/src/runs/shared/child-attempt-types.ts +118 -0
  750. package/src/runs/shared/child-attempt.ts +372 -608
  751. package/src/runs/shared/child-boundaries.ts +217 -0
  752. package/src/runs/shared/child-context-filter.ts +53 -0
  753. package/src/runs/shared/child-event-observer.ts +496 -0
  754. package/src/runs/shared/child-execution-cwd.ts +139 -68
  755. package/src/runs/shared/child-execution-outcome.ts +103 -0
  756. package/src/runs/shared/child-invocation.ts +74 -0
  757. package/src/runs/shared/child-message-validation.ts +233 -0
  758. package/src/runs/shared/child-native-accounting.ts +154 -0
  759. package/src/runs/shared/child-native-observation.ts +79 -0
  760. package/src/runs/shared/child-observation-spool.ts +188 -0
  761. package/src/runs/shared/child-observations.ts +219 -89
  762. package/src/runs/shared/child-presence.ts +6 -0
  763. package/src/runs/shared/child-route-env.ts +142 -0
  764. package/src/runs/shared/child-stream-observer.ts +302 -0
  765. package/src/runs/shared/child-stream-projection.ts +160 -0
  766. package/src/runs/shared/child-structured-tool.ts +38 -0
  767. package/src/runs/shared/claude-code.ts +201 -228
  768. package/src/runs/shared/claude-model.ts +67 -0
  769. package/src/runs/shared/claude-session.ts +124 -0
  770. package/src/runs/shared/claude-types.ts +31 -0
  771. package/src/runs/shared/completion-guard.ts +35 -26
  772. package/src/runs/shared/control-event-policy.ts +15 -0
  773. package/src/runs/shared/control-notification-owner.ts +20 -0
  774. package/src/runs/shared/dynamic-fanout.ts +402 -238
  775. package/src/runs/shared/dynamic-item.ts +114 -0
  776. package/src/runs/shared/history-index.ts +112 -51
  777. package/src/runs/shared/legacy-async-recovery.ts +153 -0
  778. package/src/runs/shared/legacy-output-recovery.ts +214 -0
  779. package/src/runs/shared/mcp-config-schema.ts +46 -0
  780. package/src/runs/shared/mcp-direct-tool-allowlist.ts +396 -307
  781. package/src/runs/shared/model-fallback.ts +302 -180
  782. package/src/runs/shared/mutating-tool-guard.ts +259 -158
  783. package/src/runs/shared/native-finalization-types.ts +29 -0
  784. package/src/runs/shared/native-finalization.ts +233 -109
  785. package/src/runs/shared/native-usage.ts +379 -96
  786. package/src/runs/shared/nested-control-records.ts +178 -0
  787. package/src/runs/shared/nested-events.ts +45 -752
  788. package/src/runs/shared/nested-path.ts +47 -34
  789. package/src/runs/shared/nested-projection.ts +230 -0
  790. package/src/runs/shared/nested-protocol.ts +67 -0
  791. package/src/runs/shared/nested-registry.ts +273 -0
  792. package/src/runs/shared/nested-render.ts +198 -100
  793. package/src/runs/shared/nested-route.ts +292 -0
  794. package/src/runs/shared/nested-validation.ts +291 -0
  795. package/src/runs/shared/owned-child-evidence.ts +184 -0
  796. package/src/runs/shared/owned-run-list.ts +187 -0
  797. package/src/runs/shared/owned-run-observation.ts +368 -0
  798. package/src/runs/shared/owned-run-progress.ts +172 -0
  799. package/src/runs/shared/owned-run-read-state.ts +7 -0
  800. package/src/runs/shared/owned-run-results.ts +289 -0
  801. package/src/runs/shared/owned-run-status.ts +239 -0
  802. package/src/runs/shared/owned-run-view.ts +65 -0
  803. package/src/runs/shared/owned-workflow-details.ts +24 -0
  804. package/src/runs/shared/owner-output.ts +73 -0
  805. package/src/runs/shared/parallel-utils.ts +93 -129
  806. package/src/runs/shared/parent-receipts.ts +177 -50
  807. package/src/runs/shared/parent-usage.ts +314 -91
  808. package/src/runs/shared/pi-args.ts +241 -311
  809. package/src/runs/shared/pi-launch-input.ts +48 -0
  810. package/src/runs/shared/pi-session-launch.ts +94 -0
  811. package/src/runs/shared/pi-spawn.ts +57 -33
  812. package/src/runs/shared/run-history-queries.ts +189 -38
  813. package/src/runs/shared/run-history.ts +197 -76
  814. package/src/runs/shared/run-id-ambiguity.ts +7 -3
  815. package/src/runs/shared/run-metadata-paths.ts +17 -0
  816. package/src/runs/shared/run-metadata.ts +240 -0
  817. package/src/runs/shared/run-persistence.ts +181 -0
  818. package/src/runs/shared/run-records.ts +19 -607
  819. package/src/runs/shared/run-restoration.ts +455 -0
  820. package/src/runs/shared/run-state-owner.ts +31 -0
  821. package/src/runs/shared/session-cwd-preload.ts +39 -19
  822. package/src/runs/shared/single-output.ts +358 -205
  823. package/src/runs/shared/stable-value.ts +15 -0
  824. package/src/runs/shared/streaming-text.ts +39 -12
  825. package/src/runs/shared/structured-output.ts +76 -46
  826. package/src/runs/shared/subagent-control.ts +330 -197
  827. package/src/runs/shared/subagent-prompt-runtime.ts +92 -174
  828. package/src/runs/shared/subagent-tool-loop-guard.ts +118 -68
  829. package/src/runs/shared/supervisor-question-state.ts +221 -0
  830. package/src/runs/shared/supervisor-questions.ts +396 -401
  831. package/src/runs/shared/workflow-graph.ts +400 -198
  832. package/src/runs/shared/workflow-policy.ts +245 -110
  833. package/src/runs/shared/worktree-contract.ts +29 -0
  834. package/src/runs/shared/worktree-hook.ts +181 -0
  835. package/src/runs/shared/worktree-location.ts +61 -0
  836. package/src/runs/shared/worktree-preservation.ts +10 -0
  837. package/src/runs/shared/worktree-process.ts +132 -0
  838. package/src/runs/shared/worktree.ts +459 -587
  839. package/src/shared/agent-context-policy.ts +176 -145
  840. package/src/shared/agent-dir.ts +15 -0
  841. package/src/shared/artifacts.ts +99 -68
  842. package/src/shared/atomic-json.ts +14 -11
  843. package/src/shared/chain-files.ts +72 -0
  844. package/src/shared/concurrency.ts +27 -0
  845. package/src/shared/content-preview.ts +117 -0
  846. package/src/shared/cooperative.ts +17 -13
  847. package/src/shared/execution-cwd.ts +34 -9
  848. package/src/shared/file-coalescer.ts +24 -22
  849. package/src/shared/fork-context.ts +52 -48
  850. package/src/shared/formatters.ts +88 -47
  851. package/src/shared/journal-frames.ts +197 -0
  852. package/src/shared/journal-projections.ts +133 -0
  853. package/src/shared/journal-reader.ts +18 -333
  854. package/src/shared/json-projection.ts +224 -0
  855. package/src/shared/lazy-tools.ts +76 -27
  856. package/src/shared/message-output.ts +146 -0
  857. package/src/shared/model-info.ts +94 -58
  858. package/src/shared/native-import.ts +25 -0
  859. package/src/shared/native-journal.ts +172 -0
  860. package/src/shared/native-session-loader.ts +13 -0
  861. package/src/shared/native-session.ts +3 -5
  862. package/src/shared/native-tui.ts +13 -3
  863. package/src/shared/native-typebox.ts +18 -3
  864. package/src/shared/output-page.ts +76 -0
  865. package/src/shared/post-exit-stdio-guard.ts +232 -172
  866. package/src/shared/prompt-sections.ts +13 -7
  867. package/src/shared/session-entries.ts +103 -44
  868. package/src/shared/session-identity.ts +17 -10
  869. package/src/shared/settings.ts +182 -367
  870. package/src/shared/status-format.ts +207 -98
  871. package/src/shared/stream-json.d.ts +10 -0
  872. package/src/shared/task-file-policy.ts +35 -0
  873. package/src/shared/temp-root.ts +122 -71
  874. package/src/shared/types/acceptance.ts +149 -0
  875. package/src/shared/types/async.ts +247 -0
  876. package/src/shared/types/config.ts +139 -0
  877. package/src/shared/types/details.ts +104 -0
  878. package/src/shared/types/history.ts +217 -0
  879. package/src/shared/types/inputs.ts +4 -0
  880. package/src/shared/types/intercom.ts +57 -0
  881. package/src/shared/types/launch.ts +39 -0
  882. package/src/shared/types/messages.ts +104 -0
  883. package/src/shared/types/nested.ts +153 -0
  884. package/src/shared/types/owned-runs.ts +148 -0
  885. package/src/shared/types/progress.ts +106 -0
  886. package/src/shared/types/questions.ts +103 -0
  887. package/src/shared/types/results.ts +119 -0
  888. package/src/shared/types/state.ts +56 -0
  889. package/src/shared/types/usage.ts +36 -0
  890. package/src/shared/types/workflow.ts +187 -0
  891. package/src/shared/types.ts +222 -1161
  892. package/src/shared/ui-mode.ts +1 -1
  893. package/src/shared/unknown.ts +59 -0
  894. package/src/shared/utils.ts +111 -305
  895. package/src/slash/command-discovery.ts +109 -0
  896. package/src/slash/command-input.ts +232 -0
  897. package/src/slash/prompt-template-bridge.ts +153 -400
  898. package/src/slash/prompt-template-contract.ts +152 -0
  899. package/src/slash/prompt-template-results.ts +189 -0
  900. package/src/slash/request-protocol.ts +87 -0
  901. package/src/slash/result-delivery.ts +312 -0
  902. package/src/slash/saved-chain.ts +115 -0
  903. package/src/slash/slash-bridge.ts +202 -156
  904. package/src/slash/slash-commands.ts +176 -598
  905. package/src/slash/slash-live-state.ts +287 -229
  906. package/src/tui/action-hints.ts +147 -61
  907. package/src/tui/activity-lines.ts +40 -0
  908. package/src/tui/agent-browser.ts +354 -0
  909. package/src/tui/agent-controls.ts +355 -0
  910. package/src/tui/agent-conversation.ts +514 -0
  911. package/src/tui/agent-dock.ts +250 -0
  912. package/src/tui/agent-history.ts +78 -203
  913. package/src/tui/agent-model.ts +78 -0
  914. package/src/tui/agent-picker.ts +325 -0
  915. package/src/tui/agent-view.ts +391 -1333
  916. package/src/tui/compact-results.ts +342 -0
  917. package/src/tui/conversation-details.ts +206 -0
  918. package/src/tui/conversation-frame.ts +184 -0
  919. package/src/tui/conversation-history.ts +246 -0
  920. package/src/tui/conversation-menu.ts +119 -0
  921. package/src/tui/conversation-notices.ts +116 -0
  922. package/src/tui/conversation-selection.ts +66 -0
  923. package/src/tui/conversation-viewport.ts +169 -0
  924. package/src/tui/direction-message.ts +35 -0
  925. package/src/tui/display.ts +409 -0
  926. package/src/tui/editor-viewport.ts +19 -0
  927. package/src/tui/expanded-content.ts +134 -0
  928. package/src/tui/expanded-multi.ts +264 -0
  929. package/src/tui/expanded-single.ts +92 -0
  930. package/src/tui/history-cards.ts +316 -0
  931. package/src/tui/history-display.ts +334 -0
  932. package/src/tui/history-json.ts +17 -0
  933. package/src/tui/history-metadata.ts +177 -0
  934. package/src/tui/history-record.ts +321 -0
  935. package/src/tui/history-text.ts +34 -0
  936. package/src/tui/indexed-history.ts +149 -0
  937. package/src/tui/nested-widget.ts +192 -0
  938. package/src/tui/picker-page.ts +80 -0
  939. package/src/tui/render.ts +100 -1546
  940. package/src/tui/result-facts.ts +99 -0
  941. package/src/tui/task-observation.ts +96 -0
  942. package/src/tui/task-store.ts +432 -0
  943. package/src/tui/text-values.ts +20 -0
  944. package/src/tui/view-connections.ts +64 -0
  945. package/src/tui/view-model.ts +181 -0
  946. package/src/tui/view-persistence.ts +57 -0
  947. package/src/tui/view-ports.ts +55 -0
  948. package/src/tui/view-session.ts +42 -0
  949. package/src/tui/widget-details.ts +220 -0
  950. package/src/tui/widget-status.ts +331 -0
  951. package/src/tui/widget.ts +208 -0
  952. package/src/tui/workflow-labels.ts +340 -0
  953. package/src/tui/workflow-outcomes.ts +150 -0
  954. package/tsconfig.build.json +11 -10
  955. package/tsconfig.json +30 -17
package/src/tui/render.ts CHANGED
@@ -1,1550 +1,104 @@
1
- /**
2
- * Rendering functions for subagent results
3
- */
4
-
5
- import * as path from "node:path";
6
- import { getMarkdownTheme, keyText, type ExtensionContext } from "@earendil-works/pi-coding-agent";
7
- import { Container, Markdown, Spacer, Text, TruncatedText, visibleWidth, type Component } from "@earendil-works/pi-tui";
8
- import {
9
- type AgentProgress,
10
- type AsyncJobState,
11
- type AsyncJobStep,
12
- type AsyncParallelGroupStatus,
13
- type Details,
14
- type SubagentExecutionResult,
15
- type NestedRunSummary,
16
- type NestedStepSummary,
17
- type WorkflowNodeStatus,
18
- MAX_WIDGET_JOBS,
19
- WIDGET_KEY,
20
- } from "../shared/types.ts";
21
- import { formatTokens, formatUsage, formatDuration, formatModelThinking, formatToolCall, shortenPath } from "../shared/formatters.ts";
22
- import { getDisplayItems, getSingleResultOutput } from "../shared/utils.ts";
23
- import { flatToLogicalStepIndex } from "../runs/background/parallel-groups.ts";
24
- import { formatNestedAggregate } from "../runs/shared/nested-render.ts";
25
- import { aggregateStepStatus, formatActivityLabel, formatAgentRunningLabel, formatParallelOutcome } from "../shared/status-format.ts";
26
- import { acceptanceHumanAction } from "../runs/shared/acceptance.ts";
27
- import { formatAgentProcessExit } from "../shared/status-format.ts";
28
- import { isTuiContext } from "../shared/ui-mode.ts";
29
-
30
- type Theme = ExtensionContext["ui"]["theme"];
31
-
32
- function getTermWidth(): number {
33
- return process.stdout.columns || 120;
34
- }
35
-
36
- const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
37
-
38
- /**
39
- * Truncate a line to maxWidth, preserving ANSI styling through the ellipsis.
40
- *
41
- * pi-tui's truncateToWidth adds \x1b[0m before ellipsis which resets all styling,
42
- * causing background color bleed in the TUI. This implementation tracks active
43
- * ANSI styles and re-applies them before the ellipsis.
44
- *
45
- * Uses Intl.Segmenter for proper Unicode/emoji handling (not char-by-char).
46
- */
47
- function truncLine(text: string, maxWidth: number): string {
48
- if (visibleWidth(text) <= maxWidth) return text;
49
-
50
- const targetWidth = maxWidth - 1;
51
- let result = "";
52
- let currentWidth = 0;
53
- let activeStyles: string[] = [];
54
- let i = 0;
55
-
56
- while (i < text.length) {
57
- const ansiMatch = text.slice(i).match(/^\x1b\[[0-9;]*m/);
58
- if (ansiMatch) {
59
- const code = ansiMatch[0];
60
- result += code;
61
-
62
- if (code === "\x1b[0m" || code === "\x1b[m") {
63
- activeStyles = [];
64
- } else {
65
- activeStyles.push(code);
66
- }
67
- i += code.length;
68
- continue;
69
- }
70
-
71
- let end = i;
72
- while (end < text.length && !text.slice(end).match(/^\x1b\[[0-9;]*m/)) {
73
- end++;
74
- }
75
-
76
- const textPortion = text.slice(i, end);
77
- for (const seg of segmenter.segment(textPortion)) {
78
- const grapheme = seg.segment;
79
- const graphemeWidth = visibleWidth(grapheme);
80
-
81
- if (currentWidth + graphemeWidth > targetWidth) {
82
- return result + activeStyles.join("") + "…";
83
- }
84
-
85
- result += grapheme;
86
- currentWidth += graphemeWidth;
87
- }
88
- i = end;
89
- }
90
-
91
- return result + activeStyles.join("") + "…";
92
- }
93
-
94
- const RUNNING_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
95
- const STATIC_RUNNING_GLYPH = "●";
96
-
97
- type ProgressSeedSource = Partial<Pick<AgentProgress, "index" | "toolCount" | "tokens" | "durationMs" | "lastActivityAt" | "currentToolStartedAt" | "turnCount">>;
98
-
99
- function runningSeed(...values: Array<number | undefined>): number | undefined {
100
- let seed: number | undefined;
101
- for (const value of values) {
102
- if (value === undefined || !Number.isFinite(value)) continue;
103
- seed = (seed ?? 0) + Math.trunc(value);
104
- }
105
- return seed;
106
- }
107
-
108
- function runningGlyph(seed?: number): string {
109
- if (seed === undefined) return STATIC_RUNNING_GLYPH;
110
- return RUNNING_FRAMES[Math.abs(seed) % RUNNING_FRAMES.length]!;
111
- }
112
-
113
- function progressRunningSeed(progress: ProgressSeedSource | undefined): number | undefined {
114
- if (!progress) return undefined;
115
- return runningSeed(
116
- progress.index,
117
- progress.toolCount,
118
- progress.tokens,
119
- progress.durationMs,
120
- progress.lastActivityAt,
121
- progress.currentToolStartedAt,
122
- progress.turnCount,
123
- );
124
- }
125
-
126
- function extractOutputTarget(task: string): string | undefined {
127
- const writeToMatch = task.match(/\[Write to:\s*([^\]\n]+)\]/i);
128
- if (writeToMatch?.[1]?.trim()) return writeToMatch[1].trim();
129
- const findingsMatch = task.match(/Write your findings to:\s*(\S+)/i);
130
- if (findingsMatch?.[1]?.trim()) return findingsMatch[1].trim();
131
- const outputMatch = task.match(/[Oo]utput(?:\s+to)?\s*:\s*(\S+)/i);
132
- if (outputMatch?.[1]?.trim()) return outputMatch[1].trim();
133
- return undefined;
134
- }
135
-
136
- function hasEmptyTextOutputWithoutOutputTarget(task: string, output: string): boolean {
137
- if (output.trim()) return false;
138
- return !extractOutputTarget(task);
139
- }
140
-
141
- function getToolCallLines(
142
- result: Pick<Details["results"][number], "messages" | "toolCalls">,
143
- expanded: boolean,
144
- ): string[] {
145
- if (result.messages) {
146
- return getDisplayItems(result.messages)
147
- .filter((item): item is { type: "tool"; name: string; args: Record<string, unknown> } => item.type === "tool")
148
- .map((item) => formatToolCall(item.name, item.args, expanded));
149
- }
150
- return result.toolCalls?.map((toolCall) => expanded ? toolCall.expandedText : toolCall.text) ?? [];
151
- }
152
-
153
-
154
- function snapshotNowForProgress(progress: Pick<AgentProgress, "currentToolStartedAt" | "durationMs" | "lastActivityAt">): number | undefined {
155
- if (progress.currentToolStartedAt !== undefined && progress.durationMs !== undefined) return progress.currentToolStartedAt + progress.durationMs;
156
- return progress.lastActivityAt;
157
- }
158
-
159
- function formatCurrentToolLine(
160
- progress: Pick<AgentProgress, "currentTool" | "currentToolArgs" | "currentToolStartedAt">,
161
- availableWidth: number,
162
- expanded: boolean,
163
- snapshotNow?: number,
164
- ): string | undefined {
165
- if (!progress.currentTool) return undefined;
166
- const maxToolArgsLen = Math.max(50, availableWidth - 20);
167
- const toolArgsPreview = progress.currentToolArgs
168
- ? (expanded || progress.currentToolArgs.length <= maxToolArgsLen
169
- ? progress.currentToolArgs
170
- : `${progress.currentToolArgs.slice(0, maxToolArgsLen)}...`)
171
- : "";
172
- const durationSuffix = progress.currentToolStartedAt !== undefined && snapshotNow !== undefined
173
- ? ` | ${formatDuration(Math.max(0, snapshotNow - progress.currentToolStartedAt))}`
174
- : "";
175
- return toolArgsPreview
176
- ? `${progress.currentTool}: ${toolArgsPreview}${durationSuffix}`
177
- : `${progress.currentTool}${durationSuffix}`;
178
- }
179
-
180
- function buildLiveStatusLine(progress: Pick<AgentProgress, "activityState" | "lastActivityAt">, snapshotNow?: number): string | undefined {
181
- if (progress.lastActivityAt !== undefined && snapshotNow !== undefined) return formatActivityLabel(progress.lastActivityAt, progress.activityState, snapshotNow);
182
- if (progress.activityState === "needs_attention") return "needs attention";
183
- if (progress.lastActivityAt !== undefined) return "active";
184
- return undefined;
185
- }
186
-
187
- function themeBold(theme: Theme, text: string): string {
188
- return ((theme as { bold?: (value: string) => string }).bold?.(text)) ?? text;
189
- }
190
-
191
- function statJoin(theme: Theme, parts: string[]): string {
192
- return parts.filter(Boolean).map((part) => theme.fg("dim", part)).join(` ${theme.fg("dim", "·")} `);
193
- }
194
-
195
- function formatTokenStat(tokens: number): string {
196
- return `${formatTokens(tokens)} token`;
197
- }
198
-
199
- function formatToolUseStat(count: number): string {
200
- return `${count} tool use${count === 1 ? "" : "s"}`;
201
- }
202
-
203
- function formatProgressStats(theme: Theme, progress: Pick<AgentProgress, "toolCount" | "tokens" | "durationMs"> | undefined, includeDuration = true): string {
204
- if (!progress) return "";
205
- const parts: string[] = [];
206
- if (progress.toolCount > 0) parts.push(formatToolUseStat(progress.toolCount));
207
- if (progress.tokens > 0) parts.push(formatTokenStat(progress.tokens));
208
- if (includeDuration && progress.durationMs > 0) parts.push(formatDuration(progress.durationMs));
209
- return statJoin(theme, parts);
210
- }
211
-
212
- function firstOutputLine(text: string): string {
213
- return text.split("\n").find((line) => line.trim())?.replace(/[\u0000-\u0008\u000b-\u001f\u007f]/g, "").trim() ?? "";
214
- }
215
-
216
- function formatAcceptanceStatus(result: Details["results"][number]): string | undefined {
217
- const acceptance = result.acceptance;
218
- if (!acceptance?.status || acceptance.status === "not-required") return undefined;
219
- const finalization = acceptance.finalization
220
- ? ` · finalization: ${acceptance.finalization.status} after ${acceptance.finalization.turns.length}/${acceptance.finalization.maxTurns} turns`
221
- : "";
222
- return `acceptance: ${acceptance.status}${finalization}`;
223
- }
224
-
225
- function resultStatusLine(result: Details["results"][number], output: string): string {
226
- if (result.detached) return result.detachedReason ? `Detached: ${result.detachedReason}` : "Detached";
227
- if (result.timedOut) return `Timed out${result.error ? `: ${result.error}` : ""}`;
228
- if (result.interrupted) return "Paused";
229
- if (result.exitCode !== 0) return `Error: ${result.error ?? (firstOutputLine(output) || `exit ${result.exitCode}`)}`;
230
- if (result.acceptance?.status === "blocked") return `Needs your action · acceptance incomplete · ${acceptanceHumanAction(result.acceptance)?.split("\n")[0]}`;
231
- const acceptance = formatAcceptanceStatus(result);
232
- if (acceptance) return `Done · ${acceptance}`;
233
- if (hasEmptyTextOutputWithoutOutputTarget(result.task, output)) return "Done (no text output)";
234
- return "Done";
235
- }
236
-
237
- function resultGlyph(result: Details["results"][number], output: string, theme: Theme, running = result.progress?.status === "running", seed = progressRunningSeed(result.progress ?? result.progressSummary)): string {
238
- if (running) return theme.fg("accent", runningGlyph(seed));
239
- if (result.detached) return theme.fg("warning", "■");
240
- if (result.timedOut) return theme.fg("error", "✗");
241
- if (result.interrupted) return theme.fg("warning", "■");
242
- if (result.exitCode !== 0) return theme.fg("error", "✗");
243
- if (result.acceptance?.status === "blocked") return theme.fg("warning", "■");
244
- if (hasEmptyTextOutputWithoutOutputTarget(result.task, output)) return theme.fg("warning", "✓");
245
- return theme.fg("success", "✓");
246
- }
247
-
248
- function compactCurrentActivity(progress: Pick<AgentProgress, "currentTool" | "currentToolArgs" | "currentToolStartedAt" | "activityState" | "lastActivityAt" | "durationMs">): string {
249
- const snapshotNow = snapshotNowForProgress(progress);
250
- return formatCurrentToolLine(progress, getTermWidth() - 4, false, snapshotNow) ?? buildLiveStatusLine(progress, snapshotNow) ?? "thinking…";
251
- }
252
-
253
- export function widgetRenderKey(job: AsyncJobState): string {
254
- return JSON.stringify({
255
- asyncDir: job.asyncDir,
256
- status: job.status,
257
- activityState: job.activityState,
258
- lastActivityAt: job.lastActivityAt,
259
- currentTool: job.currentTool,
260
- currentToolStartedAt: job.currentToolStartedAt,
261
- currentPath: job.currentPath,
262
- turnCount: job.turnCount,
263
- toolCount: job.toolCount,
264
- mode: job.mode,
265
- agents: job.agents,
266
- currentStep: job.currentStep,
267
- chainStepCount: job.chainStepCount,
268
- parallelGroups: job.parallelGroups,
269
- steps: job.steps,
270
- nestedChildren: job.nestedChildren,
271
- stepsTotal: job.stepsTotal,
272
- runningSteps: job.runningSteps,
273
- completedSteps: job.completedSteps,
274
- activeParallelGroup: job.activeParallelGroup,
275
- startedAt: job.startedAt,
276
- updatedAt: job.updatedAt,
277
- totalTokens: job.totalTokens,
278
- });
279
- }
280
-
281
- function formatWidgetAgents(agents: string[]): string {
282
- const distinct = [...new Set(agents)];
283
- if (distinct.length === 1 && agents.length > 1) return `${distinct[0]} ×${agents.length}`;
284
- if (agents.length > 3) return `${agents.slice(0, 2).join(", ")} +${agents.length - 2} more`;
285
- return agents.join(", ");
286
- }
287
-
288
- function widgetJobName(job: AsyncJobState): string {
289
- if (job.mode === "parallel") return "parallel";
290
- if (job.mode === "chain") return "chain";
291
- if (job.mode === "single" && job.agents?.length === 1) return job.agents[0]!;
292
- if (job.agents?.length) return formatWidgetAgents(job.agents);
293
- return job.mode ?? "subagent";
294
- }
295
-
296
- function widgetActivity(job: AsyncJobState, includeCounts = true): string {
297
- const facts: string[] = [];
298
- if (job.currentTool && job.currentToolStartedAt !== undefined && job.updatedAt !== undefined) facts.push(`${job.currentTool} ${formatDuration(Math.max(0, job.updatedAt - job.currentToolStartedAt))}`);
299
- else if (job.currentTool) facts.push(job.currentTool);
300
- if (job.currentPath) facts.push(shortenPath(job.currentPath));
301
- if (includeCounts && job.turnCount !== undefined) facts.push(`${job.turnCount} turns`);
302
- if (includeCounts && job.toolCount !== undefined) facts.push(`${job.toolCount} tools`);
303
- if (job.status !== "running") {
304
- const status = job.status === "queued" ? "queued…" : job.status === "paused" ? "Paused" : job.status === "failed" ? "Failed" : job.status === "blocked" ? "Needs your action" : "Done";
305
- const error = job.status === "failed" ? job.steps?.find((step) => step.error)?.error : undefined;
306
- return [status, error ? firstOutputLine(error) : "", ...facts].filter(Boolean).join(" · ");
307
- }
308
- const activity = buildLiveStatusLine(job, job.updatedAt);
309
- if (activity && facts.length) return `${activity} · ${facts.join(" · ")}`;
310
- if (activity) return activity;
311
- if (facts.length) return facts.join(" · ");
312
- return "thinking…";
313
- }
314
-
315
- function widgetStepRunningSeed(step: NonNullable<AsyncJobState["steps"]>[number], fallbackIndex?: number): number | undefined {
316
- return runningSeed(
317
- fallbackIndex,
318
- step.index,
319
- step.toolCount,
320
- step.turnCount,
321
- step.tokens?.total,
322
- step.lastActivityAt,
323
- step.currentToolStartedAt,
324
- step.durationMs,
325
- );
326
- }
327
-
328
- function widgetStepsRunningSeed(steps: Array<NonNullable<AsyncJobState["steps"]>[number]> | undefined): number | undefined {
329
- let seed: number | undefined;
330
- for (const [index, step] of (steps ?? []).entries()) seed = runningSeed(seed, widgetStepRunningSeed(step, index));
331
- return seed;
332
- }
333
-
334
- function widgetJobRunningSeed(job: AsyncJobState): number | undefined {
335
- return runningSeed(
336
- job.updatedAt,
337
- job.lastActivityAt,
338
- job.toolCount,
339
- job.turnCount,
340
- job.totalTokens?.total,
341
- job.currentStep,
342
- job.runningSteps,
343
- job.completedSteps,
344
- widgetStepsRunningSeed(job.steps),
345
- );
346
- }
347
-
348
- function widgetJobsRunningSeed(jobs: AsyncJobState[]): number | undefined {
349
- let seed: number | undefined;
350
- for (const job of jobs) seed = runningSeed(seed, widgetJobRunningSeed(job));
351
- return seed;
352
- }
353
-
354
- function widgetStatusGlyph(job: AsyncJobState, theme: Theme): string {
355
- if (job.status === "running") return theme.fg("accent", runningGlyph(widgetJobRunningSeed(job)));
356
- if (job.status === "queued") return theme.fg("muted", "◦");
357
- if (job.status === "complete") return theme.fg("success", "✓");
358
- if (job.status === "blocked") return theme.fg("warning", "■");
359
- if (job.status === "paused") return theme.fg("warning", "■");
360
- return theme.fg("error", "✗");
361
- }
362
-
363
- function widgetStepGlyph(status: AsyncJobStep["status"] | WorkflowNodeStatus, theme: Theme, seed?: number): string {
364
- if (status === "running") return theme.fg("accent", runningGlyph(seed));
365
- if (status === "complete" || status === "completed") return theme.fg("success", "✓");
366
- if (status === "failed" || status === "timed-out") return theme.fg("error", "✗");
367
- if (status === "blocked") return theme.fg("warning", "■");
368
- if (status === "paused") return theme.fg("warning", "■");
369
- return theme.fg("muted", "◦");
370
- }
371
-
372
- function widgetStepStatus(status: AsyncJobStep["status"] | WorkflowNodeStatus, theme: Theme): string {
373
- if (status === "running") return theme.fg("accent", "running");
374
- if (status === "complete" || status === "completed") return theme.fg("success", "complete");
375
- if (status === "failed") return theme.fg("error", "failed");
376
- if (status === "timed-out") return theme.fg("error", "timed out");
377
- if (status === "blocked") return theme.fg("warning", "needs your action");
378
- if (status === "paused") return theme.fg("warning", "paused");
379
- return theme.fg("dim", status);
380
- }
381
-
382
- function widgetStepActivity(step: NonNullable<AsyncJobState["steps"]>[number], snapshotNow?: number): string {
383
- if ((step.status === "failed" || step.status === "timed-out") && step.error) return firstOutputLine(step.error);
384
- const facts: string[] = [];
385
- if (step.currentTool && step.currentToolStartedAt !== undefined && snapshotNow !== undefined) facts.push(`${step.currentTool} ${formatDuration(Math.max(0, snapshotNow - step.currentToolStartedAt))}`);
386
- else if (step.currentTool) facts.push(step.currentTool);
387
- if (step.currentPath) facts.push(shortenPath(step.currentPath));
388
- if (step.turnCount !== undefined) facts.push(`${step.turnCount} turns`);
389
- if (step.toolCount !== undefined) facts.push(`${step.toolCount} tools`);
390
- if (step.tokens?.total) facts.push(formatTokenStat(step.tokens.total));
391
- const activity = buildLiveStatusLine(step, snapshotNow);
392
- if (activity && facts.length) return `${activity} · ${facts.join(" · ")}`;
393
- if (activity) return activity;
394
- return facts.join(" · ");
395
- }
396
-
397
-
398
- function widgetChainDetails(job: AsyncJobState, theme: Theme, width = getTermWidth()): string[] {
399
- if (!job.steps?.length) return [];
400
- const total = job.chainStepCount ?? job.steps.length;
401
- const lines: string[] = [];
402
- for (const span of buildAsyncChainStepSpans(total, job.steps.length, job.parallelGroups)) {
403
- const steps = job.steps.slice(span.start, span.start + span.count);
404
- if (span.isParallel) {
405
- const status = aggregateStepStatus(steps);
406
- lines.push(` ${widgetStepGlyph(status, theme, widgetStepsRunningSeed(steps))} Step ${span.stepIndex + 1}/${total}: ${themeBold(theme, "parallel group")} ${theme.fg("dim", "·")} ${theme.fg("dim", formatParallelOutcome(steps, span.count))}`);
407
- continue;
408
- }
409
- const step = steps[0];
410
- if (!step) {
411
- lines.push(` ${theme.fg("dim", `◦ Step ${span.stepIndex + 1}/${total}: pending`)}`);
412
- continue;
413
- }
414
- lines.push(...foregroundStyleWidgetStepLines(job, theme, step, "Step", span.stepIndex + 1, total, width));
415
- }
416
- return lines;
417
- }
418
-
419
- function widgetParallelAgentDetails(job: AsyncJobState, theme: Theme, width = getTermWidth()): string[] {
420
- if (!job.steps?.length) return [];
421
- if (job.mode !== "parallel" && job.mode !== "chain") return [];
422
- if (job.mode === "chain" && !job.activeParallelGroup && job.parallelGroups?.length) return widgetChainDetails(job, theme, width);
423
- const total = job.stepsTotal ?? job.steps.length;
424
- const lines: string[] = [];
425
- for (const [index, step] of job.steps.entries()) {
426
- const marker = index === job.steps.length - 1 ? "└" : "├";
427
- const activity = widgetStepActivity(step, job.updatedAt);
428
- const itemTitle = job.mode === "parallel" || job.activeParallelGroup ? "Agent" : "Step";
429
- const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);
430
- lines.push(` ${theme.fg("dim", `${marker} ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index))} ${itemTitle} ${index + 1}/${total}: ${step.agent} · ${widgetStepStatus(step.status, theme)}${modelDisplay}${activity ? ` · ${activity}` : ""}`)}`);
431
- for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, job.updatedAt, 8)) lines.push(` ${nestedLine}`);
432
- }
433
- return lines;
434
- }
435
-
436
- function parseParallelGroupAgentCount(label: string | undefined): number | undefined {
437
- if (!label || !label.startsWith("[") || !label.endsWith("]")) return undefined;
438
- const inner = label.slice(1, -1).trim();
439
- if (!inner) return 0;
440
- return inner.split("+").map((part) => part.trim()).filter(Boolean).length;
441
- }
442
-
443
- interface ChainStepSpan {
444
- stepIndex: number;
445
- start: number;
446
- count: number;
447
- isParallel: boolean;
448
- status?: WorkflowNodeStatus;
449
- label?: string;
450
- error?: string;
451
- }
452
-
453
- function buildChainStepSpans(details: Pick<Details, "chainAgents" | "workflowGraph">): ChainStepSpan[] {
454
- if (details.workflowGraph?.nodes?.length) {
455
- const spans: ChainStepSpan[] = [];
456
- let flatCursor = 0;
457
- for (const node of details.workflowGraph.nodes) {
458
- if (node.stepIndex === undefined) continue;
459
- if (node.kind === "parallel-group" || node.kind === "dynamic-parallel-group") {
460
- const childFlatIndexes = (node.children ?? [])
461
- .map((child) => child.flatIndex)
462
- .filter((value): value is number => typeof value === "number");
463
- const start = childFlatIndexes.length ? Math.min(...childFlatIndexes) : flatCursor;
464
- const count = node.children?.length ?? 0;
465
- spans.push({ stepIndex: node.stepIndex, start, count, isParallel: true, status: node.status, label: node.label, error: node.error });
466
- flatCursor = Math.max(flatCursor, start + count);
467
- continue;
468
- }
469
- const start = node.flatIndex ?? flatCursor;
470
- spans.push({ stepIndex: node.stepIndex, start, count: 1, isParallel: false, status: node.status, label: node.label, error: node.error });
471
- flatCursor = Math.max(flatCursor, start + 1);
472
- }
473
- if (spans.length) return spans.sort((left, right) => left.stepIndex - right.stepIndex);
474
- }
475
-
476
- if (!details.chainAgents?.length) return [];
477
- const spans: ChainStepSpan[] = [];
478
- let start = 0;
479
- for (let stepIndex = 0; stepIndex < details.chainAgents.length; stepIndex++) {
480
- const label = details.chainAgents[stepIndex]!;
481
- const parsedCount = parseParallelGroupAgentCount(label);
482
- const count = parsedCount ?? 1;
483
- spans.push({ stepIndex, start, count, isParallel: parsedCount !== undefined });
484
- start += count;
485
- }
486
- return spans;
487
- }
488
-
489
- function isChainParallelGroupActive(details: Pick<Details, "mode" | "chainAgents" | "currentStepIndex" | "workflowGraph">): boolean {
490
- if (details.mode !== "chain") return false;
491
- if (details.currentStepIndex === undefined) return false;
492
- return buildChainStepSpans(details).some((span) => span.stepIndex === details.currentStepIndex && span.isParallel);
493
- }
494
-
495
- function buildAsyncChainStepSpans(total: number, stepCount: number, parallelGroups: AsyncParallelGroupStatus[] = []): ChainStepSpan[] {
496
- const spans: ChainStepSpan[] = [];
497
- let flatIndex = 0;
498
- for (let stepIndex = 0; stepIndex < total; stepIndex++) {
499
- const group = parallelGroups.find((candidate) => candidate.stepIndex === stepIndex);
500
- if (group) {
501
- spans.push({ stepIndex, start: group.start, count: group.count, isParallel: true });
502
- flatIndex = Math.max(flatIndex, group.start + group.count);
503
- continue;
504
- }
505
- spans.push({ stepIndex, start: flatIndex, count: flatIndex < stepCount ? 1 : 0, isParallel: false });
506
- flatIndex++;
507
- }
508
- return spans;
509
- }
510
-
511
- function isDoneResult(result: Details["results"][number]): boolean {
512
- if (result.interrupted || result.detached || result.timedOut || result.acceptance?.status === "blocked") return false;
513
- const status = result.progress?.status;
514
- if (status === "completed") return true;
515
- if (status === "running" || status === "pending") return false;
516
- return result.exitCode === 0;
517
- }
518
-
519
- function workflowGraphHasStatus(details: Pick<Details, "workflowGraph">, statuses: WorkflowNodeStatus[]): boolean {
520
- return details.workflowGraph?.nodes.some((node) => statuses.includes(node.status)) ?? false;
521
- }
522
-
523
- interface ChainRenderResultEntry {
524
- kind: "result";
525
- resultIndex: number;
526
- rowNumber: number;
527
- agentName: string;
528
- }
529
-
530
- interface ChainRenderPlaceholderEntry {
531
- kind: "placeholder";
532
- rowNumber: number;
533
- stepLabel: string;
534
- agentName: string;
535
- status: WorkflowNodeStatus;
536
- error?: string;
537
- }
538
-
539
- type ChainRenderEntry = ChainRenderResultEntry | ChainRenderPlaceholderEntry;
540
-
541
- function buildChainRenderEntries(details: Details, label: MultiProgressLabel): ChainRenderEntry[] | undefined {
542
- if (details.mode !== "chain" || (!label.hasParallelInChain && !details.workflowGraph?.nodes.length) || label.showActiveGroupOnly) return undefined;
543
- const entries: ChainRenderEntry[] = [];
544
- for (const span of buildChainStepSpans(details)) {
545
- if (span.isParallel ? span.count === 0 : !details.results[span.start]) {
546
- entries.push({
547
- kind: "placeholder",
548
- rowNumber: span.stepIndex + 1,
549
- stepLabel: `Step ${span.stepIndex + 1}`,
550
- agentName: (!span.isParallel ? details.chainAgents?.[span.stepIndex] : undefined) ?? span.label ?? details.chainAgents?.[span.stepIndex] ?? `step-${span.stepIndex + 1}`,
551
- status: span.status ?? "pending",
552
- error: span.error,
553
- });
554
- continue;
555
- }
556
- for (let index = span.start; index < span.start + span.count; index++) {
557
- entries.push({
558
- kind: "result",
559
- resultIndex: index,
560
- rowNumber: index + 1,
561
- agentName: details.results[index]?.agent ?? details.chainAgents?.[span.stepIndex] ?? `step-${span.stepIndex + 1}`,
562
- });
563
- }
564
- }
565
- return entries;
566
- }
567
-
568
- interface MultiProgressLabel {
569
- headerLabel: string;
570
- itemTitle: "Step" | "Agent";
571
- totalCount: number;
572
- hasParallelInChain: boolean;
573
- activeParallelGroup: boolean;
574
- groupStartIndex: number;
575
- groupEndIndex: number;
576
- showActiveGroupOnly: boolean;
577
- }
578
-
579
- function buildMultiProgressLabel(details: Pick<Details, "mode" | "results" | "progress" | "totalSteps" | "currentStepIndex" | "chainAgents" | "workflowGraph">, hasRunning: boolean): MultiProgressLabel {
580
- const stepSpans = buildChainStepSpans(details);
581
- const hasParallelInChain = details.mode === "chain" && stepSpans.some((span) => span.isParallel);
582
- const activeParallelGroup = isChainParallelGroupActive(details);
583
- const itemTitle: "Step" | "Agent" = details.mode === "parallel" || activeParallelGroup ? "Agent" : "Step";
584
-
585
- if (details.mode === "parallel") {
586
- const totalCount = details.totalSteps ?? details.results.length;
587
- const statuses = new Array(totalCount).fill("pending") as WorkflowNodeStatus[];
588
- for (const progress of details.progress ?? []) {
589
- if (progress.index >= 0 && progress.index < totalCount) statuses[progress.index] = progress.status;
590
- }
591
- for (let i = 0; i < details.results.length; i++) {
592
- const result = details.results[i]!;
593
- const progressFromArray = details.progress?.find((progress) => progress.index === i)
594
- || details.progress?.find((progress) => progress.agent === result.agent && progress.status === "running");
595
- const index = result.progress?.index ?? progressFromArray?.index ?? i;
596
- if (index < 0 || index >= totalCount) continue;
597
- const status = result.progress?.status
598
- ?? (result.timedOut
599
- ? "timed-out"
600
- : result.interrupted || result.detached
601
- ? "detached"
602
- : result.exitCode === 0
603
- ? result.acceptance?.status === "blocked" ? "blocked" : "completed"
604
- : "failed");
605
- statuses[index] = status;
606
- }
607
- const running = statuses.filter((status) => status === "running").length;
608
- const succeeded = statuses.filter((status) => status === "completed").length;
609
- const failed = statuses.filter((status) => status === "failed" || status === "timed-out").length;
610
- const paused = statuses.filter((status) => status === "paused" || status === "detached").length;
611
- const blockedCount = statuses.filter((status) => status === "blocked").length;
612
- const parts = [`${succeeded}/${totalCount} succeeded`];
613
- if (blockedCount) parts.push(`${blockedCount} need human action`);
614
- if (hasRunning) parts.unshift(formatAgentRunningLabel(running));
615
- if (failed > 0) parts.push(`${failed} failed`);
616
- if (paused > 0) parts.push(`${paused} paused`);
617
- const headerLabel = parts.join(" · ");
618
- return { headerLabel, itemTitle, totalCount, hasParallelInChain, activeParallelGroup, groupStartIndex: 0, groupEndIndex: totalCount, showActiveGroupOnly: false };
619
- }
620
-
621
- if (activeParallelGroup) {
622
- const currentStepIndex = details.currentStepIndex!;
623
- const span = stepSpans[currentStepIndex];
624
- const groupSize = span?.count ?? 1;
625
- const groupStart = span?.start ?? 0;
626
- const groupEnd = groupStart + groupSize;
627
- let running = 0;
628
- let succeeded = 0;
629
- let failed = 0;
630
- let paused = 0;
631
- let blocked = 0;
632
- for (let index = groupStart; index < groupEnd; index++) {
633
- const progressEntry = details.progress?.find((progress) => progress.index === index);
634
- const resultEntry = details.results.find((result, position) => (result.progress?.index ?? position) === index);
635
- const status = progressEntry?.status ?? resultEntry?.progress?.status;
636
- if (status === "running") running++;
637
- else if (status === "failed" || status === "timed-out" || resultEntry?.timedOut) failed++;
638
- else if (status === "paused" || status === "detached" || resultEntry?.interrupted || resultEntry?.detached) paused++;
639
- else if (resultEntry && resultEntry.exitCode !== 0) failed++;
640
- else if (status === "blocked" || resultEntry?.acceptance?.status === "blocked") blocked++;
641
- else if (status === "completed" || (resultEntry && isDoneResult(resultEntry))) succeeded++;
642
- }
643
- const totalSteps = details.totalSteps ?? details.chainAgents?.length ?? 1;
644
- const groupParts = [`${succeeded}/${groupSize} succeeded`];
645
- if (hasRunning) groupParts.unshift(formatAgentRunningLabel(running));
646
- if (failed > 0) groupParts.push(`${failed} failed`);
647
- if (paused > 0) groupParts.push(`${paused} paused`);
648
- if (blocked > 0) groupParts.push(`${blocked} need human action`);
649
- const headerLabel = `step ${currentStepIndex + 1}/${totalSteps} · parallel group: ${groupParts.join(" · ")}`;
650
- return { headerLabel, itemTitle, totalCount: groupSize, hasParallelInChain, activeParallelGroup, groupStartIndex: groupStart, groupEndIndex: groupEnd, showActiveGroupOnly: true };
651
- }
652
-
653
- if (details.mode === "chain" && details.chainAgents?.length) {
654
- const totalCount = details.totalSteps ?? details.chainAgents.length;
655
- const doneLogical = stepSpans.filter((span) => {
656
- if (span.status && span.status !== "completed") return false;
657
- if (span.count === 0) return span.status === "completed";
658
- for (let index = span.start; index < span.start + span.count; index++) {
659
- const progressEntry = details.progress?.find((progress) => progress.index === index);
660
- const resultEntry = details.results.find((result) => result.progress?.index === index) ?? details.results[index];
661
- if (progressEntry?.status === "running" || progressEntry?.status === "pending" || progressEntry?.status === "failed") return false;
662
- if (!resultEntry || !isDoneResult(resultEntry)) return false;
663
- }
664
- return true;
665
- }).length;
666
- const currentStep = details.currentStepIndex !== undefined ? details.currentStepIndex + 1 : Math.min(totalCount, doneLogical + (hasRunning ? 1 : 0));
667
- const headerLabel = hasRunning ? `step ${currentStep}/${totalCount}` : `step ${doneLogical}/${totalCount}`;
668
- return { headerLabel, itemTitle, totalCount, hasParallelInChain, activeParallelGroup, groupStartIndex: 0, groupEndIndex: details.results.length, showActiveGroupOnly: false };
669
- }
670
-
671
- const totalCount = details.totalSteps ?? details.results.length;
672
- const currentStep = details.currentStepIndex !== undefined ? details.currentStepIndex + 1 : Math.min(totalCount, details.results.filter(isDoneResult).length + (hasRunning ? 1 : 0));
673
- const done = details.results.filter(isDoneResult).length;
674
- const headerLabel = hasRunning ? `step ${currentStep}/${totalCount}` : `step ${done}/${totalCount}`;
675
- return { headerLabel, itemTitle, totalCount, hasParallelInChain, activeParallelGroup, groupStartIndex: 0, groupEndIndex: details.results.length, showActiveGroupOnly: false };
676
- }
677
-
678
- function resultRowLabel(details: Pick<Details, "mode" | "chainAgents" | "workflowGraph">, label: MultiProgressLabel, resultIndex: number, stepNumber: number): string {
679
- if (details.mode === "chain" && label.hasParallelInChain) {
680
- const span = buildChainStepSpans(details).find((candidate) => resultIndex >= candidate.start && resultIndex < candidate.start + candidate.count);
681
- if (span?.isParallel) return `Agent ${resultIndex - span.start + 1}/${span.count}`;
682
- if (span) return `Step ${span.stepIndex + 1}`;
683
- }
684
- if (label.itemTitle === "Agent") {
685
- const localStepNumber = label.activeParallelGroup
686
- ? Math.max(1, stepNumber - label.groupStartIndex)
687
- : stepNumber;
688
- return `Agent ${localStepNumber}/${label.totalCount}`;
689
- }
690
- return `Step ${stepNumber}`;
691
- }
692
-
693
- function widgetStats(job: AsyncJobState, theme: Theme): string {
694
- const parts: string[] = [];
695
- const stepsTotal = job.stepsTotal ?? (job.agents?.length ?? 1);
696
- if (job.activeParallelGroup) {
697
- const running = job.runningSteps ?? (job.status === "running" ? 1 : 0);
698
- const succeeded = job.completedSteps ?? (job.status === "complete" ? stepsTotal : 0);
699
- if (job.mode === "parallel") {
700
- if (job.status === "running" && running > 0) parts.push(formatAgentRunningLabel(running));
701
- if (stepsTotal > 0) parts.push(`${succeeded}/${stepsTotal} succeeded`);
702
- } else {
703
- const activeGroup = job.currentStep !== undefined
704
- ? job.parallelGroups?.find((group) => job.currentStep! >= group.start && job.currentStep! < group.start + group.count)
705
- : job.parallelGroups?.find((group) => group.start === 0);
706
- const logicalStep = activeGroup?.stepIndex ?? job.currentStep ?? 0;
707
- const total = job.chainStepCount ?? stepsTotal;
708
- const groupParts = [`${succeeded}/${stepsTotal} succeeded`];
709
- if (job.status === "running" && running > 0) groupParts.unshift(formatAgentRunningLabel(running));
710
- parts.push(`step ${logicalStep + 1}/${total} · parallel group: ${groupParts.join(" · ")}`);
711
- }
712
- } else if (job.currentStep !== undefined) {
713
- if (job.mode === "chain" && job.parallelGroups?.length) {
714
- const total = job.chainStepCount ?? stepsTotal;
715
- parts.push(`step ${flatToLogicalStepIndex(job.currentStep, total, job.parallelGroups) + 1}/${total}`);
716
- } else {
717
- parts.push(`step ${job.currentStep + 1}/${stepsTotal}`);
718
- }
719
- } else if (stepsTotal > 1) {
720
- parts.push(`steps ${stepsTotal}`);
721
- }
722
- if (job.toolCount !== undefined) parts.push(formatToolUseStat(job.toolCount));
723
- if (job.totalTokens?.total) parts.push(formatTokenStat(job.totalTokens.total));
724
- if (job.status !== "queued" && job.startedAt !== undefined && job.updatedAt !== undefined) parts.push(formatDuration(Math.max(0, job.updatedAt - job.startedAt)));
725
- return statJoin(theme, parts);
726
- }
727
-
728
- function widgetStepStats(theme: Theme, step: NonNullable<AsyncJobState["steps"]>[number]): string {
729
- return statJoin(theme, [
730
- step.turnCount !== undefined ? `${step.turnCount} turns` : "",
731
- step.toolCount !== undefined ? formatToolUseStat(step.toolCount) : "",
732
- step.tokens?.total ? formatTokenStat(step.tokens.total) : "",
733
- step.durationMs !== undefined ? formatDuration(step.durationMs) : "",
734
- ]);
735
- }
736
-
737
- function modelThinkingBadge(theme: Theme, model?: string, thinking?: string): string {
738
- const label = formatModelThinking(model, thinking);
739
- return label ? theme.fg("dim", ` (${label})`) : "";
740
- }
741
-
742
- function modelStat(theme: Theme, model?: string): string {
743
- return model ? ` ${theme.fg("dim", "·")} ${theme.fg("dim", model)}` : "";
744
- }
745
-
746
- function widgetStepActivityLine(step: NonNullable<AsyncJobState["steps"]>[number], width: number, expanded: boolean, snapshotNow?: number): string {
747
- if ((step.status === "failed" || step.status === "timed-out") && step.error) return firstOutputLine(step.error);
748
- const toolLine = formatCurrentToolLine(step, width, expanded, snapshotNow);
749
- if (toolLine) return toolLine;
750
- const activity = buildLiveStatusLine(step, snapshotNow);
751
- if (activity) return activity;
752
- if (step.status === "running") return "thinking…";
753
- return "";
754
- }
755
-
756
- function widgetOutputPath(job: AsyncJobState, step: NonNullable<AsyncJobState["steps"]>[number]): string | undefined {
757
- if (typeof step.index !== "number") return undefined;
758
- return path.join(job.asyncDir, `output-${step.index}.log`);
759
- }
760
-
761
- function nestedRunName(run: NestedRunSummary): string {
762
- if (run.agent) return run.agent;
763
- if (run.agents?.length) return formatWidgetAgents(run.agents);
764
- return run.id;
765
- }
766
-
767
- function nestedStatusGlyph(state: NestedRunSummary["state"] | NestedStepSummary["status"], theme: Theme, seed?: number): string {
768
- if (state === "running") return theme.fg("accent", runningGlyph(seed));
769
- if (state === "complete" || state === "completed") return theme.fg("success", "✓");
770
- if (state === "failed") return theme.fg("error", "✗");
771
- if (state === "blocked") return theme.fg("warning", "■");
772
- if (state === "paused") return theme.fg("warning", "■");
773
- return theme.fg("muted", "◦");
774
- }
775
-
776
- function nestedRunSeed(run: NestedRunSummary): number | undefined {
777
- return runningSeed(run.lastUpdate, run.lastActivityAt, run.currentStep, run.toolCount, run.turnCount, run.totalTokens?.total, run.currentToolStartedAt);
778
- }
779
-
780
- function nestedActivity(input: Pick<NestedRunSummary | NestedStepSummary, "activityState" | "lastActivityAt" | "currentTool" | "currentToolStartedAt" | "currentPath" | "turnCount" | "toolCount">, state: NestedRunSummary["state"] | NestedStepSummary["status"], snapshotNow?: number): string {
781
- const facts: string[] = [];
782
- if (input.currentTool && input.currentToolStartedAt !== undefined && snapshotNow !== undefined) facts.push(`${input.currentTool} ${formatDuration(Math.max(0, snapshotNow - input.currentToolStartedAt))}`);
783
- else if (input.currentTool) facts.push(input.currentTool);
784
- if (input.currentPath) facts.push(shortenPath(input.currentPath));
785
- if (input.turnCount !== undefined) facts.push(`${input.turnCount} turns`);
786
- if (input.toolCount !== undefined) facts.push(`${input.toolCount} tools`);
787
- const activity = buildLiveStatusLine(input, snapshotNow);
788
- if (activity && facts.length) return `${activity} · ${facts.join(" · ")}`;
789
- if (activity) return activity;
790
- if (facts.length) return facts.join(" · ");
791
- if (state === "running") return "thinking…";
792
- if (state === "queued" || state === "pending") return "queued…";
793
- if (state === "blocked") return "Needs your action";
794
- if (state === "paused") return "Paused";
795
- if (state === "failed") return "Failed";
796
- return "Done";
797
- }
798
-
799
- /** Nested runs only render in the expanded widget; the collapsed view is one line per run. */
800
- function formatNestedWidgetLines(children: NestedRunSummary[] | undefined, theme: Theme, width: number, snapshotNow?: number, lineBudget = 12): string[] {
801
- if (!children?.length || lineBudget <= 0) return [];
802
- const lines: string[] = [];
803
- const maxDepth = 2;
804
- const append = (items: NestedRunSummary[] | undefined, depth: number, prefix: string): void => {
805
- if (!items?.length || lines.length >= lineBudget) return;
806
- if (depth > maxDepth) {
807
- const aggregate = formatNestedAggregate(items);
808
- if (aggregate && lines.length < lineBudget) lines.push(theme.fg("dim", `${prefix}↳ ${aggregate}`));
809
- return;
810
- }
811
- for (let index = 0; index < items.length; index++) {
812
- const child = items[index]!;
813
- if (lines.length >= lineBudget) {
814
- const aggregate = formatNestedAggregate(items.slice(index));
815
- if (aggregate) lines[lines.length - 1] = theme.fg("dim", `${prefix}↳ ${aggregate}`);
816
- return;
817
- }
818
- const activity = nestedActivity(child, child.state, snapshotNow ?? child.lastUpdate);
819
- const error = child.error ? ` · ${child.error}` : "";
820
- lines.push(theme.fg("dim", `${prefix}↳ ${nestedStatusGlyph(child.state, theme, nestedRunSeed(child))} ${nestedRunName(child)} · ${child.state} · ${activity}${error}`));
821
- if (depth === maxDepth) {
822
- const aggregate = formatNestedAggregate([...(child.steps?.flatMap((step) => step.children ?? []) ?? []), ...(child.children ?? [])]);
823
- if (aggregate && lines.length < lineBudget) lines.push(theme.fg("dim", `${prefix} ↳ ${aggregate}`));
824
- continue;
825
- }
826
- for (const step of child.steps ?? []) {
827
- if (lines.length >= lineBudget) return;
828
- lines.push(theme.fg("dim", `${prefix} ↳ ${nestedStatusGlyph(step.status, theme)} ${step.agent} · ${step.status} · ${nestedActivity(step, step.status, snapshotNow ?? child.lastUpdate)}`));
829
- append(step.children, depth + 1, `${prefix} `);
830
- }
831
- append(child.children, depth + 1, `${prefix} `);
832
- }
833
- };
834
- append(children, 0, "");
835
- return lines.map((line) => truncLine(line, width));
836
- }
837
-
838
- function foregroundStyleWidgetStepLines(
839
- job: AsyncJobState,
840
- theme: Theme,
841
- step: NonNullable<AsyncJobState["steps"]>[number],
842
- itemTitle: "Agent" | "Step",
843
- index: number,
844
- total: number,
845
- width: number,
846
- ): string[] {
847
- const status = widgetStepStatus(step.status, theme);
848
- const stats = widgetStepStats(theme, step);
849
- const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking);
850
- const lines = [` ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index - 1))} ${itemTitle} ${index}/${total}: ${themeBold(theme, step.agent)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`];
851
- const activity = widgetStepActivityLine(step, width, true, job.updatedAt);
852
- if (activity) lines.push(` ${theme.fg("dim", `⎿ ${activity}`)}`);
853
- for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, job.updatedAt)) {
854
- lines.push(` ${nestedLine}`);
855
- }
856
- if (step.status === "running") {
857
- const output = widgetOutputPath(job, step);
858
- if (output) lines.push(` ${theme.fg("dim", `output: ${shortenPath(output)}`)}`);
859
- const liveStatus = buildLiveStatusLine(step, job.updatedAt);
860
- if (liveStatus && liveStatus !== activity) lines.push(` ${theme.fg("accent", liveStatus)}`);
861
- for (const tool of step.recentTools?.slice(-3) ?? []) {
862
- const maxArgsLen = Math.max(40, width - 30);
863
- const argsPreview = tool.args.length <= maxArgsLen ? tool.args : `${tool.args.slice(0, maxArgsLen)}...`;
864
- lines.push(` ${theme.fg("dim", `${tool.tool}${argsPreview ? `: ${argsPreview}` : ""}`)}`);
865
- }
866
- for (const line of step.recentOutput?.slice(-5) ?? []) {
867
- lines.push(` ${theme.fg("dim", line)}`);
868
- }
869
- }
870
- return lines;
871
- }
872
-
873
- function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, width: number): string[] {
874
- if (!job.steps?.length) return [
875
- ` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
876
- ...formatNestedWidgetLines(job.nestedChildren, theme, width, job.updatedAt).map((line) => ` ${line}`),
877
- ];
878
- if (job.mode === "chain" && !job.activeParallelGroup && job.parallelGroups?.length) return widgetChainDetails(job, theme, width);
879
- const total = job.stepsTotal ?? job.steps.length;
880
- const itemTitle = job.mode === "parallel" || job.activeParallelGroup ? "Agent" : "Step";
881
- const lines: string[] = [];
882
- for (const [index, step] of job.steps.entries()) {
883
- lines.push(...foregroundStyleWidgetStepLines(job, theme, step, itemTitle, index + 1, total, width));
884
- }
885
- const attached = new Set(job.steps.flatMap((step) => step.children?.map((child) => child.id) ?? []));
886
- const unattached = job.nestedChildren?.filter((child) => !attached.has(child.id)) ?? [];
887
- for (const nestedLine of formatNestedWidgetLines(unattached, theme, width, job.updatedAt)) {
888
- lines.push(` ${nestedLine}`);
889
- }
890
- return lines;
891
- }
892
-
893
- /** The expanded single-run view; the collapsed view never reaches this path. */
894
- function buildSingleWidgetLines(job: AsyncJobState, theme: Theme, width: number): string[] {
895
- const stats = widgetStats(job, theme);
896
- const count = job.mode === "chain" ? job.chainStepCount : job.stepsTotal ?? job.agents?.length ?? job.steps?.length;
897
- const mode = widgetJobName(job);
898
- const title = `async subagent ${mode}${count && count > 1 ? ` (${count})` : ""}`;
899
- return [
900
- `${theme.fg("toolTitle", themeBold(theme, title))} ${theme.fg("dim", "· background")}`,
901
- `${widgetStatusGlyph(job, theme)} ${themeBold(theme, mode)}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
902
- ...foregroundStyleWidgetDetails(job, theme, width),
903
- ].map((line) => truncLine(line, width));
904
- }
905
-
906
- /** Collapsed rows carry the live tool inline, since detail lines wait for Ctrl+O. */
907
- function collapsedJobRow(job: AsyncJobState, theme: Theme, width: number): string {
908
- const stats = widgetStats(job, theme);
909
- // The job root mirrors an active step's tool but drops its args, so prefer the step itself.
910
- const runningSteps = job.steps?.filter((step) => step.status === "running") ?? [];
911
- const soloStep = runningSteps.length === 1 ? runningSteps[0] : undefined;
912
- const activity = job.status !== "running"
913
- ? widgetActivity(job, false)
914
- : soloStep
915
- ? widgetStepActivityLine(soloStep, width, false, job.updatedAt)
916
- : widgetActivity(job, false);
917
- return `${widgetStatusGlyph(job, theme)} ${themeBold(theme, widgetJobName(job))}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}${activity ? ` ${theme.fg("dim", "·")} ${theme.fg("dim", activity)}` : ""}`;
918
- }
919
-
920
- function fitWidgetLineBudget(lines: string[], theme: Theme, width: number, expanded: boolean): string[] {
921
- const rows = process.stdout.rows || 30;
922
- const budget = expanded
923
- ? Math.max(4, Math.min(24, Math.floor(rows * 0.4)))
924
- : MAX_WIDGET_JOBS + 2;
925
- if (lines.length <= budget) return lines;
926
- const visibleLines = Math.max(1, budget - 1);
927
- const hiddenCount = lines.length - visibleLines;
928
- const hint = expanded
929
- ? `… ${hiddenCount} live-detail lines hidden`
930
- : `… ${hiddenCount} lines hidden · Ctrl+O expands`;
931
- return [...lines.slice(0, visibleLines), truncLine(theme.fg("dim", hint), width)];
932
- }
933
-
934
- function buildWidgetComponent(jobs: AsyncJobState[], isExpanded: () => boolean): (_tui: unknown, theme: Theme) => Component {
935
- return (_tui, theme) => ({
936
- render(availableWidth: number): string[] {
937
- // Text pads one column on each side, so build against its inner width on every render.
938
- const width = Math.max(1, availableWidth - 2);
939
- const expanded = isExpanded();
940
- const lines = fitWidgetLineBudget(buildWidgetLines(jobs, theme, width, expanded), theme, width, expanded);
941
- const container = new Container();
942
- for (const line of lines) container.addChild(new Text(line, 1, 0));
943
- return container.render(availableWidth);
944
- },
945
- invalidate(): void {},
946
- });
947
- }
948
-
949
- export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = getTermWidth(), expanded = false): string[] {
950
- if (jobs.length === 0) return [];
951
- if (jobs.length === 1 && expanded) return buildSingleWidgetLines(jobs[0]!, theme, width);
952
- const running = jobs.filter((job) => job.status === "running");
953
- const queued = jobs.filter((job) => job.status === "queued");
954
- const finished = jobs.filter((job) => job.status !== "running" && job.status !== "queued");
955
-
956
- const jobItem = (job: AsyncJobState): string[] => {
957
- if (!expanded) return [collapsedJobRow(job, theme, width)];
958
- const stats = widgetStats(job, theme);
959
- return [
960
- `${widgetStatusGlyph(job, theme)} ${themeBold(theme, widgetJobName(job))}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
961
- ` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
962
- ...widgetParallelAgentDetails(job, theme, width),
963
- ];
964
- };
965
-
966
- const items: string[][] = [];
967
- let hiddenRunning = 0;
968
- let hiddenFinished = 0;
969
- let queuedShown = false;
970
- let slots = MAX_WIDGET_JOBS;
971
-
972
- for (const job of running) {
973
- if (slots <= 0) { hiddenRunning++; continue; }
974
- items.push(jobItem(job));
975
- slots--;
976
- }
977
-
978
- if (queued.length > 0 && slots > 0) {
979
- // One collapsed queued run has room for its own name; a backlog collapses to a count.
980
- items.push(queued.length === 1 && !expanded ? jobItem(queued[0]!) : [`${theme.fg("muted", "◦")} ${theme.fg("dim", `${queued.length} queued`)}`]);
981
- queuedShown = true;
982
- slots--;
983
- }
984
-
985
- for (const job of finished) {
986
- if (slots <= 0) { hiddenFinished++; continue; }
987
- items.push(jobItem(job));
988
- slots--;
989
- }
990
-
991
- const hiddenQueued = queued.length > 0 && !queuedShown ? queued.length : 0;
992
- const hiddenTotal = hiddenRunning + hiddenFinished + hiddenQueued;
993
- if (hiddenTotal > 0) {
994
- const parts: string[] = [];
995
- if (hiddenRunning > 0) parts.push(`${hiddenRunning} running`);
996
- if (hiddenQueued > 0) parts.push(`${hiddenQueued} queued`);
997
- if (hiddenFinished > 0) parts.push(`${hiddenFinished} finished`);
998
- items.push([theme.fg("dim", `+${hiddenTotal} more (${parts.join(", ")})`)]);
999
- }
1000
-
1001
- const hint = running.length > 0 && !expanded ? ` ${theme.fg("dim", "·")} ${theme.fg("accent", "Ctrl+O")}` : "";
1002
- if (items.length === 1 && !expanded) return [truncLine(`${items[0]![0]}${hint}`, width)];
1003
-
1004
- const lines: string[] = [];
1005
- const hasActive = running.length > 0 || queued.length > 0;
1006
- const headerGlyph = running.length > 0 ? runningGlyph(widgetJobsRunningSeed(running)) : hasActive ? "●" : "○";
1007
- lines.push(truncLine(`${theme.fg(hasActive ? "accent" : "dim", headerGlyph)} ${theme.fg(hasActive ? "accent" : "dim", "Async agents")} ${theme.fg("dim", "· background")}${hint}`, width));
1008
-
1009
- for (let i = 0; i < items.length; i++) {
1010
- const item = items[i]!;
1011
- const last = i === items.length - 1;
1012
- const branch = last ? "└─" : "├─";
1013
- const continuation = last ? " " : "│ ";
1014
- lines.push(truncLine(`${theme.fg("dim", branch)} ${item[0]}`, width));
1015
- for (const detail of item.slice(1)) {
1016
- lines.push(truncLine(`${theme.fg("dim", continuation)} ${detail}`, width));
1017
- }
1018
- }
1019
-
1020
- return lines;
1021
- }
1022
-
1023
- /**
1024
- * Render the async jobs widget
1025
- */
1026
- export function renderWidget(ctx: ExtensionContext, jobs: AsyncJobState[]): void {
1027
- if (jobs.length === 0) {
1028
- if (isTuiContext(ctx)) ctx.ui.setWidget(WIDGET_KEY, undefined);
1029
- return;
1030
- }
1031
- if (!isTuiContext(ctx)) return;
1032
- ctx.ui.setWidget(WIDGET_KEY, buildWidgetComponent(jobs, () => ctx.ui.getToolsExpanded()));
1033
- }
1034
-
1035
- function renderSingleCompact(d: Details, r: Details["results"][number], theme: Theme, isError = false): Component {
1036
- const expandKey = keyText("app.tools.expand");
1037
- const output = r.truncation?.text || getSingleResultOutput(r);
1038
- const statusOutput = output || (d.intercomDelivery?.delivered ? d.intercomDelivery.summary : "");
1039
- const progress = r.progress || r.progressSummary;
1040
- const isRunning = !isError && r.progress?.status === "running";
1041
- const contextBadge = d.context === "fork" ? theme.fg("warning", " [fork]") : "";
1042
- const stats = statJoin(theme, [
1043
- r.usage?.turns ? `⟳ ${r.usage.turns}` : "",
1044
- formatProgressStats(theme, progress),
1045
- ]);
1046
- const c = new Container();
1047
- const width = getTermWidth() - 4;
1048
- const modelDisplay = modelThinkingBadge(theme, r.model);
1049
- c.addChild(new TruncatedText(`${isError ? theme.fg("error", "✗") : resultGlyph(r, statusOutput, theme, isRunning)} ${theme.fg("toolTitle", theme.bold(r.agent))}${modelDisplay}${contextBadge}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`));
1050
-
1051
- if (isRunning && r.progress) {
1052
- const progressSnapshotNow = snapshotNowForProgress(r.progress);
1053
- const activity = compactCurrentActivity(r.progress);
1054
- c.addChild(new Text(truncLine(theme.fg("dim", ` ⎿ ${activity}`), width), 0, 0));
1055
- const liveStatus = buildLiveStatusLine(r.progress, progressSnapshotNow);
1056
- if (liveStatus && liveStatus !== activity) c.addChild(new Text(truncLine(theme.fg("dim", ` ${liveStatus}`), width), 0, 0));
1057
- if (expandKey) c.addChild(new Text(truncLine(theme.fg("accent", ` Press ${expandKey} for live detail`), width), 0, 0));
1058
- if (r.artifactPaths) c.addChild(new Text(truncLine(theme.fg("dim", ` output: ${shortenPath(r.artifactPaths.outputPath)}`), width), 0, 0));
1059
- return c;
1060
- }
1061
-
1062
- c.addChild(new TruncatedText(theme.fg("dim", ` ⎿ ${resultStatusLine(r, statusOutput)}`)));
1063
- const preview = firstOutputLine(output);
1064
- if (preview && r.exitCode === 0 && !hasEmptyTextOutputWithoutOutputTarget(r.task, output)) {
1065
- c.addChild(new TruncatedText(theme.fg("dim", ` ${preview}`)));
1066
- }
1067
- if (r.sessionFile) c.addChild(new TruncatedText(theme.fg("dim", ` session: ${shortenPath(r.sessionFile)}`)));
1068
- if (r.artifactPaths) c.addChild(new TruncatedText(theme.fg("dim", ` output: ${shortenPath(r.artifactPaths.outputPath)}`)));
1069
- if (r.truncation?.artifactPath) c.addChild(new TruncatedText(theme.fg("dim", ` full output: ${shortenPath(r.truncation.artifactPath)}`)));
1070
- c.addChild(new TruncatedText(theme.fg("dim", ` ${keyText("app.tools.expand")} ${d.intercomDelivery?.delivered ? "receipt details" : "full response"}`)));
1071
- return c;
1072
- }
1073
-
1074
- function renderMultiCompact(d: Details, theme: Theme, isError = false): Component {
1075
- const expandKey = keyText("app.tools.expand");
1076
- const hasRunning = !isError && (d.progress?.some((p) => p.status === "running")
1077
- || d.results.some((r) => r.progress?.status === "running")
1078
- || workflowGraphHasStatus(d, ["running"]));
1079
- const failed = isError || d.results.some((r) => r.exitCode !== 0 && r.progress?.status !== "running")
1080
- || workflowGraphHasStatus(d, ["failed", "timed-out"]);
1081
- const blocked = d.results.some((r) => r.acceptance?.status === "blocked" && r.exitCode === 0);
1082
- const paused = d.results.some((r) => (r.interrupted || r.detached) && r.progress?.status !== "running")
1083
- || workflowGraphHasStatus(d, ["paused", "detached"]);
1084
- let totalSummary = d.progressSummary;
1085
- if (!totalSummary) {
1086
- let sawProgress = false;
1087
- const summary = { toolCount: 0, tokens: 0, durationMs: 0 };
1088
- for (const r of d.results) {
1089
- const prog = r.progress || r.progressSummary;
1090
- if (!prog) continue;
1091
- sawProgress = true;
1092
- summary.toolCount += prog.toolCount;
1093
- summary.tokens += prog.tokens;
1094
- summary.durationMs = d.mode === "chain" ? summary.durationMs + prog.durationMs : Math.max(summary.durationMs, prog.durationMs);
1095
- }
1096
- if (sawProgress) totalSummary = summary;
1097
- }
1098
- const multiLabel = buildMultiProgressLabel(d, hasRunning);
1099
- const itemTitle = multiLabel.itemTitle;
1100
- const stats = statJoin(theme, [multiLabel.headerLabel, formatProgressStats(theme, totalSummary)]);
1101
- const glyph = hasRunning
1102
- ? theme.fg("accent", runningGlyph(runningSeed(progressRunningSeed(totalSummary), d.currentStepIndex)))
1103
- : failed
1104
- ? theme.fg("error", "✗")
1105
- : paused || blocked
1106
- ? theme.fg("warning", "■")
1107
- : theme.fg("success", "✓");
1108
- const contextBadge = d.context === "fork" ? theme.fg("warning", " [fork]") : "";
1109
- const c = new Container();
1110
- const width = getTermWidth() - 4;
1111
- c.addChild(new TruncatedText(`${glyph} ${theme.fg("toolTitle", theme.bold(d.mode))}${contextBadge}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`));
1112
-
1113
- const useResultsDirectly = multiLabel.hasParallelInChain || !d.chainAgents?.length;
1114
- const displayStart = multiLabel.showActiveGroupOnly ? multiLabel.groupStartIndex : 0;
1115
- const displayEnd = multiLabel.showActiveGroupOnly ? multiLabel.groupEndIndex : (useResultsDirectly ? d.results.length : d.chainAgents!.length);
1116
- const chainEntries = buildChainRenderEntries(d, multiLabel);
1117
- const renderEntries = chainEntries ?? Array.from({ length: displayEnd - displayStart }, (_, offset): ChainRenderEntry => {
1118
- const i = displayStart + offset;
1119
- const r = d.results[i];
1120
- const fallbackLabel = itemTitle.toLowerCase();
1121
- const rowNumber = multiLabel.showActiveGroupOnly ? (i - multiLabel.groupStartIndex + 1) : (i + 1);
1122
- return { kind: "result", resultIndex: i, rowNumber, agentName: useResultsDirectly ? (r?.agent || `${fallbackLabel}-${rowNumber}`) : (d.chainAgents![i] || r?.agent || `${fallbackLabel}-${rowNumber}`) };
1123
- });
1124
- const maxCompactRows = 6;
1125
- const visibleEntries = hasRunning || failed || paused || blocked ? renderEntries : renderEntries.slice(0, maxCompactRows);
1126
- let showLiveDetailHint = false;
1127
- for (const entry of visibleEntries) {
1128
- if (entry.kind === "placeholder") {
1129
- const glyph = widgetStepGlyph(entry.status as AsyncJobStep["status"], theme);
1130
- const statusLabel = widgetStepStatus(entry.status as AsyncJobStep["status"], theme);
1131
- c.addChild(new TruncatedText(` ${glyph} ${entry.stepLabel}: ${themeBold(theme, entry.agentName)} ${theme.fg("dim", "·")} ${statusLabel}`));
1132
- if (entry.error) c.addChild(new TruncatedText(theme.fg("error", ` ⎿ Error: ${entry.error}`)));
1133
- continue;
1134
- }
1135
- const i = entry.resultIndex;
1136
- const r = d.results[i];
1137
- const rowNumber = entry.rowNumber;
1138
- const agentName = entry.agentName;
1139
- if (!r) {
1140
- const pendingLabel = chainEntries ? resultRowLabel(d, multiLabel, i, rowNumber) : `${itemTitle} ${rowNumber}`;
1141
- c.addChild(new TruncatedText(theme.fg("dim", ` ◦ ${pendingLabel}: ${agentName} · pending`)));
1142
- continue;
1143
- }
1144
- const output = getSingleResultOutput(r) || (d.intercomDelivery?.delivered ? d.intercomDelivery.summary : "");
1145
- const progressFromArray = d.progress?.find((p) => p.index === i) || d.progress?.find((p) => p.agent === r.agent && p.status === "running");
1146
- const rProg = r.progress || progressFromArray || r.progressSummary;
1147
- const rRunning = rProg && "status" in rProg && rProg.status === "running";
1148
- const rPending = rProg && "status" in rProg && rProg.status === "pending";
1149
- const stepNumber = r.progress?.index !== undefined ? r.progress.index + 1 : progressFromArray?.index !== undefined ? progressFromArray.index + 1 : i + 1;
1150
- const stepStats = formatProgressStats(theme, rProg);
1151
- const glyph = rPending ? theme.fg("dim", "◦") : resultGlyph(r, output, theme, rRunning, progressRunningSeed(rProg));
1152
- const pendingLabel = rPending ? ` ${theme.fg("dim", "· pending")}` : "";
1153
- const stepLabel = resultRowLabel(d, multiLabel, i, stepNumber);
1154
- const line = `${glyph} ${stepLabel}: ${themeBold(theme, agentName)}${modelStat(theme, r.model)}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}${pendingLabel}`;
1155
- c.addChild(new TruncatedText(` ${line}`));
1156
- if (rRunning && rProg && "status" in rProg) {
1157
- const activity = compactCurrentActivity(rProg);
1158
- c.addChild(new Text(truncLine(theme.fg("dim", ` ⎿ ${activity}`), width), 0, 0));
1159
- showLiveDetailHint = true;
1160
- } else if (!rPending && (r.exitCode !== 0 || r.interrupted || r.detached || r.timedOut || r.acceptance?.status === "blocked" || hasEmptyTextOutputWithoutOutputTarget(r.task, output))) {
1161
- c.addChild(new TruncatedText(theme.fg(r.exitCode !== 0 ? "error" : "dim", ` ⎿ ${resultStatusLine(r, output)}`)));
1162
- }
1163
- const outputTarget = extractOutputTarget(r.task);
1164
- if (outputTarget) c.addChild(new TruncatedText(theme.fg("dim", ` output: ${outputTarget}`)));
1165
- if (r.artifactPaths) c.addChild(new TruncatedText(theme.fg("dim", ` output: ${shortenPath(r.artifactPaths.outputPath)}`)));
1166
- }
1167
- if (renderEntries.length > visibleEntries.length) c.addChild(new Text(theme.fg("dim", ` +${renderEntries.length - visibleEntries.length} more${expandKey ? ` · ${expandKey} expands` : ""}`), 0, 0));
1168
- if (showLiveDetailHint && expandKey) c.addChild(new Text(theme.fg("accent", ` Press ${expandKey} for live detail`), 0, 0));
1169
- if (d.artifacts) c.addChild(new TruncatedText(theme.fg("dim", ` artifacts: ${shortenPath(d.artifacts.dir)}`)));
1170
- if (!hasRunning) c.addChild(new TruncatedText(theme.fg("dim", ` ${keyText("app.tools.expand")} ${d.intercomDelivery?.delivered ? "receipt details" : "full response"}`)));
1171
- return c;
1
+ import { keyText } from "@earendil-works/pi-coding-agent";
2
+ import { Container, Text, TruncatedText, type Component } from "@earendil-works/pi-tui";
3
+ import type { ReadonlyInput, SubagentExecutionResult } from "../shared/types.ts";
4
+ import { firstOutputLine, getTermWidth, truncLine, type Theme } from "./display.ts";
5
+ import { renderSingleCompact, renderMultiCompact } from "./compact-results.ts";
6
+ import { renderSingleExpanded } from "./expanded-single.ts";
7
+ import { renderMultiExpanded } from "./expanded-multi.ts";
8
+ import { workflowFacts, type DetailsInput } from "./result-facts.ts";
9
+ import { hasText } from "./text-values.ts";
10
+ export { widgetRenderKey } from "./widget-status.ts";
11
+ export { buildWidgetLines, renderWidget } from "./widget.ts";
12
+ // Native rendering can receive text-only failures before application details exist.
13
+ type Result = ReadonlyInput<Pick<SubagentExecutionResult, "content" | "isError">> & {
14
+ readonly details?: DetailsInput;
15
+ };
16
+ function asyncReceipt(
17
+ d: DetailsInput | undefined,
18
+ text: string,
19
+ prefix: string,
20
+ theme: Theme,
21
+ ): Component | undefined {
22
+ if (!d || !hasText(d.asyncId) || d.mode === "management") {
23
+ return;
24
+ }
25
+ const headline = hasText(d.managementControl?.revivedFromRunId)
26
+ ? "Revived async"
27
+ : firstOutputLine(text).replace(` [${d.asyncId}]`, "");
28
+ const c = new Container();
29
+ c.addChild(new TruncatedText(`${prefix}[${d.asyncId.slice(0, 8)}] ${headline}`));
30
+ c.addChild(new TruncatedText(theme.fg("dim", `${keyText("app.tools.expand")} details`)));
31
+ return c;
32
+ }
33
+ function compactText(text: string, prefix: string, theme: Theme): Component {
34
+ const lines = text.replace(/\n+$/, "").split("\n");
35
+ if (lines.length === 1) {
36
+ return new Text(truncLine(`${prefix}${lines[0] ?? ""}`, getTermWidth() - 4), 0, 0);
37
+ }
38
+ const visible = lines.slice(0, 12),
39
+ expand = keyText("app.tools.expand");
40
+ if (lines.length > visible.length) {
41
+ visible.push(
42
+ theme.fg(
43
+ "dim",
44
+ `+${lines.length - visible.length} more${hasText(expand) ? ` · ${expand} expands` : ""}`,
45
+ ),
46
+ );
47
+ }
48
+ return new Text(`${prefix}${visible.join("\n")}`, 0, 0);
49
+ }
50
+ function textResult(result: Result, expanded: boolean, theme: Theme, isError: boolean): Component {
51
+ const d = result.details,
52
+ first = result.content.at(0),
53
+ text = first?.type === "text" ? first.text : "(no output)";
54
+ const prefix = d?.context === "fork" ? `${theme.fg("warning", "[fork]")} ` : "";
55
+ if (expanded) {
56
+ return new Text(`${prefix}${text}`, 0, 0);
57
+ }
58
+ const receipt = isError ? undefined : asyncReceipt(d, text, prefix, theme);
59
+ return receipt ?? compactText(text, prefix, theme);
60
+ }
61
+ function singleResult(d: DetailsInput): boolean {
62
+ return d.mode === "single" && d.results.length === 1;
63
+ }
64
+ function expandedResult(
65
+ result: Result,
66
+ d: DetailsInput,
67
+ theme: Theme,
68
+ isError: boolean,
69
+ ): Component {
70
+ const facts = workflowFacts(d, isError),
71
+ showRun = isError || facts.failed || facts.blocked || facts.paused;
72
+ const receipt =
73
+ showRun || d.intercomDelivery?.delivered === true
74
+ ? result.content
75
+ .filter((part) => part.type === "text")
76
+ .map((part) => part.text)
77
+ .join("\n")
78
+ : undefined;
79
+ const settings = { isError, showRun, receipt },
80
+ first = d.results.at(0);
81
+ if (singleResult(d) && first) {
82
+ return renderSingleExpanded(d, first, theme, settings);
83
+ }
84
+ return renderMultiExpanded(d, theme, settings);
1172
85
  }
1173
-
1174
- /**
1175
- * Render a subagent result
1176
- */
1177
86
  export function renderSubagentResult(
1178
- result: SubagentExecutionResult,
1179
- options: { expanded: boolean },
1180
- theme: Theme,
1181
- context?: { isError: boolean },
87
+ result: Result,
88
+ options: { readonly expanded: boolean },
89
+ theme: Theme,
90
+ context?: { readonly isError: boolean },
1182
91
  ): Component {
1183
- const isError = context?.isError ?? result.isError;
1184
- const expandKey = keyText("app.tools.expand");
1185
- const d = result.details;
1186
- if (!d || !d.results.length) {
1187
- const t = result.content[0];
1188
- const text = t?.type === "text" ? t.text : "(no output)";
1189
- const contextPrefix = d?.context === "fork" ? `${theme.fg("warning", "[fork]")} ` : "";
1190
- if (options.expanded) return new Text(`${contextPrefix}${text}`, 0, 0);
1191
- if (d?.asyncId && d.mode !== "management" && !isError) {
1192
- const headline = d.managementControl?.revivedFromRunId ? "Revived async" : firstOutputLine(text).replace(` [${d.asyncId}]`, "");
1193
- const c = new Container();
1194
- c.addChild(new TruncatedText(`${contextPrefix}[${d.asyncId.slice(0, 8)}] ${headline}`));
1195
- c.addChild(new TruncatedText(theme.fg("dim", `${keyText("app.tools.expand")} details`)));
1196
- return c;
1197
- }
1198
- const maxCompactLines = 12;
1199
- const lines = text.replace(/\n+$/, "").split("\n");
1200
- if (lines.length === 1) return new Text(truncLine(`${contextPrefix}${lines[0]}`, getTermWidth() - 4), 0, 0);
1201
- const visibleLines = lines.slice(0, maxCompactLines);
1202
- if (lines.length > visibleLines.length) visibleLines.push(theme.fg("dim", `+${lines.length - visibleLines.length} more${expandKey ? ` · ${expandKey} expands` : ""}`));
1203
- return new Text(`${contextPrefix}${visibleLines.join("\n")}`, 0, 0);
1204
- }
1205
-
1206
- const expanded = options.expanded;
1207
- const hasWorkflowFailure = workflowGraphHasStatus(d, ["failed", "timed-out"]);
1208
- const hasWorkflowBlocked = d.results.some((r) => r.exitCode === 0 && r.acceptance?.status === "blocked") || workflowGraphHasStatus(d, ["blocked"]);
1209
- const hasWorkflowPause = d.results.some((r) => r.interrupted || r.detached)
1210
- || workflowGraphHasStatus(d, ["paused", "detached"]);
1211
- const receiptText = expanded && (isError || hasWorkflowFailure || hasWorkflowBlocked || hasWorkflowPause || d.intercomDelivery?.delivered)
1212
- ? result.content.filter((part) => part.type === "text").map((part) => part.text).join("\n")
1213
- : undefined;
1214
- const mdTheme = getMarkdownTheme();
1215
-
1216
- if (d.mode === "single" && d.results.length === 1) {
1217
- const r = d.results[0];
1218
- if (!expanded) return renderSingleCompact(d, r, theme, isError);
1219
- const isRunning = !isError && r.progress?.status === "running";
1220
- const icon = isRunning
1221
- ? theme.fg("warning", "running")
1222
- : isError || r.timedOut
1223
- ? theme.fg("error", "failed")
1224
- : r.detached || r.interrupted
1225
- ? theme.fg("warning", r.detached ? "detached" : "paused")
1226
- : r.exitCode === 0
1227
- ? r.acceptance?.status === "blocked" ? theme.fg("warning", "needs your action") : theme.fg("success", "ok")
1228
- : theme.fg("error", "failed");
1229
- const contextBadge = d.context === "fork" ? theme.fg("warning", " [fork]") : "";
1230
- const output = r.truncation?.text || getSingleResultOutput(r);
1231
-
1232
- const progressInfo = isRunning && r.progress
1233
- ? ` | ${r.progress.toolCount} tools, ${formatTokens(r.progress.tokens)} tok, ${formatDuration(r.progress.durationMs)}`
1234
- : r.progressSummary
1235
- ? ` | ${r.progressSummary.toolCount} tools, ${formatTokens(r.progressSummary.tokens)} tok, ${formatDuration(r.progressSummary.durationMs)}`
1236
- : "";
1237
-
1238
- const w = getTermWidth() - 4;
1239
- const fit = (text: string) => expanded ? text : truncLine(text, w);
1240
- const toolCallLines = getToolCallLines(r, expanded);
1241
- const c = new Container();
1242
- c.addChild(new Text(fit(`${icon} ${theme.fg("toolTitle", theme.bold(r.agent))}${contextBadge}${progressInfo}`), 0, 0));
1243
- if ((isError || hasWorkflowFailure || hasWorkflowBlocked || hasWorkflowPause) && d.runId) c.addChild(new Text(theme.fg("dim", `Run: ${d.runId}`), 0, 0));
1244
- c.addChild(new Spacer(1));
1245
- const taskMaxLen = Math.max(20, w - 8);
1246
- const taskPreview = expanded || r.task.length <= taskMaxLen
1247
- ? r.task
1248
- : `${r.task.slice(0, taskMaxLen)}...`;
1249
- c.addChild(
1250
- new Text(fit(theme.fg("dim", `Task: ${taskPreview}`)), 0, 0),
1251
- );
1252
- c.addChild(new Spacer(1));
1253
-
1254
- if (isRunning && r.progress) {
1255
- const progressSnapshotNow = snapshotNowForProgress(r.progress);
1256
- const toolLine = formatCurrentToolLine(r.progress, w, expanded, progressSnapshotNow);
1257
- if (toolLine) {
1258
- c.addChild(new Text(fit(theme.fg("warning", `> ${toolLine}`)), 0, 0));
1259
- }
1260
- const liveStatusLine = buildLiveStatusLine(r.progress, progressSnapshotNow);
1261
- if (liveStatusLine) {
1262
- c.addChild(new Text(fit(theme.fg("accent", liveStatusLine)), 0, 0));
1263
- }
1264
- if (expandKey) c.addChild(new Text(fit(theme.fg("accent", `Press ${expandKey} for live detail`)), 0, 0));
1265
- if (r.artifactPaths) {
1266
- c.addChild(new Text(fit(theme.fg("dim", `Artifacts: ${shortenPath(r.artifactPaths.outputPath)}`)), 0, 0));
1267
- }
1268
- if (r.progress.recentTools?.length) {
1269
- for (const t of r.progress.recentTools.slice(-3)) {
1270
- const maxArgsLen = Math.max(40, w - 24);
1271
- const argsPreview = expanded || t.args.length <= maxArgsLen
1272
- ? t.args
1273
- : `${t.args.slice(0, maxArgsLen)}...`;
1274
- c.addChild(new Text(fit(theme.fg("dim", `${t.tool}: ${argsPreview}`)), 0, 0));
1275
- }
1276
- }
1277
- for (const line of (r.progress.recentOutput ?? []).slice(-5)) {
1278
- c.addChild(new Text(fit(theme.fg("dim", ` ${line}`)), 0, 0));
1279
- }
1280
- if (toolLine || liveStatusLine || r.progress.recentTools?.length || r.progress.recentOutput?.length || r.artifactPaths) {
1281
- c.addChild(new Spacer(1));
1282
- }
1283
- }
1284
-
1285
- if (expanded) {
1286
- for (const line of toolCallLines) {
1287
- c.addChild(new Text(fit(theme.fg("muted", line)), 0, 0));
1288
- }
1289
- if (toolCallLines.length) c.addChild(new Spacer(1));
1290
- }
1291
-
1292
- if (r.error && !output.includes(r.error)) c.addChild(new Text(theme.fg("error", r.error), 0, 0));
1293
- if (output) c.addChild(new Markdown(output, 0, 0, mdTheme));
1294
- if (receiptText) c.addChild(new Text(receiptText, 0, 0));
1295
- c.addChild(new Spacer(1));
1296
- if (r.skills?.length) {
1297
- c.addChild(new Text(fit(theme.fg("dim", `Skills: ${r.skills.join(", ")}`)), 0, 0));
1298
- }
1299
- if (r.skillsWarning) {
1300
- c.addChild(new Text(fit(theme.fg("warning", `Warning: ${r.skillsWarning}`)), 0, 0));
1301
- }
1302
- if (r.attemptedModels && r.attemptedModels.length > 1) {
1303
- c.addChild(new Text(fit(theme.fg("dim", `Fallbacks: ${r.attemptedModels.join(" → ")}`)), 0, 0));
1304
- }
1305
- c.addChild(new Text(fit(theme.fg("dim", [formatUsage(r.usage, r.model), r.accounting?.state === "incomplete" ? "accounting incomplete · reported totals only" : undefined].filter(Boolean).join(" · "))), 0, 0));
1306
- if (!isRunning) c.addChild(new Text(fit(theme.fg("dim", formatAgentProcessExit(r.agentProcessExit))), 0, 0));
1307
- if (r.sessionFile) {
1308
- c.addChild(new Text(fit(theme.fg("dim", `Session: ${shortenPath(r.sessionFile)}`)), 0, 0));
1309
- }
1310
-
1311
- if (!isRunning && r.artifactPaths) {
1312
- c.addChild(new Spacer(1));
1313
- c.addChild(new Text(fit(theme.fg("dim", `Artifacts: ${shortenPath(r.artifactPaths.outputPath)}`)), 0, 0));
1314
- }
1315
- return c;
1316
- }
1317
-
1318
- if (!expanded) return renderMultiCompact(d, theme, isError);
1319
-
1320
- const hasRunning = !isError && (d.progress?.some((p) => p.status === "running")
1321
- || d.results.some((r) => r.progress?.status === "running")
1322
- || workflowGraphHasStatus(d, ["running"]));
1323
- const hasFailure = isError || hasWorkflowFailure || d.results.some((r) =>
1324
- (r.exitCode !== 0 || r.timedOut) && !r.interrupted && !r.detached && r.progress?.status !== "running",
1325
- );
1326
- const hasEmptyWithoutTarget = !d.intercomDelivery?.delivered && d.results.some((r) =>
1327
- r.exitCode === 0
1328
- && r.progress?.status !== "running"
1329
- && hasEmptyTextOutputWithoutOutputTarget(r.task, getSingleResultOutput(r)),
1330
- );
1331
- const icon = hasRunning
1332
- ? theme.fg("warning", "running")
1333
- : hasFailure
1334
- ? theme.fg("error", "failed")
1335
- : hasWorkflowBlocked
1336
- ? theme.fg("warning", "needs your action")
1337
- : hasWorkflowPause
1338
- ? theme.fg("warning", "paused")
1339
- : hasEmptyWithoutTarget
1340
- ? theme.fg("warning", "warning")
1341
- : theme.fg("success", "ok");
1342
-
1343
- const totalSummary =
1344
- d.progressSummary ||
1345
- d.results.reduce(
1346
- (acc, r) => {
1347
- const prog = r.progress || r.progressSummary;
1348
- if (prog) {
1349
- acc.toolCount += prog.toolCount;
1350
- acc.tokens += prog.tokens;
1351
- acc.durationMs =
1352
- d.mode === "chain"
1353
- ? acc.durationMs + prog.durationMs
1354
- : Math.max(acc.durationMs, prog.durationMs);
1355
- }
1356
- return acc;
1357
- },
1358
- { toolCount: 0, tokens: 0, durationMs: 0 },
1359
- );
1360
-
1361
- const summaryStr =
1362
- totalSummary.toolCount || totalSummary.tokens
1363
- ? ` | ${totalSummary.toolCount} tools, ${formatTokens(totalSummary.tokens)} tok, ${formatDuration(totalSummary.durationMs)}`
1364
- : "";
1365
-
1366
- const modeLabel = d.mode;
1367
- const contextBadge = d.context === "fork" ? theme.fg("warning", " [fork]") : "";
1368
- const multiLabel = buildMultiProgressLabel(d, hasRunning);
1369
- if (!hasRunning) multiLabel.showActiveGroupOnly = false;
1370
- const itemTitle = multiLabel.itemTitle;
1371
-
1372
- const chainVis = d.chainAgents?.length && !multiLabel.hasParallelInChain
1373
- ? d.chainAgents
1374
- .map((agent, i) => {
1375
- const result = d.results[i];
1376
- const isFailed = result && result.exitCode !== 0 && result.progress?.status !== "running";
1377
- const isComplete = result && isDoneResult(result);
1378
- const isEmptyWithoutTarget = !d.intercomDelivery?.delivered && Boolean(result)
1379
- && Boolean(isComplete)
1380
- && hasEmptyTextOutputWithoutOutputTarget(result.task, getSingleResultOutput(result));
1381
- const isCurrent = i === (d.currentStepIndex ?? d.results.length);
1382
- const stepIcon = isFailed
1383
- ? theme.fg("error", "failed")
1384
- : result?.acceptance?.status === "blocked" ? theme.fg("warning", "needs your action")
1385
- : result?.interrupted || result?.detached ? theme.fg("warning", result.detached ? "detached" : "paused")
1386
- : isEmptyWithoutTarget
1387
- ? theme.fg("warning", "warning")
1388
- : isComplete
1389
- ? theme.fg("success", "done")
1390
- : isCurrent && hasRunning
1391
- ? theme.fg("warning", "running")
1392
- : theme.fg("dim", "pending");
1393
- return `${stepIcon} ${agent}`;
1394
- })
1395
- .join(theme.fg("dim", " → "))
1396
- : null;
1397
-
1398
- const w = getTermWidth() - 4;
1399
- const fit = (text: string) => expanded ? text : truncLine(text, w);
1400
- const c = new Container();
1401
- c.addChild(
1402
- new Text(
1403
- fit(`${icon} ${theme.fg("toolTitle", theme.bold(modeLabel))}${contextBadge} · ${multiLabel.headerLabel}${summaryStr}`),
1404
- 0,
1405
- 0,
1406
- ),
1407
- );
1408
- if ((isError || hasWorkflowFailure || hasWorkflowBlocked || hasWorkflowPause) && d.runId) c.addChild(new Text(theme.fg("dim", `Run: ${d.runId}`), 0, 0));
1409
- if (chainVis) {
1410
- c.addChild(new Text(fit(` ${chainVis}`), 0, 0));
1411
- }
1412
- if (receiptText) c.addChild(new Text(receiptText, 0, 0));
1413
-
1414
- const useResultsDirectly = multiLabel.hasParallelInChain || !d.chainAgents?.length;
1415
- const displayStart = multiLabel.showActiveGroupOnly ? multiLabel.groupStartIndex : 0;
1416
- const displayEnd = multiLabel.showActiveGroupOnly ? multiLabel.groupEndIndex : (useResultsDirectly ? d.results.length : d.chainAgents!.length);
1417
- const chainEntries = buildChainRenderEntries(d, multiLabel);
1418
- const renderEntries = chainEntries ?? Array.from({ length: displayEnd - displayStart }, (_, offset): ChainRenderEntry => {
1419
- const i = displayStart + offset;
1420
- const r = d.results[i];
1421
- const rowNumber = multiLabel.showActiveGroupOnly ? (i - multiLabel.groupStartIndex + 1) : (i + 1);
1422
- return { kind: "result", resultIndex: i, rowNumber, agentName: useResultsDirectly ? (r?.agent || `step-${rowNumber}`) : (d.chainAgents![i] || r?.agent || `step-${rowNumber}`) };
1423
- });
1424
-
1425
- c.addChild(new Spacer(1));
1426
-
1427
- for (const entry of renderEntries) {
1428
- if (entry.kind === "placeholder") {
1429
- const statusLabel = widgetStepStatus(entry.status as AsyncJobStep["status"], theme);
1430
- c.addChild(new Text(fit(` ${statusLabel} ${entry.stepLabel}: ${theme.bold(entry.agentName)}`), 0, 0));
1431
- c.addChild(new Text(theme.fg(entry.status === "failed" ? "error" : "dim", ` status: ${entry.status}`), 0, 0));
1432
- if (entry.error) c.addChild(new Text(theme.fg("error", ` error: ${entry.error}`), 0, 0));
1433
- c.addChild(new Spacer(1));
1434
- continue;
1435
- }
1436
- const i = entry.resultIndex;
1437
- const r = d.results[i];
1438
- const rowNumber = entry.rowNumber;
1439
- const agentName = entry.agentName;
1440
-
1441
- if (!r) {
1442
- const pendingLabel = chainEntries ? resultRowLabel(d, multiLabel, i, rowNumber) : `${itemTitle} ${rowNumber}`;
1443
- c.addChild(new Text(fit(theme.fg("dim", ` ${pendingLabel}: ${agentName}`)), 0, 0));
1444
- c.addChild(new Text(theme.fg("dim", ` status: pending`), 0, 0));
1445
- c.addChild(new Spacer(1));
1446
- continue;
1447
- }
1448
-
1449
- const progressFromArray = d.progress?.find((p) => p.index === i)
1450
- || d.progress?.find((p) => p.agent === r.agent && p.status === "running");
1451
- const rProg = r.progress || progressFromArray || r.progressSummary;
1452
- const rRunning = rProg?.status === "running";
1453
- const stepNumber = typeof rProg?.index === "number" ? rProg.index + 1 : i + 1;
1454
-
1455
- const resultOutput = r.truncation?.text || getSingleResultOutput(r);
1456
- const statusIcon = rRunning
1457
- ? theme.fg("warning", "running")
1458
- : r.detached || r.interrupted
1459
- ? theme.fg("warning", r.detached ? "detached" : "paused")
1460
- : r.exitCode !== 0
1461
- ? theme.fg("error", "failed")
1462
- : r.acceptance?.status === "blocked"
1463
- ? theme.fg("warning", "needs your action")
1464
- : !d.intercomDelivery?.delivered && hasEmptyTextOutputWithoutOutputTarget(r.task, resultOutput)
1465
- ? theme.fg("warning", "warning")
1466
- : theme.fg("success", "done");
1467
- const stats = rProg ? ` | ${rProg.toolCount} tools, ${formatDuration(rProg.durationMs)}` : "";
1468
- const modelDisplay = modelThinkingBadge(theme, r.model);
1469
- const stepLabel = resultRowLabel(d, multiLabel, i, stepNumber);
1470
- const stepHeader = rRunning
1471
- ? `${statusIcon} ${stepLabel}: ${theme.bold(theme.fg("warning", r.agent))}${modelDisplay}${stats}`
1472
- : `${statusIcon} ${stepLabel}: ${theme.bold(r.agent)}${modelDisplay}${stats}`;
1473
- const toolCallLines = getToolCallLines(r, expanded);
1474
- c.addChild(new Text(fit(stepHeader), 0, 0));
1475
-
1476
- const taskMaxLen = Math.max(20, w - 12);
1477
- const taskPreview = expanded || r.task.length <= taskMaxLen
1478
- ? r.task
1479
- : `${r.task.slice(0, taskMaxLen)}...`;
1480
- c.addChild(new Text(fit(theme.fg("dim", ` task: ${taskPreview}`)), 0, 0));
1481
-
1482
- const outputTarget = extractOutputTarget(r.task);
1483
- if (outputTarget) {
1484
- c.addChild(new Text(fit(theme.fg("dim", ` output: ${outputTarget}`)), 0, 0));
1485
- }
1486
-
1487
- if (r.skills?.length) {
1488
- c.addChild(new Text(fit(theme.fg("dim", ` skills: ${r.skills.join(", ")}`)), 0, 0));
1489
- }
1490
- if (r.skillsWarning) {
1491
- c.addChild(new Text(fit(theme.fg("warning", ` Warning: ${r.skillsWarning}`)), 0, 0));
1492
- }
1493
- if (r.attemptedModels && r.attemptedModels.length > 1) {
1494
- c.addChild(new Text(fit(theme.fg("dim", ` model attempts: ${r.attemptedModels.join(" → ")}`)), 0, 0));
1495
- }
1496
-
1497
- if (rRunning && rProg) {
1498
- if (rProg.skills?.length) {
1499
- c.addChild(new Text(fit(theme.fg("accent", ` skills: ${rProg.skills.join(", ")}`)), 0, 0));
1500
- }
1501
- const progressSnapshotNow = snapshotNowForProgress(rProg);
1502
- const toolLine = formatCurrentToolLine(rProg, w, expanded, progressSnapshotNow);
1503
- if (toolLine) {
1504
- c.addChild(new Text(fit(theme.fg("warning", ` > ${toolLine}`)), 0, 0));
1505
- }
1506
- const liveStatusLine = buildLiveStatusLine(rProg, progressSnapshotNow);
1507
- if (liveStatusLine) {
1508
- c.addChild(new Text(fit(theme.fg("accent", ` ${liveStatusLine}`)), 0, 0));
1509
- }
1510
- if (expandKey) c.addChild(new Text(fit(theme.fg("accent", ` Press ${expandKey} for live detail`)), 0, 0));
1511
- if (r.artifactPaths) {
1512
- c.addChild(new Text(fit(theme.fg("dim", ` artifacts: ${shortenPath(r.artifactPaths.outputPath)}`)), 0, 0));
1513
- }
1514
- if (rProg.recentTools?.length) {
1515
- for (const t of rProg.recentTools.slice(-3)) {
1516
- const maxArgsLen = Math.max(40, w - 30);
1517
- const argsPreview = expanded || t.args.length <= maxArgsLen
1518
- ? t.args
1519
- : `${t.args.slice(0, maxArgsLen)}...`;
1520
- c.addChild(new Text(fit(theme.fg("dim", ` ${t.tool}: ${argsPreview}`)), 0, 0));
1521
- }
1522
- }
1523
- const recentLines = (rProg.recentOutput ?? []).slice(-5);
1524
- for (const line of recentLines) {
1525
- c.addChild(new Text(fit(theme.fg("dim", ` ${line}`)), 0, 0));
1526
- }
1527
- }
1528
-
1529
- if (!rRunning && r.artifactPaths) {
1530
- c.addChild(new Text(fit(theme.fg("dim", ` artifacts: ${shortenPath(r.artifactPaths.outputPath)}`)), 0, 0));
1531
- }
1532
-
1533
- if (expanded && !rRunning) {
1534
- for (const line of toolCallLines) {
1535
- c.addChild(new Text(fit(theme.fg("muted", ` ${line}`)), 0, 0));
1536
- }
1537
- if (toolCallLines.length) c.addChild(new Spacer(1));
1538
- if (r.error && !resultOutput.includes(r.error)) c.addChild(new Text(theme.fg("error", r.error), 0, 0));
1539
- if (resultOutput) c.addChild(new Markdown(resultOutput, 0, 0, mdTheme));
1540
- }
1541
-
1542
- c.addChild(new Spacer(1));
1543
- }
1544
-
1545
- if (d.artifacts) {
1546
- c.addChild(new Spacer(1));
1547
- c.addChild(new Text(fit(theme.fg("dim", `Artifacts dir: ${shortenPath(d.artifacts.dir)}`)), 0, 0));
1548
- }
1549
- return c;
92
+ const isError = context?.isError ?? result.isError ?? false,
93
+ d = result.details;
94
+ if (!d || d.results.length === 0) {
95
+ return textResult(result, options.expanded, theme, isError);
96
+ }
97
+ if (options.expanded) {
98
+ return expandedResult(result, d, theme, isError);
99
+ }
100
+ const first = d.results.at(0);
101
+ return singleResult(d) && first
102
+ ? renderSingleCompact(d, first, theme, isError)
103
+ : renderMultiCompact(d, theme, isError);
1550
104
  }