@fitchmultz/pi-subagents 0.44.7 → 0.45.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 (947) hide show
  1. package/CHANGELOG.md +258 -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 +79 -0
  111. package/dist/pi-intercom/bridge-http.js +86 -0
  112. package/dist/pi-intercom/bridge-protocol.js +106 -0
  113. package/dist/pi-intercom/bridge-routes.js +64 -0
  114. package/dist/pi-intercom/bridge-session.js +416 -0
  115. package/dist/pi-intercom/bridge.js +182 -603
  116. package/dist/pi-intercom/broker/broker.js +236 -244
  117. package/dist/pi-intercom/broker/client.js +189 -304
  118. package/dist/pi-intercom/broker/connection.js +165 -0
  119. package/dist/pi-intercom/broker/framing.js +5 -3
  120. package/dist/pi-intercom/broker/paths.js +4 -1
  121. package/dist/pi-intercom/broker/pid.js +53 -25
  122. package/dist/pi-intercom/broker/snapshot-reader.js +78 -0
  123. package/dist/pi-intercom/broker/snapshots.js +188 -0
  124. package/dist/pi-intercom/broker/spawn.js +36 -25
  125. package/dist/pi-intercom/config.js +85 -71
  126. package/dist/pi-intercom/connection.js +297 -0
  127. package/dist/pi-intercom/identity.js +58 -0
  128. package/dist/pi-intercom/inbound-delivery.js +356 -0
  129. package/dist/pi-intercom/inbound-journal.js +303 -0
  130. package/dist/pi-intercom/inbound-record.js +113 -0
  131. package/dist/pi-intercom/inbound.js +179 -0
  132. package/dist/pi-intercom/index.js +79 -2933
  133. package/dist/pi-intercom/inspection.js +62 -0
  134. package/dist/pi-intercom/interview.js +236 -0
  135. package/dist/pi-intercom/lifecycle.js +168 -0
  136. package/dist/pi-intercom/message-format.js +69 -0
  137. package/dist/pi-intercom/outbound.js +304 -0
  138. package/dist/pi-intercom/peer-awareness.js +66 -0
  139. package/dist/pi-intercom/reply-tracker.js +62 -43
  140. package/dist/pi-intercom/reply-wait.js +201 -0
  141. package/dist/pi-intercom/runtime-types.js +3 -0
  142. package/dist/pi-intercom/session-events.js +180 -0
  143. package/dist/pi-intercom/session-targets.js +42 -34
  144. package/dist/pi-intercom/subagent-events.js +238 -0
  145. package/dist/pi-intercom/subagent-live-events.js +129 -86
  146. package/dist/pi-intercom/supervisor.js +282 -0
  147. package/dist/pi-intercom/tool-arguments.js +74 -0
  148. package/dist/pi-intercom/tool-registration.js +95 -0
  149. package/dist/pi-intercom/tool-results.js +38 -0
  150. package/dist/pi-intercom/tool-schemas.js +82 -0
  151. package/dist/pi-intercom/tools.js +51 -0
  152. package/dist/pi-intercom/topic-actions.js +177 -0
  153. package/dist/pi-intercom/topics.js +177 -92
  154. package/dist/pi-intercom/transport.js +1 -0
  155. package/dist/pi-intercom/types.js +151 -114
  156. package/dist/pi-intercom/ui/compose-paste.js +93 -0
  157. package/dist/pi-intercom/ui/compose.js +121 -143
  158. package/dist/pi-intercom/ui/inline-message.js +59 -59
  159. package/dist/pi-intercom/ui/message-renderers.js +147 -0
  160. package/dist/pi-intercom/ui/overlay.js +181 -0
  161. package/dist/pi-intercom/ui/session-format.js +124 -0
  162. package/dist/pi-intercom/ui/session-list.js +76 -43
  163. package/dist/pi-intercom/ui/tool-renderers.js +110 -0
  164. package/dist/pi-intercom/ui/topic-view.js +88 -0
  165. package/dist/pi-intercom/validation.js +59 -0
  166. package/dist/runs/background/async-chain-plan.js +265 -0
  167. package/dist/runs/background/async-control.js +134 -58
  168. package/dist/runs/background/async-execution.js +136 -633
  169. package/dist/runs/background/async-job-admission.js +40 -0
  170. package/dist/runs/background/async-job-projection.js +169 -0
  171. package/dist/runs/background/async-job-tracker.js +297 -327
  172. package/dist/runs/background/async-launch.js +216 -0
  173. package/dist/runs/background/async-plan.js +116 -0
  174. package/dist/runs/background/async-result-file.js +47 -23
  175. package/dist/runs/background/async-resume.js +195 -253
  176. package/dist/runs/background/async-run-discovery.js +213 -0
  177. package/dist/runs/background/async-run-location.js +135 -0
  178. package/dist/runs/background/async-run-record.js +1 -0
  179. package/dist/runs/background/async-run-repair.js +223 -0
  180. package/dist/runs/background/async-run-summary.js +1 -0
  181. package/dist/runs/background/async-single-plan.js +105 -0
  182. package/dist/runs/background/async-status-projection.js +155 -0
  183. package/dist/runs/background/async-status-render.js +117 -0
  184. package/dist/runs/background/async-status-validation.js +181 -0
  185. package/dist/runs/background/async-status.js +3 -442
  186. package/dist/runs/background/async-value.js +21 -0
  187. package/dist/runs/background/completion-dedupe.js +36 -12
  188. package/dist/runs/background/completion-delivery.js +211 -205
  189. package/dist/runs/background/completion-receipts.js +237 -0
  190. package/dist/runs/background/legacy-status-acceptance.js +34 -0
  191. package/dist/runs/background/notify.js +96 -48
  192. package/dist/runs/background/parallel-groups.js +36 -19
  193. package/dist/runs/background/result-payload.js +178 -0
  194. package/dist/runs/background/result-watcher.js +348 -339
  195. package/dist/runs/background/run-id-resolver.js +53 -33
  196. package/dist/runs/background/run-schemas.js +189 -0
  197. package/dist/runs/background/run-status-contracts.js +14 -0
  198. package/dist/runs/background/run-status-render.js +270 -0
  199. package/dist/runs/background/run-status.js +231 -339
  200. package/dist/runs/background/runner-attempt-output.js +155 -0
  201. package/dist/runs/background/runner-attempt.js +263 -0
  202. package/dist/runs/background/runner-child-executor.js +166 -0
  203. package/dist/runs/background/runner-child-observer.js +192 -0
  204. package/dist/runs/background/runner-completion-summary.js +71 -0
  205. package/dist/runs/background/runner-completion.js +211 -0
  206. package/dist/runs/background/runner-config.js +1 -0
  207. package/dist/runs/background/runner-contract.js +1 -0
  208. package/dist/runs/background/runner-dynamic.js +121 -0
  209. package/dist/runs/background/runner-finalization.js +149 -0
  210. package/dist/runs/background/runner-graph.js +68 -0
  211. package/dist/runs/background/runner-initial-status.js +60 -0
  212. package/dist/runs/background/runner-lifecycle.js +142 -0
  213. package/dist/runs/background/runner-monitor.js +309 -0
  214. package/dist/runs/background/runner-parallel.js +204 -0
  215. package/dist/runs/background/runner-prepare-step.js +86 -0
  216. package/dist/runs/background/runner-reporting.js +153 -0
  217. package/dist/runs/background/runner-status.js +148 -0
  218. package/dist/runs/background/runner-step-output.js +92 -0
  219. package/dist/runs/background/runner-step-record.js +45 -0
  220. package/dist/runs/background/runner-step.js +112 -0
  221. package/dist/runs/background/runner-streaming.js +173 -0
  222. package/dist/runs/background/runner-workflow.js +213 -0
  223. package/dist/runs/background/stale-run-reconciler.js +155 -292
  224. package/dist/runs/background/subagent-runner-launcher.js +33 -13
  225. package/dist/runs/background/subagent-runner.js +51 -1842
  226. package/dist/runs/background/top-level-async.js +2 -1
  227. package/dist/runs/foreground/chain-clarify.js +293 -1017
  228. package/dist/runs/foreground/clarification-draft.js +87 -0
  229. package/dist/runs/foreground/clarify-contracts.js +1 -0
  230. package/dist/runs/foreground/clarify-invocation.js +129 -38
  231. package/dist/runs/foreground/clarify-text-editor.js +210 -0
  232. package/dist/runs/foreground/clarify-view.js +167 -0
  233. package/dist/runs/foreground/execution-input.js +226 -353
  234. package/dist/runs/foreground/execution-routing.js +33 -0
  235. package/dist/runs/foreground/execution-timeout.js +81 -0
  236. package/dist/runs/foreground/foreground-control.js +222 -763
  237. package/dist/runs/foreground/foreground-memory.js +99 -0
  238. package/dist/runs/foreground/invocation-context.js +55 -0
  239. package/dist/runs/foreground/invocation-execution.js +331 -0
  240. package/dist/runs/foreground/invocation-expansion.js +48 -0
  241. package/dist/runs/foreground/management-actions.js +295 -0
  242. package/dist/runs/foreground/nested-control.js +179 -0
  243. package/dist/runs/foreground/question-continuation.js +78 -0
  244. package/dist/runs/foreground/question-control.js +155 -50
  245. package/dist/runs/foreground/resume-target.js +221 -0
  246. package/dist/runs/foreground/revival-launch-policy.js +58 -0
  247. package/dist/runs/foreground/run-async-path.js +190 -178
  248. package/dist/runs/foreground/run-interrupt.js +164 -0
  249. package/dist/runs/foreground/run-nudge.js +278 -0
  250. package/dist/runs/foreground/saved-revival.js +251 -0
  251. package/dist/runs/foreground/subagent-executor.js +135 -424
  252. package/dist/runs/foreground/subagent-params.js +67 -30
  253. package/dist/runs/foreground/wait-observation.js +104 -0
  254. package/dist/runs/foreground/wait-registration.js +17 -0
  255. package/dist/runs/foreground/wait-run.js +283 -123
  256. package/dist/runs/shared/acceptance-contract.js +193 -99
  257. package/dist/runs/shared/acceptance-defaults.js +1 -0
  258. package/dist/runs/shared/acceptance-evaluation.js +201 -181
  259. package/dist/runs/shared/acceptance-finalization.js +256 -180
  260. package/dist/runs/shared/acceptance-outcome.js +53 -0
  261. package/dist/runs/shared/acceptance-reports.js +137 -72
  262. package/dist/runs/shared/acceptance-submission.js +167 -0
  263. package/dist/runs/shared/acceptance-verification.js +100 -0
  264. package/dist/runs/shared/chain-outputs.js +80 -52
  265. package/dist/runs/shared/child-attempt-types.js +1 -0
  266. package/dist/runs/shared/child-attempt.js +314 -667
  267. package/dist/runs/shared/child-boundaries.js +173 -0
  268. package/dist/runs/shared/child-context-filter.js +44 -0
  269. package/dist/runs/shared/child-event-observer.js +390 -0
  270. package/dist/runs/shared/child-execution-cwd.js +73 -43
  271. package/dist/runs/shared/child-execution-outcome.js +72 -0
  272. package/dist/runs/shared/child-invocation.js +54 -0
  273. package/dist/runs/shared/child-message-validation.js +215 -0
  274. package/dist/runs/shared/child-native-accounting.js +106 -0
  275. package/dist/runs/shared/child-native-observation.js +69 -0
  276. package/dist/runs/shared/child-observation-spool.js +164 -0
  277. package/dist/runs/shared/child-observations.js +157 -76
  278. package/dist/runs/shared/child-presence.js +3 -0
  279. package/dist/runs/shared/child-route-env.js +119 -0
  280. package/dist/runs/shared/child-stream-observer.js +221 -0
  281. package/dist/runs/shared/child-stream-projection.js +137 -0
  282. package/dist/runs/shared/child-structured-tool.js +27 -0
  283. package/dist/runs/shared/claude-code.js +121 -158
  284. package/dist/runs/shared/claude-model.js +42 -0
  285. package/dist/runs/shared/claude-session.js +83 -0
  286. package/dist/runs/shared/claude-types.js +1 -0
  287. package/dist/runs/shared/completion-guard.js +18 -18
  288. package/dist/runs/shared/control-event-policy.js +8 -0
  289. package/dist/runs/shared/control-notification-owner.js +13 -0
  290. package/dist/runs/shared/dynamic-fanout.js +188 -173
  291. package/dist/runs/shared/dynamic-item.js +102 -0
  292. package/dist/runs/shared/history-index.js +62 -36
  293. package/dist/runs/shared/legacy-async-recovery.js +114 -0
  294. package/dist/runs/shared/legacy-output-recovery.js +148 -0
  295. package/dist/runs/shared/mcp-config-schema.js +36 -0
  296. package/dist/runs/shared/mcp-direct-tool-allowlist.js +147 -116
  297. package/dist/runs/shared/model-fallback.js +97 -38
  298. package/dist/runs/shared/mutating-tool-guard.js +128 -88
  299. package/dist/runs/shared/native-finalization-types.js +1 -0
  300. package/dist/runs/shared/native-finalization.js +155 -76
  301. package/dist/runs/shared/native-usage.js +238 -98
  302. package/dist/runs/shared/nested-control-records.js +128 -0
  303. package/dist/runs/shared/nested-events.js +8 -700
  304. package/dist/runs/shared/nested-path.js +32 -28
  305. package/dist/runs/shared/nested-projection.js +155 -0
  306. package/dist/runs/shared/nested-protocol.js +18 -0
  307. package/dist/runs/shared/nested-registry.js +191 -0
  308. package/dist/runs/shared/nested-render.js +106 -58
  309. package/dist/runs/shared/nested-route.js +220 -0
  310. package/dist/runs/shared/nested-validation.js +216 -0
  311. package/dist/runs/shared/owned-child-evidence.js +125 -0
  312. package/dist/runs/shared/owned-run-list.js +146 -0
  313. package/dist/runs/shared/owned-run-observation.js +267 -0
  314. package/dist/runs/shared/owned-run-progress.js +109 -0
  315. package/dist/runs/shared/owned-run-read-state.js +1 -0
  316. package/dist/runs/shared/owned-run-results.js +202 -0
  317. package/dist/runs/shared/owned-run-status.js +180 -0
  318. package/dist/runs/shared/owned-run-view.js +51 -0
  319. package/dist/runs/shared/owned-workflow-details.js +17 -0
  320. package/dist/runs/shared/owner-output.js +43 -0
  321. package/dist/runs/shared/parallel-utils.js +36 -28
  322. package/dist/runs/shared/parent-receipts.js +90 -32
  323. package/dist/runs/shared/parent-usage.js +169 -57
  324. package/dist/runs/shared/pi-args.js +108 -201
  325. package/dist/runs/shared/pi-launch-input.js +1 -0
  326. package/dist/runs/shared/pi-session-launch.js +86 -0
  327. package/dist/runs/shared/pi-spawn.js +35 -21
  328. package/dist/runs/shared/run-history-queries.js +126 -35
  329. package/dist/runs/shared/run-history.js +101 -35
  330. package/dist/runs/shared/run-metadata-paths.js +14 -0
  331. package/dist/runs/shared/run-metadata.js +158 -0
  332. package/dist/runs/shared/run-persistence.js +131 -0
  333. package/dist/runs/shared/run-records.js +9 -679
  334. package/dist/runs/shared/run-restoration.js +338 -0
  335. package/dist/runs/shared/run-state-owner.js +26 -0
  336. package/dist/runs/shared/session-cwd-preload.js +27 -18
  337. package/dist/runs/shared/single-output.js +148 -70
  338. package/dist/runs/shared/stable-value.js +15 -0
  339. package/dist/runs/shared/streaming-text.js +23 -12
  340. package/dist/runs/shared/structured-output.js +25 -13
  341. package/dist/runs/shared/subagent-control.js +167 -100
  342. package/dist/runs/shared/subagent-prompt-runtime.js +27 -130
  343. package/dist/runs/shared/subagent-tool-loop-guard.js +81 -52
  344. package/dist/runs/shared/supervisor-question-state.js +145 -0
  345. package/dist/runs/shared/supervisor-questions.js +185 -254
  346. package/dist/runs/shared/workflow-graph.js +259 -167
  347. package/dist/runs/shared/workflow-policy.js +120 -48
  348. package/dist/runs/shared/worktree-contract.js +1 -0
  349. package/dist/runs/shared/worktree-hook.js +138 -0
  350. package/dist/runs/shared/worktree-location.js +47 -0
  351. package/dist/runs/shared/worktree-preservation.js +8 -0
  352. package/dist/runs/shared/worktree-process.js +107 -0
  353. package/dist/runs/shared/worktree.js +183 -322
  354. package/dist/shared/agent-context-policy.js +34 -44
  355. package/dist/shared/agent-dir.js +14 -0
  356. package/dist/shared/artifacts.js +30 -12
  357. package/dist/shared/atomic-json.js +4 -1
  358. package/dist/shared/chain-files.js +72 -0
  359. package/dist/shared/concurrency.js +18 -0
  360. package/dist/shared/content-preview.js +115 -0
  361. package/dist/shared/cooperative.js +5 -2
  362. package/dist/shared/execution-cwd.js +13 -5
  363. package/dist/shared/file-coalescer.js +2 -1
  364. package/dist/shared/fork-context.js +10 -12
  365. package/dist/shared/formatters.js +45 -19
  366. package/dist/shared/journal-frames.js +164 -0
  367. package/dist/shared/journal-projections.js +121 -0
  368. package/dist/shared/journal-reader.js +6 -407
  369. package/dist/shared/json-projection.js +191 -0
  370. package/dist/shared/lazy-tools.js +19 -8
  371. package/dist/shared/message-output.js +116 -0
  372. package/dist/shared/model-info.js +31 -14
  373. package/dist/shared/native-import.js +20 -0
  374. package/dist/shared/native-journal.js +140 -0
  375. package/dist/shared/native-session.js +5 -2
  376. package/dist/shared/native-tui.js +7 -2
  377. package/dist/shared/native-typebox.js +15 -3
  378. package/dist/shared/output-page.js +63 -0
  379. package/dist/shared/post-exit-stdio-guard.js +128 -88
  380. package/dist/shared/prompt-sections.js +2 -0
  381. package/dist/shared/session-entries.js +70 -35
  382. package/dist/shared/session-identity.js +5 -3
  383. package/dist/shared/settings.js +62 -192
  384. package/dist/shared/status-format.js +97 -37
  385. package/dist/shared/task-file-policy.js +24 -0
  386. package/dist/shared/temp-root.js +44 -15
  387. package/dist/shared/types/acceptance.js +1 -0
  388. package/dist/shared/types/async.js +1 -0
  389. package/dist/shared/types/config.js +1 -0
  390. package/dist/shared/types/details.js +1 -0
  391. package/dist/shared/types/history.js +1 -0
  392. package/dist/shared/types/inputs.js +1 -0
  393. package/dist/shared/types/intercom.js +1 -0
  394. package/dist/shared/types/launch.js +1 -0
  395. package/dist/shared/types/messages.js +1 -0
  396. package/dist/shared/types/nested.js +1 -0
  397. package/dist/shared/types/owned-runs.js +1 -0
  398. package/dist/shared/types/progress.js +1 -0
  399. package/dist/shared/types/questions.js +1 -0
  400. package/dist/shared/types/results.js +1 -0
  401. package/dist/shared/types/state.js +1 -0
  402. package/dist/shared/types/usage.js +1 -0
  403. package/dist/shared/types/workflow.js +1 -0
  404. package/dist/shared/types.js +75 -30
  405. package/dist/shared/unknown.js +52 -0
  406. package/dist/shared/utils.js +54 -260
  407. package/dist/slash/command-discovery.js +82 -0
  408. package/dist/slash/command-input.js +190 -0
  409. package/dist/slash/prompt-template-bridge.js +94 -270
  410. package/dist/slash/prompt-template-contract.js +77 -0
  411. package/dist/slash/prompt-template-results.js +125 -0
  412. package/dist/slash/request-protocol.js +68 -0
  413. package/dist/slash/result-delivery.js +237 -0
  414. package/dist/slash/saved-chain.js +78 -0
  415. package/dist/slash/slash-bridge.js +123 -89
  416. package/dist/slash/slash-commands.js +101 -572
  417. package/dist/slash/slash-live-state.js +68 -47
  418. package/dist/tui/action-hints.js +50 -22
  419. package/dist/tui/activity-lines.js +32 -0
  420. package/dist/tui/agent-browser.js +290 -0
  421. package/dist/tui/agent-controls.js +289 -0
  422. package/dist/tui/agent-conversation.js +444 -0
  423. package/dist/tui/agent-dock.js +171 -0
  424. package/dist/tui/agent-history.js +50 -183
  425. package/dist/tui/agent-model.js +62 -0
  426. package/dist/tui/agent-picker.js +262 -0
  427. package/dist/tui/agent-view.js +232 -1564
  428. package/dist/tui/compact-results.js +193 -0
  429. package/dist/tui/conversation-details.js +190 -0
  430. package/dist/tui/conversation-frame.js +135 -0
  431. package/dist/tui/conversation-history.js +201 -0
  432. package/dist/tui/conversation-menu.js +78 -0
  433. package/dist/tui/conversation-notices.js +79 -0
  434. package/dist/tui/conversation-selection.js +58 -0
  435. package/dist/tui/conversation-viewport.js +129 -0
  436. package/dist/tui/direction-message.js +27 -0
  437. package/dist/tui/display.js +282 -0
  438. package/dist/tui/editor-viewport.js +18 -0
  439. package/dist/tui/expanded-content.js +87 -0
  440. package/dist/tui/expanded-multi.js +170 -0
  441. package/dist/tui/expanded-single.js +65 -0
  442. package/dist/tui/history-cards.js +216 -0
  443. package/dist/tui/history-display.js +238 -0
  444. package/dist/tui/history-json.js +16 -0
  445. package/dist/tui/history-metadata.js +152 -0
  446. package/dist/tui/history-record.js +251 -0
  447. package/dist/tui/history-text.js +34 -0
  448. package/dist/tui/indexed-history.js +116 -0
  449. package/dist/tui/nested-widget.js +120 -0
  450. package/dist/tui/picker-page.js +59 -0
  451. package/dist/tui/render.js +58 -1501
  452. package/dist/tui/result-facts.js +64 -0
  453. package/dist/tui/task-observation.js +59 -0
  454. package/dist/tui/task-store.js +366 -0
  455. package/dist/tui/text-values.js +16 -0
  456. package/dist/tui/view-connections.js +47 -0
  457. package/dist/tui/view-model.js +94 -0
  458. package/dist/tui/view-persistence.js +51 -0
  459. package/dist/tui/view-ports.js +1 -0
  460. package/dist/tui/view-session.js +37 -0
  461. package/dist/tui/widget-details.js +144 -0
  462. package/dist/tui/widget-status.js +245 -0
  463. package/dist/tui/widget.js +145 -0
  464. package/dist/tui/workflow-labels.js +223 -0
  465. package/dist/tui/workflow-outcomes.js +124 -0
  466. package/docs/code-quality.md +38 -0
  467. package/docs/development.md +99 -0
  468. package/docs/intercom-bridge.md +22 -17
  469. package/docs/intercom.md +132 -106
  470. package/docs/performance.md +19 -17
  471. package/docs/reference.md +1472 -0
  472. package/generated/RunValidators.d.ts +28 -0
  473. package/generated/RunValidators.js +2 -0
  474. package/package.json +27 -6
  475. package/prompts/parallel-cleanup.md +3 -0
  476. package/prompts/parallel-context-build.md +3 -0
  477. package/prompts/parallel-handoff-plan.md +6 -0
  478. package/prompts/parallel-research.md +4 -0
  479. package/prompts/parallel-review.md +2 -0
  480. package/prompts/review-loop.md +2 -0
  481. package/scripts/build.mjs +129 -85
  482. package/scripts/check-compat.mjs +48 -11
  483. package/scripts/compat-host.mjs +35 -7
  484. package/scripts/generate-run-schemas.mjs +210 -0
  485. package/scripts/intercom-bridge-credentials.mjs +155 -28
  486. package/scripts/intercom-remote.mjs +175 -50
  487. package/scripts/local-install-smoke.mjs +47 -37
  488. package/scripts/native-package-smoke.mjs +135 -25
  489. package/scripts/package-smoke.mjs +360 -140
  490. package/scripts/prepare.mjs +54 -34
  491. package/scripts/real-pi-processes.mjs +261 -0
  492. package/scripts/real-pi-smoke.mjs +432 -351
  493. package/scripts/run-tests.mjs +29 -15
  494. package/skills/pi-intercom/SKILL.md +11 -11
  495. package/skills/pi-intercom/references/peer-sessions.md +7 -2
  496. package/skills/pi-subagents/SKILL.md +1 -1
  497. package/skills/pi-subagents/references/full-orchestration-guide.md +254 -112
  498. package/src/agents/agent-defaults.ts +13 -0
  499. package/src/agents/agent-definition.ts +253 -0
  500. package/src/agents/agent-management.ts +111 -808
  501. package/src/agents/agent-scope.ts +7 -3
  502. package/src/agents/agent-selection.ts +26 -16
  503. package/src/agents/agent-serializer.ts +84 -93
  504. package/src/agents/agents.ts +199 -906
  505. package/src/agents/builtin-overrides.ts +297 -0
  506. package/src/agents/chain-serializer.ts +3 -329
  507. package/src/agents/config-values.ts +57 -0
  508. package/src/agents/discovery-paths.ts +134 -0
  509. package/src/agents/frontmatter.ts +35 -17
  510. package/src/agents/identity.ts +49 -18
  511. package/src/agents/json-chain.ts +305 -0
  512. package/src/agents/management-config.ts +342 -0
  513. package/src/agents/management-format.ts +243 -0
  514. package/src/agents/management-persistence.ts +408 -0
  515. package/src/agents/management-targets.ts +260 -0
  516. package/src/agents/management-warnings.ts +115 -0
  517. package/src/agents/markdown-chain.ts +237 -0
  518. package/src/agents/package-agents.ts +231 -0
  519. package/src/agents/skill-files.ts +182 -0
  520. package/src/agents/skill-search-paths.ts +289 -0
  521. package/src/agents/skill-types.ts +31 -0
  522. package/src/agents/skills.ts +152 -610
  523. package/src/extension/acceptance-schema.ts +88 -0
  524. package/src/extension/chain-schema.ts +276 -0
  525. package/src/extension/child-runtime.ts +302 -0
  526. package/src/extension/compact-tools.ts +259 -102
  527. package/src/extension/config-schema.ts +59 -0
  528. package/src/extension/config.ts +39 -17
  529. package/src/extension/control-notice-schema.ts +55 -0
  530. package/src/extension/control-notices.ts +57 -33
  531. package/src/extension/delegation-bridges.ts +59 -0
  532. package/src/extension/doctor.ts +171 -135
  533. package/src/extension/everyday-schemas.ts +252 -0
  534. package/src/extension/fanout-child.ts +29 -308
  535. package/src/extension/index.ts +17 -642
  536. package/src/extension/message-renderers.ts +334 -0
  537. package/src/extension/nested-control-inbox.ts +196 -0
  538. package/src/extension/parent-runtime.ts +405 -0
  539. package/src/extension/primary-tool.ts +130 -0
  540. package/src/extension/result-snapshot.ts +35 -0
  541. package/src/extension/runtime-reload.ts +72 -0
  542. package/src/extension/runtime-storage.ts +49 -0
  543. package/src/extension/schema-overrides.ts +114 -0
  544. package/src/extension/schemas.ts +451 -418
  545. package/src/extension/tool-input.ts +40 -13
  546. package/src/extension/tool-result.ts +45 -14
  547. package/src/history/answer-texts.ts +74 -0
  548. package/src/history/canonical-result.ts +127 -51
  549. package/src/history/canonical-selection.ts +54 -0
  550. package/src/history/database-generation.ts +86 -0
  551. package/src/history/database.ts +86 -48
  552. package/src/history/entry-ingestion.ts +129 -0
  553. package/src/history/index.ts +426 -148
  554. package/src/history/ingest.ts +372 -342
  555. package/src/history/output-digests.ts +41 -0
  556. package/src/history/preview.ts +235 -0
  557. package/src/history/prune.ts +48 -0
  558. package/src/history/queries.ts +122 -256
  559. package/src/history/query-scope.ts +278 -0
  560. package/src/history/rows.ts +118 -0
  561. package/src/history/run-projection.ts +164 -0
  562. package/src/history/run-queries.ts +199 -0
  563. package/src/history/schema.ts +34 -0
  564. package/src/history/search-queries.ts +216 -0
  565. package/src/history/selected-record.ts +140 -0
  566. package/src/history/source-file.ts +51 -0
  567. package/src/history/source-watches.ts +156 -0
  568. package/src/history/store.ts +310 -197
  569. package/src/history/text-chunks.ts +181 -0
  570. package/src/history/text.ts +43 -0
  571. package/src/history/transcript-queries.ts +251 -0
  572. package/src/history/types.ts +27 -164
  573. package/src/history/values.ts +36 -0
  574. package/src/history/wire-inputs.ts +141 -0
  575. package/src/history/wire-models.ts +51 -0
  576. package/src/history/worker-runtime.ts +502 -0
  577. package/src/history/worker.ts +94 -336
  578. package/src/intercom/intercom-bridge.ts +136 -69
  579. package/src/intercom/live-intercom.ts +197 -92
  580. package/src/intercom/nested-result-tree.ts +213 -0
  581. package/src/intercom/result-delivery.ts +56 -0
  582. package/src/intercom/result-intercom.ts +364 -382
  583. package/src/pi-intercom/agent-dir.ts +9 -3
  584. package/src/pi-intercom/async.ts +33 -0
  585. package/src/pi-intercom/bridge-ask.ts +64 -0
  586. package/src/pi-intercom/bridge-config.ts +101 -0
  587. package/src/pi-intercom/bridge-http.ts +97 -0
  588. package/src/pi-intercom/bridge-protocol.ts +123 -0
  589. package/src/pi-intercom/bridge-routes.ts +90 -0
  590. package/src/pi-intercom/bridge-session.ts +506 -0
  591. package/src/pi-intercom/bridge.ts +258 -547
  592. package/src/pi-intercom/broker/broker.ts +326 -263
  593. package/src/pi-intercom/broker/client.ts +273 -354
  594. package/src/pi-intercom/broker/connection.ts +192 -0
  595. package/src/pi-intercom/broker/framing.ts +11 -6
  596. package/src/pi-intercom/broker/paths.ts +28 -25
  597. package/src/pi-intercom/broker/pid.ts +68 -24
  598. package/src/pi-intercom/broker/snapshot-reader.ts +91 -0
  599. package/src/pi-intercom/broker/snapshots.ts +241 -0
  600. package/src/pi-intercom/broker/spawn.ts +58 -34
  601. package/src/pi-intercom/config.ts +113 -108
  602. package/src/pi-intercom/connection.ts +346 -0
  603. package/src/pi-intercom/identity.ts +71 -0
  604. package/src/pi-intercom/inbound-delivery.ts +450 -0
  605. package/src/pi-intercom/inbound-journal.ts +350 -0
  606. package/src/pi-intercom/inbound-record.ts +141 -0
  607. package/src/pi-intercom/inbound.ts +237 -0
  608. package/src/pi-intercom/index.ts +86 -2923
  609. package/src/pi-intercom/inspection.ts +82 -0
  610. package/src/pi-intercom/interview.ts +295 -0
  611. package/src/pi-intercom/lifecycle.ts +183 -0
  612. package/src/pi-intercom/message-format.ts +77 -0
  613. package/src/pi-intercom/outbound.ts +418 -0
  614. package/src/pi-intercom/peer-awareness.ts +84 -0
  615. package/src/pi-intercom/reply-tracker.ts +121 -59
  616. package/src/pi-intercom/reply-wait.ts +247 -0
  617. package/src/pi-intercom/runtime-types.ts +97 -0
  618. package/src/pi-intercom/session-events.ts +247 -0
  619. package/src/pi-intercom/session-targets.ts +124 -72
  620. package/src/pi-intercom/subagent-events.ts +299 -0
  621. package/src/pi-intercom/subagent-live-events.ts +212 -109
  622. package/src/pi-intercom/supervisor.ts +362 -0
  623. package/src/pi-intercom/tool-arguments.ts +120 -0
  624. package/src/pi-intercom/tool-registration.ts +121 -0
  625. package/src/pi-intercom/tool-results.ts +54 -0
  626. package/src/pi-intercom/tool-schemas.ts +134 -0
  627. package/src/pi-intercom/tools.ts +80 -0
  628. package/src/pi-intercom/topic-actions.ts +243 -0
  629. package/src/pi-intercom/topics.ts +267 -121
  630. package/src/pi-intercom/transport.ts +14 -0
  631. package/src/pi-intercom/types.ts +288 -190
  632. package/src/pi-intercom/ui/compose-paste.ts +109 -0
  633. package/src/pi-intercom/ui/compose.ts +173 -157
  634. package/src/pi-intercom/ui/inline-message.ts +80 -62
  635. package/src/pi-intercom/ui/message-renderers.ts +182 -0
  636. package/src/pi-intercom/ui/overlay.ts +238 -0
  637. package/src/pi-intercom/ui/session-format.ts +146 -0
  638. package/src/pi-intercom/ui/session-list.ts +145 -62
  639. package/src/pi-intercom/ui/tool-renderers.ts +148 -0
  640. package/src/pi-intercom/ui/topic-view.ts +107 -0
  641. package/src/pi-intercom/validation.ts +69 -0
  642. package/src/runs/background/async-chain-plan.ts +451 -0
  643. package/src/runs/background/async-control.ts +201 -71
  644. package/src/runs/background/async-execution.ts +206 -829
  645. package/src/runs/background/async-job-admission.ts +54 -0
  646. package/src/runs/background/async-job-projection.ts +225 -0
  647. package/src/runs/background/async-job-tracker.ts +416 -360
  648. package/src/runs/background/async-launch.ts +319 -0
  649. package/src/runs/background/async-plan.ts +285 -0
  650. package/src/runs/background/async-result-file.ts +79 -37
  651. package/src/runs/background/async-resume.ts +338 -285
  652. package/src/runs/background/async-run-discovery.ts +270 -0
  653. package/src/runs/background/async-run-location.ts +194 -0
  654. package/src/runs/background/async-run-record.ts +14 -0
  655. package/src/runs/background/async-run-repair.ts +307 -0
  656. package/src/runs/background/async-run-summary.ts +71 -0
  657. package/src/runs/background/async-single-plan.ts +168 -0
  658. package/src/runs/background/async-status-projection.ts +188 -0
  659. package/src/runs/background/async-status-render.ts +164 -0
  660. package/src/runs/background/async-status-validation.ts +238 -0
  661. package/src/runs/background/async-status.ts +9 -481
  662. package/src/runs/background/async-value.ts +29 -0
  663. package/src/runs/background/completion-dedupe.ts +103 -54
  664. package/src/runs/background/completion-delivery.ts +313 -187
  665. package/src/runs/background/completion-receipts.ts +309 -0
  666. package/src/runs/background/legacy-status-acceptance.ts +38 -0
  667. package/src/runs/background/notify.ts +167 -106
  668. package/src/runs/background/parallel-groups.ts +95 -46
  669. package/src/runs/background/result-payload.ts +268 -0
  670. package/src/runs/background/result-watcher.ts +484 -387
  671. package/src/runs/background/run-id-resolver.ts +126 -57
  672. package/src/runs/background/run-schemas.ts +240 -0
  673. package/src/runs/background/run-status-contracts.ts +46 -0
  674. package/src/runs/background/run-status-render.ts +364 -0
  675. package/src/runs/background/run-status.ts +338 -356
  676. package/src/runs/background/runner-attempt-output.ts +281 -0
  677. package/src/runs/background/runner-attempt.ts +354 -0
  678. package/src/runs/background/runner-child-executor.ts +232 -0
  679. package/src/runs/background/runner-child-observer.ts +270 -0
  680. package/src/runs/background/runner-completion-summary.ts +106 -0
  681. package/src/runs/background/runner-completion.ts +274 -0
  682. package/src/runs/background/runner-config.ts +52 -0
  683. package/src/runs/background/runner-contract.ts +51 -0
  684. package/src/runs/background/runner-dynamic.ts +185 -0
  685. package/src/runs/background/runner-finalization.ts +203 -0
  686. package/src/runs/background/runner-graph.ts +91 -0
  687. package/src/runs/background/runner-initial-status.ts +72 -0
  688. package/src/runs/background/runner-lifecycle.ts +159 -0
  689. package/src/runs/background/runner-monitor.ts +399 -0
  690. package/src/runs/background/runner-parallel.ts +280 -0
  691. package/src/runs/background/runner-prepare-step.ts +128 -0
  692. package/src/runs/background/runner-reporting.ts +192 -0
  693. package/src/runs/background/runner-status.ts +220 -0
  694. package/src/runs/background/runner-step-output.ts +158 -0
  695. package/src/runs/background/runner-step-record.ts +76 -0
  696. package/src/runs/background/runner-step.ts +150 -0
  697. package/src/runs/background/runner-streaming.ts +220 -0
  698. package/src/runs/background/runner-workflow.ts +284 -0
  699. package/src/runs/background/stale-run-reconciler.ts +279 -336
  700. package/src/runs/background/subagent-runner-launcher.ts +67 -35
  701. package/src/runs/background/subagent-runner.ts +80 -2048
  702. package/src/runs/background/top-level-async.ts +9 -7
  703. package/src/runs/foreground/chain-clarify.ts +499 -1334
  704. package/src/runs/foreground/clarification-draft.ts +92 -0
  705. package/src/runs/foreground/clarify-contracts.ts +33 -0
  706. package/src/runs/foreground/clarify-invocation.ts +192 -65
  707. package/src/runs/foreground/clarify-text-editor.ts +229 -0
  708. package/src/runs/foreground/clarify-view.ts +222 -0
  709. package/src/runs/foreground/execution-input.ts +358 -424
  710. package/src/runs/foreground/execution-routing.ts +41 -0
  711. package/src/runs/foreground/execution-timeout.ts +92 -0
  712. package/src/runs/foreground/foreground-control.ts +333 -812
  713. package/src/runs/foreground/foreground-memory.ts +132 -0
  714. package/src/runs/foreground/invocation-context.ts +88 -0
  715. package/src/runs/foreground/invocation-execution.ts +465 -0
  716. package/src/runs/foreground/invocation-expansion.ts +58 -0
  717. package/src/runs/foreground/management-actions.ts +360 -0
  718. package/src/runs/foreground/nested-control.ts +248 -0
  719. package/src/runs/foreground/question-continuation.ts +112 -0
  720. package/src/runs/foreground/question-control.ts +297 -70
  721. package/src/runs/foreground/resume-target.ts +330 -0
  722. package/src/runs/foreground/revival-launch-policy.ts +112 -0
  723. package/src/runs/foreground/run-async-path.ts +307 -223
  724. package/src/runs/foreground/run-interrupt.ts +202 -0
  725. package/src/runs/foreground/run-nudge.ts +355 -0
  726. package/src/runs/foreground/saved-revival.ts +356 -0
  727. package/src/runs/foreground/subagent-executor.ts +195 -493
  728. package/src/runs/foreground/subagent-params.ts +314 -242
  729. package/src/runs/foreground/wait-observation.ts +160 -0
  730. package/src/runs/foreground/wait-registration.ts +18 -0
  731. package/src/runs/foreground/wait-run.ts +361 -119
  732. package/src/runs/shared/acceptance-contract.ts +410 -209
  733. package/src/runs/shared/acceptance-defaults.ts +1 -0
  734. package/src/runs/shared/acceptance-evaluation.ts +314 -240
  735. package/src/runs/shared/acceptance-finalization.ts +447 -305
  736. package/src/runs/shared/acceptance-outcome.ts +69 -0
  737. package/src/runs/shared/acceptance-reports.ts +220 -126
  738. package/src/runs/shared/acceptance-submission.ts +251 -0
  739. package/src/runs/shared/acceptance-verification.ts +118 -0
  740. package/src/runs/shared/acceptance.ts +25 -25
  741. package/src/runs/shared/chain-outputs.ts +190 -98
  742. package/src/runs/shared/child-attempt-types.ts +118 -0
  743. package/src/runs/shared/child-attempt.ts +372 -608
  744. package/src/runs/shared/child-boundaries.ts +217 -0
  745. package/src/runs/shared/child-context-filter.ts +53 -0
  746. package/src/runs/shared/child-event-observer.ts +496 -0
  747. package/src/runs/shared/child-execution-cwd.ts +139 -68
  748. package/src/runs/shared/child-execution-outcome.ts +103 -0
  749. package/src/runs/shared/child-invocation.ts +74 -0
  750. package/src/runs/shared/child-message-validation.ts +233 -0
  751. package/src/runs/shared/child-native-accounting.ts +154 -0
  752. package/src/runs/shared/child-native-observation.ts +79 -0
  753. package/src/runs/shared/child-observation-spool.ts +188 -0
  754. package/src/runs/shared/child-observations.ts +219 -89
  755. package/src/runs/shared/child-presence.ts +6 -0
  756. package/src/runs/shared/child-route-env.ts +142 -0
  757. package/src/runs/shared/child-stream-observer.ts +302 -0
  758. package/src/runs/shared/child-stream-projection.ts +160 -0
  759. package/src/runs/shared/child-structured-tool.ts +38 -0
  760. package/src/runs/shared/claude-code.ts +201 -228
  761. package/src/runs/shared/claude-model.ts +67 -0
  762. package/src/runs/shared/claude-session.ts +124 -0
  763. package/src/runs/shared/claude-types.ts +31 -0
  764. package/src/runs/shared/completion-guard.ts +35 -26
  765. package/src/runs/shared/control-event-policy.ts +15 -0
  766. package/src/runs/shared/control-notification-owner.ts +20 -0
  767. package/src/runs/shared/dynamic-fanout.ts +402 -238
  768. package/src/runs/shared/dynamic-item.ts +114 -0
  769. package/src/runs/shared/history-index.ts +112 -51
  770. package/src/runs/shared/legacy-async-recovery.ts +153 -0
  771. package/src/runs/shared/legacy-output-recovery.ts +214 -0
  772. package/src/runs/shared/mcp-config-schema.ts +46 -0
  773. package/src/runs/shared/mcp-direct-tool-allowlist.ts +396 -307
  774. package/src/runs/shared/model-fallback.ts +302 -180
  775. package/src/runs/shared/mutating-tool-guard.ts +259 -158
  776. package/src/runs/shared/native-finalization-types.ts +29 -0
  777. package/src/runs/shared/native-finalization.ts +233 -109
  778. package/src/runs/shared/native-usage.ts +379 -96
  779. package/src/runs/shared/nested-control-records.ts +178 -0
  780. package/src/runs/shared/nested-events.ts +45 -752
  781. package/src/runs/shared/nested-path.ts +47 -34
  782. package/src/runs/shared/nested-projection.ts +230 -0
  783. package/src/runs/shared/nested-protocol.ts +67 -0
  784. package/src/runs/shared/nested-registry.ts +273 -0
  785. package/src/runs/shared/nested-render.ts +198 -100
  786. package/src/runs/shared/nested-route.ts +292 -0
  787. package/src/runs/shared/nested-validation.ts +291 -0
  788. package/src/runs/shared/owned-child-evidence.ts +184 -0
  789. package/src/runs/shared/owned-run-list.ts +187 -0
  790. package/src/runs/shared/owned-run-observation.ts +368 -0
  791. package/src/runs/shared/owned-run-progress.ts +172 -0
  792. package/src/runs/shared/owned-run-read-state.ts +7 -0
  793. package/src/runs/shared/owned-run-results.ts +289 -0
  794. package/src/runs/shared/owned-run-status.ts +239 -0
  795. package/src/runs/shared/owned-run-view.ts +65 -0
  796. package/src/runs/shared/owned-workflow-details.ts +24 -0
  797. package/src/runs/shared/owner-output.ts +73 -0
  798. package/src/runs/shared/parallel-utils.ts +93 -129
  799. package/src/runs/shared/parent-receipts.ts +177 -50
  800. package/src/runs/shared/parent-usage.ts +314 -91
  801. package/src/runs/shared/pi-args.ts +241 -311
  802. package/src/runs/shared/pi-launch-input.ts +48 -0
  803. package/src/runs/shared/pi-session-launch.ts +94 -0
  804. package/src/runs/shared/pi-spawn.ts +57 -33
  805. package/src/runs/shared/run-history-queries.ts +189 -38
  806. package/src/runs/shared/run-history.ts +197 -76
  807. package/src/runs/shared/run-id-ambiguity.ts +7 -3
  808. package/src/runs/shared/run-metadata-paths.ts +17 -0
  809. package/src/runs/shared/run-metadata.ts +240 -0
  810. package/src/runs/shared/run-persistence.ts +181 -0
  811. package/src/runs/shared/run-records.ts +19 -607
  812. package/src/runs/shared/run-restoration.ts +455 -0
  813. package/src/runs/shared/run-state-owner.ts +31 -0
  814. package/src/runs/shared/session-cwd-preload.ts +39 -19
  815. package/src/runs/shared/single-output.ts +358 -205
  816. package/src/runs/shared/stable-value.ts +15 -0
  817. package/src/runs/shared/streaming-text.ts +39 -12
  818. package/src/runs/shared/structured-output.ts +76 -46
  819. package/src/runs/shared/subagent-control.ts +330 -197
  820. package/src/runs/shared/subagent-prompt-runtime.ts +92 -174
  821. package/src/runs/shared/subagent-tool-loop-guard.ts +118 -68
  822. package/src/runs/shared/supervisor-question-state.ts +221 -0
  823. package/src/runs/shared/supervisor-questions.ts +396 -401
  824. package/src/runs/shared/workflow-graph.ts +400 -198
  825. package/src/runs/shared/workflow-policy.ts +245 -110
  826. package/src/runs/shared/worktree-contract.ts +29 -0
  827. package/src/runs/shared/worktree-hook.ts +181 -0
  828. package/src/runs/shared/worktree-location.ts +61 -0
  829. package/src/runs/shared/worktree-preservation.ts +10 -0
  830. package/src/runs/shared/worktree-process.ts +132 -0
  831. package/src/runs/shared/worktree.ts +459 -587
  832. package/src/shared/agent-context-policy.ts +176 -145
  833. package/src/shared/agent-dir.ts +15 -0
  834. package/src/shared/artifacts.ts +99 -68
  835. package/src/shared/atomic-json.ts +14 -11
  836. package/src/shared/chain-files.ts +72 -0
  837. package/src/shared/concurrency.ts +27 -0
  838. package/src/shared/content-preview.ts +117 -0
  839. package/src/shared/cooperative.ts +17 -13
  840. package/src/shared/execution-cwd.ts +34 -9
  841. package/src/shared/file-coalescer.ts +24 -22
  842. package/src/shared/fork-context.ts +52 -48
  843. package/src/shared/formatters.ts +88 -47
  844. package/src/shared/journal-frames.ts +197 -0
  845. package/src/shared/journal-projections.ts +133 -0
  846. package/src/shared/journal-reader.ts +18 -333
  847. package/src/shared/json-projection.ts +224 -0
  848. package/src/shared/lazy-tools.ts +76 -27
  849. package/src/shared/message-output.ts +146 -0
  850. package/src/shared/model-info.ts +94 -58
  851. package/src/shared/native-import.ts +25 -0
  852. package/src/shared/native-journal.ts +172 -0
  853. package/src/shared/native-session.ts +8 -3
  854. package/src/shared/native-tui.ts +13 -3
  855. package/src/shared/native-typebox.ts +18 -3
  856. package/src/shared/output-page.ts +76 -0
  857. package/src/shared/post-exit-stdio-guard.ts +232 -172
  858. package/src/shared/prompt-sections.ts +13 -7
  859. package/src/shared/session-entries.ts +103 -44
  860. package/src/shared/session-identity.ts +17 -10
  861. package/src/shared/settings.ts +182 -367
  862. package/src/shared/status-format.ts +207 -98
  863. package/src/shared/stream-json.d.ts +10 -0
  864. package/src/shared/task-file-policy.ts +35 -0
  865. package/src/shared/temp-root.ts +122 -71
  866. package/src/shared/types/acceptance.ts +149 -0
  867. package/src/shared/types/async.ts +247 -0
  868. package/src/shared/types/config.ts +139 -0
  869. package/src/shared/types/details.ts +104 -0
  870. package/src/shared/types/history.ts +217 -0
  871. package/src/shared/types/inputs.ts +4 -0
  872. package/src/shared/types/intercom.ts +57 -0
  873. package/src/shared/types/launch.ts +39 -0
  874. package/src/shared/types/messages.ts +104 -0
  875. package/src/shared/types/nested.ts +153 -0
  876. package/src/shared/types/owned-runs.ts +148 -0
  877. package/src/shared/types/progress.ts +106 -0
  878. package/src/shared/types/questions.ts +103 -0
  879. package/src/shared/types/results.ts +119 -0
  880. package/src/shared/types/state.ts +56 -0
  881. package/src/shared/types/usage.ts +36 -0
  882. package/src/shared/types/workflow.ts +187 -0
  883. package/src/shared/types.ts +222 -1161
  884. package/src/shared/ui-mode.ts +1 -1
  885. package/src/shared/unknown.ts +59 -0
  886. package/src/shared/utils.ts +111 -305
  887. package/src/slash/command-discovery.ts +109 -0
  888. package/src/slash/command-input.ts +232 -0
  889. package/src/slash/prompt-template-bridge.ts +153 -400
  890. package/src/slash/prompt-template-contract.ts +152 -0
  891. package/src/slash/prompt-template-results.ts +189 -0
  892. package/src/slash/request-protocol.ts +87 -0
  893. package/src/slash/result-delivery.ts +312 -0
  894. package/src/slash/saved-chain.ts +115 -0
  895. package/src/slash/slash-bridge.ts +202 -156
  896. package/src/slash/slash-commands.ts +176 -598
  897. package/src/slash/slash-live-state.ts +287 -229
  898. package/src/tui/action-hints.ts +147 -61
  899. package/src/tui/activity-lines.ts +40 -0
  900. package/src/tui/agent-browser.ts +354 -0
  901. package/src/tui/agent-controls.ts +355 -0
  902. package/src/tui/agent-conversation.ts +514 -0
  903. package/src/tui/agent-dock.ts +250 -0
  904. package/src/tui/agent-history.ts +78 -203
  905. package/src/tui/agent-model.ts +78 -0
  906. package/src/tui/agent-picker.ts +325 -0
  907. package/src/tui/agent-view.ts +391 -1333
  908. package/src/tui/compact-results.ts +342 -0
  909. package/src/tui/conversation-details.ts +206 -0
  910. package/src/tui/conversation-frame.ts +184 -0
  911. package/src/tui/conversation-history.ts +246 -0
  912. package/src/tui/conversation-menu.ts +119 -0
  913. package/src/tui/conversation-notices.ts +116 -0
  914. package/src/tui/conversation-selection.ts +66 -0
  915. package/src/tui/conversation-viewport.ts +169 -0
  916. package/src/tui/direction-message.ts +35 -0
  917. package/src/tui/display.ts +409 -0
  918. package/src/tui/editor-viewport.ts +19 -0
  919. package/src/tui/expanded-content.ts +134 -0
  920. package/src/tui/expanded-multi.ts +264 -0
  921. package/src/tui/expanded-single.ts +92 -0
  922. package/src/tui/history-cards.ts +316 -0
  923. package/src/tui/history-display.ts +334 -0
  924. package/src/tui/history-json.ts +17 -0
  925. package/src/tui/history-metadata.ts +177 -0
  926. package/src/tui/history-record.ts +321 -0
  927. package/src/tui/history-text.ts +34 -0
  928. package/src/tui/indexed-history.ts +149 -0
  929. package/src/tui/nested-widget.ts +192 -0
  930. package/src/tui/picker-page.ts +80 -0
  931. package/src/tui/render.ts +100 -1546
  932. package/src/tui/result-facts.ts +99 -0
  933. package/src/tui/task-observation.ts +96 -0
  934. package/src/tui/task-store.ts +432 -0
  935. package/src/tui/text-values.ts +20 -0
  936. package/src/tui/view-connections.ts +64 -0
  937. package/src/tui/view-model.ts +181 -0
  938. package/src/tui/view-persistence.ts +57 -0
  939. package/src/tui/view-ports.ts +55 -0
  940. package/src/tui/view-session.ts +42 -0
  941. package/src/tui/widget-details.ts +220 -0
  942. package/src/tui/widget-status.ts +331 -0
  943. package/src/tui/widget.ts +208 -0
  944. package/src/tui/workflow-labels.ts +340 -0
  945. package/src/tui/workflow-outcomes.ts +150 -0
  946. package/tsconfig.build.json +11 -10
  947. package/tsconfig.json +30 -17
