pi-crew 0.9.53 → 0.9.55

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 (493) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/package.json +6 -3
  3. package/scripts/clean-strip-types.mjs +60 -0
  4. package/src/adapters/claude-adapter.ts +23 -0
  5. package/src/adapters/codex-adapter.ts +21 -0
  6. package/src/adapters/cursor-adapter.ts +17 -0
  7. package/src/adapters/export-util.ts +143 -0
  8. package/src/adapters/index.ts +15 -0
  9. package/src/adapters/registry.ts +18 -0
  10. package/src/adapters/types.ts +23 -0
  11. package/src/agents/agent-config.ts +194 -0
  12. package/src/agents/agent-search.ts +98 -0
  13. package/src/agents/agent-serializer.ts +38 -0
  14. package/src/agents/discover-agents.ts +623 -0
  15. package/src/benchmark/benchmark-runner.ts +313 -0
  16. package/src/benchmark/feedback-loop.ts +73 -0
  17. package/src/config/config.ts +1270 -0
  18. package/src/config/defaults.ts +193 -0
  19. package/src/config/drift-detector.ts +290 -0
  20. package/src/config/markers.ts +330 -0
  21. package/src/config/resilient-parser.ts +117 -0
  22. package/src/config/role-tools.ts +118 -0
  23. package/src/config/suggestions.ts +75 -0
  24. package/src/config/types.ts +280 -0
  25. package/src/errors.ts +194 -0
  26. package/src/extension/action-suggestions.ts +117 -0
  27. package/src/extension/async-notifier.ts +179 -0
  28. package/src/extension/autonomous-policy.ts +209 -0
  29. package/src/extension/command-completions.ts +120 -0
  30. package/src/extension/context-status-injection.ts +185 -0
  31. package/src/extension/crew-autocomplete.ts +131 -0
  32. package/src/extension/crew-cleanup.ts +168 -0
  33. package/src/extension/crew-input-router.ts +93 -0
  34. package/src/extension/crew-shortcuts.ts +87 -0
  35. package/src/extension/crew-vibes/cat-frames.ts +18 -0
  36. package/src/extension/crew-vibes/config.ts +195 -0
  37. package/src/extension/crew-vibes/figures.ts +101 -0
  38. package/src/extension/crew-vibes/font-detect.ts +71 -0
  39. package/src/extension/crew-vibes/footer.ts +292 -0
  40. package/src/extension/crew-vibes/index.ts +432 -0
  41. package/src/extension/crew-vibes/provider-usage.ts +396 -0
  42. package/src/extension/crew-vibes/render.ts +206 -0
  43. package/src/extension/crew-vibes/speed.ts +286 -0
  44. package/src/extension/cross-extension-rpc.ts +350 -0
  45. package/src/extension/help.ts +62 -0
  46. package/src/extension/import-index.ts +74 -0
  47. package/src/extension/knowledge-injection.ts +402 -0
  48. package/src/extension/management.ts +571 -0
  49. package/src/extension/message-renderers.ts +108 -0
  50. package/src/extension/notification-router.ts +152 -0
  51. package/src/extension/notification-sink.ts +54 -0
  52. package/src/extension/pi-api.ts +56 -0
  53. package/src/extension/plan-orchestrate.ts +302 -0
  54. package/src/extension/project-init.ts +170 -0
  55. package/src/extension/register.ts +113 -0
  56. package/src/extension/registration/artifact-cleanup.ts +20 -0
  57. package/src/extension/registration/command-registration.ts +58 -0
  58. package/src/extension/registration/command-utils.ts +58 -0
  59. package/src/extension/registration/commands.ts +1228 -0
  60. package/src/extension/registration/compaction-guard.ts +333 -0
  61. package/src/extension/registration/context-builder.ts +135 -0
  62. package/src/extension/registration/crash-recovery-cache.ts +54 -0
  63. package/src/extension/registration/foreground-run-controller.ts +238 -0
  64. package/src/extension/registration/hook-registration.ts +119 -0
  65. package/src/extension/registration/lazy-configurers.ts +124 -0
  66. package/src/extension/registration/lifecycle-handlers.ts +923 -0
  67. package/src/extension/registration/lifecycle.ts +259 -0
  68. package/src/extension/registration/observability.ts +322 -0
  69. package/src/extension/registration/registration-types.ts +158 -0
  70. package/src/extension/registration/runtime-cleanup.ts +230 -0
  71. package/src/extension/registration/subagent-helpers.ts +125 -0
  72. package/src/extension/registration/subagent-manager-setup.ts +330 -0
  73. package/src/extension/registration/subagent-tools.ts +456 -0
  74. package/src/extension/registration/team-tool.ts +225 -0
  75. package/src/extension/registration/tool-registration.ts +50 -0
  76. package/src/extension/registration/ui.ts +172 -0
  77. package/src/extension/registration/viewers.ts +121 -0
  78. package/src/extension/registration/wire-cross-extension.ts +37 -0
  79. package/src/extension/result-watcher.ts +139 -0
  80. package/src/extension/rpc-hmac.ts +256 -0
  81. package/src/extension/run-bundle-schema.ts +104 -0
  82. package/src/extension/run-export.ts +97 -0
  83. package/src/extension/run-import.ts +168 -0
  84. package/src/extension/run-index.ts +131 -0
  85. package/src/extension/run-maintenance.ts +190 -0
  86. package/src/extension/session-summary.ts +18 -0
  87. package/src/extension/team-manager-command.ts +153 -0
  88. package/src/extension/team-onboard.ts +180 -0
  89. package/src/extension/team-recommendation.ts +288 -0
  90. package/src/extension/team-tool/anchor.ts +172 -0
  91. package/src/extension/team-tool/api.ts +1244 -0
  92. package/src/extension/team-tool/auto-summarize.ts +142 -0
  93. package/src/extension/team-tool/cache-control.ts +19 -0
  94. package/src/extension/team-tool/cancel.ts +337 -0
  95. package/src/extension/team-tool/chain-dispatch.ts +95 -0
  96. package/src/extension/team-tool/chain-executor.ts +379 -0
  97. package/src/extension/team-tool/config-patch.ts +50 -0
  98. package/src/extension/team-tool/context.ts +149 -0
  99. package/src/extension/team-tool/destructive-gate.ts +52 -0
  100. package/src/extension/team-tool/dispatch/automate.ts +80 -0
  101. package/src/extension/team-tool/dispatch/control.ts +42 -0
  102. package/src/extension/team-tool/dispatch/index.ts +95 -0
  103. package/src/extension/team-tool/dispatch/manage.ts +161 -0
  104. package/src/extension/team-tool/dispatch/run.ts +53 -0
  105. package/src/extension/team-tool/dispatch/status.ts +187 -0
  106. package/src/extension/team-tool/doctor.ts +431 -0
  107. package/src/extension/team-tool/explain.ts +282 -0
  108. package/src/extension/team-tool/failure-patterns.ts +118 -0
  109. package/src/extension/team-tool/goal-wrap.ts +330 -0
  110. package/src/extension/team-tool/goal.ts +556 -0
  111. package/src/extension/team-tool/handle-schedule.ts +332 -0
  112. package/src/extension/team-tool/handle-settings.ts +520 -0
  113. package/src/extension/team-tool/health-monitor.ts +481 -0
  114. package/src/extension/team-tool/inspect.ts +88 -0
  115. package/src/extension/team-tool/intent-policy.ts +45 -0
  116. package/src/extension/team-tool/lifecycle-actions.ts +615 -0
  117. package/src/extension/team-tool/orchestrate.ts +98 -0
  118. package/src/extension/team-tool/parallel-dispatch.ts +183 -0
  119. package/src/extension/team-tool/plan.ts +50 -0
  120. package/src/extension/team-tool/respond.ts +150 -0
  121. package/src/extension/team-tool/run-deadline.ts +66 -0
  122. package/src/extension/team-tool/run-not-found.ts +49 -0
  123. package/src/extension/team-tool/run.ts +1051 -0
  124. package/src/extension/team-tool/status.ts +260 -0
  125. package/src/extension/team-tool/workflow-manage.ts +261 -0
  126. package/src/extension/team-tool-types.ts +26 -0
  127. package/src/extension/team-tool.ts +750 -0
  128. package/src/extension/tool-result.ts +16 -0
  129. package/src/extension/validate-resources.ts +108 -0
  130. package/src/hooks/registry.ts +200 -0
  131. package/src/hooks/types.ts +69 -0
  132. package/src/i18n.ts +212 -0
  133. package/src/observability/correlation.ts +50 -0
  134. package/src/observability/event-bus.ts +86 -0
  135. package/src/observability/event-to-metric.ts +170 -0
  136. package/src/observability/exporters/adapter.ts +27 -0
  137. package/src/observability/exporters/otlp-exporter.ts +356 -0
  138. package/src/observability/exporters/prometheus-exporter.ts +54 -0
  139. package/src/observability/metric-registry.ts +100 -0
  140. package/src/observability/metric-retention.ts +64 -0
  141. package/src/observability/metric-sink.ts +99 -0
  142. package/src/observability/metrics-primitives.ts +219 -0
  143. package/src/plugins/plugin-define.ts +6 -0
  144. package/src/plugins/plugin-registry.ts +32 -0
  145. package/src/plugins/plugins/index.ts +3 -0
  146. package/src/plugins/plugins/nextjs.ts +19 -0
  147. package/src/plugins/plugins/vite.ts +10 -0
  148. package/src/plugins/plugins/vitest.ts +9 -0
  149. package/src/prompt/prompt-runtime.ts +380 -0
  150. package/src/runtime/adaptive-plan.ts +563 -0
  151. package/src/runtime/agent-control.ts +215 -0
  152. package/src/runtime/agent-memory.ts +81 -0
  153. package/src/runtime/agent-observability.ts +122 -0
  154. package/src/runtime/anchor-manager.ts +475 -0
  155. package/src/runtime/async-marker.ts +32 -0
  156. package/src/runtime/async-runner.ts +381 -0
  157. package/src/runtime/attention-events.ts +28 -0
  158. package/src/runtime/auto-summarize.ts +344 -0
  159. package/src/runtime/background-runner.ts +840 -0
  160. package/src/runtime/batch-barrier.ts +147 -0
  161. package/src/runtime/broker-issuer.ts +39 -0
  162. package/src/runtime/cancellation-token.ts +99 -0
  163. package/src/runtime/cancellation.ts +99 -0
  164. package/src/runtime/capability-inventory.ts +137 -0
  165. package/src/runtime/chain-parser.ts +237 -0
  166. package/src/runtime/chain-runner.ts +606 -0
  167. package/src/runtime/checkpoint.ts +291 -0
  168. package/src/runtime/child-pi-constants.ts +42 -0
  169. package/src/runtime/child-pi-kill.ts +180 -0
  170. package/src/runtime/child-pi-pool.ts +68 -0
  171. package/src/runtime/child-pi-spawn.ts +287 -0
  172. package/src/runtime/child-pi-steering.ts +128 -0
  173. package/src/runtime/child-pi-streams.ts +296 -0
  174. package/src/runtime/child-pi-transcript.ts +169 -0
  175. package/src/runtime/child-pi.ts +1105 -0
  176. package/src/runtime/coalesce-tasks.ts +268 -0
  177. package/src/runtime/code-summary.ts +292 -0
  178. package/src/runtime/command-trace.ts +105 -0
  179. package/src/runtime/compact-pipeline.ts +56 -0
  180. package/src/runtime/compact-stages/ansi-strip-stage.ts +25 -0
  181. package/src/runtime/compact-stages/blank-collapse-stage.ts +31 -0
  182. package/src/runtime/compact-stages/deduplicate-stage.ts +34 -0
  183. package/src/runtime/compact-stages/head-snap-stage.ts +57 -0
  184. package/src/runtime/compact-stages/index.ts +23 -0
  185. package/src/runtime/compact-stages/tail-capture-stage.ts +77 -0
  186. package/src/runtime/compact-stages/truncation-stage.ts +74 -0
  187. package/src/runtime/compaction-summary.ts +278 -0
  188. package/src/runtime/completion-guard.ts +207 -0
  189. package/src/runtime/concurrency.ts +58 -0
  190. package/src/runtime/crash-classification.ts +229 -0
  191. package/src/runtime/crash-recovery.ts +594 -0
  192. package/src/runtime/crew-agent-records.ts +595 -0
  193. package/src/runtime/crew-agent-runtime.ts +62 -0
  194. package/src/runtime/crew-broker-child.ts +88 -0
  195. package/src/runtime/crew-broker-client.ts +673 -0
  196. package/src/runtime/crew-broker-tokens.ts +159 -0
  197. package/src/runtime/crew-broker.ts +1304 -0
  198. package/src/runtime/crew-hooks.ts +227 -0
  199. package/src/runtime/cross-extension-rpc.ts +143 -0
  200. package/src/runtime/custom-tools/irc-tool.ts +282 -0
  201. package/src/runtime/custom-tools/submit-result-tool.ts +102 -0
  202. package/src/runtime/deadletter.ts +47 -0
  203. package/src/runtime/delivery-coordinator.ts +211 -0
  204. package/src/runtime/delta-conflict.ts +348 -0
  205. package/src/runtime/deterministic-ast.ts +161 -0
  206. package/src/runtime/diagnostic-export.ts +171 -0
  207. package/src/runtime/direct-run.ts +45 -0
  208. package/src/runtime/dwf-state-store.ts +102 -0
  209. package/src/runtime/dynamic-workflow-context.ts +1013 -0
  210. package/src/runtime/dynamic-workflow-runner.ts +325 -0
  211. package/src/runtime/effectiveness.ts +90 -0
  212. package/src/runtime/errors/crew-errors.ts +162 -0
  213. package/src/runtime/event-stream-bridge.ts +98 -0
  214. package/src/runtime/foreground-control.ts +193 -0
  215. package/src/runtime/foreground-watchdog.ts +132 -0
  216. package/src/runtime/global-worker-cap.ts +96 -0
  217. package/src/runtime/goal-achievement.ts +148 -0
  218. package/src/runtime/goal-evaluator.ts +365 -0
  219. package/src/runtime/goal-loop-runner.ts +825 -0
  220. package/src/runtime/goal-state-store.ts +219 -0
  221. package/src/runtime/green-contract.ts +55 -0
  222. package/src/runtime/group-join.ts +267 -0
  223. package/src/runtime/handoff-manager.ts +598 -0
  224. package/src/runtime/heartbeat-gradient.ts +36 -0
  225. package/src/runtime/heartbeat-watcher.ts +239 -0
  226. package/src/runtime/hidden-handoff.ts +407 -0
  227. package/src/runtime/important-line-classifier.ts +140 -0
  228. package/src/runtime/intercom-bridge.ts +187 -0
  229. package/src/runtime/iteration-hooks.ts +305 -0
  230. package/src/runtime/live-agent-control.ts +136 -0
  231. package/src/runtime/live-agent-manager.ts +704 -0
  232. package/src/runtime/live-control-realtime.ts +56 -0
  233. package/src/runtime/live-extension-bridge.ts +148 -0
  234. package/src/runtime/live-irc.ts +97 -0
  235. package/src/runtime/live-session-health.ts +108 -0
  236. package/src/runtime/live-session-runtime.ts +1146 -0
  237. package/src/runtime/loop-gates.ts +128 -0
  238. package/src/runtime/manifest-cache.ts +375 -0
  239. package/src/runtime/mcp-proxy.ts +105 -0
  240. package/src/runtime/metric-parser.ts +36 -0
  241. package/src/runtime/model-fallback.ts +409 -0
  242. package/src/runtime/model-resolver.ts +126 -0
  243. package/src/runtime/model-scope.ts +158 -0
  244. package/src/runtime/orphan-worker-registry.ts +465 -0
  245. package/src/runtime/output-validator.ts +202 -0
  246. package/src/runtime/overflow-recovery.ts +206 -0
  247. package/src/runtime/parallel-research.ts +68 -0
  248. package/src/runtime/parallel-utils.ts +160 -0
  249. package/src/runtime/parent-guard.ts +134 -0
  250. package/src/runtime/path-overlap.ts +150 -0
  251. package/src/runtime/peer-dep.ts +292 -0
  252. package/src/runtime/per-write-validator.ts +181 -0
  253. package/src/runtime/phase-progress.ts +217 -0
  254. package/src/runtime/phase-tracker.ts +385 -0
  255. package/src/runtime/pi-args.ts +655 -0
  256. package/src/runtime/pi-json-output.ts +170 -0
  257. package/src/runtime/pi-spawn.ts +273 -0
  258. package/src/runtime/pipeline-runner.ts +523 -0
  259. package/src/runtime/plan-templates.ts +201 -0
  260. package/src/runtime/policy-engine.ts +115 -0
  261. package/src/runtime/post-checks.ts +142 -0
  262. package/src/runtime/post-exit-stdio-guard.ts +109 -0
  263. package/src/runtime/process-lifecycle.ts +491 -0
  264. package/src/runtime/process-status.ts +154 -0
  265. package/src/runtime/progress-event-coalescer.ts +44 -0
  266. package/src/runtime/progress-tracker.ts +124 -0
  267. package/src/runtime/prose-compressor.ts +162 -0
  268. package/src/runtime/recovery-recipes.ts +203 -0
  269. package/src/runtime/replace.ts +570 -0
  270. package/src/runtime/resilient-edit.ts +153 -0
  271. package/src/runtime/result-extractor.ts +217 -0
  272. package/src/runtime/retry-executor.ts +109 -0
  273. package/src/runtime/retry-runner.ts +336 -0
  274. package/src/runtime/role-permission.ts +59 -0
  275. package/src/runtime/run-coalesced-task-group.ts +308 -0
  276. package/src/runtime/run-drift.ts +219 -0
  277. package/src/runtime/run-tracker.ts +116 -0
  278. package/src/runtime/run-worker.ts +78 -0
  279. package/src/runtime/runtime-policy.ts +29 -0
  280. package/src/runtime/runtime-resolver.ts +172 -0
  281. package/src/runtime/runtime-warmup.ts +160 -0
  282. package/src/runtime/scheduler.ts +353 -0
  283. package/src/runtime/semaphore.ts +141 -0
  284. package/src/runtime/sensitive-paths.ts +93 -0
  285. package/src/runtime/session-resources.ts +25 -0
  286. package/src/runtime/session-snapshot.ts +59 -0
  287. package/src/runtime/session-usage.ts +79 -0
  288. package/src/runtime/settings-store.ts +155 -0
  289. package/src/runtime/sidechain-output.ts +35 -0
  290. package/src/runtime/single-agent-compose.ts +84 -0
  291. package/src/runtime/skill-effectiveness.ts +524 -0
  292. package/src/runtime/skill-instructions.ts +394 -0
  293. package/src/runtime/stale-reconciler.ts +680 -0
  294. package/src/runtime/stream-preview.ts +184 -0
  295. package/src/runtime/streaming-output.ts +50 -0
  296. package/src/runtime/subagent-manager.ts +561 -0
  297. package/src/runtime/subprocess-tool-registry.ts +70 -0
  298. package/src/runtime/supervisor-contact.ts +64 -0
  299. package/src/runtime/task-display.ts +52 -0
  300. package/src/runtime/task-graph-scheduler.ts +227 -0
  301. package/src/runtime/task-graph.ts +290 -0
  302. package/src/runtime/task-health.ts +83 -0
  303. package/src/runtime/task-id.ts +161 -0
  304. package/src/runtime/task-output-context.ts +602 -0
  305. package/src/runtime/task-packet.ts +268 -0
  306. package/src/runtime/task-quality.ts +199 -0
  307. package/src/runtime/task-runner/capabilities.ts +78 -0
  308. package/src/runtime/task-runner/child-executor.ts +790 -0
  309. package/src/runtime/task-runner/context-retrieval.ts +163 -0
  310. package/src/runtime/task-runner/live-executor.ts +227 -0
  311. package/src/runtime/task-runner/output-splitter.ts +152 -0
  312. package/src/runtime/task-runner/post-execution.ts +480 -0
  313. package/src/runtime/task-runner/pre-execution.ts +365 -0
  314. package/src/runtime/task-runner/progress.ts +157 -0
  315. package/src/runtime/task-runner/prompt-builder.ts +278 -0
  316. package/src/runtime/task-runner/prompt-pipeline.ts +88 -0
  317. package/src/runtime/task-runner/result-utils.ts +16 -0
  318. package/src/runtime/task-runner/retrieval-orchestrator.ts +310 -0
  319. package/src/runtime/task-runner/run-projection.ts +125 -0
  320. package/src/runtime/task-runner/scaffold-executor.ts +25 -0
  321. package/src/runtime/task-runner/state-helpers.ts +176 -0
  322. package/src/runtime/task-runner/tail-read.ts +34 -0
  323. package/src/runtime/task-runner.ts +216 -0
  324. package/src/runtime/team-runner-artifacts.ts +13 -0
  325. package/src/runtime/team-runner.ts +2396 -0
  326. package/src/runtime/tool-output-pruner.ts +333 -0
  327. package/src/runtime/tool-progress.ts +278 -0
  328. package/src/runtime/usage-tracker.ts +73 -0
  329. package/src/runtime/verification-gates.ts +579 -0
  330. package/src/runtime/verification-integrity.ts +108 -0
  331. package/src/runtime/verification-worktree.ts +186 -0
  332. package/src/runtime/worker-heartbeat.ts +25 -0
  333. package/src/runtime/worker-startup.ts +83 -0
  334. package/src/runtime/workflow-state.ts +195 -0
  335. package/src/runtime/workspace-lock.ts +443 -0
  336. package/src/runtime/workspace-tree.ts +312 -0
  337. package/src/runtime/yield-handler.ts +224 -0
  338. package/src/runtime/zombie-scanner.ts +298 -0
  339. package/src/schema/config-schema.ts +327 -0
  340. package/src/schema/team-tool-schema.ts +492 -0
  341. package/src/schema/validation-types.ts +159 -0
  342. package/src/skills/discover-skills.ts +203 -0
  343. package/src/skills/skill-templates.ts +456 -0
  344. package/src/skills/validate.ts +285 -0
  345. package/src/state/active-run-registry.ts +439 -0
  346. package/src/state/artifact-store.ts +176 -0
  347. package/src/state/atomic-write.ts +970 -0
  348. package/src/state/blob-store.ts +308 -0
  349. package/src/state/contracts.ts +160 -0
  350. package/src/state/crew-init.ts +269 -0
  351. package/src/state/decision-ledger.ts +372 -0
  352. package/src/state/event-log-rotation.ts +399 -0
  353. package/src/state/event-log.ts +1465 -0
  354. package/src/state/event-reconstructor.ts +229 -0
  355. package/src/state/gitignore-manager.ts +46 -0
  356. package/src/state/health-store.ts +79 -0
  357. package/src/state/hook-instinct-bridge.ts +94 -0
  358. package/src/state/hook-integrations.ts +51 -0
  359. package/src/state/instinct-store.ts +275 -0
  360. package/src/state/jsonl-writer.ts +107 -0
  361. package/src/state/locks.ts +562 -0
  362. package/src/state/mailbox.ts +916 -0
  363. package/src/state/observation-store.ts +176 -0
  364. package/src/state/run-cache.ts +193 -0
  365. package/src/state/run-graph.ts +183 -0
  366. package/src/state/run-metrics.ts +165 -0
  367. package/src/state/schedule.ts +166 -0
  368. package/src/state/session-state-map.ts +51 -0
  369. package/src/state/state-store.ts +973 -0
  370. package/src/state/task-claims.ts +61 -0
  371. package/src/state/tiered-eval.ts +480 -0
  372. package/src/state/types-eval.ts +58 -0
  373. package/src/state/types.ts +447 -0
  374. package/src/state/usage.ts +138 -0
  375. package/src/state/worker-atomic-writer.ts +198 -0
  376. package/src/subagents/async-entry.ts +1 -0
  377. package/src/subagents/index.ts +3 -0
  378. package/src/subagents/live/control.ts +1 -0
  379. package/src/subagents/live/manager.ts +1 -0
  380. package/src/subagents/live/realtime.ts +1 -0
  381. package/src/subagents/live/session-runtime.ts +1 -0
  382. package/src/subagents/manager.ts +1 -0
  383. package/src/subagents/spawn.ts +1 -0
  384. package/src/teams/discover-teams.ts +187 -0
  385. package/src/teams/team-config.ts +27 -0
  386. package/src/teams/team-serializer.ts +38 -0
  387. package/src/tools/safe-bash-extension.ts +54 -0
  388. package/src/tools/safe-bash.ts +505 -0
  389. package/src/types/diff.d.ts +18 -0
  390. package/src/types/new-api-types.ts +35 -0
  391. package/src/ui/agent-management-overlay.ts +160 -0
  392. package/src/ui/card-colors.ts +139 -0
  393. package/src/ui/crew-footer.ts +102 -0
  394. package/src/ui/crew-select-list.ts +112 -0
  395. package/src/ui/dashboard-panes/agents-pane.ts +164 -0
  396. package/src/ui/dashboard-panes/cancellation-pane.ts +66 -0
  397. package/src/ui/dashboard-panes/capability-pane.ts +77 -0
  398. package/src/ui/dashboard-panes/health-pane.ts +31 -0
  399. package/src/ui/dashboard-panes/mailbox-pane.ts +35 -0
  400. package/src/ui/dashboard-panes/metrics-pane.ts +37 -0
  401. package/src/ui/dashboard-panes/progress-pane.ts +38 -0
  402. package/src/ui/dashboard-panes/transcript-pane.ts +10 -0
  403. package/src/ui/deploy-bundled-themes.ts +71 -0
  404. package/src/ui/dwf-phase-display.ts +151 -0
  405. package/src/ui/dynamic-border.ts +35 -0
  406. package/src/ui/format-helpers.ts +31 -0
  407. package/src/ui/heartbeat-aggregator.ts +74 -0
  408. package/src/ui/key-utils.ts +42 -0
  409. package/src/ui/keybinding-map.ts +248 -0
  410. package/src/ui/layout-primitives.ts +106 -0
  411. package/src/ui/live-conversation-overlay.ts +183 -0
  412. package/src/ui/live-duration.ts +55 -0
  413. package/src/ui/live-run-sidebar.ts +293 -0
  414. package/src/ui/loaders.ts +6 -0
  415. package/src/ui/mascot.ts +449 -0
  416. package/src/ui/overlays/agent-picker-overlay.ts +66 -0
  417. package/src/ui/overlays/confirm-overlay.ts +60 -0
  418. package/src/ui/overlays/help-overlay.ts +177 -0
  419. package/src/ui/overlays/mailbox-compose-overlay.ts +181 -0
  420. package/src/ui/overlays/mailbox-compose-preview.ts +78 -0
  421. package/src/ui/overlays/mailbox-detail-overlay.ts +155 -0
  422. package/src/ui/pi-ui-compat.ts +68 -0
  423. package/src/ui/powerbar-publisher.ts +459 -0
  424. package/src/ui/render-coalescer.ts +101 -0
  425. package/src/ui/render-diff.ts +160 -0
  426. package/src/ui/render-scheduler.ts +278 -0
  427. package/src/ui/run-action-dispatcher.ts +182 -0
  428. package/src/ui/run-dashboard.ts +910 -0
  429. package/src/ui/run-event-bus.ts +381 -0
  430. package/src/ui/run-snapshot-cache.ts +1057 -0
  431. package/src/ui/settings-overlay.ts +1067 -0
  432. package/src/ui/shared-overlay-scheduler.ts +96 -0
  433. package/src/ui/snapshot-types.ts +90 -0
  434. package/src/ui/spinner.ts +17 -0
  435. package/src/ui/status-colors.ts +143 -0
  436. package/src/ui/syntax-highlight.ts +136 -0
  437. package/src/ui/terminal-status.ts +269 -0
  438. package/src/ui/theme-adapter.ts +271 -0
  439. package/src/ui/theme-discovery.ts +199 -0
  440. package/src/ui/tool-progress-formatter.ts +93 -0
  441. package/src/ui/tool-renderers/brief-mode.ts +225 -0
  442. package/src/ui/tool-renderers/index.ts +726 -0
  443. package/src/ui/transcript-cache.ts +128 -0
  444. package/src/ui/transcript-entries.ts +256 -0
  445. package/src/ui/transcript-viewer.ts +424 -0
  446. package/src/ui/widget/index.ts +418 -0
  447. package/src/ui/widget/widget-formatters.ts +182 -0
  448. package/src/ui/widget/widget-model.ts +110 -0
  449. package/src/ui/widget/widget-renderer.ts +182 -0
  450. package/src/ui/widget/widget-types.ts +36 -0
  451. package/src/utils/bm25-search.ts +235 -0
  452. package/src/utils/completion-dedupe.ts +63 -0
  453. package/src/utils/conflict-detect.ts +668 -0
  454. package/src/utils/env-allowlist.ts +30 -0
  455. package/src/utils/env-filter.ts +155 -0
  456. package/src/utils/file-coalescer.ts +98 -0
  457. package/src/utils/fingerprint.ts +180 -0
  458. package/src/utils/frontmatter.ts +70 -0
  459. package/src/utils/fs-watch.ts +37 -0
  460. package/src/utils/gh-protocol.ts +556 -0
  461. package/src/utils/git.ts +260 -0
  462. package/src/utils/guards.ts +107 -0
  463. package/src/utils/ids.ts +24 -0
  464. package/src/utils/incremental-reader.ts +220 -0
  465. package/src/utils/internal-error.ts +10 -0
  466. package/src/utils/names.ts +36 -0
  467. package/src/utils/ndjson.ts +115 -0
  468. package/src/utils/paths.ts +227 -0
  469. package/src/utils/project-detector.ts +160 -0
  470. package/src/utils/redaction.ts +370 -0
  471. package/src/utils/resolve-shell.ts +36 -0
  472. package/src/utils/run-watcher-registry.ts +152 -0
  473. package/src/utils/safe-paths.ts +393 -0
  474. package/src/utils/scan-cache.ts +144 -0
  475. package/src/utils/session-utils.ts +110 -0
  476. package/src/utils/sleep.ts +54 -0
  477. package/src/utils/socket-path.ts +164 -0
  478. package/src/utils/sse-parser.ts +131 -0
  479. package/src/utils/task-name-generator.ts +337 -0
  480. package/src/utils/timings.ts +33 -0
  481. package/src/utils/token-counter.ts +200 -0
  482. package/src/utils/visual.ts +207 -0
  483. package/src/workflows/cost-estimator.ts +34 -0
  484. package/src/workflows/discover-workflows.ts +308 -0
  485. package/src/workflows/intermediate-store.ts +166 -0
  486. package/src/workflows/preflight-validator.ts +168 -0
  487. package/src/workflows/topology-analyzer.ts +203 -0
  488. package/src/workflows/validate-workflow.ts +50 -0
  489. package/src/workflows/workflow-config.ts +75 -0
  490. package/src/workflows/workflow-serializer.ts +32 -0
  491. package/src/worktree/branch-freshness.ts +112 -0
  492. package/src/worktree/cleanup.ts +341 -0
  493. package/src/worktree/worktree-manager.ts +1204 -0