@@ -1,1683 +1,351 @@
1
- import { randomUUID } from "node:crypto";
2
- import { AssistantMessageComponent, UserMessageComponent, ToolExecutionComponent, DynamicBorder, createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createPowerShellToolDefinition, createReadToolDefinition, createWriteToolDefinition, getMarkdownTheme, getSelectListTheme, keyText, rawKeyHint, renderDiff } from "@earendil-works/pi-coding-agent";
3
- import { Box, Container, CURSOR_MARKER, Editor, Input, MouseRegion, ScrollView, SelectList, Spacer, Text, getKeybindings, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
4
- import { resolveSubagentIntercomTarget } from "../intercom/intercom-bridge.js";
1
+ import { hasText, errorText } from "./text-values.js";
2
+ import { isRecord } from "./history-text.js";
3
+ import { directionRenderer } from "./direction-message.js";
4
+ import { pickerPort, conversationPort } from "./view-connections.js";
5
+ import { AgentControls } from "./agent-controls.js";
6
+ import { AgentBrowser } from "./agent-browser.js";
7
+ import { ViewSession } from "./view-session.js";
8
+ import { AgentTaskStore } from "./task-store.js";
9
+ import { AgentDock } from "./agent-dock.js";
5
10
  import { loadConfig as loadIntercomConfig } from "../pi-intercom/config.js";
6
- import { sendLiveSubagentMessage } from "../intercom/live-intercom.js";
7
- import { ownedRunView } from "../runs/shared/run-records.js";
8
- import { listOwnedRunQuestions, questionProcessAlive } from "../runs/shared/supervisor-questions.js";
9
- import { getSingleResultOutput } from "../shared/utils.js";
10
- import { formatModelThinking } from "../shared/formatters.js";
11
11
  import { isTuiContext } from "../shared/ui-mode.js";
12
- import { acceptanceHumanAction } from "../runs/shared/acceptance.js";
13
- import { stripAcceptanceReport } from "../runs/shared/acceptance-reports.js";
14
- import { formatAgentProcessExit } from "../shared/status-format.js";
15
12
  import { WIDGET_KEY } from "../shared/types.js";
16
- import { buildWidgetLines } from "./render.js";
17
- import { indexedHistory, readableText, withFinalResult } from "./agent-history.js";
18
- import { runHistoryIndex } from "../runs/shared/history-index.js";
19
- import { actionHints, withMouseExpansion } from "./action-hints.js";
13
+ import { AgentConversation } from "./agent-conversation.js";
14
+ import { AgentPicker } from "./agent-picker.js";
20
15
  const VIEW_ENTRY = "subagent-view";
21
16
  const DIRECTION_MESSAGE = "subagent-human-direction";
22
- const UNAVAILABLE_ASSIGNMENT = "Assignment identity unavailable. Your draft is kept; no message or Stop was sent. Inspect the run and choose a verified assignment before sending it.";
23
- const PENDING_MESSAGE_NOTICE = "This message is already waiting for the child. You can keep working or write a different message.";
24
- const CONTINUE_HINT = "Continue with this message (Alt+C)";
25
- const CONTINUE_NOTICES = {
26
- finished: "This agent has finished. Your draft is kept. Choose ",
27
- blocked: "This agent needs your action; acceptance is incomplete. Your draft is kept. Choose ",
28
- };
29
- const short = (text, width = 48) => truncateToWidth(readableText(text).replace(/\s+/g, " ").trim(), width);
30
- const resultText = (result) => result.content.map((part) => part.type === "text" ? part.text : "").join("\n");
31
- function primaryKey(action) {
32
- const key = getKeybindings().getKeys(action)[0];
33
- return { enter: "Enter", escape: "Esc", up: "↑", down: "↓" }[key ?? ""] ?? readableText(rawKeyHint(key ?? "", "")).trim();
17
+ function isSessionView(value) {
18
+ return (isRecord(value) &&
19
+ isRecord(value.sessionManager) &&
20
+ typeof value.sessionManager.getSessionId === "function");
34
21
  }
35
- export function agentTaskLabel(child) {
36
- return readableText(child.label || child.task?.split("\n").find((line) => line.trim()) || `${child.agent} · assignment unavailable`).replace(/\s+/g, " ").trim();
37
- }
38
- function activity(task) {
39
- if (task.child.identityUnavailable)
40
- return "assignment unavailable";
41
- if (task.question)
42
- return task.question.state === "answer_pending" ? "answer saved · waiting" : "needs an answer";
43
- if (task.child.state === "blocked")
44
- return "needs your action · acceptance incomplete";
45
- if (task.child.state !== "live")
46
- return task.child.state === "completed" ? "done" : task.child.state;
47
- const live = task.child.activity;
48
- if (live?.status === "pending")
49
- return "waiting to start";
50
- return live?.currentTool ? short(`${live.currentTool} ${live.currentToolArgs || live.currentPath || ""}`, 42)
51
- : live?.streamingText ? short(live.streamingText, 42) : "working";
52
- }
53
- function taskSummary(task) {
54
- let status;
55
- if (task.child.identityUnavailable)
56
- status = "unavailable";
57
- else if (task.question)
58
- status = task.question.state === "answer_pending" ? "answer pending" : "needs answer";
59
- else if (task.child.state === "blocked")
60
- status = "needs action";
61
- else if (task.child.state === "live")
62
- status = task.child.activity?.status === "pending" ? "queued" : "working";
63
- else
64
- status = task.child.state === "completed" ? "done" : task.child.state;
65
- return { status, badge: task.unread ? task.replied ? " · replied" : " · new" : "" };
66
- }
67
- const unavailableModel = { summary: "model unavailable", details: "Model unavailable. No provider/model was recorded for this assignment." };
68
- function agentModel(child, native) {
69
- if (child.identityUnavailable)
70
- return unavailableModel;
71
- const live = child.state === "live", selected = child.modelSelection;
72
- // A continuation shares its file with the finished run; only the latter's frozen snapshot belongs to it.
73
- const recorded = live ? native : native?.model ? { ...(child.savedConfiguration ?? child.launch), ...native } : child.savedConfiguration ?? child.launch;
74
- const current = selected?.modelStartedAt !== undefined && (recorded?.modelRecordedAt ?? 0) > selected.modelStartedAt;
75
- let source = "selected", value = selected;
76
- if (recorded?.model && (current || !live && selected?.modelStartedAt === undefined)) {
77
- value = recorded;
78
- source = live ? "session" : "saved";
79
- }
80
- else if (!value?.model) {
81
- value = live && native?.model ? native : child.launch?.model ? child.launch : child.result;
82
- source = "saved";
83
- }
84
- if (!value?.model)
85
- return unavailableModel;
86
- const formatted = readableText(formatModelThinking(value.model, value.thinking));
87
- const selectedText = selected?.model && readableText(formatModelThinking(selected.model, selected.thinking));
88
- const nativeText = native?.model && readableText(formatModelThinking(native.model, native.thinking));
89
- const details = [`Model (${source}): ${formatted}`];
90
- if (selectedText && selectedText !== formatted)
91
- details.push(`Selected model: ${selectedText}`);
92
- if (live && nativeText && nativeText !== formatted)
93
- details.push(`Last saved session model (may precede this attempt): ${nativeText}`);
94
- return { summary: source === "session" ? formatted : `${source}: ${formatted}`, details: details.join("\n") };
95
- }
96
- function runningIndicator(theme) {
97
- // Six seconds, sampled by the existing 500 ms refresh; typing cannot speed up the pulse.
98
- const phase = Math.floor(Date.now() / 500) % 12;
99
- const ansi = theme.getFgAnsi("success");
100
- const rgb = theme.getColorMode() === "truecolor" && /^\x1b\[38;2;(\d+);(\d+);(\d+)m$/.exec(ansi);
101
- if (!rgb)
102
- return theme.fg("success", phase < 6 ? theme.bold("●") : "●");
103
- const brightness = 0.9 + 0.1 * Math.cos(phase * Math.PI / 6);
104
- return theme.fg("success", "●").replace(ansi, `\x1b[38;2;${rgb.slice(1).map((value) => Math.round(Number(value) * brightness)).join(";")}m`);
22
+ function isOpenRequest(value) {
23
+ return (isRecord(value) &&
24
+ (value.ctx === undefined || isSessionView(value.ctx)) &&
25
+ (value.handled === undefined || typeof value.handled === "boolean"));
105
26
  }
106
27
  /** One UI controller over the existing owned runs, session files, and executor. */
107
28
  export class AgentViewController {
108
- ctx;
109
- ownerSessionId;
110
- generation = 0;
111
- pending = new AbortController();
112
29
  timer;
113
30
  saveTimer;
114
31
  unsubscribe;
115
- indexUnsubscribe;
116
- refreshPromise;
117
- listRequest = 0;
118
- appliedListRequest = -1;
119
- filter = "";
120
- offset = 0;
121
- selectedKey;
122
- dockTasks = [];
123
- listPage;
124
- listLoading = false;
125
- listError;
126
- taskKeys = new Map();
127
- visits = new Map();
128
- busy = new Set();
129
32
  lastSaved = "";
130
33
  render;
131
34
  closeOverlay;
132
35
  widget;
133
36
  shortcut = loadIntercomConfig().shortcut;
134
37
  overlay;
135
- tasks = [];
136
- pinned;
137
38
  pi;
138
39
  state;
139
- execute;
40
+ store;
41
+ session;
42
+ browser;
43
+ controls;
44
+ picker;
45
+ conversation;
46
+ /** Parent runtime notifications trigger an observation refresh, not a UI port operation. */
47
+ refresh(browse = false) {
48
+ return this.browser.refresh(browse);
49
+ }
50
+ reportFailure(error) {
51
+ if (this.live()) {
52
+ this.session.ctx?.ui.notify(`Agents: ${errorText(error)}`, "error");
53
+ }
54
+ }
140
55
  constructor(pi, state, execute) {
141
56
  this.pi = pi;
142
57
  this.state = state;
143
- this.execute = execute;
144
- pi.registerCommand("agents", { description: "View, message, answer, stop, or continue your agents", handler: async (_args, ctx) => this.open(undefined, ctx) });
145
- pi.registerMessageRenderer(DIRECTION_MESSAGE, withMouseExpansion((message, options, theme) => {
146
- const details = message.details;
147
- return {
148
- render(width) {
149
- const key = keyText("app.tools.expand");
150
- return new Text(options.expanded ? `User → ${details.label}\n${details.text}${details.quote ? `\n\nReplying to ${details.quote.title}:\n${details.quote.text}` : ""}`
151
- : theme.fg("dim", `User → ${details.label}: ${short(details.text, 100)} · sent directly${key ? ` · ${key}` : ""}`), 0, 0).render(width);
152
- },
153
- invalidate() { },
154
- };
155
- }));
58
+ this.store = new AgentTaskStore(state, () => this.overlay?.syncDraft());
59
+ this.session = new ViewSession(state);
60
+ this.browser = new AgentBrowser(state, this.store, this.session, () => {
61
+ this.overlay?.refresh();
62
+ this.render?.();
63
+ });
64
+ this.controls = new AgentControls(pi, this.store, this.session, {
65
+ execute,
66
+ changed: () => this.changed(),
67
+ save: () => this.save(),
68
+ refreshSelected: (key) => this.refreshSelected(key),
69
+ syncDraft: () => this.overlay?.syncDraft(),
70
+ });
71
+ const layout = {
72
+ shortcut: this.shortcut,
73
+ availableHeight: (tui) => this.availableHeight(tui),
74
+ };
75
+ this.picker = pickerPort(this.store, this.browser, layout);
76
+ this.conversation = conversationPort(this.store, this.browser, this.controls, {
77
+ ...layout,
78
+ changed: () => this.changed(),
79
+ pin: (key) => this.pin(key),
80
+ });
81
+ pi.registerCommand("agents", {
82
+ description: "View, message, answer, stop, or continue your agents",
83
+ handler: async (_args, ctx) => this.open(undefined, ctx),
84
+ });
85
+ pi.registerMessageRenderer(DIRECTION_MESSAGE, directionRenderer());
156
86
  }
157
87
  start(ctx) {
158
88
  this.dispose();
159
- if (!isTuiContext(ctx))
89
+ if (!isTuiContext(ctx)) {
160
90
  return;
161
- this.ctx = ctx;
162
- this.pending = new AbortController();
91
+ }
92
+ this.session.start(ctx);
163
93
  const ownerSessionId = ctx.sessionManager.getSessionId();
164
- this.ownerSessionId = ownerSessionId;
165
94
  for (const entry of ctx.sessionManager.getEntries()) {
166
- if (entry.type !== "custom" || entry.customType !== VIEW_ENTRY)
95
+ if (entry.type !== "custom" || entry.customType !== VIEW_ENTRY) {
167
96
  continue;
168
- const saved = entry.data;
169
- if (saved?.ownerSessionId === ownerSessionId && Array.isArray(saved.visits)) {
170
- this.visits = new Map(saved.visits);
171
- this.pinned = saved.pinned;
172
97
  }
98
+ this.store.restore(entry.data, ownerSessionId);
173
99
  }
174
- for (const visit of this.visits.values())
175
- for (const sent of visit.outbox)
176
- if (sent.status === "sending")
177
- sent.status = "unconfirmed";
178
- this.unsubscribe = this.pi.events.on("subagent:open-agents", (data) => {
179
- const request = data;
180
- if (!this.live() || request.ctx?.sessionManager.getSessionId() !== ownerSessionId)
100
+ this.unsubscribe = this.pi.events.on("subagent:open-agents", (request) => {
101
+ if (!isOpenRequest(request)) {
181
102
  return;
103
+ }
104
+ if (!this.live() || request.ctx?.sessionManager.getSessionId() !== ownerSessionId) {
105
+ return;
106
+ }
182
107
  request.handled = true;
183
- void this.open();
108
+ this.defer(this.open());
184
109
  });
185
110
  ctx.ui.setWidget(WIDGET_KEY, (tui, theme) => {
186
111
  this.render = () => tui.requestRender();
187
- let hits = [];
188
- const widget = new MouseRegion({
189
- invalidate() { },
190
- render: (width) => {
191
- hits = [];
192
- const active = this.dockTasks.filter((task) => task.child.state === "live" || task.child.state === "blocked" || task.question)
193
- .map((task) => ({ task, state: task.child.state === "blocked" || task.question?.state === "awaiting_input" ? "needs action" : task.child.activity?.status === "pending" || task.question?.state === "answer_pending" ? "waiting" : "running" }));
194
- if (!active.length) {
195
- if (!this.listError && !this.listLoading && this.listPage?.freshness.state !== "catching-up")
196
- return [];
197
- const label = this.listError ? "Agents history unavailable · /agents to retry" : "Agents · loading history…";
198
- hits.push({ start: 0, end: width, row: 0 });
199
- return [truncateToWidth(theme.fg(this.listError ? "warning" : "dim", label), width)];
200
- }
201
- const running = active.filter((row) => row.state === "running").length;
202
- const waiting = active.filter((row) => row.state === "waiting").length;
203
- const needsAction = active.filter((row) => row.state === "needs action").length;
204
- const counts = `${running} running${waiting ? ` · ${waiting} waiting` : ""}${needsAction ? ` · ${needsAction} needs action` : ""}`;
205
- const hint = readableText(rawKeyHint(this.shortcut, "")).trim();
206
- const entrance = `${theme.fg("accent", theme.bold("Agents"))} · ${theme.fg(running ? "success" : "muted", counts)} ${theme.fg("dim", `[${hint}]`)}`;
207
- const lines = [truncateToWidth(entrance, width)];
208
- hits.push({ start: 0, end: Math.min(width, visibleWidth(entrance)), row: 0 });
209
- const visible = active.slice(0, Math.max(1, Math.min(4, Math.floor(tui.terminal.rows / 5))));
210
- const pinned = this.tasks.find((task) => task.key === this.pinned);
211
- const details = ctx.ui.getToolsExpanded() ? buildWidgetLines([...this.state.asyncJobs.values()], theme, width, true) : [];
212
- // MainScreen repaints all scrollback if an offscreen row's ANSI changes.
213
- const pulseFrom = tui.mode === "regular" ? visible.length + details.length + Number(Boolean(pinned)) + Number(visible.length < active.length)
214
- + (this.componentsBelowWidget(tui) ?? []).reduce((height, child) => height + child.render(width).length, 0) - tui.terminal.rows : 0;
215
- for (const [index, { task, state }] of visible.entries()) {
216
- const color = state === "running" ? "success" : state === "waiting" ? "dim" : "warning";
217
- const symbol = state === "running" ? "●" : state === "waiting" ? "◷" : "!";
218
- const indicator = state === "running" && index >= pulseFrom ? runningIndicator(theme) : theme.fg(color, symbol);
219
- const { status, badge } = taskSummary(task);
220
- const statusText = status === "working" ? "" : theme.fg(color, ` · ${status}`);
221
- const available = width - visibleWidth(statusText + badge) - 4;
222
- const modelWidth = available - Math.min(30, visibleWidth(task.label)) - 3;
223
- const model = modelWidth >= 16 ? ` · ${short(task.model.summary, modelWidth)}` : "";
224
- const label = short(task.label, Math.max(1, available - visibleWidth(model)));
225
- hits.push({ start: 0, end: width, row: lines.length, key: task.key });
226
- lines.push(truncateToWidth(` ${indicator} ${theme.bold(label)}${statusText}${theme.fg("accent", badge)}${theme.fg("dim", model)}`, width));
227
- }
228
- if (visible.length < active.length) {
229
- const more = ` +${active.length - visible.length} more · /agents`, line = truncateToWidth(more, width, "...");
230
- hits.push({ start: 2, end: visibleWidth(line) - (visibleWidth(more) > width ? 3 : 0), row: lines.length });
231
- lines.push(theme.fg("dim", line));
232
- }
233
- if (pinned) {
234
- const preview = pinned.child.identityUnavailable ? UNAVAILABLE_ASSIGNMENT : pinned.child.state === "live" ? activity(pinned) : (pinned.child.result && getSingleResultOutput(pinned.child.result)) || pinned.history.findLast((item) => item.kind === "assistant")?.text || activity(pinned);
235
- const unpin = "[Unpin] ", row = lines.length;
236
- lines.push(theme.fg("dim", truncateToWidth(`${unpin}Pinned · ${pinned.child.state === "completed" ? "finished · " : ""}${short(pinned.label, 30)}: ${short(preview, width)}`, width)));
237
- hits.push({ start: 0, end: unpin.length, row, unpin: true }, { start: unpin.length, end: width, row, key: pinned.key });
238
- }
239
- lines.push(...details);
240
- return lines;
112
+ const dock = new AgentDock(tui, theme, {
113
+ tasks: () => this.store.dockTasks,
114
+ pinned: () => this.store.tasks.find((task) => task.key === this.store.pinned),
115
+ page: () => this.browser.listPage,
116
+ error: () => this.browser.listError,
117
+ loading: () => this.browser.listLoading,
118
+ jobs: () => [...this.state.asyncJobs.values()],
119
+ expanded: () => ctx.ui.getToolsExpanded(),
120
+ below: () => this.componentsBelowWidget(tui) ?? [],
121
+ shortcut: this.shortcut,
122
+ live: () => this.live(),
123
+ unpin: () => this.pin(),
124
+ open: (key) => {
125
+ this.open(key).catch((error) => this.reportFailure(error));
241
126
  },
242
- }, (event) => {
243
- if (tui.mode !== "fullscreen" || event.button !== "left" || !this.live())
244
- return;
245
- const hit = hits.find((hit) => hit.row === event.y && event.x >= hit.start && event.x < hit.end);
246
- if (!hit)
247
- return;
248
- if (event.type === "press")
249
- return { handled: true };
250
- if (event.type !== "click")
251
- return;
252
- if (hit.unpin)
253
- this.pin(undefined);
254
- else
255
- void this.open(hit.key);
256
- return { handled: true };
257
127
  });
258
- this.widget = widget;
259
- return widget;
128
+ this.widget = dock.component;
129
+ return dock.component;
260
130
  });
261
- void this.refresh();
131
+ this.defer(this.browser.refresh());
262
132
  this.timer = setInterval(() => {
263
- if (!this.listError && (this.overlay || this.pinned || this.dockTasks.some((task) => task.child.state === "live" || task.question)
264
- || this.tasks.some((task) => this.visits.get(task.key)?.outbox.length))) {
133
+ if (this.shouldRefresh()) {
265
134
  this.render?.();
266
- void this.refresh();
135
+ this.defer(this.browser.refresh());
267
136
  }
268
137
  }, 500);
269
- this.timer.unref?.();
138
+ this.timer.unref();
270
139
  }
271
- live(generation = this.generation) {
272
- return generation === this.generation && Boolean(this.ctx && this.ctx.sessionManager.getSessionId() === this.ownerSessionId
273
- && this.state.lastUiContext?.sessionManager.getSessionId() === this.ownerSessionId);
140
+ defer(operation) {
141
+ operation.catch((error) => this.reportFailure(error));
274
142
  }
275
- visit(key) {
276
- let visit = this.visits.get(key);
277
- if (!visit) {
278
- visit = { draft: "", outbox: [] };
279
- this.visits.set(key, visit);
143
+ shouldRefresh() {
144
+ if (hasText(this.browser.listError)) {
145
+ return false;
280
146
  }
281
- return visit;
282
- }
283
- taskKey(run, child) {
284
- const path = new Set();
285
- let key;
286
- for (;;) {
287
- const cacheKey = `${run.runId}:${child.workflowNodeId ?? (run.mode === "chain" ? child.sessionFile ?? child.index : child.index)}`, cached = this.taskKeys.get(cacheKey);
288
- if (cached) {
289
- key = cached;
290
- break;
291
- }
292
- if (path.has(cacheKey))
293
- throw new Error("Cyclic saved continuation ancestry");
294
- path.add(cacheKey);
295
- const predecessor = run.predecessorRunId && this.state.ownedRuns?.get(run.predecessorRunId);
296
- if (!predecessor) {
297
- key = `${run.runId}:${child.workflowNodeId ?? (run.mode === "chain" && child.sessionFile && !child.identityUnavailable ? `session:${child.sessionFile}` : child.index)}`;
298
- break;
299
- }
300
- const index = run.predecessorIndex ?? 0;
301
- child = predecessor.children.find((candidate) => candidate.index === index) ?? { index };
302
- run = predecessor;
303
- }
304
- for (const cacheKey of path)
305
- this.taskKeys.set(cacheKey, key);
306
- return key;
307
- }
308
- fromView(view, questions = view.questions ?? [], canonical = false) {
309
- return view.children.filter((child) => !view.matchedChildIndexes || view.matchedChildIndexes.includes(child.index)).map((child) => {
310
- const key = this.taskKey(view, child), prior = this.task(key) ?? this.dockTasks.find((task) => task.key === key), visit = this.visits.get(key);
311
- const sameAttempt = prior?.run.runId === view.runId;
312
- const observed = sameAttempt && prior.child.state === child.state && prior.metadataAt === view.updatedAt;
313
- const selected = !canonical && this.selectedKey === key && sameAttempt;
314
- const displayChild = selected ? { ...child, task: prior.child.task, launch: prior.child.launch, result: child.result && prior.run.updatedAt === view.updatedAt ? prior.child.result : child.result,
315
- activity: { ...prior.child.activity, ...child.activity } } : child;
316
- return { key, label: child.identityUnavailable ? "Saved assignment unavailable" : sameAttempt ? agentTaskLabel(child) : prior?.label ?? agentTaskLabel(child), run: view, child: displayChild,
317
- model: agentModel(displayChild, child.nativeConfiguration ?? (canonical && sameAttempt && child.sessionFile === prior.child.sessionFile ? prior.page?.configuration : undefined)), history: sameAttempt ? prior.history : [], historyIds: sameAttempt ? prior.historyIds : [],
318
- page: sameAttempt ? prior.page : undefined, historySource: sameAttempt ? prior.historySource : undefined, metadataAt: observed ? prior.metadataAt : undefined, historyLoading: sameAttempt ? prior.historyLoading : false, finalId: sameAttempt ? prior.finalId : undefined, unavailable: child.missingSession && child.state !== "live" ? "Saved conversation unavailable." : view.diagnosis,
319
- question: child.identityUnavailable ? undefined : questions.findLast((question) => question.index === child.index && ["awaiting_input", "answer_pending"].includes(question.state)),
320
- unread: observed ? prior.unread : Boolean(visit && (child.activity?.lastActivityAt ?? view.updatedAt) > (visit.seenActivityAt ?? 0)), replied: sameAttempt ? prior.replied : false };
321
- });
147
+ return (this.overlay !== undefined ||
148
+ hasText(this.store.pinned) ||
149
+ this.store.dockTasks.some((task) => task.child.state === "live" || task.question !== undefined) ||
150
+ this.store.tasks.some((task) => (this.store.visits.get(task.key)?.outbox.length ?? 0) > 0));
322
151
  }
323
- unavailableTask(key, run) {
324
- return { key, label: "Saved assignment unavailable", run,
325
- child: { agent: "unknown", index: -1, state: "unknown", configuration: "legacy-partial", identityUnavailable: true },
326
- model: unavailableModel, history: [], historyIds: [], unavailable: UNAVAILABLE_ASSIGNMENT, unread: false, replied: false };
152
+ live(generation = this.session.generation) {
153
+ return this.session.live(generation);
327
154
  }
328
- /** SQLite is browse-only. Recheck just this root lineage before any control. */
329
155
  refreshSelected(key) {
330
- const current = this.task(key), root = current?.run.rootRunId ?? this.state.ownedRuns?.get(key.split(":")[0])?.rootRunId;
331
- if (!root || !this.live())
332
- return;
333
- this.taskKeys.clear();
334
- let selected, rootView;
335
- try {
336
- for (const run of this.state.ownedRuns?.values() ?? []) {
337
- if (run.rootRunId !== root || run.ownerSessionId !== this.ownerSessionId)
338
- continue;
339
- const questions = listOwnedRunQuestions(run.ownerSessionId, run.runId);
340
- const view = ownedRunView(run, this.state, { pendingInput: questions.some((question) => ["awaiting_input", "answer_pending"].includes(question.state)), includeContinuations: false, readConfiguration: false });
341
- if (view.runId === key.split(":")[0])
342
- rootView = view;
343
- const candidate = this.fromView(view, questions, true).find((task) => task.key === key);
344
- if (candidate && (!selected || selected.run.startedAt <= candidate.run.startedAt))
345
- selected = candidate;
346
- }
347
- }
348
- catch (error) {
349
- if (current)
350
- selected = { ...current, child: { ...current.child, state: "unknown", identityUnavailable: true }, question: undefined,
351
- unavailable: `Run authority unavailable: ${String(error)}` };
352
- }
353
- if (!selected && current)
354
- selected = { ...current, child: { ...current.child, state: "unknown", identityUnavailable: true }, question: undefined, unavailable: UNAVAILABLE_ASSIGNMENT };
355
- if (!selected && rootView && this.visits.has(key))
356
- selected = this.unavailableTask(key, rootView);
357
- if (selected) {
358
- const position = this.tasks.findIndex((task) => task.key === key);
359
- if (position < 0)
360
- this.tasks.push(selected);
361
- else
362
- this.tasks[position] = selected;
363
- this.dockTasks = this.dockTasks.map((task) => task.key === key ? selected : task);
364
- }
156
+ this.store.refreshSelected(key, this.session.ownerSessionId);
365
157
  this.overlay?.refresh();
366
158
  this.render?.();
367
159
  }
368
- /** Bounded asynchronous observations; no owner files or native bodies on this path. */
369
- refresh(browse = false) {
370
- if (!this.live())
371
- return Promise.resolve();
372
- if (!browse && !this.state.ownedRuns?.size && !this.state.historyIndex)
373
- return Promise.resolve();
374
- if (this.refreshPromise)
375
- return this.refreshPromise;
376
- const generation = this.generation, request = this.listRequest, offset = this.offset, text = this.filter;
377
- this.listLoading = this.listPending;
378
- this.refreshPromise = (async () => {
379
- try {
380
- const index = await runHistoryIndex(this.state);
381
- if (!this.live(generation))
382
- return;
383
- this.indexUnsubscribe ??= index.onChanged(() => { void this.refresh(); });
384
- const page = await index.listRuns({ offset, limit: 50, text: text || undefined, sort: "attention", latestTasksOnly: true });
385
- const dock = text || offset ? await index.listRuns({ limit: 50, sort: "attention", latestTasksOnly: true }) : page;
386
- if (!this.live(generation) || request !== this.listRequest)
387
- return;
388
- this.listPage = page;
389
- this.appliedListRequest = request;
390
- this.listError = undefined;
391
- const tasks = new Map();
392
- for (const row of page.rows)
393
- for (const task of this.fromView(row)) {
394
- const prior = tasks.get(task.key);
395
- if (!prior || prior.run.startedAt <= task.run.startedAt)
396
- tasks.set(task.key, task);
397
- }
398
- for (const [key, visit] of this.visits) {
399
- if (tasks.has(key) || !visit.draft && !visit.quote && !visit.outbox.length && this.pinned !== key)
400
- continue;
401
- const row = page.rows.find((run) => run.runId === key.split(":")[0]);
402
- if (row)
403
- tasks.set(key, this.unavailableTask(key, row));
404
- }
405
- // Keep only the open conversation/pin, not every historical terminal view.
406
- for (const key of [this.selectedKey, this.pinned]) {
407
- const retained = key && this.task(key);
408
- if (retained && !tasks.has(key))
409
- tasks.set(key, retained);
410
- }
411
- this.tasks = [...tasks.values()];
412
- this.dockTasks = dock.rows.flatMap((row) => this.fromView(row));
413
- const markers = new Map([...this.tasks, ...this.dockTasks].map((task) => [task.key, task]));
414
- for (const task of markers.values()) {
415
- const visit = this.visits.get(task.key);
416
- if (!visit || task.child.identityUnavailable)
417
- continue;
418
- if (task.child.state !== "live" && !task.question && task.key !== this.selectedKey && task.key !== this.pinned
419
- && !visit.outbox.length && task.metadataAt === task.run.updatedAt)
420
- continue;
421
- try {
422
- const facts = await index.historyPage({ ...this.historyInput(task), limit: 1 });
423
- if (!this.live(generation) || request !== this.listRequest)
424
- return;
425
- for (const observed of [...this.tasks, ...this.dockTasks].filter((candidate) => candidate.key === task.key))
426
- this.applyMetadata(observed, facts);
427
- }
428
- catch (error) {
429
- if (!this.live(generation) || request !== this.listRequest)
430
- return;
431
- task.unavailable = `Conversation metadata unavailable: ${error instanceof Error ? error.message : String(error)}`;
432
- }
433
- }
434
- }
435
- catch (error) {
436
- if (this.live(generation) && request === this.listRequest)
437
- this.listError = `Agents history unavailable: ${error instanceof Error ? error.message : String(error)}. Retry (F5).`;
438
- }
439
- finally {
440
- if (this.live(generation)) {
441
- this.listLoading = false;
442
- this.refreshPromise = undefined;
443
- this.overlay?.refresh();
444
- this.render?.();
445
- if (request !== this.listRequest)
446
- void this.refresh();
447
- }
448
- }
449
- })();
450
- return this.refreshPromise;
451
- }
452
- get listFilter() { return this.filter; }
453
- get listPending() { return !this.listPage || this.appliedListRequest !== this.listRequest; }
454
- filterTasks(text) {
455
- if (text === this.filter)
456
- return;
457
- this.filter = text.slice(0, 256);
458
- this.offset = 0;
459
- this.listRequest++;
460
- this.tasks = this.tasks.filter((task) => task.key === this.selectedKey || task.key === this.pinned);
461
- void this.refresh();
462
- }
463
- pageTasks(direction) {
464
- const next = direction === "later" ? this.listPage?.nextOffset : Math.max(0, this.offset - 50);
465
- if (next === undefined || next === this.offset)
466
- return;
467
- this.offset = next;
468
- this.listRequest++;
469
- void this.refresh();
470
- }
471
- async retry(key) {
472
- const generation = this.generation;
473
- try {
474
- const index = await runHistoryIndex(this.state, true);
475
- await index.refresh(key ? this.task(key)?.run.runId : undefined);
476
- }
477
- catch (error) {
478
- if (this.live(generation))
479
- this.listError = `History refresh unavailable: ${String(error)}`;
480
- }
481
- if (this.live(generation))
482
- await this.refresh();
483
- }
484
- async historyPage(key, paging = {}, anchor) {
485
- const task = this.task(key), generation = this.generation;
486
- if (!task || !this.live() || task.child.identityUnavailable)
487
- return;
488
- const published = task.page, source = task.historySource, sessionFile = task.child.sessionFile, childState = task.child.state;
489
- const input = this.historyInput(task), index = await runHistoryIndex(this.state);
490
- // Versions can reset on index reopen. Fence the actual owner/read baseline,
491
- // including metadata-only source transitions, rather than ordering unrelated versions.
492
- const isCurrent = () => {
493
- const current = this.task(key);
494
- if (!this.live(generation) || this.state.historyIndex !== index || index.failure || !current || current.child.identityUnavailable
495
- || current.page !== published || current.historySource !== source || current.child.sessionFile !== sessionFile || current.child.state !== childState)
496
- return false;
497
- const boundary = this.historyInput(current);
498
- return boundary.runId === input.runId && boundary.index === input.index && boundary.leaf === input.leaf
499
- && boundary.terminalEntryId === input.terminalEntryId && boundary.endedAt === input.endedAt;
500
- };
501
- const boundary = { runId: task.run.runId, index: task.child.index, terminalEntryId: input.terminalEntryId, endedAt: input.endedAt };
502
- const marker = typeof input.readThrough === "string" && !input.readThrough.startsWith("result:")
503
- ? await index.entry({ ...boundary, entryId: input.readThrough.replace(/:(?:\d+|error)$/, "") }) : undefined;
504
- if (anchor === null)
505
- paging = { after: 0 };
506
- else if (anchor && !anchor.startsWith("result:")) {
507
- const entry = anchor === input.readThrough ? marker : await index.entry({ ...boundary, entryId: anchor.replace(/:(?:\d+|error)$/, "") });
508
- if (entry)
509
- paging = { after: Math.max(0, entry.sequence - 1) };
510
- }
511
- const result = await indexedHistory(index, { ...input, limit: 100, ...paging });
512
- if (!this.live(generation) || this.task(key)?.run.runId !== task.run.runId)
513
- return;
514
- return { ...result, isCurrent, latestPage: paging.after !== undefined ? !result.page.hasMore : paging.before === undefined && paging.cursor === undefined,
515
- readThroughSequence: marker?.sequence ?? (input.readThrough == null ? -1 : undefined) };
516
- }
517
- async savedResult(key, id) {
518
- const task = this.task(key), generation = this.generation;
519
- if (!task || !this.live())
520
- throw new Error("The owning session is no longer available.");
521
- const index = await runHistoryIndex(this.state);
522
- const result = await index.result({ runId: task.run.runId, index: task.child.index });
523
- if (!this.live(generation) || this.task(key)?.run.runId !== task.run.runId)
524
- throw new Error("The selected attempt changed; choose the report again.");
525
- if (!result)
526
- throw new Error("The selected saved report is unavailable.");
527
- return { id, kind: "assistant", title: "Saved result", text: readableText(result.text), timestamp: result.timestamp };
528
- }
529
- historyInput(task) {
530
- const visit = this.visits.get(task.key);
531
- return { runId: task.run.runId, index: task.child.index,
532
- messageIds: [...new Set([...(visit?.outbox.slice(0, 999).map((sent) => sent.id) ?? []), ...(visit?.lastSentId ? [visit.lastSentId] : [])])], readThrough: visit?.readThrough,
533
- ...(task.child.state === "live" ? {} : { leaf: task.child.result?.terminalLeafId, terminalEntryId: task.child.result?.terminalEntryId, endedAt: task.child.result?.terminalEntryId ? undefined : task.run.updatedAt }) };
534
- }
535
- applyMetadata(task, page) {
536
- const visit = this.visits.get(task.key), delivered = new Map(page.deliveredMessages);
537
- task.historySource = JSON.stringify([page.sourceId, page.generation, page.sessionId]);
538
- if (page.freshness.state !== "catching-up")
539
- task.metadataAt = task.run.updatedAt;
540
- task.model = agentModel(task.child, page.configuration);
541
- task.unread = Boolean(page.unreadAfter || visit && (task.child.activity?.lastActivityAt ?? 0) > (visit.seenActivityAt ?? 0));
542
- task.replied = Boolean(visit?.lastSentId && delivered.get(visit.lastSentId));
543
- if (!visit)
544
- return;
545
- if (visit.lastSentId && delivered.has(visit.lastSentId) && visit.notice === PENDING_MESSAGE_NOTICE)
546
- visit.notice = undefined;
547
- for (const sent of visit.outbox) {
548
- if (!delivered.has(sent.id))
549
- continue;
550
- if (visit.draft === sent.draft) {
551
- visit.draft = "";
552
- visit.quote = undefined;
553
- if (this.selectedKey === task.key)
554
- this.overlay?.syncDraft();
555
- }
556
- }
557
- visit.outbox = visit.outbox.filter((sent) => !delivered.has(sent.id));
558
- }
559
- task(key) { return this.tasks.find((task) => task.key === key); }
560
- isBusy(key) { return this.busy.has(key); }
561
160
  changed() {
562
- if (!this.live())
161
+ if (!this.live()) {
563
162
  return;
564
- if (this.saveTimer)
163
+ }
164
+ if (this.saveTimer) {
565
165
  clearTimeout(this.saveTimer);
166
+ }
566
167
  this.saveTimer = setTimeout(() => this.save(), 300);
567
- this.saveTimer.unref?.();
168
+ this.saveTimer.unref();
568
169
  this.render?.();
569
170
  }
570
171
  save() {
571
- if (this.saveTimer)
172
+ if (this.saveTimer) {
572
173
  clearTimeout(this.saveTimer);
174
+ }
573
175
  this.saveTimer = undefined;
574
- if (!this.live())
176
+ if (!this.live()) {
575
177
  return;
576
- const saved = { ownerSessionId: this.ctx.sessionManager.getSessionId(), visits: [...this.visits], pinned: this.pinned };
178
+ }
179
+ const saved = {
180
+ ownerSessionId: this.session.context().sessionManager.getSessionId(),
181
+ visits: [...this.store.visits],
182
+ pinned: this.store.pinned,
183
+ };
577
184
  const serialized = JSON.stringify(saved);
578
- if (serialized === this.lastSaved)
185
+ if (serialized === this.lastSaved) {
579
186
  return;
187
+ }
580
188
  this.pi.appendEntry(VIEW_ENTRY, saved);
581
189
  this.lastSaved = serialized;
582
190
  }
583
- pin(key) { this.pinned = key; this.save(); this.render?.(); }
191
+ pin(key) {
192
+ this.store.pinned = key;
193
+ this.save();
194
+ this.render?.();
195
+ }
584
196
  componentsBelowWidget(tui) {
585
- if (!this.widget)
197
+ if (!this.widget) {
586
198
  return undefined;
199
+ }
587
200
  // Public children work across loader boundaries; never render the parent history to measure the dock.
588
- const parent = tui.children.find((child) => "children" in child && Array.isArray(child.children) && child.children.includes(this.widget));
589
- return parent && [...parent.children.slice(parent.children.indexOf(this.widget) + 1), ...tui.children.slice(tui.children.indexOf(parent) + 1)];
201
+ const parent = tui.children.find((child) => "children" in child &&
202
+ Array.isArray(child.children) &&
203
+ child.children.includes(this.widget));
204
+ return (parent && [
205
+ ...parent.children.slice(parent.children.indexOf(this.widget) + 1),
206
+ ...tui.children.slice(tui.children.indexOf(parent) + 1),
207
+ ]);
590
208
  }
591
209
  availableHeight(tui) {
592
- if (tui.mode !== "fullscreen" || !this.widget)
210
+ if (tui.mode !== "fullscreen" || !this.widget) {
593
211
  return Math.max(1, tui.terminal.rows - 2);
212
+ }
594
213
  const below = this.componentsBelowWidget(tui);
595
- if (!below)
214
+ if (!below) {
596
215
  return Math.max(1, tui.terminal.rows - 2);
216
+ }
597
217
  const dock = [this.widget, ...below].reduce((height, child) => height + child.render(tui.terminal.columns).length, 0);
598
218
  return Math.max(1, tui.terminal.rows - dock - 1);
599
219
  }
600
- async open(key, ctx = this.ctx) {
601
- if (!ctx || !isTuiContext(ctx))
602
- return;
603
- if (!this.ctx || this.ctx.sessionManager.getSessionId() !== ctx.sessionManager.getSessionId())
220
+ prepareOpen(ctx, key) {
221
+ if (this.session.ctx?.sessionManager.getSessionId() !== ctx.sessionManager.getSessionId()) {
604
222
  this.start(ctx);
605
- if (!this.live())
223
+ }
224
+ if (!this.live()) {
606
225
  return;
226
+ }
607
227
  if (this.overlay) {
608
228
  this.closeOverlay?.(this.overlay instanceof AgentConversation && this.overlay.key === key ? undefined : key);
609
229
  return;
610
230
  }
611
- const generation = this.generation;
612
- if (!this.listPage)
613
- await this.refresh(true);
614
- if (!this.live(generation))
615
- return;
616
- let selected = key ?? (!this.filter && this.tasks.length === 1 && this.listPage?.total === 1 ? this.tasks[0].key : undefined);
617
- while (this.live(generation)) {
618
- this.selectedKey = selected;
619
- if (selected)
620
- this.refreshSelected(selected);
621
- let height;
622
- const overlayOptions = { width: "96%", margin: 1, anchor: "center", get maxHeight() { return height?.(); } };
623
- const result = await ctx.ui.custom((tui, theme, keys, done) => {
624
- height = () => this.availableHeight(tui);
625
- if (tui.mode === "fullscreen")
626
- overlayOptions.anchor = "top-center";
627
- this.closeOverlay = (next) => done(next);
628
- this.overlay = selected && this.task(selected)
629
- ? new AgentConversation(tui, theme, this, selected, done, keys)
630
- : new AgentPicker(tui, theme, this, done);
631
- return this.overlay;
632
- }, { overlay: true, overlayOptions });
633
- if (!this.live(generation))
634
- return;
635
- this.overlay = undefined;
636
- for (const task of [...this.tasks, ...this.dockTasks])
637
- if (task.key === this.selectedKey) {
638
- task.history = [];
639
- task.historyIds = [];
640
- task.page = undefined;
641
- task.finalId = undefined;
642
- task.historyLoading = false;
643
- }
644
- this.selectedKey = undefined;
645
- this.closeOverlay = undefined;
646
- this.save();
647
- void this.refresh();
648
- if (result === "peers") {
649
- this.pi.events.emit("intercom:open", {});
650
- return;
651
- }
652
- if (!result)
653
- return;
654
- selected = result === "picker" ? undefined : result;
231
+ return this.session.generation;
232
+ }
233
+ defaultSelection(key) {
234
+ if (key !== undefined) {
235
+ return key;
655
236
  }
237
+ return this.browser.listFilter.length === 0 &&
238
+ this.store.tasks.length === 1 &&
239
+ this.browser.listPage?.total === 1
240
+ ? this.store.tasks.at(0)?.key
241
+ : undefined;
656
242
  }
657
- breadcrumb(task, text, quote) {
658
- const label = short(task.label, 42);
659
- this.pi.sendMessage({ customType: DIRECTION_MESSAGE, display: true,
660
- content: `For context only: the user sent this directly to ${label} (run ${task.run.runId}, child ${task.child.index}). No relay or approval is needed.\n\n${text}${quote ? `\n\nRegarding ${quote.title}:\n${quote.text}` : ""}`,
661
- details: { label, text, quote, runId: task.run.runId, index: task.child.index } }, { triggerTurn: false });
243
+ showOverlay(ctx, selected) {
244
+ let height;
245
+ const overlayOptions = {
246
+ width: "96%",
247
+ margin: 1,
248
+ anchor: "center",
249
+ get maxHeight() {
250
+ return height?.();
251
+ },
252
+ };
253
+ return ctx.ui.custom((tui, theme, keys, done) => {
254
+ height = () => this.availableHeight(tui);
255
+ if (tui.mode === "fullscreen") {
256
+ overlayOptions.anchor = "top-center";
257
+ }
258
+ this.closeOverlay = (next) => done(next);
259
+ this.overlay =
260
+ hasText(selected) && this.store.task(selected)
261
+ ? new AgentConversation(tui, theme, {
262
+ controller: this.conversation,
263
+ key: selected,
264
+ done,
265
+ keys,
266
+ })
267
+ : new AgentPicker(tui, theme, this.picker, done);
268
+ return this.overlay;
269
+ }, { overlay: true, overlayOptions });
270
+ }
271
+ finishOverlay() {
272
+ this.overlay = undefined;
273
+ this.store.releaseSelected();
274
+ this.closeOverlay = undefined;
275
+ this.save();
276
+ this.defer(this.browser.refresh());
662
277
  }
663
- async send(key, text, continueExplicitly = false) {
664
- if (!text.trim() || !this.live() || this.busy.has(key))
665
- return;
666
- this.refreshSelected(key);
667
- const task = this.task(key), visit = this.visit(key), generation = this.generation;
668
- if (!task)
669
- return;
670
- if (task.child.identityUnavailable) {
671
- visit.notice = UNAVAILABLE_ASSIGNMENT;
672
- this.changed();
278
+ routeOverlay(result) {
279
+ if (result === "peers") {
280
+ this.pi.events.emit("intercom:open", {});
673
281
  return;
674
282
  }
675
- if (task.child.activity?.status === "pending")
676
- return;
677
- const question = task.question;
678
- const liveQuestion = question && questionProcessAlive(question);
679
- if (task.child.state !== "live" && !liveQuestion && !continueExplicitly) {
680
- visit.notice = { continue: task.child.state === "blocked" ? "blocked" : "finished" };
681
- this.changed();
283
+ if (!hasText(result)) {
682
284
  return;
683
285
  }
684
- if (!liveQuestion && task.child.state !== "live" && (task.child.missingSession || (!task.child.sessionFile && continueExplicitly))) {
685
- visit.notice = "The saved conversation is unavailable. No agent was started; your draft is kept.";
686
- this.changed();
286
+ return { selected: result === "picker" ? undefined : result };
287
+ }
288
+ async open(key, ctx = this.session.ctx) {
289
+ if (!ctx || !isTuiContext(ctx)) {
687
290
  return;
688
291
  }
689
- if (continueExplicitly && task.child.state !== "live" && !task.child.launch) {
690
- visit.notice = `This older run has no saved profile. Inspect it, then use agent_runs continue with agent: ${JSON.stringify(task.child.agent)} to explicitly choose the current profile. Your draft is kept.`;
691
- this.changed();
292
+ await this.openContext(ctx, key);
293
+ }
294
+ async openContext(ctx, key) {
295
+ const generation = this.prepareOpen(ctx, key);
296
+ if (generation === undefined) {
692
297
  return;
693
298
  }
694
- const draft = visit.draft, quote = visit.quote;
695
- if (!continueExplicitly && visit.outbox.some((sent) => sent.status !== "unconfirmed" && sent.text === text && JSON.stringify(sent.quote) === JSON.stringify(quote))) {
696
- visit.notice = PENDING_MESSAGE_NOTICE;
697
- this.changed();
299
+ if (!this.browser.listPage) {
300
+ await this.browser.refresh(true);
301
+ }
302
+ if (!this.live(generation)) {
698
303
  return;
699
304
  }
700
- const message = `${text}${quote ? `\n\nRegarding ${quote.title}:\n${quote.text}` : ""}`;
701
- this.busy.add(key);
702
- visit.notice = undefined;
703
- this.changed();
704
- try {
705
- if (question || task.child.state !== "live") {
706
- const result = await this.execute({ action: question ? "answer" : "resume", id: task.run.runId, index: task.child.index,
707
- ...(question ? { questionId: question.questionId } : {}), message, messageOrigin: "human" }, this.ctx);
708
- if (!this.live(generation))
709
- return;
710
- visit.notice = result.isError ? resultText(result) : question ? "Answer saved; waiting for the child. This is not proof it has acted." : "Continuation started on the saved conversation.";
711
- if (!result.isError) {
712
- if (visit.draft === draft) {
713
- visit.draft = "";
714
- visit.quote = undefined;
715
- }
716
- visit.outbox = visit.outbox.filter((sent) => sent.draft !== draft);
717
- this.breadcrumb(task, text, quote);
718
- }
719
- }
720
- else {
721
- const sent = { id: randomUUID(), runId: task.run.runId, index: task.child.index, text, draft, quote, at: Date.now(), status: "sending" };
722
- visit.outbox.push(sent);
723
- visit.lastSentId = sent.id;
724
- this.save();
725
- const receipt = await sendLiveSubagentMessage(this.pi.events, {
726
- to: resolveSubagentIntercomTarget(task.run.runId, task.child.agent, task.child.index), message: text, delivery: "steer", timeoutMs: 15_000, signal: this.pending.signal,
727
- extra: { messageId: sent.id, human: { ownerSessionId: this.ctx.sessionManager.getSessionId(), runId: task.run.runId, index: task.child.index },
728
- ...(quote ? { attachments: [{ type: "context", name: quote.title, content: quote.text }] } : {}) },
729
- });
730
- if (!this.live(generation))
731
- return;
732
- sent.status = receipt.accepted || receipt.delivered ? "waiting" : "unconfirmed";
733
- sent.reason = receipt.reason;
734
- if (receipt.accepted || receipt.delivered)
735
- this.breadcrumb(task, text, quote);
736
- else
737
- visit.notice = `Delivery unconfirmed. Your draft is kept. ${receipt.reason ?? "Check the conversation before retrying."}`;
305
+ let selected = this.defaultSelection(key);
306
+ while (this.live(generation)) {
307
+ this.store.selectedKey = selected;
308
+ if (hasText(selected)) {
309
+ this.refreshSelected(selected);
738
310
  }
739
- }
740
- catch (error) {
741
- if (this.live(generation))
742
- visit.notice = `Message not confirmed; draft kept. ${error instanceof Error ? error.message : String(error)}`;
743
- }
744
- finally {
745
- if (this.live(generation)) {
746
- this.busy.delete(key);
747
- this.refreshSelected(key);
748
- this.overlay?.syncDraft();
749
- this.save();
311
+ // Only one native overlay owns focus at a time; each selection must close before its successor opens.
312
+ // oxlint-disable-next-line no-await-in-loop
313
+ const result = await this.showOverlay(ctx, selected);
314
+ if (!this.live(generation)) {
315
+ return;
750
316
  }
751
- }
752
- }
753
- async stop(key) {
754
- if (!this.live() || this.busy.has(key))
755
- return;
756
- this.refreshSelected(key);
757
- const task = this.task(key);
758
- if (!task || task.child.identityUnavailable || (task.child.state !== "live" && !task.question))
759
- return;
760
- if (task.child.activity?.status === "pending")
761
- return;
762
- const generation = this.generation;
763
- this.busy.add(key);
764
- try {
765
- const result = await this.execute({ action: "interrupt", id: task.run.runId, index: task.child.index }, this.ctx);
766
- if (!this.live(generation))
317
+ this.finishOverlay();
318
+ const next = this.routeOverlay(result);
319
+ if (!next) {
767
320
  return;
768
- this.visit(key).notice = result.isError ? resultText(result) : "Stop requested for this child only. Independent siblings continue; dependent workflow steps may not run.";
769
- }
770
- catch (error) {
771
- if (this.live(generation))
772
- this.visit(key).notice = `Stop not confirmed: ${error instanceof Error ? error.message : String(error)}`;
773
- }
774
- finally {
775
- if (this.live(generation)) {
776
- this.busy.delete(key);
777
- this.refreshSelected(key);
778
- this.changed();
779
321
  }
322
+ selected = next.selected;
780
323
  }
781
324
  }
782
- async changes(key) {
783
- const task = this.task(key), generation = this.generation;
784
- if (!task || !this.live())
785
- return;
786
- const cwd = task.child.launch?.cwd ?? task.run.cwd;
787
- const result = await this.pi.exec("git", ["--no-pager", "diff", "--no-ext-diff", "--no-textconv", "HEAD", "--"], { cwd, timeout: 10_000, signal: this.pending.signal });
788
- if (!this.live(generation))
789
- return;
790
- return { id: "working-tree-changes", kind: "change", title: `Working tree changes · ${cwd}`, timestamp: Date.now(),
791
- text: "This is the working tree, not a claim that this child made every change. Untracked files are not included; inspect write/tool results for their full content.",
792
- diff: result.code === 0 ? readableText(result.stdout) || "No tracked changes." : undefined,
793
- details: result.code === 0 ? undefined : readableText(result.stderr) };
794
- }
795
325
  dispose() {
796
326
  this.save();
797
- this.generation++;
798
- this.pending.abort();
327
+ this.session.dispose();
328
+ this.browser.dispose();
799
329
  this.closeOverlay?.();
800
330
  this.overlay?.dispose();
801
331
  this.closeOverlay = undefined;
802
332
  this.overlay = undefined;
803
333
  this.unsubscribe?.();
804
334
  this.unsubscribe = undefined;
805
- this.indexUnsubscribe?.();
806
- this.indexUnsubscribe = undefined;
807
- this.refreshPromise = undefined;
808
- this.listRequest++;
809
- this.listPage = undefined;
810
- this.listError = undefined;
811
- this.listLoading = false;
812
- this.selectedKey = undefined;
813
- this.offset = 0;
814
- this.filter = "";
815
- if (this.timer)
335
+ if (this.timer) {
816
336
  clearInterval(this.timer);
817
- if (this.saveTimer)
337
+ }
338
+ if (this.saveTimer) {
818
339
  clearTimeout(this.saveTimer);
819
- this.timer = this.saveTimer = undefined;
340
+ }
341
+ this.timer = undefined;
342
+ this.saveTimer = undefined;
820
343
  this.render = undefined;
821
344
  this.widget = undefined;
822
- this.ctx = undefined;
823
- this.ownerSessionId = undefined;
824
- this.tasks = [];
825
- this.dockTasks = [];
826
- this.taskKeys.clear();
827
- this.visits.clear();
828
- this.busy.clear();
829
- this.pinned = undefined;
345
+ this.controls.dispose();
346
+ this.store.reset();
830
347
  this.lastSaved = "";
831
348
  }
832
349
  }
833
- class AgentPicker extends Container {
834
- list;
835
- itemKeys = [];
836
- closed = false;
837
- search;
838
- signature = "";
839
- wasPending = false;
840
- hasFocus = false;
841
- tui;
842
- theme;
843
- controller;
844
- done;
845
- constructor(tui, theme, controller, done) {
846
- super();
847
- this.tui = tui;
848
- this.theme = theme;
849
- this.controller = controller;
850
- this.done = done;
851
- this.search = new Input({ placeholder: "Filter agents or assignments…", placeholderStyle: (text) => theme.fg("dim", text) });
852
- this.search.setValue(controller.listFilter);
853
- this.render(tui.terminal.columns);
854
- }
855
- get focused() { return this.hasFocus; }
856
- set focused(value) { this.hasFocus = value; this.search.focused = value; }
857
- refresh() { this.tui.requestRender(); }
858
- render(width) {
859
- const height = this.controller.availableHeight(this.tui), innerWidth = Math.max(1, width - 2);
860
- const query = this.search.getValue();
861
- const tasks = this.controller.tasks.filter((task) => this.controller.listPage?.rows.some((run) => run.runId === task.run.runId));
862
- const items = tasks.map((task) => {
863
- const { status, badge } = taskSummary(task);
864
- return { value: task.key, label: `${task.child.agent} · ${task.label}`, description: status + badge };
865
- });
866
- if (this.controller.listPage?.offset)
867
- items.push({ value: "previous-page", label: "Previous agents", description: "PgUp" });
868
- if (this.controller.listPage?.nextOffset !== undefined)
869
- items.push({ value: "next-page", label: "More agents", description: "PgDn" });
870
- items.push({ value: "retry", label: "Refresh / retry history", description: "F5" });
871
- if (!query)
872
- items.push({ value: "peers", label: "Other connected sessions", description: "All projects" });
873
- const labelWidth = Math.min(Math.max(0, ...items.map((item) => visibleWidth(item.label))), Math.max(40, Math.floor(innerWidth / 2)));
874
- for (const [index, task] of tasks.entries()) {
875
- const item = items[index], modelWidth = innerWidth - labelWidth - visibleWidth(item.description) - 7;
876
- if (modelWidth >= 16)
877
- item.description += ` · ${short(task.model.summary, modelWidth)}`;
878
- }
879
- this.itemKeys = items.map((item) => item.value);
880
- const selected = this.controller.listPending || this.wasPending ? undefined : this.list?.getSelectedItem()?.value;
881
- this.wasPending = this.controller.listPending;
882
- const selectedIndex = Math.max(0, items.findIndex((item) => item.value === selected));
883
- const task = this.controller.task(items[selectedIndex]?.value ?? "");
884
- const status = this.controller.listError ?? (this.controller.listPage?.freshness.state === "catching-up" ? "Catching up · partial results" : this.controller.listPage?.freshness.state === "degraded" ? "Some history unavailable · F5 retry" : this.controller.listLoading ? "Loading…" : "");
885
- const signature = JSON.stringify([items, query, width, height, status, this.controller.listPage?.total, items[selectedIndex]?.value, task && activity(task), task?.model, task?.child.task, task?.run.runId]);
886
- if (signature !== this.signature) {
887
- const compact = height < 16;
888
- const header = new Container();
889
- header.addChild(new Text(this.theme.fg("accent", this.theme.bold(truncateToWidth(`Agents · ${this.controller.listPage?.total ?? "…"} owned runs · page ${Math.floor((this.controller.listPage?.offset ?? 0) / 50) + 1}`, innerWidth))), 0, 0));
890
- header.addChild(this.search);
891
- if (status)
892
- header.addChild(new Text(this.theme.fg(this.controller.listError ? "warning" : "dim", truncateToWidth(status, innerWidth)), 0, 0));
893
- if (!compact)
894
- header.addChild(new Spacer(1));
895
- const footer = new Container();
896
- if (!compact)
897
- footer.addChild(new Spacer(1));
898
- if (task && !compact) {
899
- footer.addChild(new Text(this.theme.fg("muted", short(`${task.child.agent} · ${activity(task)} · ${task.run.runId.slice(0, 8)}`, innerWidth)), 0, 0));
900
- footer.addChild(new Text(this.theme.fg("dim", short(task.model.summary, innerWidth)), 0, 0));
901
- const assignment = new Text(readableText(task.child.task ?? "Original assignment unavailable."), 0, 0).render(innerWidth);
902
- footer.addChild(new Text(assignment.slice(0, 3).join("\n"), 0, 0));
903
- }
904
- const up = primaryKey("tui.select.up"), down = primaryKey("tui.select.down");
905
- const choose = [{ text: up, run: () => this.act("up") }, up && down ? "/" : "", { text: `${down}${compact ? "" : " Choose"}`, run: () => this.act("down") }];
906
- const open = { text: `${primaryKey("tui.select.confirm")}${compact ? "" : " Open"}`.trim(), run: () => this.act("open") };
907
- const back = { text: `${primaryKey("tui.select.cancel")}${compact ? "" : " Back"}`.trim(), run: () => this.act("back") };
908
- const filter = { text: "Type to filter", run: () => this.act("filter") };
909
- const controls = compact ? [...choose, " · ", open, " · ", back]
910
- : width < 60 ? [...choose, " · ", open, "\n", back, " · ", filter] : [filter, " · ", ...choose, " · ", open, " · ", back];
911
- footer.addChild(actionHints(controls, (text) => this.theme.fg("dim", text)));
912
- const visible = Math.max(1, height - 3 - header.render(innerWidth).length - footer.render(innerWidth).length);
913
- // Native SelectList needs more than ten cells to show its description column.
914
- const descriptionWidth = innerWidth > 40 ? Math.max(11, ...items.map((item) => visibleWidth(item.description ?? ""))) : 0;
915
- const primaryWidth = Math.max(1, innerWidth - descriptionWidth - (descriptionWidth ? 4 : 2));
916
- this.list = new SelectList(items, visible, getSelectListTheme(), { minPrimaryColumnWidth: Math.min(24, primaryWidth), maxPrimaryColumnWidth: primaryWidth, truncatePrimary: ({ text, maxWidth }) => truncateToWidth(text, maxWidth) });
917
- this.list.setSelectedIndex(selectedIndex);
918
- this.list.onSelect = (item) => {
919
- if (this.controller.listPending && !["retry", "peers"].includes(item.value))
920
- return;
921
- if (item.value === "next-page" || item.value === "previous-page")
922
- this.controller.pageTasks(item.value === "next-page" ? "later" : "earlier");
923
- else if (item.value === "retry")
924
- void this.controller.retry();
925
- else
926
- this.done(item.value);
927
- };
928
- this.list.onCancel = () => this.act("back");
929
- const body = new Box(1, 0, (text) => this.theme.bg("customMessageBg", text));
930
- body.addChild(header);
931
- body.addChild(items.length ? this.list : new Text(query ? "No matching agents." : "No agents owned by this session yet.", 0, 0));
932
- body.addChild(footer);
933
- this.clear();
934
- this.addChild(new DynamicBorder((text) => this.theme.fg("borderAccent", text)));
935
- this.addChild(body);
936
- this.addChild(new DynamicBorder((text) => this.theme.fg("borderAccent", text)));
937
- this.signature = signature;
938
- }
939
- return super.render(width);
940
- }
941
- invalidate() { this.signature = ""; super.invalidate(); }
942
- syncDraft() { }
943
- act(action) {
944
- if (this.closed)
945
- return;
946
- if (action === "back")
947
- this.done();
948
- else if (action === "filter")
949
- this.tui.setFocus(this);
950
- else if (action === "open") {
951
- const item = this.list?.getSelectedItem();
952
- if (item)
953
- this.list?.onSelect?.(item);
954
- }
955
- else if (this.itemKeys.length) {
956
- const index = this.itemKeys.indexOf(this.list?.getSelectedItem()?.value ?? "");
957
- this.list?.setSelectedIndex((index + (action === "up" ? -1 : 1) + this.itemKeys.length) % this.itemKeys.length);
958
- }
959
- this.tui.requestRender();
960
- }
961
- handleInput(data) {
962
- if (this.closed)
963
- return;
964
- if (matchesKey(data, this.controller.shortcut)) {
965
- this.done();
966
- return;
967
- }
968
- if (matchesKey(data, "pageDown") || matchesKey(data, "pageUp")) {
969
- this.controller.pageTasks(matchesKey(data, "pageDown") ? "later" : "earlier");
970
- return;
971
- }
972
- if (matchesKey(data, "f5")) {
973
- void this.controller.retry();
974
- return;
975
- }
976
- const keys = getKeybindings();
977
- if (keys.matches(data, "tui.select.cancel")) {
978
- this.act("back");
979
- return;
980
- }
981
- if (["tui.select.up", "tui.select.down", "tui.select.confirm"].some((key) => keys.matches(data, key)))
982
- this.list?.handleInput(data);
983
- else {
984
- const previous = this.search.getValue();
985
- this.search.handleInput(data);
986
- if (this.search.getValue() !== previous)
987
- this.list = undefined;
988
- this.controller.filterTasks(this.search.getValue());
989
- }
990
- this.tui.requestRender();
991
- }
992
- dispose() { this.closed = true; }
993
- }
994
- /** Native ScrollView owns follow/scroll state; overlays only need a bounded render adapter. */
995
- export class AgentConversation extends Container {
996
- editor;
997
- scroll;
998
- viewport;
999
- editorViewport;
1000
- editorHeight = Infinity;
1001
- editorFocus = true;
1002
- hasFocus = false;
1003
- closed = false;
1004
- beforeInput = "";
1005
- height = 1;
1006
- lastWidth;
1007
- selectedId;
1008
- detail;
1009
- detailError;
1010
- menu;
1011
- lines = [];
1012
- components = new Map();
1013
- initialPosition = true;
1014
- initialVisit;
1015
- restoreAnchor;
1016
- conversationAnchor;
1017
- contentItems = [];
1018
- pendingDetail = false;
1019
- loadingDetail = false;
1020
- toolsExpanded = false;
1021
- toolExpansion = new Map();
1022
- pageRequest = 0;
1023
- detailRequest = 0;
1024
- loadingPage = false;
1025
- refreshPending = false;
1026
- pageError;
1027
- latestPage = true;
1028
- readSequence = -1;
1029
- seenSource = false;
1030
- toolDefinitions;
1031
- keys;
1032
- tui;
1033
- theme;
1034
- controller;
1035
- key;
1036
- done;
1037
- constructor(tui, theme, controller, key, done, keys) {
1038
- super();
1039
- this.tui = tui;
1040
- this.theme = theme;
1041
- this.controller = controller;
1042
- this.key = key;
1043
- this.done = done;
1044
- this.keys = keys;
1045
- this.seenSource = Boolean(this.task?.page?.count);
1046
- this.initialVisit = { anchor: this.visit.anchor, readThrough: this.visit.readThrough };
1047
- const cwd = this.task?.child.launch?.cwd ?? this.task?.run.cwd ?? process.cwd();
1048
- this.toolDefinitions = new Map([createReadToolDefinition, createBashToolDefinition, createEditToolDefinition, createWriteToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createPowerShellToolDefinition].map((create) => { const definition = create(cwd); return [definition.name, definition]; }));
1049
- this.editor = new Editor(tui, { borderColor: (text) => theme.fg("accent", text), selectList: getSelectListTheme() }, { paddingX: 0 });
1050
- this.editor.setText(this.visit.draft);
1051
- this.editor.onChange = () => { if (this.closed)
1052
- return; this.visit.draft = this.editor.getExpandedText(); this.controller.changed(); };
1053
- this.editor.onSubmit = (text) => {
1054
- this.editor.setText(this.beforeInput);
1055
- if (!this.loadingDetail)
1056
- void this.controller.send(this.key, text);
1057
- };
1058
- let editorOffset = 0;
1059
- this.editorViewport = {
1060
- invalidate: () => this.editor.invalidate(),
1061
- render: (width) => {
1062
- const lines = this.editor.render(width);
1063
- // Native overlays do not allocate editor height; keep Pi's cursor and input handling intact when space is short.
1064
- const cursor = lines.findIndex((line) => line.includes(CURSOR_MARKER));
1065
- if (cursor >= 0)
1066
- editorOffset = Math.max(0, cursor - this.editorHeight + 1);
1067
- editorOffset = Math.max(0, Math.min(editorOffset, lines.length - this.editorHeight));
1068
- return lines.slice(editorOffset, editorOffset + this.editorHeight);
1069
- },
1070
- handleMouse: (event) => this.editor.handleMouse({ ...event, y: event.y + editorOffset }),
1071
- };
1072
- this.scroll = new ScrollView({ render: (width) => this.renderHistory(width), invalidate() { } }, { follow: "end", scrollbar: "hidden" });
1073
- this.viewport = { invalidate: () => this.scroll.invalidate(), render: (width) => {
1074
- if (this.lastWidth !== width && !this.scroll.isFollowingEnd)
1075
- this.restoreAnchor ??= this.anchor();
1076
- this.lastWidth = width;
1077
- const lines = this.scroll.render(width);
1078
- this.scroll.updateLayout(lines.length, this.height, () => this.tui.requestRender());
1079
- if (this.initialPosition && !this.loadingPage && (this.task?.page || this.task?.child.identityUnavailable)) {
1080
- this.initialPosition = false;
1081
- const ids = this.task?.historyIds ?? [];
1082
- const readIndex = this.initialVisit.readThrough ? ids.indexOf(this.initialVisit.readThrough) : -1;
1083
- const unread = this.initialVisit.readThrough === null ? ids[0] : readIndex >= 0 ? ids[readIndex + 1] : undefined;
1084
- this.restoreAnchor = unread ? { id: unread, line: 0 } : this.initialVisit.anchor;
1085
- if (!this.restoreAnchor && this.task?.finalId) {
1086
- this.restoreAnchor = { id: this.task.finalId, line: 0 };
1087
- // The finished report is the starting point; earlier activity stays available above it.
1088
- this.visit.readThrough ??= ids[ids.indexOf(this.task.finalId) - 1] ?? null;
1089
- }
1090
- }
1091
- if (this.restoreAnchor) {
1092
- const line = this.lines.find((line) => line.entryIds.includes(this.restoreAnchor.id));
1093
- if (line)
1094
- this.scroll.scrollTo(line.start + Math.min(this.restoreAnchor.line, Math.max(0, line.end - line.start - 1)), { disableFollow: true });
1095
- this.restoreAnchor = undefined;
1096
- }
1097
- const visible = lines.slice(this.scroll.scrollTop, this.scroll.scrollTop + this.height);
1098
- while (visible.length < this.height)
1099
- visible.push("");
1100
- if (!this.detail && !this.loadingPage && !this.initialPosition) {
1101
- this.visit.anchor = this.scroll.isFollowingEnd ? undefined : this.anchor();
1102
- const ids = this.task?.historyIds ?? [];
1103
- const seen = this.lines.filter((line) => line.end > this.scroll.scrollTop && line.end <= this.scroll.scrollTop + this.height).flatMap((line) => line.entryIds);
1104
- const last = ids.findLastIndex((id) => seen.includes(id));
1105
- if (last >= 0) {
1106
- const id = ids[last].startsWith("result:") ? this.task?.page?.latestEntryId ?? ids[last] : ids[last];
1107
- const nativeId = id.replace(/:(?:\d+|error)$/, ""), sequence = this.task?.page?.entries.find((entry) => entry.id === nativeId)?.sequence;
1108
- if (sequence !== undefined && (sequence > this.readSequence || sequence === this.readSequence && last > (this.visit.readThrough ? ids.indexOf(this.visit.readThrough) : -1))) {
1109
- this.visit.readThrough = id;
1110
- this.readSequence = sequence;
1111
- }
1112
- }
1113
- this.visit.readThrough ??= null;
1114
- if (this.scroll.isFollowingEnd)
1115
- this.visit.seenActivityAt = this.task?.child.activity?.lastActivityAt ?? Date.now();
1116
- }
1117
- return visible;
1118
- }, handleMouse: (event) => this.historyMouse(event) };
1119
- void this.loadPage({}, this.visit.anchor?.id ?? this.visit.readThrough);
1120
- }
1121
- async loadPage(paging = {}, anchor) {
1122
- if (this.closed)
1123
- return;
1124
- const request = ++this.pageRequest;
1125
- this.refreshPending = false;
1126
- this.loadingPage = true;
1127
- this.pageError = undefined;
1128
- if (this.task)
1129
- this.task.historyLoading = true;
1130
- this.latestPage = paging.before === undefined && paging.after === undefined && paging.cursor === undefined;
1131
- this.tui.requestRender();
1132
- try {
1133
- let result = await this.controller.historyPage(this.key, paging, anchor);
1134
- if (this.closed || request !== this.pageRequest || !result || !this.task)
1135
- return;
1136
- if (this.initialPosition && this.initialVisit.readThrough === undefined && !this.initialVisit.anchor && this.task.child.state !== "live" && result.history.finalId && !result.history.entryIds.includes(result.history.finalId)) {
1137
- const reportPage = await this.controller.historyPage(this.key, {}, result.history.finalId);
1138
- if (this.closed || request !== this.pageRequest || !this.task)
1139
- return;
1140
- if (reportPage)
1141
- result = reportPage;
1142
- }
1143
- let task = this.task;
1144
- let history = result.history;
1145
- if ((this.initialPosition || result.latestPage || history.finalId && history.entryIds.includes(history.finalId)) && task.child.state !== "live" && task.child.result && getSingleResultOutput(task.child.result)) {
1146
- const report = await this.controller.savedResult(this.key, `result:${task.run.runId}`);
1147
- const currentTask = this.task;
1148
- if (this.closed || request !== this.pageRequest || !currentTask || currentTask.run.runId !== task.run.runId)
1149
- return;
1150
- task = currentTask;
1151
- history = withFinalResult(history, report.text, task.run.runId, task.run.updatedAt);
1152
- }
1153
- if (!result.isCurrent()) {
1154
- void this.loadPage(paging, anchor);
1155
- return;
1156
- }
1157
- if (result.readThroughSequence !== undefined)
1158
- this.readSequence = result.readThroughSequence;
1159
- this.latestPage = result.latestPage;
1160
- task.history = history.items;
1161
- task.historyIds = history.entryIds;
1162
- task.finalId = history.finalId;
1163
- task.page = result.page;
1164
- for (const item of task.history)
1165
- if (item.id === `result:${task.run.runId}`)
1166
- item.load = () => this.controller.savedResult(this.key, item.id);
1167
- this.seenSource ||= result.page.count > 0;
1168
- task.unavailable = !this.seenSource && task.child.state === "live" && ["missing", "pending", "indexing", "unlinked"].includes(result.page.sourceState) ? undefined : history.unavailable;
1169
- this.controller.applyMetadata(task, result.page);
1170
- this.components.clear();
1171
- }
1172
- catch (error) {
1173
- if (!this.closed && request === this.pageRequest)
1174
- this.pageError = `History unavailable: ${error instanceof Error ? error.message : String(error)}. F5 retries the selected run.`;
1175
- }
1176
- finally {
1177
- if (!this.closed && request === this.pageRequest) {
1178
- this.loadingPage = false;
1179
- if (this.task)
1180
- this.task.historyLoading = false;
1181
- if (this.refreshPending && !this.pageError) {
1182
- this.refreshPending = false;
1183
- this.refresh();
1184
- }
1185
- this.tui.requestRender();
1186
- }
1187
- }
1188
- }
1189
- get visit() { return this.controller.visit(this.key); }
1190
- get task() { return this.controller.task(this.key); }
1191
- get focused() { return this.hasFocus; }
1192
- set focused(value) { this.hasFocus = value; this.editor.focused = value && this.editorFocus && !this.detail && !this.menu; }
1193
- anchor() {
1194
- const line = this.lines.findLast((line) => line.start <= this.scroll.scrollTop);
1195
- return line ? { id: line.id, line: this.scroll.scrollTop - line.start } : undefined;
1196
- }
1197
- refresh() {
1198
- if (this.closed)
1199
- return;
1200
- if (this.detail?.id === "assignment" && this.task)
1201
- this.detail = this.assignment();
1202
- if (!this.initialPosition && !this.scroll.isFollowingEnd)
1203
- this.restoreAnchor = this.anchor();
1204
- if (this.latestPage && this.scroll.isFollowingEnd && !this.detail && !this.pageError) {
1205
- if (this.loadingPage)
1206
- this.refreshPending = true;
1207
- else
1208
- void this.loadPage();
1209
- }
1210
- this.tui.requestRender();
1211
- }
1212
- syncDraft() { if (!this.closed && this.editor.getExpandedText() !== this.visit.draft)
1213
- this.editor.setText(this.visit.draft); }
1214
- assignment() {
1215
- const task = this.task;
1216
- return { id: "assignment", kind: "notice", title: task.child.identityUnavailable ? "Assignment unavailable" : `Assignment · ${task.child.agent}`, text: task.child.identityUnavailable ? UNAVAILABLE_ASSIGNMENT : `${task.model.details}\n\n${task.child.task ?? "The original per-child assignment was not saved for this older run."}`, timestamp: task.run.startedAt };
1217
- }
1218
- items() {
1219
- const task = this.task;
1220
- if (!task)
1221
- return [{ id: "unavailable", kind: "notice", title: "Agent unavailable", text: "The owning session or run is no longer available.", timestamp: 0 }];
1222
- if (this.detail)
1223
- return [this.detail];
1224
- const page = task.page;
1225
- const status = this.pageError ?? (this.loadingPage && !task.page ? "Loading conversation…" : page?.freshness.state === "catching-up" ? "Catching up · partial history" : page?.freshness.state === "degraded" ? "Some saved history unavailable · F5 retry" : undefined);
1226
- const items = [this.assignment(), ...(status ? [{ id: "history-loading", kind: "notice", title: status, text: "", timestamp: 0 }] : []),
1227
- ...(page && page.count > page.entries.length ? [{ id: "history-page", kind: "notice", title: `History · ${page.entries.length} of ${page.count} native records${this.latestPage ? " · latest" : ""}`, text: "F2 Actions → Earlier history / Later history / Latest. Full details validates the selected native record.", timestamp: 0 }] : []),
1228
- ...(!task.child.identityUnavailable && task.child.state !== "live" ? [{ id: "process-exit", kind: "notice", title: `Agent: ${task.child.state}`, text: formatAgentProcessExit(task.child.result?.agentProcessExit), timestamp: task.run.updatedAt }] : []), ...task.history];
1229
- const humanAction = task.child.humanAction ?? acceptanceHumanAction(task.child.result?.acceptance);
1230
- if (humanAction && !task.child.identityUnavailable)
1231
- items.push({ id: "human-action", kind: "notice", title: "Needs your action — acceptance incomplete", text: humanAction, timestamp: task.run.updatedAt });
1232
- if (task.unavailable && !task.child.identityUnavailable)
1233
- items.push({ id: "unavailable", kind: "notice", title: "Conversation unavailable", text: task.unavailable, timestamp: 0 });
1234
- if (task.question)
1235
- items.push({ id: `question:${task.question.questionId}`, kind: "notice", title: "Waiting for your answer", text: task.question.message, timestamp: task.question.createdAt });
1236
- if (!task.child.identityUnavailable && task.child.state === "live" && task.child.activity?.streamingText)
1237
- items.push({ id: `live:${task.run.runId}`, kind: "assistant", title: "Agent · writing", text: task.child.activity.streamingText, timestamp: task.child.activity.lastActivityAt ?? 0 });
1238
- for (const sent of this.visit.outbox)
1239
- items.push({ id: `outgoing:${sent.id}`, kind: "user", title: sent.status === "sending" ? "You · sending" : sent.status === "waiting" ? "You · waiting for the child / tool boundary" : "You · delivery unconfirmed",
1240
- text: `${sent.text}${sent.quote ? `\n\nRegarding ${sent.quote.title}:\n${sent.quote.text}` : ""}${sent.reason ? `\n\n${sent.reason}` : ""}`, timestamp: sent.at });
1241
- return items;
1242
- }
1243
- renderHistory(width) {
1244
- this.contentItems = this.items();
1245
- const visibleIds = new Set(this.contentItems.map((item) => item.id));
1246
- for (const id of this.components.keys())
1247
- if (!visibleIds.has(id))
1248
- this.components.delete(id);
1249
- this.lines = [];
1250
- const lines = [];
1251
- for (const item of this.contentItems) {
1252
- const selected = item.id === this.selectedId && !this.editorFocus;
1253
- const expanded = Boolean(this.detail) || (this.toolExpansion.get(item.id) ?? this.toolsExpanded);
1254
- // Native cards and selected details are immutable snapshots. Comparing
1255
- // identity avoids evaluating/serializing their potentially large bodies.
1256
- const signature = `${expanded}:${Boolean(this.detail)}`;
1257
- let cached = this.components.get(item.id);
1258
- const sameDisplay = cached && !item.load && !cached.item.load && !item.assistant && !cached.item.assistant && !item.call && !cached.item.call && !item.result && !cached.item.result
1259
- && item.kind === cached.item.kind && item.title === cached.item.title && item.text === cached.item.text
1260
- && item.details === cached.item.details && item.diff === cached.item.diff && item.model === cached.item.model
1261
- && item.timestamp === cached.item.timestamp && item.messageId === cached.item.messageId;
1262
- if (cached?.signature !== signature || cached.item !== item && !sameDisplay) {
1263
- const component = new Container();
1264
- if (this.detail && item.model)
1265
- component.addChild(new Text(this.theme.fg("muted", `Message model: ${readableText(formatModelThinking(item.model))}`), 0, 0));
1266
- if (item.call || item.result) {
1267
- const name = item.call?.name ?? item.result.toolName;
1268
- const definition = (item.call && this.toolDefinitions.get(name)) || { renderCall: () => new Text(this.theme.fg("toolTitle", this.theme.bold(readableText(item.title))), 0, 0) };
1269
- const options = { compactView: true, showImages: false };
1270
- const tool = new ToolExecutionComponent(name, item.call?.id ?? item.result.toolCallId, item.call?.arguments ?? {}, options, definition, this.tui, this.task?.child.launch?.cwd ?? this.task?.run.cwd ?? process.cwd());
1271
- // Replay only saved data: execution/args-complete hooks would invent timing or re-read today's file for an edit preview.
1272
- if (item.result)
1273
- tool.updateResult(item.result);
1274
- tool.setExpanded(expanded);
1275
- component.addChild(tool);
1276
- // Native edit cards already show their recorded diff.
1277
- if (this.detail && item.diff && item.call?.name !== "edit")
1278
- component.addChild(new Text(renderDiff(item.diff), 0, 0));
1279
- if (!item.result)
1280
- component.addChild(new Text(this.theme.fg("dim", "Result not recorded · exit unconfirmed"), 1, 0));
1281
- }
1282
- else if (item.kind === "assistant" || item.kind === "thinking") {
1283
- // Live text and canonical reports are display-only, never saved as synthetic model messages.
1284
- const message = item.assistant ?? { role: "assistant", content: [{ type: "text", text: item.text }], timestamp: item.timestamp,
1285
- api: "", provider: "", model: "", stopReason: "stop", usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } } };
1286
- component.addChild(new AssistantMessageComponent(this.detail ? message : { ...message, content: message.content.map((part) => part.type === "text" ? { ...part, text: stripAcceptanceReport(readableText(part.text)) } : part) }, !this.detail, getMarkdownTheme(), "Thinking · open details to read"));
1287
- }
1288
- else if (item.kind === "user") {
1289
- if (item.id.startsWith("outgoing:") || item.messageId)
1290
- component.addChild(new Text(this.theme.fg("muted", item.title), 0, 0));
1291
- component.addChild(new UserMessageComponent(item.text, getMarkdownTheme(), 1));
1292
- }
1293
- else {
1294
- component.addChild(new Text(this.theme.fg("muted", readableText(item.title)), 0, 0));
1295
- component.addChild(new Text(item.text, 0, 0));
1296
- if (item.diff)
1297
- component.addChild(new Text(renderDiff(item.diff), 0, 0));
1298
- }
1299
- if (this.detail && item.details)
1300
- component.addChild(new Text(item.details, 0, 1));
1301
- component.addChild(new Spacer(1));
1302
- cached = { signature, item, component };
1303
- this.components.set(item.id, cached);
1304
- }
1305
- const start = lines.length;
1306
- if (selected)
1307
- lines.push(this.theme.fg("accent", short(`› ${item.title}`, width)));
1308
- const contentStart = lines.length;
1309
- lines.push(...cached.component.render(width));
1310
- this.lines.push({ id: item.id, entryIds: item.entryIds ?? [item.id], start, contentStart, end: lines.length });
1311
- }
1312
- return lines;
1313
- }
1314
- render(width) {
1315
- const task = this.task, innerWidth = Math.max(1, width - 2);
1316
- const height = this.controller.availableHeight(this.tui), compact = height < 16;
1317
- const terminal = task?.child.state !== "live" && !task?.question;
1318
- const primary = task?.child.identityUnavailable || task?.child.activity?.status === "pending" ? undefined : terminal ? "continue" : this.editorFocus ? "send" : "details";
1319
- const actionHint = this.loadingDetail && !this.detail ? "Loading quoted context…" : task?.child.identityUnavailable ? "Assignment unavailable · draft kept" : task?.child.activity?.status === "pending" ? "Waiting to start · draft kept" : terminal ? width < 60 ? "Alt+C Continue" : "Alt+C Continue with message" : this.editorFocus ? `${primaryKey("tui.input.submit")} Send`.trim() : "Enter Details";
1320
- const primaryHint = primary ? { text: actionHint, run: () => this.act(primary) } : actionHint;
1321
- const actions = { text: compact ? "F2" : "F2 Actions", run: () => this.act("actions") };
1322
- const focus = { text: compact ? "Tab" : "Tab Read/write", run: () => this.act("focus") };
1323
- const reply = { text: compact ? "Alt+R" : "Alt+R Reply", run: () => this.act("reply") };
1324
- const back = { text: this.menu ? `${primaryKey("tui.select.cancel")}${compact ? "" : this.detail ? " Back to details" : " Back to conversation"}`.trim() : compact ? "Esc" : "Esc Back", run: () => this.act("back") };
1325
- const choose = { text: `${primaryKey("tui.select.confirm")}${compact ? "" : " Choose"}`.trim(), run: () => this.act("choose") };
1326
- const controls = this.menu ? [choose, " · ", back] : this.detail ? [reply, " · ", actions, " · ", back] : compact ? [actions, " · ", focus, " · ", back]
1327
- : width < 60 ? [actions, " · ", back, "\n", primaryHint, " · ", focus] : [primaryHint, " · ", actions, " · ", focus, " · ", back];
1328
- const footer = actionHints(controls, this.menu ? undefined : (text) => this.theme.fg("dim", text));
1329
- // Reserve title, controls and required content before spending rows on status or borders.
1330
- const contentRows = this.menu ? this.menu.render(innerWidth).length : 1 + (this.detail ? 0 : 1 + Number(!compact) + Number(Boolean(this.visit.notice)) + Number(Boolean(this.visit.quote)));
1331
- const requiredRows = 1 + footer.render(innerWidth).length + contentRows;
1332
- const showStatus = height > requiredRows;
1333
- const framed = height >= requiredRows + Number(showStatus) + 2;
1334
- this.clear();
1335
- const body = new Box(1, 0, (text) => this.theme.bg("customMessageBg", text));
1336
- const header = new Container();
1337
- header.addChild(new Text(this.theme.fg("accent", this.theme.bold(truncateToWidth(`${this.loadingPage && !task?.page && !this.detail ? "Loading conversation…" : this.pageError && !this.detail ? "History unavailable · F5 retry" : "Agents"} › ${task?.label ?? "unavailable"}${this.detail ? " › details" : ""}`, innerWidth))), 0, 0));
1338
- if (showStatus)
1339
- header.addChild(actionHints([
1340
- ...(task?.unread && !this.scroll.isFollowingEnd ? ["New activity · ", ...(this.menu ? [] : [{ text: "Alt+L latest", run: () => this.act("latest") }, " · "])] : []),
1341
- task ? `${task.child.agent} · ${activity(task)} · ${task.model.summary}` : "Unavailable",
1342
- ], (text) => this.theme.fg("dim", text), "..."));
1343
- body.addChild(header);
1344
- if (this.menu) {
1345
- body.addChild(this.menu);
1346
- body.addChild(footer);
1347
- }
1348
- else {
1349
- const bottom = new Container();
1350
- if (!this.detail) {
1351
- const notice = this.visit.notice;
1352
- if (notice)
1353
- bottom.addChild(actionHints(typeof notice === "string" ? [readableText(notice).replace(/\s+/g, " ").trim()]
1354
- : [CONTINUE_NOTICES[notice.continue], { text: CONTINUE_HINT, run: () => this.act("continue") }, "."], (text) => this.theme.fg("warning", text), "..."));
1355
- if (this.visit.quote)
1356
- bottom.addChild(actionHints(["Quote · ", { text: "Alt+Q remove", run: () => this.act("unquote") }, ": ", this.visit.quote.title], (text) => this.theme.fg("dim", text), "..."));
1357
- if (!compact)
1358
- bottom.addChild(new Text(this.theme.fg("accent", truncateToWidth(`${task?.question ? "Answer" : "Message"} ${task?.label ?? "agent"}${this.controller.isBusy(this.key) ? " · sending" : ""}`, innerWidth)), 0, 0));
1359
- bottom.addChild(this.editorViewport);
1360
- }
1361
- bottom.addChild(footer);
1362
- const space = height - (framed ? 2 : 0) - header.render(innerWidth).length;
1363
- this.editorHeight = Infinity;
1364
- const bottomHeight = bottom.render(innerWidth).length;
1365
- if (!this.detail)
1366
- this.editorHeight = Math.max(1, space - (bottomHeight - this.editor.render(innerWidth).length) - 1);
1367
- this.height = Math.max(1, space - bottom.render(innerWidth).length);
1368
- body.addChild(this.viewport);
1369
- body.addChild(bottom);
1370
- }
1371
- if (framed)
1372
- this.addChild(new DynamicBorder((text) => this.theme.fg("borderAccent", text)));
1373
- this.addChild(body);
1374
- if (framed)
1375
- this.addChild(new DynamicBorder((text) => this.theme.fg("borderAccent", text)));
1376
- this.focused = this.hasFocus;
1377
- return super.render(width);
1378
- }
1379
- invalidate() { this.components.clear(); super.invalidate(); }
1380
- handleMouse(event) {
1381
- const result = super.handleMouse(event);
1382
- if (result?.target.component === this.editorViewport) {
1383
- this.editorFocus = true;
1384
- this.focused = this.hasFocus;
1385
- }
1386
- return result;
1387
- }
1388
- historyMouse(event) {
1389
- if (event.type === "wheel") {
1390
- this.scroll.scrollBy(event.wheelDelta ?? 0);
1391
- this.restoreAnchor = this.anchor();
1392
- return { handled: true };
1393
- }
1394
- if (event.type !== "click" || event.button !== "left")
1395
- return;
1396
- const line = this.lines.find((line) => line.start <= event.y + this.scroll.scrollTop && line.end > event.y + this.scroll.scrollTop);
1397
- if (!line)
1398
- return;
1399
- this.restoreAnchor = this.anchor();
1400
- this.editorFocus = false;
1401
- this.selectedId = line.id;
1402
- const item = this.selected();
1403
- if ((item?.call || item?.result) && !this.detail)
1404
- this.toolExpansion.set(line.id, !(this.toolExpansion.get(line.id) ?? this.toolsExpanded));
1405
- else
1406
- this.components.get(line.id)?.component.handleMouse?.({ ...event, y: event.y + this.scroll.scrollTop - line.contentStart, height: line.end - line.contentStart });
1407
- return { handled: true, focus: true };
1408
- }
1409
- select(delta) {
1410
- const current = this.lines.findIndex((line) => line.id === this.selectedId);
1411
- const index = Math.max(0, Math.min(this.lines.length - 1, (current < 0 ? this.lines.findIndex((line) => line.end > this.scroll.scrollTop) : current) + delta));
1412
- const line = this.lines[index];
1413
- if (!line)
1414
- return;
1415
- this.selectedId = line.id;
1416
- if (line.start < this.scroll.scrollTop || line.start >= this.scroll.scrollTop + this.height)
1417
- this.scroll.scrollTo(line.start, { disableFollow: true });
1418
- this.restoreAnchor = this.anchor();
1419
- }
1420
- selected() { return this.detail ?? this.contentItems.find((item) => item.id === this.selectedId) ?? this.contentItems.findLast((item) => item.kind === "assistant"); }
1421
- async inspect(item) {
1422
- const request = ++this.detailRequest, runId = this.task?.run.runId;
1423
- this.conversationAnchor = this.scroll.isFollowingEnd ? undefined : this.anchor();
1424
- this.loadingDetail = Boolean(item.load);
1425
- this.detailError = undefined;
1426
- this.detail = item.load ? { id: item.id, kind: "notice", title: "Loading selected details…", text: "Native record validation is running in the history process.", timestamp: item.timestamp } : item;
1427
- this.menu = undefined;
1428
- this.editorFocus = false;
1429
- this.scroll.scrollToStart();
1430
- this.restoreAnchor = { id: item.id, line: 0 };
1431
- this.tui.requestRender();
1432
- try {
1433
- const detail = item.load ? await item.load() : item;
1434
- if (!this.closed && request === this.detailRequest && this.task?.run.runId === runId)
1435
- this.detail = detail;
1436
- }
1437
- catch (error) {
1438
- if (!this.closed && request === this.detailRequest && this.task?.run.runId === runId) {
1439
- this.detailError = readableText(error instanceof Error ? error.message : String(error));
1440
- this.detail = { id: item.id, kind: "notice", title: "Details unavailable", text: this.detailError, timestamp: item.timestamp };
1441
- }
1442
- }
1443
- if (!this.closed && request === this.detailRequest) {
1444
- this.loadingDetail = false;
1445
- this.tui.requestRender();
1446
- }
1447
- }
1448
- async reply() {
1449
- if (this.loadingDetail)
1450
- return;
1451
- if (this.detail && this.detailError) {
1452
- this.visit.notice = `Quoted context unavailable; draft kept. ${this.detailError}`;
1453
- this.act("back");
1454
- this.controller.changed();
1455
- return;
1456
- }
1457
- let item = this.selected();
1458
- if (!item)
1459
- return;
1460
- const anchor = this.detail ? this.conversationAnchor : this.scroll.isFollowingEnd ? undefined : this.anchor();
1461
- if (item.load && !this.detail) {
1462
- const request = ++this.detailRequest, runId = this.task?.run.runId;
1463
- this.loadingDetail = true;
1464
- this.editorFocus = true;
1465
- this.focused = this.hasFocus;
1466
- this.tui.requestRender();
1467
- try {
1468
- item = await item.load();
1469
- if (this.closed || request !== this.detailRequest || this.task?.run.runId !== runId)
1470
- return;
1471
- }
1472
- catch (error) {
1473
- if (!this.closed && request === this.detailRequest) {
1474
- this.visit.notice = `Quoted context unavailable; draft kept. ${readableText(error instanceof Error ? error.message : String(error))}`;
1475
- this.controller.changed();
1476
- }
1477
- return;
1478
- }
1479
- finally {
1480
- if (!this.closed && request === this.detailRequest) {
1481
- this.loadingDetail = false;
1482
- this.tui.requestRender();
1483
- }
1484
- }
1485
- }
1486
- this.visit.quote = { title: item.title, text: (item.call || item.result ? [item.diff, item.details ?? item.text] : [item.text, item.diff, item.details]).filter(Boolean).join("\n\n") };
1487
- this.detailRequest++;
1488
- this.detail = undefined;
1489
- this.menu = undefined;
1490
- this.editorFocus = true;
1491
- this.restoreAnchor = anchor;
1492
- this.controller.changed();
1493
- }
1494
- actions() {
1495
- const task = this.task;
1496
- const choices = [
1497
- { value: "reply", label: "Reply to selected message / tool / change" },
1498
- { value: "details", label: "Full details / diff" },
1499
- { value: "expand", label: this.toolsExpanded ? "Collapse tool output" : "Expand tool output" },
1500
- { value: "assignment", label: "Full original assignment" },
1501
- { value: "changes", label: "Inspect working tree changes" },
1502
- { value: "latest", label: "Jump to latest activity" },
1503
- { value: "pin", label: this.controller.pinned === this.key ? "Unpin this agent" : "Keep this agent visible" },
1504
- ...(this.visit.quote ? [{ value: "unquote", label: "Remove quoted context" }] : []),
1505
- ...(task?.child.identityUnavailable || task?.child.activity?.status === "pending" ? [] : task?.child.state === "live" || task?.question ? [{ value: "stop", label: "Stop this agent only" }] : [{ value: "continue", label: "Continue with this message" }]),
1506
- { value: "picker", label: "Your other agents" }, { value: "peers", label: "Other connected sessions" },
1507
- { value: "earlier", label: "Earlier history" },
1508
- { value: "later", label: "Later history" },
1509
- { value: "retry", label: "Refresh / retry selected history" },
1510
- ];
1511
- this.menu = new SelectList(choices, Math.max(1, this.controller.availableHeight(this.tui) - 7), getSelectListTheme());
1512
- this.menu.onSelect = (item) => { this.menu = undefined; this.act(item.value); };
1513
- this.menu.onCancel = () => { this.menu = undefined; };
1514
- }
1515
- act(action) {
1516
- if (this.closed)
1517
- return;
1518
- if (action === "back") {
1519
- if (this.menu)
1520
- this.menu.onCancel?.();
1521
- else if (this.detail) {
1522
- this.detailRequest++;
1523
- this.loadingDetail = false;
1524
- this.detail = undefined;
1525
- this.editorFocus = true;
1526
- this.restoreAnchor = this.conversationAnchor;
1527
- if (!this.restoreAnchor)
1528
- this.scroll.scrollToEnd();
1529
- }
1530
- else
1531
- this.finish();
1532
- }
1533
- else if (action === "choose") {
1534
- const item = this.menu?.getSelectedItem();
1535
- if (item)
1536
- this.menu?.onSelect?.(item);
1537
- }
1538
- else if (action === "actions")
1539
- this.actions();
1540
- else if (action === "focus" && !this.detail) {
1541
- this.editorFocus = !this.editorFocus;
1542
- if (!this.editorFocus)
1543
- this.select(0);
1544
- }
1545
- else if (action === "send" && !this.loadingDetail)
1546
- void this.controller.send(this.key, this.editor.getExpandedText().trim());
1547
- else if (action === "reply")
1548
- void this.reply();
1549
- else if (action === "details") {
1550
- const item = this.selected();
1551
- if (item)
1552
- void this.inspect(item);
1553
- }
1554
- else if (action === "earlier" || action === "later") {
1555
- const page = this.task?.page;
1556
- if (action === "earlier" && page?.previousBefore !== undefined)
1557
- void this.loadPage({ before: page.previousBefore });
1558
- else if (action === "later" && !this.latestPage && page?.nextAfter !== undefined)
1559
- void this.loadPage({ after: page.nextAfter });
1560
- this.components.clear();
1561
- this.selectedId = undefined;
1562
- this.scroll.scrollToEnd();
1563
- this.tui.requestRender();
1564
- }
1565
- else if (action === "retry") {
1566
- void this.controller.retry(this.key).then(() => { if (!this.closed)
1567
- void this.loadPage(); });
1568
- }
1569
- else if (action === "assignment" && this.task)
1570
- void this.inspect(this.assignment());
1571
- else if (action === "expand") {
1572
- if (!this.scroll.isFollowingEnd)
1573
- this.restoreAnchor = this.anchor();
1574
- this.toolsExpanded = !this.toolsExpanded;
1575
- this.toolExpansion.clear();
1576
- }
1577
- else if (action === "changes" && !this.pendingDetail) {
1578
- const request = ++this.detailRequest, runId = this.task?.run.runId;
1579
- this.pendingDetail = true;
1580
- void this.controller.changes(this.key).then((item) => { if (!this.closed && request === this.detailRequest && this.task?.run.runId === runId && item) {
1581
- void this.inspect(item);
1582
- this.tui.requestRender();
1583
- } })
1584
- .catch((error) => { if (!this.closed && request === this.detailRequest && this.task?.run.runId === runId)
1585
- this.visit.notice = `Changes unavailable: ${String(error)}`; }).finally(() => { this.pendingDetail = false; });
1586
- }
1587
- else if (action === "latest") {
1588
- this.initialPosition = false;
1589
- this.detailRequest++;
1590
- this.loadingDetail = false;
1591
- this.detail = undefined;
1592
- this.components.clear();
1593
- void this.loadPage();
1594
- this.restoreAnchor = undefined;
1595
- this.conversationAnchor = undefined;
1596
- this.scroll.scrollToEnd();
1597
- this.selectedId = this.editorFocus ? undefined : this.items().at(-1)?.id;
1598
- }
1599
- else if (action === "pin")
1600
- this.controller.pin(this.controller.pinned === this.key ? undefined : this.key);
1601
- else if (action === "unquote") {
1602
- if (this.loadingDetail && !this.detail) {
1603
- this.detailRequest++;
1604
- this.loadingDetail = false;
1605
- }
1606
- this.visit.quote = undefined;
1607
- this.controller.changed();
1608
- }
1609
- else if (action === "stop")
1610
- void this.controller.stop(this.key);
1611
- else if (action === "continue" && !this.loadingDetail)
1612
- void this.controller.send(this.key, this.editor.getExpandedText(), true);
1613
- else if (action === "picker" || action === "peers")
1614
- this.finish(action);
1615
- this.focused = this.hasFocus;
1616
- this.tui.requestRender();
1617
- }
1618
- handleInput(data) {
1619
- if (this.closed)
1620
- return;
1621
- if (matchesKey(data, this.controller.shortcut)) {
1622
- this.finish();
1623
- return;
1624
- }
1625
- if (this.menu) {
1626
- this.menu.handleInput(data);
1627
- this.tui.requestRender();
1628
- return;
1629
- }
1630
- if (matchesKey(data, "escape"))
1631
- this.act("back");
1632
- else if (this.keys?.matches(data, "app.tools.expand") ?? matchesKey(data, "ctrl+o"))
1633
- this.act("expand");
1634
- else if (matchesKey(data, "f2"))
1635
- this.act("actions");
1636
- else if (matchesKey(data, "f5"))
1637
- this.act("retry");
1638
- else if (matchesKey(data, "alt+r"))
1639
- this.act("reply");
1640
- else if (matchesKey(data, "alt+d") && (!this.editorFocus || this.detail))
1641
- this.act("details");
1642
- else if (matchesKey(data, "alt+g"))
1643
- this.act("changes");
1644
- else if (matchesKey(data, "alt+l"))
1645
- this.act("latest");
1646
- else if (matchesKey(data, "alt+p"))
1647
- this.act("pin");
1648
- else if (matchesKey(data, "alt+q"))
1649
- this.act("unquote");
1650
- else if (matchesKey(data, "alt+s"))
1651
- this.act("stop");
1652
- else if (matchesKey(data, "alt+c"))
1653
- this.act("continue");
1654
- else if (matchesKey(data, "tab") && !this.detail)
1655
- this.act("focus");
1656
- else if (matchesKey(data, "pageUp") || matchesKey(data, "pageDown")) {
1657
- this.scroll.scrollBy(matchesKey(data, "pageUp") ? -this.height : this.height);
1658
- this.restoreAnchor = this.anchor();
1659
- }
1660
- else if (!this.editorFocus || this.detail) {
1661
- if (matchesKey(data, "up"))
1662
- this.select(-1);
1663
- else if (matchesKey(data, "down"))
1664
- this.select(1);
1665
- else if (matchesKey(data, "home")) {
1666
- this.scroll.scrollToStart();
1667
- this.restoreAnchor = this.anchor();
1668
- }
1669
- else if (matchesKey(data, "end"))
1670
- this.act("latest");
1671
- else if (matchesKey(data, "enter"))
1672
- this.act("details");
1673
- }
1674
- else {
1675
- this.beforeInput = this.editor.getExpandedText();
1676
- this.editor.handleInput(data);
1677
- }
1678
- this.focused = this.hasFocus;
1679
- this.tui.requestRender();
1680
- }
1681
- finish(key) { this.controller.changed(); this.done(key); }
1682
- dispose() { this.closed = true; this.pageRequest++; this.detailRequest++; this.editor.onChange = undefined; this.editor.onSubmit = undefined; this.components.clear(); }
1683
- }
350
+ export { AgentConversation } from "./agent-conversation.js";
351
+ export { agentTaskLabel } from "./view-model.js";