@@ -0,0 +1,42 @@
1
+ /**
2
+ * key-utils.ts — Centralised key matching helpers.
3
+ *
4
+ * Pi 0.81+'s TUI input layer (`@earendil-works/pi-tui`) ships a `matchesKey()`
5
+ * helper that handles multiple terminal key encodings: legacy CSI escapes
6
+ * (`\x1b[A`), application cursor mode (`\x1bOA`), and Kitty keyboard protocol
7
+ * variants. Pi-crew components previously compared against raw escape bytes
8
+ * (`data === "\x1b[A"`), which silently failed on terminals that emit the
9
+ * alternate encodings. This module wraps `matchesKey` with a single
10
+ * `keyOf()` helper so overlay code reads naturally while picking up the
11
+ * terminal-aware match logic for free.
12
+ *
13
+ * Returns the canonical KeyId when matchesKey recognises it, otherwise the
14
+ * raw input string so callers can fall through to ASCII-letter shortcuts.
15
+ */
16
+ import { type KeyId, matchesKey } from "@earendil-works/pi-tui";
17
+
18
+ export type PiKeyName = KeyId | string;
19
+
20
+ const COMMON_IDS: readonly KeyId[] = [
21
+ "up",
22
+ "down",
23
+ "left",
24
+ "right",
25
+ "enter",
26
+ "escape",
27
+ "tab",
28
+ "shift+tab",
29
+ "space",
30
+ "backspace",
31
+ "home",
32
+ "end",
33
+ "pageUp",
34
+ "pageDown",
35
+ ];
36
+
37
+ export function keyOf(data: string): KeyId | string {
38
+ for (const id of COMMON_IDS) {
39
+ if (matchesKey(data, id)) return id;
40
+ }
41
+ return data;
42
+ }
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Dashboard keybinding map (L2 refactor: data-driven dispatch).
3
+ *
4
+ * Before L2 this module exposed `DASHBOARD_KEYS` (a data table) but dispatched
5
+ * via a 30-line `if (includes(...)) return "..."` chain — adding a key meant
6
+ * editing BOTH the table AND the dispatch, a DRY violation. L2 collapses the
7
+ * dispatch into a single `for (const b of BINDINGS)` loop driven by the
8
+ * `BINDINGS` table below. `DASHBOARD_KEYS` is retained as the raw key data so
9
+ * existing imports and the dead-but-intentional `KEY_RESERVED` set keep working.
10
+ *
11
+ * Recalibration vs. the original L2 plan: the plan also called for an
12
+ * `inTextInput` guard to prevent letter-key leaks into TUI text inputs.
13
+ * Verified during implementation that this is NOT needed — overlays are
14
+ * mutually exclusive and each has its own `handleInput`. `mailbox-compose-overlay.ts:111`
15
+ * captures every single-char key via `appendText(data)` and never delegates to
16
+ * `dashboardActionForKey`, so there is no leak path. Adding the guard would
17
+ * complicate the API (`run-dashboard.ts:485` has no text-input state to pass)
18
+ * for zero benefit. The input-guard half of L2 is therefore intentionally
19
+ * skipped; only the DRY/data-driven dispatch refactor landed.
20
+ *
21
+ * Origin pattern: deer-flow `frontend/src/components/workspace/command-palette.tsx:39-50`
22
+ * drives shortcuts from a single data array consumed by one loop in
23
+ * `use-global-shortcuts.ts:38-61`.
24
+ */
25
+
26
+ import { type KeyId, matchesKey } from "@earendil-works/pi-tui";
27
+ import { keyOf } from "./key-utils.ts";
28
+
29
+ export const DASHBOARD_KEYS = {
30
+ close: ["q", "escape", "\u001b"],
31
+ select: ["enter", "s", "\r", "\n", "tab", "\t", " "],
32
+ help: ["?"],
33
+ root: {
34
+ summary: ["u"],
35
+ artifacts: ["a"],
36
+ api: ["i"],
37
+ agents: ["d"],
38
+ mailbox: ["m"],
39
+ events: ["e"],
40
+ output: ["o"],
41
+ transcript: ["v"],
42
+ liveConversation: ["V"],
43
+ reload: ["r"],
44
+ progressToggle: ["p"],
45
+ },
46
+ pane: {
47
+ agents: ["1"],
48
+ progress: ["2"],
49
+ mailbox: ["3"],
50
+ output: ["4"],
51
+ health: ["5"],
52
+ metrics: ["6"],
53
+ },
54
+ navigation: { up: ["k", "up"], down: ["j", "down"] },
55
+ mailbox: {
56
+ ack: ["A"],
57
+ nudge: ["N"],
58
+ compose: ["C"],
59
+ preview: ["P"],
60
+ ackAll: ["X"],
61
+ openDetail: ["\r", "\n"],
62
+ },
63
+ health: { recovery: ["R"], killStale: ["K"], diagnosticExport: ["D"] },
64
+ notification: { dismissAll: ["H"] },
65
+ } as const;
66
+
67
+ /**
68
+ * Pane identifiers that can scope a binding. `undefined` means the binding
69
+ * fires in every pane.
70
+ */
71
+ export type ActivePane = "agents" | "progress" | "mailbox" | "output" | "health" | "metrics";
72
+
73
+ /**
74
+ * A single keybinding: the keys that trigger it, the action it produces, and
75
+ * an optional pane restriction. The dispatch loop returns the FIRST matching
76
+ * binding, so table ORDER IS SIGNIFICANT and must mirror the old if-chain
77
+ * precedence (pane-specific overrides before their generic competitors).
78
+ */
79
+ export interface KeyBinding {
80
+ readonly keys: readonly string[];
81
+ readonly action: DashboardKeyAction;
82
+ /** When set, the binding only fires when `activePane === pane`. */
83
+ readonly pane?: ActivePane;
84
+ }
85
+
86
+ export type DashboardKeyAction =
87
+ | "close"
88
+ | "help"
89
+ | "select"
90
+ | "summary"
91
+ | "artifacts"
92
+ | "api"
93
+ | "agents"
94
+ | "mailbox"
95
+ | "events"
96
+ | "output"
97
+ | "transcript"
98
+ | "live-conversation"
99
+ | "reload"
100
+ | "progressToggle"
101
+ | "pane-agents"
102
+ | "pane-progress"
103
+ | "pane-mailbox"
104
+ | "pane-output"
105
+ | "pane-health"
106
+ | "pane-metrics"
107
+ | "up"
108
+ | "down"
109
+ | "mailbox-detail"
110
+ | "health-recovery"
111
+ | "health-kill-stale"
112
+ | "health-diagnostic-export"
113
+ | "notifications-dismiss";
114
+
115
+ /**
116
+ * The dispatch table. ORDER MATTERS — first match wins.
117
+ *
118
+ * Precedence notes (must match the pre-L2 if-chain exactly):
119
+ * 1. `close` always wins (q / Esc).
120
+ * 2. `mailbox-detail` (\r, \n) is pane-scoped to mailbox and MUST precede
121
+ * `select` (which also binds \r, \n) so Enter opens the detail instead of
122
+ * triggering select while in the mailbox pane.
123
+ * 3. `health-*` are pane-scoped to health.
124
+ * 4. `notifications-dismiss` (H) is global.
125
+ * 5. `select`, then the root actions, pane switches, and navigation.
126
+ *
127
+ * NOTE: mailbox action keys A/N/C/P/X (ack/nudge/compose/preview/ackAll) are
128
+ * intentionally NOT in this table. They live in `DASHBOARD_KEYS.mailbox` for
129
+ * reservation but are handled by the mailbox overlay's own `handleInput`,
130
+ * not by the dashboard dispatch. Adding them here would change behavior.
131
+ */
132
+ const BINDINGS: readonly KeyBinding[] = [
133
+ { keys: DASHBOARD_KEYS.close, action: "close" },
134
+ { keys: DASHBOARD_KEYS.help, action: "help" },
135
+ {
136
+ keys: DASHBOARD_KEYS.mailbox.openDetail,
137
+ action: "mailbox-detail",
138
+ pane: "mailbox",
139
+ },
140
+ {
141
+ keys: DASHBOARD_KEYS.health.recovery,
142
+ action: "health-recovery",
143
+ pane: "health",
144
+ },
145
+ {
146
+ keys: DASHBOARD_KEYS.health.killStale,
147
+ action: "health-kill-stale",
148
+ pane: "health",
149
+ },
150
+ {
151
+ keys: DASHBOARD_KEYS.health.diagnosticExport,
152
+ action: "health-diagnostic-export",
153
+ pane: "health",
154
+ },
155
+ {
156
+ keys: DASHBOARD_KEYS.notification.dismissAll,
157
+ action: "notifications-dismiss",
158
+ },
159
+ { keys: DASHBOARD_KEYS.select, action: "select" },
160
+ { keys: DASHBOARD_KEYS.root.summary, action: "summary" },
161
+ { keys: DASHBOARD_KEYS.root.artifacts, action: "artifacts" },
162
+ { keys: DASHBOARD_KEYS.root.api, action: "api" },
163
+ { keys: DASHBOARD_KEYS.root.agents, action: "agents" },
164
+ { keys: DASHBOARD_KEYS.root.mailbox, action: "mailbox" },
165
+ { keys: DASHBOARD_KEYS.root.events, action: "events" },
166
+ { keys: DASHBOARD_KEYS.root.output, action: "output" },
167
+ { keys: DASHBOARD_KEYS.root.transcript, action: "transcript" },
168
+ { keys: DASHBOARD_KEYS.root.liveConversation, action: "live-conversation" },
169
+ { keys: DASHBOARD_KEYS.root.reload, action: "reload" },
170
+ { keys: DASHBOARD_KEYS.root.progressToggle, action: "progressToggle" },
171
+ { keys: DASHBOARD_KEYS.pane.agents, action: "pane-agents" },
172
+ { keys: DASHBOARD_KEYS.pane.progress, action: "pane-progress" },
173
+ { keys: DASHBOARD_KEYS.pane.mailbox, action: "pane-mailbox" },
174
+ { keys: DASHBOARD_KEYS.pane.output, action: "pane-output" },
175
+ { keys: DASHBOARD_KEYS.pane.health, action: "pane-health" },
176
+ { keys: DASHBOARD_KEYS.pane.metrics, action: "pane-metrics" },
177
+ { keys: DASHBOARD_KEYS.navigation.up, action: "up" },
178
+ { keys: DASHBOARD_KEYS.navigation.down, action: "down" },
179
+ ];
180
+
181
+ /**
182
+ * Reserved keys — every key the dashboard claims, including mailbox/health
183
+ * action keys that are NOT dispatched here but are handled by their own
184
+ * overlays. Derived from `DASHBOARD_KEYS` (the full key set) rather than from
185
+ * `BINDINGS` (the dispatched subset) so overlay-handled keys stay reserved.
186
+ *
187
+ * @internal Consumed by `test/unit/keybinding-map.parity.test.ts` (asserts
188
+ * reserved-key membership) and the L2 dispatch smoke script. It is the
189
+ * canonical "keys the dashboard ecosystem owns" set — NOT dead code.
190
+ */
191
+ const KEY_RESERVED = new Set<string>([
192
+ ...DASHBOARD_KEYS.close,
193
+ ...DASHBOARD_KEYS.select,
194
+ ...DASHBOARD_KEYS.help,
195
+ ...Object.values(DASHBOARD_KEYS.root).flat(),
196
+ ...Object.values(DASHBOARD_KEYS.pane).flat(),
197
+ ...Object.values(DASHBOARD_KEYS.navigation).flat(),
198
+ ...Object.values(DASHBOARD_KEYS.mailbox).flat(),
199
+ ...Object.values(DASHBOARD_KEYS.health).flat(),
200
+ ...Object.values(DASHBOARD_KEYS.notification).flat(),
201
+ ]);
202
+
203
+ export { KEY_RESERVED };
204
+
205
+ /**
206
+ * Resolve a raw input `data` string to a dashboard action.
207
+ *
208
+ * Data-driven dispatch: iterates `BINDINGS` in order and returns the action of
209
+ * the first binding whose `keys` contain `data` and whose optional `pane`
210
+ * restriction matches `activePane`. Behavior is identical to the pre-L2
211
+ * if-chain (verified by `test/unit/keybinding-map.parity.test.ts`).
212
+ *
213
+ * @param data Raw key input (single char or escape sequence).
214
+ * @param activePane Currently focused pane; pane-scoped bindings only fire
215
+ * when this matches. `undefined` disables all pane-scoped
216
+ * bindings (matching the old behavior where omitting the
217
+ * arg skipped the `activePane === ...` branches).
218
+ */
219
+ export function dashboardActionForKey(data: string, activePane?: ActivePane): DashboardKeyAction | undefined {
220
+ // Two-pass dispatch to preserve case-sensitivity for plain ASCII keys
221
+ // while still normalizing escape sequences via matchesKey().
222
+ //
223
+ // Background: pi-tui's matchesKey() is case-insensitive, so matchesKey("d",
224
+ // "D") === true. A single-pass loop that intermixes exact + matchesKey
225
+ // checks would let the pane-scoped health-diagnostic-export binding
226
+ // (candidate "D") win over the unscoped agents binding (candidate "d")
227
+ // when activePane === "health" — collapsing the d/D case distinction.
228
+ //
229
+ // Pass 1 — exact string match (case-sensitive). Handles literal ASCII
230
+ // keystrokes ('d', 'D', 'q', 'S', …) and preserves their distinct meanings.
231
+ for (const binding of BINDINGS) {
232
+ if (binding.pane !== undefined && binding.pane !== activePane) continue;
233
+ if (binding.keys.includes(data)) return binding.action;
234
+ }
235
+ // Pass 2 — terminal-aware match for escape sequences / canonical KeyIds.
236
+ // Only reached when no exact ASCII match exists (data is e.g. '\x1b[A',
237
+ // '\x1bOA', or an app-cursor-mode variant). Uses matchesKey() to normalize
238
+ // legacy CSI, app-cursor-mode, and Kitty-protocol variants uniformly.
239
+ const key = keyOf(data);
240
+ for (const binding of BINDINGS) {
241
+ if (binding.pane !== undefined && binding.pane !== activePane) continue;
242
+ for (const candidate of binding.keys) {
243
+ if (key === candidate) return binding.action;
244
+ if (matchesKey(data, candidate as KeyId)) return binding.action;
245
+ }
246
+ }
247
+ return undefined;
248
+ }
@@ -0,0 +1,106 @@
1
+ import { pad, wrapHard } from "../utils/visual.ts";
2
+
3
+ export interface RenderableComponent {
4
+ invalidate(): void;
5
+ render(width: number): string[];
6
+ }
7
+
8
+ export class Container implements RenderableComponent {
9
+ private children: RenderableComponent[] = [];
10
+
11
+ addChild(child: RenderableComponent): void {
12
+ this.children.push(child);
13
+ }
14
+
15
+ clear(): void {
16
+ this.children = [];
17
+ }
18
+
19
+ invalidate(): void {
20
+ for (const child of this.children) {
21
+ child.invalidate();
22
+ }
23
+ }
24
+
25
+ render(width: number): string[] {
26
+ const lines: string[] = [];
27
+ for (const child of this.children) {
28
+ lines.push(...child.render(width));
29
+ }
30
+ return lines;
31
+ }
32
+ }
33
+
34
+ export class Box extends Container {
35
+ private readonly paddingX: number;
36
+ private readonly paddingY: number;
37
+
38
+ constructor(paddingX = 0, paddingY = 0) {
39
+ super();
40
+ this.paddingX = paddingX;
41
+ this.paddingY = paddingY;
42
+ }
43
+
44
+ render(width: number): string[] {
45
+ const innerWidth = Math.max(1, width - this.paddingX * 2);
46
+ const rows = super.render(innerWidth);
47
+ const paddedRows: string[] = [];
48
+ const left = " ".repeat(this.paddingX);
49
+ const right = " ".repeat(this.paddingX);
50
+ for (const row of rows) {
51
+ paddedRows.push(pad(`${left}${row}${right}`, width));
52
+ }
53
+ const emptyRow = pad("", width);
54
+ if (this.paddingY <= 0) return paddedRows;
55
+ if (this.paddingY > 0) {
56
+ const topAndBottom = Array.from({ length: this.paddingY }, () => emptyRow);
57
+ return [...topAndBottom, ...paddedRows, ...topAndBottom];
58
+ }
59
+ return paddedRows;
60
+ }
61
+ }
62
+
63
+ export class Text implements RenderableComponent {
64
+ private text: string;
65
+ private cachedWidth = 0;
66
+ private cachedResult: string[] = [];
67
+
68
+ constructor(text = "") {
69
+ this.text = text;
70
+ }
71
+
72
+ setText(text: string): void {
73
+ if (text === this.text) return;
74
+ this.text = text;
75
+ this.invalidate();
76
+ }
77
+
78
+ invalidate(): void {
79
+ this.cachedWidth = 0;
80
+ this.cachedResult = [];
81
+ }
82
+
83
+ render(width: number): string[] {
84
+ if (this.cachedWidth === width) return this.cachedResult;
85
+ const wrapped = wrapHard(this.text, Math.max(1, width));
86
+ const lines = wrapped.length ? wrapped : [""];
87
+ this.cachedWidth = width;
88
+ this.cachedResult = lines.map((line) => pad(line, width));
89
+ return this.cachedResult;
90
+ }
91
+ }
92
+
93
+ export class Spacer implements RenderableComponent {
94
+ private readonly rows: number;
95
+
96
+ constructor(rows = 0) {
97
+ this.rows = rows;
98
+ }
99
+
100
+ render(width: number): string[] {
101
+ if (this.rows <= 0) return [];
102
+ return Array.from({ length: Math.max(0, this.rows) }, () => pad("", width));
103
+ }
104
+
105
+ invalidate(): void {}
106
+ }
@@ -0,0 +1,183 @@
1
+ /**
2
+ * live-conversation-overlay.ts — Live conversation overlay for viewing live-session agent output.
3
+ *
4
+ * R8: Subscribes to session events for real-time streaming updates.
5
+ * Falls back to polling LiveAgentHandle.activity when subscribe is unavailable.
6
+ */
7
+ import type { LiveAgentHandle } from "../runtime/live-agent-manager.ts";
8
+ import { pad, truncate } from "../utils/visual.ts";
9
+ import { computeLiveDurationMs } from "./live-duration.ts";
10
+ import { spinnerFrame } from "./spinner.ts";
11
+ import { iconForStatus } from "./status-colors.ts";
12
+ import type { CrewTheme } from "./theme-adapter.ts";
13
+
14
+ const CHROME_LINES = 6;
15
+ const MIN_VIEWPORT = 3;
16
+
17
+ export class LiveConversationOverlay {
18
+ private scrollOffset = 0;
19
+ private autoScroll = true;
20
+ private closed = false;
21
+ private pollTimer: ReturnType<typeof setInterval> | undefined;
22
+ cachedLines: string[] = [];
23
+ // H-4 fix (code-review 2026-06-23): cap the in-memory line buffer to avoid
24
+ // unbounded growth (OOM) during long-running live sessions. Oldest lines are
25
+ // dropped first; scrollOffset is adjusted to keep the viewport stable.
26
+ static readonly MAX_CACHED_LINES = 5000;
27
+ private columns: number;
28
+ private rows: number;
29
+ private unsubscribe: (() => void) | undefined;
30
+
31
+ private handle: LiveAgentHandle;
32
+ private theme: CrewTheme;
33
+
34
+ constructor(handle: LiveAgentHandle, theme: CrewTheme, columns = 80, rows = 24) {
35
+ this.handle = handle;
36
+ this.theme = theme;
37
+ this.columns = columns;
38
+ this.rows = rows;
39
+ // R8: Subscribe to real session events if available
40
+ const session = handle.session as Record<string, unknown>;
41
+ if (typeof session.subscribe === "function") {
42
+ try {
43
+ this.unsubscribe = (session.subscribe as (cb: (event: unknown) => void) => () => void)((event) => {
44
+ if (this.closed) return;
45
+ const obj = event as Record<string, unknown>;
46
+ const text = typeof obj.text === "string" ? obj.text : typeof obj.content === "string" ? obj.content : "";
47
+ if (text.trim()) {
48
+ this.pushLine(text);
49
+ if (this.autoScroll) this.scrollOffset = Math.max(0, this.cachedLines.length - this.viewportHeight());
50
+ }
51
+ });
52
+ } catch {
53
+ /* ignore */
54
+ }
55
+ }
56
+ // Also poll for summary updates. Skip when the user has scrolled up
57
+ // (autoScroll === false): the summary refresh also bumps scrollOffset
58
+ // to the tail, which would yank the viewport out from under them.
59
+ this.pollTimer = setInterval(() => {
60
+ if (this.closed) return;
61
+ if (!this.autoScroll) return;
62
+ try {
63
+ this.refreshSummary();
64
+ } catch {
65
+ /* ignore */
66
+ }
67
+ }, 200);
68
+ this.pollTimer.unref();
69
+ try {
70
+ this.refreshSummary();
71
+ } catch {
72
+ /* ignore */
73
+ }
74
+ }
75
+
76
+ private pushLine(line: string): void {
77
+ this.cachedLines.push(line);
78
+ if (this.cachedLines.length > LiveConversationOverlay.MAX_CACHED_LINES) {
79
+ const drop = this.cachedLines.length - LiveConversationOverlay.MAX_CACHED_LINES;
80
+ this.cachedLines.splice(0, drop);
81
+ this.scrollOffset = Math.max(0, this.scrollOffset - drop);
82
+ }
83
+ }
84
+
85
+ private static readonly SUMMARY_PREFIX = "\u200B"; // zero-width space as summary sentinel
86
+
87
+ private refreshSummary(): void {
88
+ const act = this.handle.activity;
89
+ const summary = `${LiveConversationOverlay.SUMMARY_PREFIX}[${act.turnCount} turns · ${act.toolUses} tools · ${(computeLiveDurationMs(act) / 1000).toFixed(1)}s]`;
90
+ const lastLine = this.cachedLines[this.cachedLines.length - 1];
91
+ if (lastLine?.startsWith(LiveConversationOverlay.SUMMARY_PREFIX)) {
92
+ this.cachedLines[this.cachedLines.length - 1] = summary;
93
+ } else {
94
+ this.pushLine(summary);
95
+ }
96
+ if (this.autoScroll) this.scrollOffset = Math.max(0, this.cachedLines.length - this.viewportHeight());
97
+ }
98
+
99
+ private viewportHeight(): number {
100
+ return Math.max(MIN_VIEWPORT, this.rows - CHROME_LINES);
101
+ }
102
+
103
+ render(width?: number): string[] {
104
+ const w = width ?? this.columns;
105
+ if (w < 6) return [];
106
+ const th = this.theme;
107
+ const innerW = w - 4;
108
+ const row = (content: string) => th.fg("border", "│") + " " + pad(truncate(content, innerW), innerW) + " " + th.fg("border", "│");
109
+ const hrTop = th.fg("border", `╭${"─".repeat(w - 2)}╮`);
110
+ const hrBot = th.fg("border", `╰${"─".repeat(w - 2)}╯`);
111
+ const hrMid = row(th.fg("dim", "─".repeat(innerW)));
112
+
113
+ const lines: string[] = [];
114
+ lines.push(hrTop);
115
+
116
+ // Header
117
+ const statusIcon =
118
+ this.handle.status === "running"
119
+ ? th.fg("accent", spinnerFrame(this.handle.taskId ?? this.handle.agentId))
120
+ : iconForStatus(this.handle.status);
121
+ const name = this.handle.agent ?? this.handle.taskId;
122
+ const act = this.handle.activity;
123
+ const elapsed = `${(computeLiveDurationMs(act) / 1000).toFixed(1)}s`;
124
+ const headerParts: string[] = [];
125
+ if (act.maxTurns != null) headerParts.push(`turn ${act.turnCount}/${act.maxTurns}`);
126
+ else if (act.turnCount > 0) headerParts.push(`turn ${act.turnCount}`);
127
+ if (act.toolUses > 0) headerParts.push(`${act.toolUses} tools`);
128
+ headerParts.push(elapsed);
129
+
130
+ // Context % + compaction
131
+ try {
132
+ const ctxPct = this.handle.session.getSessionStats?.()?.contextUsage?.percent;
133
+ if (ctxPct != null) {
134
+ const color = ctxPct >= 85 ? "error" : ctxPct >= 70 ? "warning" : "dim";
135
+ headerParts.push(th.fg(color, `${Math.round(ctxPct)}% ctx`));
136
+ }
137
+ } catch {
138
+ /* ignore */
139
+ }
140
+ if (act.compactionCount > 0) headerParts.push(th.fg("dim", `↻${act.compactionCount}`));
141
+ // Model name
142
+ if (this.handle.modelName) headerParts.push(th.fg("muted", this.handle.modelName));
143
+
144
+ const desc = this.handle.description ?? this.handle.role ?? "";
145
+ lines.push(
146
+ row(
147
+ `${statusIcon} ${th.fg("accent", name)} ${th.fg("muted", desc)} ${th.fg("dim", "·")} ${th.fg("dim", headerParts.join(" · "))}`,
148
+ ),
149
+ );
150
+ lines.push(hrMid);
151
+
152
+ // Content
153
+ const vh = this.viewportHeight();
154
+ const visible = this.cachedLines.slice(this.scrollOffset, this.scrollOffset + vh);
155
+ for (const line of visible) {
156
+ lines.push(row(th.fg("dim", line)));
157
+ }
158
+
159
+ // Footer
160
+ lines.push(hrMid);
161
+ const footerText = this.autoScroll
162
+ ? "auto-scroll · esc/q close"
163
+ : `line ${this.scrollOffset + 1}/${this.cachedLines.length} · ↑/k ↓/j G/g · esc/q close`;
164
+ lines.push(row(th.fg("dim", footerText)));
165
+ lines.push(hrBot);
166
+ return lines;
167
+ }
168
+
169
+ close(): void {
170
+ if (this.closed) return;
171
+ this.closed = true;
172
+ this.unsubscribe?.();
173
+ this.unsubscribe = undefined;
174
+ if (this.pollTimer) {
175
+ clearInterval(this.pollTimer);
176
+ this.pollTimer = undefined;
177
+ }
178
+ }
179
+
180
+ dispose(): void {
181
+ this.close();
182
+ }
183
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Round 23 (BUG 1 fix): live-agent duration computation.
3
+ *
4
+ * The naive `(completedAtMs ?? Date.now()) - startedAtMs` produced giant
5
+ * NEGATIVE durations for every running live agent whenever startedAtMs was
6
+ * 0/undefined/out-of-range, or a race set completedAtMs < startedAtMs.
7
+ *
8
+ * This module consolidates the validated duration math (previously duplicated
9
+ * between widget-formatters.ts and agents-pane.ts) into one pure, fully
10
+ * testable function: it normalizes seconds-vs-ms, sanity-checks the start
11
+ * timestamp against the current time, and never returns a negative value.
12
+ */
13
+
14
+ export interface LiveActivity {
15
+ startedAtMs?: number;
16
+ completedAtMs?: number;
17
+ }
18
+
19
+ /** Normalize a raw timestamp that may be seconds or milliseconds. */
20
+ function toMs(v: number): number {
21
+ if (v <= 0) return 0;
22
+ // 1e9 < seconds < 1e10 → seconds, scale up
23
+ if (v > 1_000_000_000 && v < 10_000_000_000) return v * 1000;
24
+ // 1e10 < ms < 1e13 → already ms (>=10B seconds is 317+ years, implausible)
25
+ if (v > 10_000_000_000 && v < 10_000_000_000_000) return v;
26
+ return v;
27
+ }
28
+
29
+ /**
30
+ * Compute the live elapsed duration in milliseconds for an agent activity.
31
+ *
32
+ * - Never negative (clamped to >= 0).
33
+ * - Returns 0 if the start timestamp is missing or implausible.
34
+ * - Uses `completedAtMs` when present and sane; otherwise `nowMs` (running).
35
+ *
36
+ * @param activity the live agent activity handle
37
+ * @param nowMs optional override for `Date.now()` (tests / determinism)
38
+ */
39
+ export function computeLiveDurationMs(activity: LiveActivity, nowMs: number = Date.now()): number {
40
+ const rawStarted = activity.startedAtMs || 0;
41
+ const rawCompleted = activity.completedAtMs || 0;
42
+ const startedMs = toMs(rawStarted);
43
+ const completedMs = rawCompleted > 0 ? toMs(rawCompleted) : 0;
44
+ // A valid start is positive, not more than 1 minute in the future, and not
45
+ // more than ~1000 years in the past (guards against 0 / garbage / clock skew).
46
+ const isValidStarted = startedMs > 0 && startedMs < nowMs + 60_000 && startedMs > nowMs - 31_556_926_000_000;
47
+ const end = completedMs > 0 && completedMs < nowMs + 60_000 ? completedMs : nowMs;
48
+ const ms = end - (isValidStarted ? startedMs : nowMs);
49
+ return Number.isFinite(ms) && ms >= 0 ? ms : 0;
50
+ }
51
+
52
+ /** Format a live duration in seconds, e.g. `12.3s`. Returns `0.0s` for 0. */
53
+ export function formatLiveDuration(activity: LiveActivity, nowMs: number = Date.now()): string {
54
+ return `${(computeLiveDurationMs(activity, nowMs) / 1000).toFixed(1)}s`;
55
+ }