pi-crew 0.9.55 → 0.9.57

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 (309) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +58 -0
  3. package/README.md +53 -5
  4. package/dist/index.mjs +5666 -4370
  5. package/docs/README.md +54 -0
  6. package/package.json +12 -13
  7. package/skills/real-test-pi-crew/SKILL.md +93 -3
  8. package/src/agents/discover-agents.ts +68 -10
  9. package/src/benchmark/feedback-loop.ts +1 -1
  10. package/src/config/config.ts +2 -1
  11. package/src/config/types.ts +12 -0
  12. package/src/extension/action-suggestions.ts +16 -63
  13. package/src/extension/async-notifier.ts +3 -3
  14. package/src/extension/command-completions.ts +2 -2
  15. package/src/extension/crew-cleanup.ts +3 -3
  16. package/src/extension/crew-vibes/footer.ts +4 -2
  17. package/src/extension/cross-extension-rpc.ts +10 -5
  18. package/src/extension/knowledge-injection.ts +57 -10
  19. package/src/extension/register.ts +2 -2
  20. package/src/extension/registration/artifact-cleanup.ts +41 -3
  21. package/src/extension/registration/commands.ts +4 -4
  22. package/src/extension/registration/context-builder.ts +10 -10
  23. package/src/extension/registration/crash-recovery-cache.ts +3 -3
  24. package/src/extension/registration/foreground-run-controller.ts +1 -1
  25. package/src/extension/registration/lazy-configurers.ts +2 -2
  26. package/src/extension/registration/lifecycle-handlers.ts +12 -12
  27. package/src/extension/registration/lifecycle.ts +2 -2
  28. package/src/extension/registration/observability.ts +2 -2
  29. package/src/extension/registration/registration-types.ts +3 -3
  30. package/src/extension/registration/runtime-cleanup.ts +1 -1
  31. package/src/extension/registration/subagent-helpers.ts +2 -2
  32. package/src/extension/registration/subagent-manager-setup.ts +2 -2
  33. package/src/extension/registration/subagent-tools.ts +21 -16
  34. package/src/extension/registration/team-tool.ts +109 -17
  35. package/src/extension/registration/viewers.ts +3 -4
  36. package/src/extension/registration/wire-cross-extension.ts +7 -7
  37. package/src/extension/rpc-hmac.ts +3 -2
  38. package/src/extension/run-bundle-schema.ts +13 -4
  39. package/src/extension/run-export.ts +2 -2
  40. package/src/extension/run-index.ts +2 -3
  41. package/src/extension/run-maintenance.ts +57 -1
  42. package/src/extension/team-manager-command.ts +3 -1
  43. package/src/extension/team-tool/anchor.ts +8 -2
  44. package/src/extension/team-tool/api.ts +117 -39
  45. package/src/extension/team-tool/cancel.ts +23 -8
  46. package/src/extension/team-tool/chain-executor.ts +1 -1
  47. package/src/extension/team-tool/dispatch/automate.ts +17 -2
  48. package/src/extension/team-tool/dispatch/control.ts +17 -2
  49. package/src/extension/team-tool/dispatch/manage.ts +34 -2
  50. package/src/extension/team-tool/dispatch/run.ts +17 -2
  51. package/src/extension/team-tool/dispatch/status.ts +37 -5
  52. package/src/extension/team-tool/doctor.ts +2 -2
  53. package/src/extension/team-tool/explain.ts +1 -1
  54. package/src/extension/team-tool/goal-wrap.ts +5 -5
  55. package/src/extension/team-tool/goal.ts +30 -9
  56. package/src/extension/team-tool/handle-schedule.ts +37 -19
  57. package/src/extension/team-tool/inspect.ts +21 -5
  58. package/src/extension/team-tool/lifecycle-actions.ts +53 -10
  59. package/src/extension/team-tool/parallel-dispatch.ts +14 -5
  60. package/src/extension/team-tool/param-error.ts +138 -0
  61. package/src/extension/team-tool/respond.ts +20 -6
  62. package/src/extension/team-tool/run.ts +27 -17
  63. package/src/extension/team-tool/status.ts +17 -6
  64. package/src/extension/team-tool.ts +26 -19
  65. package/src/hooks/registry.ts +1 -1
  66. package/src/i18n.ts +22 -0
  67. package/src/observability/event-bus.ts +1 -1
  68. package/src/prompt/prompt-runtime.ts +2 -2
  69. package/src/runtime/README.md +35 -0
  70. package/src/runtime/agent-control.ts +1 -1
  71. package/src/runtime/anchor-manager.ts +0 -2
  72. package/src/runtime/async-runner.ts +1 -1
  73. package/src/runtime/attention-events.ts +3 -2
  74. package/src/runtime/auto-summarize.ts +0 -2
  75. package/src/runtime/background-runner.ts +103 -30
  76. package/src/runtime/{crew-broker-child.ts → broker/crew-broker-child.ts} +2 -2
  77. package/src/runtime/{crew-broker-client.ts → broker/crew-broker-client.ts} +4 -7
  78. package/src/runtime/{crew-broker.ts → broker/crew-broker.ts} +13 -21
  79. package/src/runtime/chain-runner.ts +50 -5
  80. package/src/runtime/{child-pi-constants.ts → child-pi/child-pi-constants.ts} +1 -1
  81. package/src/runtime/{child-pi-kill.ts → child-pi/child-pi-kill.ts} +3 -3
  82. package/src/runtime/{child-pi-spawn.ts → child-pi/child-pi-spawn.ts} +28 -12
  83. package/src/runtime/{child-pi-steering.ts → child-pi/child-pi-steering.ts} +1 -1
  84. package/src/runtime/{child-pi-streams.ts → child-pi/child-pi-streams.ts} +23 -4
  85. package/src/runtime/{child-pi-transcript.ts → child-pi/child-pi-transcript.ts} +6 -6
  86. package/src/runtime/{child-pi.ts → child-pi/child-pi.ts} +47 -78
  87. package/src/runtime/compaction/compact-stages/bounded-tail.ts +84 -0
  88. package/src/runtime/{compact-stages → compaction/compact-stages}/index.ts +2 -2
  89. package/src/runtime/{compaction-summary.ts → compaction/compaction-summary.ts} +3 -3
  90. package/src/runtime/{tool-output-pruner.ts → compaction/tool-output-pruner.ts} +1 -1
  91. package/src/runtime/crew-agent-records.ts +48 -2
  92. package/src/runtime/custom-tools/irc-tool.ts +2 -2
  93. package/src/runtime/delivery-coordinator.ts +0 -3
  94. package/src/runtime/diagnostic-export.ts +5 -4
  95. package/src/runtime/foreground-control.ts +1 -1
  96. package/src/runtime/foreground-watchdog.ts +1 -1
  97. package/src/runtime/{adaptive-plan.ts → goal-workflow/adaptive-plan.ts} +8 -8
  98. package/src/runtime/{dynamic-workflow-context.ts → goal-workflow/dynamic-workflow-context.ts} +16 -16
  99. package/src/runtime/{dynamic-workflow-runner.ts → goal-workflow/dynamic-workflow-runner.ts} +10 -10
  100. package/src/runtime/{goal-achievement.ts → goal-workflow/goal-achievement.ts} +3 -3
  101. package/src/runtime/{goal-evaluator.ts → goal-workflow/goal-evaluator.ts} +8 -8
  102. package/src/runtime/{goal-loop-runner.ts → goal-workflow/goal-loop-runner.ts} +27 -25
  103. package/src/runtime/{goal-state-store.ts → goal-workflow/goal-state-store.ts} +8 -8
  104. package/src/runtime/{plan-templates.ts → goal-workflow/plan-templates.ts} +1 -1
  105. package/src/runtime/group-join.ts +3 -3
  106. package/src/runtime/handoff-manager.ts +0 -2
  107. package/src/runtime/{heartbeat-watcher.ts → heartbeat/heartbeat-watcher.ts} +7 -7
  108. package/src/runtime/iteration-hooks.ts +1 -1
  109. package/src/runtime/{live-agent-control.ts → live-session/live-agent-control.ts} +2 -2
  110. package/src/runtime/{live-agent-manager.ts → live-session/live-agent-manager.ts} +4 -4
  111. package/src/runtime/{live-session-runtime.ts → live-session/live-session-runtime.ts} +39 -41
  112. package/src/runtime/manifest-cache.ts +6 -2
  113. package/src/runtime/{model-fallback.ts → model/model-fallback.ts} +1 -1
  114. package/src/runtime/{pi-args.ts → model/pi-args.ts} +20 -5
  115. package/src/runtime/model/provider-extensions.ts +116 -0
  116. package/src/runtime/{runtime-policy.ts → model/runtime-policy.ts} +2 -2
  117. package/src/runtime/{runtime-resolver.ts → model/runtime-resolver.ts} +3 -3
  118. package/src/runtime/{runtime-warmup.ts → model/runtime-warmup.ts} +8 -8
  119. package/src/runtime/orphan-worker-registry.ts +1 -1
  120. package/src/runtime/{progress-tracker.ts → output/progress-tracker.ts} +1 -1
  121. package/src/runtime/{result-extractor.ts → output/result-extractor.ts} +4 -4
  122. package/src/runtime/{sidechain-output.ts → output/sidechain-output.ts} +2 -2
  123. package/src/runtime/{stream-preview.ts → output/stream-preview.ts} +1 -1
  124. package/src/runtime/{streaming-output.ts → output/streaming-output.ts} +2 -2
  125. package/src/runtime/{tool-progress.ts → output/tool-progress.ts} +1 -1
  126. package/src/runtime/parent-guard.ts +30 -10
  127. package/src/runtime/pipeline-runner.ts +2 -2
  128. package/src/runtime/policy-engine.ts +2 -2
  129. package/src/runtime/{cancellation.ts → process/cancellation.ts} +1 -1
  130. package/src/runtime/{post-exit-stdio-guard.ts → process/post-exit-stdio-guard.ts} +1 -1
  131. package/src/runtime/{process-lifecycle.ts → process/process-lifecycle.ts} +3 -3
  132. package/src/runtime/{checkpoint.ts → recovery/checkpoint.ts} +4 -4
  133. package/src/runtime/{crash-recovery.ts → recovery/crash-recovery.ts} +93 -21
  134. package/src/runtime/{overflow-recovery.ts → recovery/overflow-recovery.ts} +1 -1
  135. package/src/runtime/{recovery-recipes.ts → recovery/recovery-recipes.ts} +1 -1
  136. package/src/runtime/{retry-executor.ts → recovery/retry-executor.ts} +2 -2
  137. package/src/runtime/{retry-runner.ts → recovery/retry-runner.ts} +2 -8
  138. package/src/runtime/run-tracker.ts +1 -1
  139. package/src/runtime/run-worker.ts +3 -3
  140. package/src/runtime/{coalesce-tasks.ts → scheduling/coalesce-tasks.ts} +3 -3
  141. package/src/runtime/{concurrency.ts → scheduling/concurrency.ts} +9 -3
  142. package/src/runtime/{parallel-research.ts → scheduling/parallel-research.ts} +4 -2
  143. package/src/runtime/{run-coalesced-task-group.ts → scheduling/run-coalesced-task-group.ts} +95 -36
  144. package/src/runtime/{semaphore.ts → scheduling/semaphore.ts} +4 -1
  145. package/src/runtime/{task-graph-scheduler.ts → scheduling/task-graph-scheduler.ts} +1 -1
  146. package/src/runtime/session-resources.ts +1 -1
  147. package/src/runtime/settings-store.ts +1 -1
  148. package/src/runtime/single-agent-compose.ts +1 -0
  149. package/src/runtime/stale-reconciler.ts +1 -1
  150. package/src/runtime/subagent-manager.ts +1 -1
  151. package/src/runtime/supervisor-contact.ts +28 -13
  152. package/src/runtime/task-output-context.ts +7 -5
  153. package/src/runtime/task-runner/child-executor.ts +67 -31
  154. package/src/runtime/task-runner/live-executor.ts +8 -8
  155. package/src/runtime/task-runner/post-execution.ts +12 -12
  156. package/src/runtime/task-runner/pre-execution.ts +6 -6
  157. package/src/runtime/task-runner/progress.ts +1 -1
  158. package/src/runtime/task-runner/run-projection.ts +1 -1
  159. package/src/runtime/task-runner/scaffold-executor.ts +1 -1
  160. package/src/runtime/task-runner/state-helpers.ts +2 -2
  161. package/src/runtime/task-runner.ts +4 -4
  162. package/src/runtime/team-runner.ts +441 -175
  163. package/src/runtime/{completion-guard.ts → verification/completion-guard.ts} +1 -1
  164. package/src/runtime/{green-contract.ts → verification/green-contract.ts} +1 -1
  165. package/src/runtime/{post-checks.ts → verification/post-checks.ts} +4 -4
  166. package/src/runtime/{verification-gates.ts → verification/verification-gates.ts} +5 -5
  167. package/src/runtime/{verification-integrity.ts → verification/verification-integrity.ts} +1 -1
  168. package/src/schema/config-schema.ts +1 -0
  169. package/src/schema/team-tool-schema.ts +149 -185
  170. package/src/state/README.md +44 -0
  171. package/src/state/atomic-write.ts +24 -3
  172. package/src/state/{locks.ts → coordination/locks.ts} +118 -38
  173. package/src/state/{mailbox.ts → coordination/mailbox.ts} +14 -8
  174. package/src/state/{schedule.ts → coordination/schedule.ts} +3 -3
  175. package/src/state/{task-claims.ts → coordination/task-claims.ts} +1 -1
  176. package/src/state/decision-ledger.ts +1 -1
  177. package/src/state/{event-log-rotation.ts → event-log/event-log-rotation.ts} +189 -31
  178. package/src/state/{event-log.ts → event-log/event-log.ts} +77 -39
  179. package/src/state/{jsonl-writer.ts → event-log/jsonl-writer.ts} +14 -6
  180. package/src/state/hook-instinct-bridge.ts +2 -2
  181. package/src/state/{active-run-registry.ts → stores/active-run-registry.ts} +7 -7
  182. package/src/state/{artifact-store.ts → stores/artifact-store.ts} +62 -4
  183. package/src/state/{blob-store.ts → stores/blob-store.ts} +3 -3
  184. package/src/state/{health-store.ts → stores/health-store.ts} +3 -3
  185. package/src/state/{instinct-store.ts → stores/instinct-store.ts} +3 -3
  186. package/src/state/{observation-store.ts → stores/observation-store.ts} +2 -2
  187. package/src/state/{run-cache.ts → stores/run-cache.ts} +4 -4
  188. package/src/state/{run-graph.ts → stores/run-graph.ts} +4 -4
  189. package/src/state/{run-metrics.ts → stores/run-metrics.ts} +3 -3
  190. package/src/state/{state-store.ts → stores/state-store.ts} +254 -21
  191. package/src/state/types.ts +27 -3
  192. package/src/ui/component.ts +55 -0
  193. package/src/ui/crew-select-list.ts +3 -1
  194. package/src/ui/dashboard-panes/agents-pane.ts +1 -1
  195. package/src/ui/dwf-phase-display.ts +1 -1
  196. package/src/ui/heartbeat-aggregator.ts +1 -1
  197. package/src/ui/keybinding-map.ts +179 -6
  198. package/src/ui/layout-primitives.ts +9 -5
  199. package/src/ui/live-conversation-overlay.ts +1 -1
  200. package/src/ui/live-run-sidebar.ts +1 -1
  201. package/src/ui/overlay-stack.ts +148 -0
  202. package/src/ui/overlays/agent-picker-overlay.ts +1 -1
  203. package/src/ui/overlays/mailbox-compose-overlay.ts +1 -1
  204. package/src/ui/overlays/mailbox-detail-overlay.ts +2 -2
  205. package/src/ui/powerbar-publisher.ts +1 -1
  206. package/src/ui/render-scheduler.ts +1 -1
  207. package/src/ui/run-action-dispatcher.ts +3 -3
  208. package/src/ui/run-dashboard.ts +5 -33
  209. package/src/ui/run-event-bus.ts +3 -3
  210. package/src/ui/run-snapshot-cache.ts +37 -18
  211. package/src/ui/settings-overlay.ts +13 -21
  212. package/src/ui/snapshot-types.ts +1 -1
  213. package/src/ui/status-colors.ts +0 -2
  214. package/src/ui/terminal-status.ts +1 -1
  215. package/src/ui/theme-adapter.ts +33 -13
  216. package/src/ui/tool-renderers/index.ts +8 -0
  217. package/src/ui/transcript-viewer.ts +37 -13
  218. package/src/ui/widget/index.ts +32 -9
  219. package/src/ui/widget/widget-formatters.ts +62 -4
  220. package/src/ui/widget/widget-model.ts +2 -2
  221. package/src/ui/widget/widget-renderer.ts +1 -1
  222. package/src/utils/conflict-detect.ts +58 -5
  223. package/src/utils/ndjson.ts +2 -2
  224. package/src/utils/redaction.ts +39 -0
  225. package/src/utils/socket-path.ts +1 -1
  226. package/src/workflows/workflow-config.ts +1 -1
  227. package/src/worktree/cleanup.ts +1 -1
  228. package/src/worktree/worktree-manager.ts +111 -47
  229. package/workflows/chain.workflow.md +2 -2
  230. package/dist/build-meta.json +0 -16807
  231. package/index.bundle.ts +0 -25
  232. package/scripts/bench-check.mjs +0 -96
  233. package/scripts/bench-cold-start.mjs +0 -216
  234. package/scripts/build-bundle.mjs +0 -92
  235. package/scripts/check-all-skills.ts +0 -294
  236. package/scripts/check-bundle-size.mjs +0 -38
  237. package/scripts/check-bundle-staleness.mjs +0 -97
  238. package/scripts/check-conflict-markers.mjs +0 -91
  239. package/scripts/check-lazy-imports.mjs +0 -28
  240. package/scripts/dev-runner.mjs +0 -106
  241. package/scripts/profile-startup.mjs +0 -125
  242. package/scripts/release-smoke.mjs +0 -74
  243. package/scripts/run-bench.mjs +0 -47
  244. package/scripts/run-real-chain.ts +0 -41
  245. package/scripts/test-issue-29-crash.ts +0 -111
  246. package/scripts/test-issue-29-e2e.ts +0 -183
  247. package/scripts/test-issue-29-real-runtime.ts +0 -330
  248. package/scripts/test-issue-29-real-tasks.ts +0 -387
  249. package/scripts/test-issue-29-team-tool.ts +0 -105
  250. package/scripts/test-runner.mjs +0 -84
  251. package/scripts/verify-flicker-fix.ts +0 -109
  252. package/scripts/verify-skill.ts +0 -550
  253. package/scripts/watch-bundle.mjs +0 -259
  254. package/scripts/watchdog-harness.ts +0 -119
  255. package/src/adapters/claude-adapter.ts +0 -23
  256. package/src/adapters/codex-adapter.ts +0 -21
  257. package/src/adapters/cursor-adapter.ts +0 -17
  258. package/src/adapters/export-util.ts +0 -143
  259. package/src/adapters/index.ts +0 -15
  260. package/src/adapters/registry.ts +0 -18
  261. package/src/adapters/types.ts +0 -23
  262. package/src/runtime/code-summary.ts +0 -292
  263. package/src/runtime/cross-extension-rpc.ts +0 -143
  264. package/src/runtime/hidden-handoff.ts +0 -407
  265. package/src/runtime/prose-compressor.ts +0 -162
  266. package/src/subagents/async-entry.ts +0 -1
  267. package/src/subagents/index.ts +0 -3
  268. package/src/subagents/live/control.ts +0 -1
  269. package/src/subagents/live/manager.ts +0 -1
  270. package/src/subagents/live/realtime.ts +0 -1
  271. package/src/subagents/live/session-runtime.ts +0 -1
  272. package/src/subagents/manager.ts +0 -1
  273. package/src/subagents/spawn.ts +0 -1
  274. package/test-integration-check.ts +0 -118
  275. /package/src/runtime/{broker-issuer.ts → broker/broker-issuer.ts} +0 -0
  276. /package/src/runtime/{crew-broker-tokens.ts → broker/crew-broker-tokens.ts} +0 -0
  277. /package/src/runtime/{child-pi-pool.ts → child-pi/child-pi-pool.ts} +0 -0
  278. /package/src/runtime/{compact-pipeline.ts → compaction/compact-pipeline.ts} +0 -0
  279. /package/src/runtime/{compact-stages → compaction/compact-stages}/ansi-strip-stage.ts +0 -0
  280. /package/src/runtime/{compact-stages → compaction/compact-stages}/blank-collapse-stage.ts +0 -0
  281. /package/src/runtime/{compact-stages → compaction/compact-stages}/deduplicate-stage.ts +0 -0
  282. /package/src/runtime/{compact-stages → compaction/compact-stages}/head-snap-stage.ts +0 -0
  283. /package/src/runtime/{compact-stages → compaction/compact-stages}/tail-capture-stage.ts +0 -0
  284. /package/src/runtime/{compact-stages → compaction/compact-stages}/truncation-stage.ts +0 -0
  285. /package/src/runtime/{important-line-classifier.ts → compaction/important-line-classifier.ts} +0 -0
  286. /package/src/runtime/{heartbeat-gradient.ts → heartbeat/heartbeat-gradient.ts} +0 -0
  287. /package/src/runtime/{worker-heartbeat.ts → heartbeat/worker-heartbeat.ts} +0 -0
  288. /package/src/runtime/{worker-startup.ts → heartbeat/worker-startup.ts} +0 -0
  289. /package/src/runtime/{intercom-bridge.ts → live-session/intercom-bridge.ts} +0 -0
  290. /package/src/runtime/{live-control-realtime.ts → live-session/live-control-realtime.ts} +0 -0
  291. /package/src/runtime/{live-extension-bridge.ts → live-session/live-extension-bridge.ts} +0 -0
  292. /package/src/runtime/{live-irc.ts → live-session/live-irc.ts} +0 -0
  293. /package/src/runtime/{live-session-health.ts → live-session/live-session-health.ts} +0 -0
  294. /package/src/runtime/{model-resolver.ts → model/model-resolver.ts} +0 -0
  295. /package/src/runtime/{model-scope.ts → model/model-scope.ts} +0 -0
  296. /package/src/runtime/{output-validator.ts → output/output-validator.ts} +0 -0
  297. /package/src/runtime/{pi-json-output.ts → output/pi-json-output.ts} +0 -0
  298. /package/src/runtime/{progress-event-coalescer.ts → output/progress-event-coalescer.ts} +0 -0
  299. /package/src/runtime/{cancellation-token.ts → process/cancellation-token.ts} +0 -0
  300. /package/src/runtime/{zombie-scanner.ts → process/zombie-scanner.ts} +0 -0
  301. /package/src/runtime/{crash-classification.ts → recovery/crash-classification.ts} +0 -0
  302. /package/src/runtime/{batch-barrier.ts → scheduling/batch-barrier.ts} +0 -0
  303. /package/src/runtime/{global-worker-cap.ts → scheduling/global-worker-cap.ts} +0 -0
  304. /package/src/runtime/{parallel-utils.ts → scheduling/parallel-utils.ts} +0 -0
  305. /package/src/runtime/{scheduler.ts → scheduling/scheduler.ts} +0 -0
  306. /package/src/runtime/{task-graph.ts → scheduling/task-graph.ts} +0 -0
  307. /package/src/runtime/{verification-worktree.ts → verification/verification-worktree.ts} +0 -0
  308. /package/src/state/{event-reconstructor.ts → event-log/event-reconstructor.ts} +0 -0
  309. /package/src/state/{worker-atomic-writer.ts → event-log/worker-atomic-writer.ts} +0 -0
@@ -442,7 +442,9 @@ class SelectSubmenu {
442
442
  this.scrollOffset = Math.max(0, this.selectedIndex - Math.floor(this.maxVisible / 2));
443
443
  }
444
444
 
445
- invalidate(): void {}
445
+ invalidate(): void {
446
+ /* no-op */
447
+ }
446
448
 
447
449
  private ensureVisible(): void {
448
450
  if (this.selectedIndex < this.scrollOffset) {
@@ -535,7 +537,9 @@ class TextinputSubmenu {
535
537
  this.onCancel = onCancel;
536
538
  }
537
539
 
538
- invalidate(): void {}
540
+ invalidate(): void {
541
+ /* no-op */
542
+ }
539
543
 
540
544
  render(width: number): string[] {
541
545
  const lines: string[] = [];
@@ -585,7 +589,6 @@ class AgentOverridesSubmenu {
585
589
  private selectedIndex = 0;
586
590
  private editField: "model" | "thinking" | null = null;
587
591
  private editBuffer = "";
588
- private readonly onApply: (overrides: Record<string, unknown>) => void;
589
592
  private readonly onCancel: () => void;
590
593
 
591
594
  constructor(
@@ -595,7 +598,6 @@ class AgentOverridesSubmenu {
595
598
  onCancel: () => void,
596
599
  ) {
597
600
  this.theme = theme;
598
- this.onApply = onApply;
599
601
  this.onCancel = onCancel;
600
602
  const existing = (config.agents as Record<string, unknown>)?.overrides as
601
603
  | Record<string, { model?: string; thinking?: string }>
@@ -616,7 +618,9 @@ class AgentOverridesSubmenu {
616
618
  ];
617
619
  }
618
620
 
619
- invalidate(): void {}
621
+ invalidate(): void {
622
+ /* no-op */
623
+ }
620
624
 
621
625
  render(width: number): string[] {
622
626
  if (this.editField) return this.renderEdit(width);
@@ -661,7 +665,10 @@ class AgentOverridesSubmenu {
661
665
  }
662
666
 
663
667
  handleInput(data: string): void {
664
- if (this.editField) return this.handleEditInput(data);
668
+ if (this.editField) {
669
+ this.handleEditInput(data);
670
+ return;
671
+ }
665
672
 
666
673
  const k = keyOf(data);
667
674
  if (k === "up" || k === "k") {
@@ -738,7 +745,6 @@ class SettingsOverlay {
738
745
  private scrollOffset = 0;
739
746
  private maxVisible = 10;
740
747
  private submenu: SelectSubmenu | TextinputSubmenu | AgentOverridesSubmenu | null = null;
741
- private submenuSettingId: string | null = null;
742
748
  private changedValues = new Map<string, unknown>();
743
749
 
744
750
  constructor(config: Record<string, unknown>, theme: CrewTheme, callbacks: SettingsOverlayCallbacks) {
@@ -926,7 +932,6 @@ class SettingsOverlay {
926
932
  }
927
933
  case "enum": {
928
934
  if (!def.values?.length) return;
929
- this.submenuSettingId = def.id;
930
935
  this.submenu = new SelectSubmenu(
931
936
  def.label,
932
937
  def.description ?? "",
@@ -937,17 +942,14 @@ class SettingsOverlay {
937
942
  this.changedValues.set(def.id, value);
938
943
  this.callbacks.onChange(def.id, value);
939
944
  this.submenu = null;
940
- this.submenuSettingId = null;
941
945
  },
942
946
  () => {
943
947
  this.submenu = null;
944
- this.submenuSettingId = null;
945
948
  },
946
949
  );
947
950
  break;
948
951
  }
949
952
  case "number": {
950
- this.submenuSettingId = def.id;
951
953
  this.submenu = new TextinputSubmenu(
952
954
  def.label,
953
955
  def.description ?? "",
@@ -963,17 +965,14 @@ class SettingsOverlay {
963
965
  this.callbacks.onChange(def.id, undefined);
964
966
  }
965
967
  this.submenu = null;
966
- this.submenuSettingId = null;
967
968
  },
968
969
  () => {
969
970
  this.submenu = null;
970
- this.submenuSettingId = null;
971
971
  },
972
972
  );
973
973
  break;
974
974
  }
975
975
  case "string": {
976
- this.submenuSettingId = def.id;
977
976
  this.submenu = new TextinputSubmenu(
978
977
  def.label,
979
978
  def.description ?? "",
@@ -983,11 +982,9 @@ class SettingsOverlay {
983
982
  this.changedValues.set(def.id, value || undefined);
984
983
  this.callbacks.onChange(def.id, value || undefined);
985
984
  this.submenu = null;
986
- this.submenuSettingId = null;
987
985
  },
988
986
  () => {
989
987
  this.submenu = null;
990
- this.submenuSettingId = null;
991
988
  },
992
989
  );
993
990
  break;
@@ -1000,11 +997,9 @@ class SettingsOverlay {
1000
997
  this.changedValues.set("agents.overrides", overrides);
1001
998
  this.callbacks.onChange("agents.overrides", overrides);
1002
999
  this.submenu = null;
1003
- this.submenuSettingId = null;
1004
1000
  },
1005
1001
  () => {
1006
1002
  this.submenu = null;
1007
- this.submenuSettingId = null;
1008
1003
  },
1009
1004
  );
1010
1005
  break;
@@ -1015,7 +1010,6 @@ class SettingsOverlay {
1015
1010
  typeof this.changedValues.get(def.id) === "string"
1016
1011
  ? (this.changedValues.get(def.id) as string)
1017
1012
  : ((currentValueFor(this.config, def.id) as string | undefined) ?? "");
1018
- this.submenuSettingId = def.id;
1019
1013
  this.submenu = new SelectSubmenu(
1020
1014
  def.label,
1021
1015
  def.description ?? "",
@@ -1026,11 +1020,9 @@ class SettingsOverlay {
1026
1020
  this.changedValues.set(def.id, value);
1027
1021
  this.callbacks.onAction?.(def.action!, value);
1028
1022
  this.submenu = null;
1029
- this.submenuSettingId = null;
1030
1023
  },
1031
1024
  () => {
1032
1025
  this.submenu = null;
1033
- this.submenuSettingId = null;
1034
1026
  },
1035
1027
  );
1036
1028
  break;
@@ -1,5 +1,5 @@
1
1
  import type { CrewAgentRecord } from "../runtime/crew-agent-runtime.ts";
2
- import type { TeamEvent } from "../state/event-log.ts";
2
+ import type { TeamEvent } from "../state/event-log/event-log.ts";
3
3
  import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
4
4
  import type { DwfPhaseState } from "./dwf-phase-display.ts";
5
5
 
@@ -30,7 +30,6 @@ export function colorForStatus(status: RunStatus): CrewThemeColor {
30
30
  return "warning";
31
31
  case "needs_attention":
32
32
  return "warning";
33
- case "queued":
34
33
  default:
35
34
  return "dim";
36
35
  }
@@ -100,7 +99,6 @@ function glyphColor(glyph: string): CrewThemeColor {
100
99
  case "◦":
101
100
  case "·":
102
101
  return "dim";
103
- case "▶":
104
102
  default:
105
103
  return "accent";
106
104
  }
@@ -30,7 +30,7 @@
30
30
  */
31
31
 
32
32
  import { writeFileSync } from "node:fs";
33
- import { listLiveAgents } from "../runtime/live-agent-manager.ts";
33
+ import { listLiveAgents } from "../runtime/live-session/live-agent-manager.ts";
34
34
 
35
35
  /**
36
36
  * Minimal UI surface this module needs. Deliberately narrower than
@@ -94,12 +94,6 @@ export const THEME_COLOR_FALLBACKS: Record<CrewThemeColor, string> = {
94
94
  bashMode: "\x1b[32m",
95
95
  };
96
96
 
97
- /** Map a thinking intensity level (0–5) to a theme color slot. */
98
- export function thinkingColorForLevel(level: number): CrewThemeColor {
99
- const slots: CrewThemeColor[] = ["thinkingOff", "thinkingMinimal", "thinkingLow", "thinkingMedium", "thinkingHigh", "thinkingXhigh"];
100
- return slots[Math.min(Math.max(level, 0), 5)] ?? "thinkingOff";
101
- }
102
-
103
97
  export interface CrewTheme {
104
98
  fg(color: CrewThemeColor, text: string): string;
105
99
  bg?(color: CrewThemeBg, text: string): string;
@@ -202,6 +196,31 @@ function asUnsubscribe(value: unknown): Unsubscribe | undefined {
202
196
  return undefined;
203
197
  }
204
198
 
199
+ /**
200
+ * Register a "change" listener through whichever event API the theme object
201
+ * exposes (DOM EventTarget, Node EventEmitter `on`/`off`, or its legacy
202
+ * `addListener`/`removeListener` alias) and return a teardown that removes it.
203
+ * A registration is only performed when a paired removal exists, guaranteeing
204
+ * no orphaned listener (UI-6). Returns undefined when no recognised event API
205
+ * is present so the caller falls back to signature polling (UI-11).
206
+ */
207
+ function registerChangeListener(record: Record<string, unknown>, emit: () => void): Unsubscribe | undefined {
208
+ const apis: Array<{ register: string; revoke: string }> = [
209
+ { register: "addEventListener", revoke: "removeEventListener" },
210
+ { register: "on", revoke: "off" },
211
+ { register: "addListener", revoke: "removeListener" },
212
+ ];
213
+ for (const { register, revoke } of apis) {
214
+ const registerFn = record[register];
215
+ const revokeFn = record[revoke];
216
+ if (typeof registerFn === "function" && typeof revokeFn === "function") {
217
+ (registerFn as (...args: unknown[]) => void).call(record, "change", emit);
218
+ return () => (revokeFn as (...args: unknown[]) => void).call(record, "change", emit);
219
+ }
220
+ }
221
+ return undefined;
222
+ }
223
+
205
224
  function startThemeSourceSubscription(theme: object, subscription: ThemeSourceSubscription): void {
206
225
  const record = theme as Record<string, unknown>;
207
226
  const emit = () => {
@@ -212,12 +231,13 @@ function startThemeSourceSubscription(theme: object, subscription: ThemeSourceSu
212
231
  subscription.unsubscribeSource = asUnsubscribe(result);
213
232
  return;
214
233
  }
215
- if (typeof record.addEventListener === "function") {
216
- (record.addEventListener as (type: string, callback: () => void) => void)("change", emit);
217
- if (typeof record.removeEventListener === "function") {
218
- subscription.unsubscribeSource = () =>
219
- (record.removeEventListener as (type: string, callback: () => void) => void)("change", emit);
220
- }
234
+ // Event-driven detection (UI-6/UI-11): register through whichever event API
235
+ // the theme exposes. registerChangeListener only registers when a paired
236
+ // removal exists, so every listener is teardown-able. Falls back to polling
237
+ // below when no event API is recognised.
238
+ const removeListener = registerChangeListener(record, emit);
239
+ if (removeListener) {
240
+ subscription.unsubscribeSource = removeListener;
221
241
  return;
222
242
  }
223
243
  subscription.pollTimer = setInterval(() => {
@@ -230,7 +250,7 @@ function startThemeSourceSubscription(theme: object, subscription: ThemeSourceSu
230
250
  }
231
251
 
232
252
  export function subscribeThemeChange(theme: unknown, callback: () => void): () => void {
233
- if (!theme || typeof theme !== "object") return () => {};
253
+ if (!theme || typeof theme !== "object") return () => undefined;
234
254
  const key = theme;
235
255
  let subscription = themeSubscriptions.get(key);
236
256
  if (!subscription) {
@@ -9,6 +9,7 @@
9
9
  import { type Container, Text, visibleWidth } from "@earendil-works/pi-tui";
10
10
  import type { CrewAgentRecord } from "../../runtime/crew-agent-runtime.ts";
11
11
  import { truncateToWidth } from "../../utils/visual.ts";
12
+ import type { CrewComponent } from "../component.ts";
12
13
  import { formatDuration, formatTokens, truncLine } from "../format-helpers.ts";
13
14
  import { spinnerFrame } from "../spinner.ts";
14
15
  import type { CrewTheme } from "../theme-adapter.ts";
@@ -31,6 +32,13 @@ export interface ToolRenderer {
31
32
 
32
33
  export type Component = Container | Text;
33
34
 
35
+ // PR-G3 (UI-3): `Container | Text` structurally satisfies the shared
36
+ // CrewComponent contract — both come from @earendil-works/pi-tui and each
37
+ // implements the library `Component` interface (render + invalidate). The
38
+ // assertion below makes the relationship compiler-enforced without changing
39
+ // the exported union type or any behavior.
40
+ type _AssertComponentIsCrewComponent = Container extends CrewComponent ? (Text extends CrewComponent ? true : never) : never;
41
+
34
42
  // ── ANSI-aware padding ─────────────────────────────────────────────────
35
43
 
36
44
  /** Pad a string (which may contain ANSI codes) to a target VISUAL width. */
@@ -3,6 +3,7 @@ import { agentOutputPath, readCrewAgents } from "../runtime/crew-agent-records.t
3
3
  import type { TeamRunManifest } from "../state/types.ts";
4
4
  import { resolveRealContainedPath } from "../utils/safe-paths.ts";
5
5
  import { pad, truncate, truncateToVisualLines } from "../utils/visual.ts";
6
+ import type { InteractiveComponent } from "./component.ts";
6
7
  import { renderDiff } from "./render-diff.ts";
7
8
  import { colorForStatus, iconForStatus, type RunStatus } from "./status-colors.ts";
8
9
  import { highlightCode, highlightJson } from "./syntax-highlight.ts";
@@ -10,11 +11,11 @@ import type { CrewTheme } from "./theme-adapter.ts";
10
11
  import { asCrewTheme, subscribeThemeChange } from "./theme-adapter.ts";
11
12
  import { DEFAULT_TRANSCRIPT_TAIL_BYTES, getTranscriptCacheEntry, readTranscriptLinesCached } from "./transcript-cache.ts";
12
13
 
13
- type Component = {
14
- invalidate(): void;
15
- render(width: number): string[];
16
- handleInput(data: string): void;
17
- };
14
+ // PR-G3 (UI-3): alias the shared InteractiveComponent contract (render +
15
+ // invalidate + handleInput). Structurally identical to the previous local
16
+ // declaration — no behavior change. DurableTextViewer / DurableTranscriptViewer
17
+ // also have an extra `dispose()`, which is allowed (superset).
18
+ type Component = InteractiveComponent;
18
19
 
19
20
  type TranscriptTheme = CrewTheme;
20
21
 
@@ -282,7 +283,9 @@ export class DurableTextViewer implements Component {
282
283
  this.unsubscribeTheme = subscribeThemeChange(theme, () => this.invalidate());
283
284
  }
284
285
 
285
- invalidate(): void {}
286
+ invalidate(): void {
287
+ /* no-op */
288
+ }
286
289
 
287
290
  dispose(): void {
288
291
  this.unsubscribeTheme();
@@ -344,23 +347,38 @@ export class DurableTranscriptViewer implements Component {
344
347
  private fullTranscript = false;
345
348
  private maxTailBytes: number;
346
349
  private readonly unsubscribeTheme: () => void;
350
+ // UI-9: transcript content is read once when the viewer opens and then
351
+ // reused for per-keypress scroll math, so handleInput no longer performs a
352
+ // synchronous fs read on every keystroke. The cache is refreshed on the
353
+ // explicit full/tail toggle ("f") and kept fresh by the periodic render()
354
+ // call (which still reads, TTL-cached at ~500ms). `readTranscript` defaults
355
+ // to the module function and is overridable for tests.
356
+ private readTranscript: typeof readRunTranscript;
357
+ private cached: ReadRunTranscriptResult;
347
358
 
348
359
  constructor(
349
360
  manifest: TeamRunManifest,
350
361
  theme: unknown,
351
362
  done: (result: undefined) => void,
352
363
  taskId?: string,
353
- options: { maxTailBytes?: number } = {},
364
+ options: { maxTailBytes?: number; readTranscript?: typeof readRunTranscript } = {},
354
365
  ) {
355
366
  this.manifest = manifest;
356
367
  this.theme = asCrewTheme(theme);
357
368
  this.done = done;
358
369
  this.taskId = taskId;
359
370
  this.maxTailBytes = options.maxTailBytes ?? DEFAULT_TRANSCRIPT_TAIL_BYTES;
371
+ this.readTranscript = options.readTranscript ?? readRunTranscript;
360
372
  this.unsubscribeTheme = subscribeThemeChange(theme, () => this.invalidate());
373
+ this.cached = this.readTranscript(this.manifest, this.taskId, {
374
+ full: this.fullTranscript,
375
+ maxTailBytes: this.maxTailBytes,
376
+ });
361
377
  }
362
378
 
363
- invalidate(): void {}
379
+ invalidate(): void {
380
+ /* no-op */
381
+ }
364
382
 
365
383
  dispose(): void {
366
384
  this.unsubscribeTheme();
@@ -371,10 +389,7 @@ export class DurableTranscriptViewer implements Component {
371
389
  this.done(undefined);
372
390
  return;
373
391
  }
374
- const content = readRunTranscript(this.manifest, this.taskId, {
375
- full: this.fullTranscript,
376
- maxTailBytes: this.maxTailBytes,
377
- }).lines;
392
+ const content = this.cached.lines;
378
393
  const maxScroll = Math.max(0, content.length - this.lastHeight);
379
394
  if (data === "k" || data === "\u001b[A") {
380
395
  this.scroll = Math.max(0, this.scroll - 1);
@@ -400,14 +415,23 @@ export class DurableTranscriptViewer implements Component {
400
415
  this.fullTranscript = !this.fullTranscript;
401
416
  this.scroll = 0;
402
417
  this.autoScroll = !this.fullTranscript;
418
+ // The full/tail toggle changes the read options, so refresh the cache
419
+ // with a single explicit read rather than per keystroke.
420
+ this.cached = this.readTranscript(this.manifest, this.taskId, {
421
+ full: this.fullTranscript,
422
+ maxTailBytes: this.maxTailBytes,
423
+ });
403
424
  }
404
425
  }
405
426
 
406
427
  render(width: number): string[] {
407
- const data = readRunTranscript(this.manifest, this.taskId, {
428
+ const data = this.readTranscript(this.manifest, this.taskId, {
408
429
  full: this.fullTranscript,
409
430
  maxTailBytes: this.maxTailBytes,
410
431
  });
432
+ // Keep the per-keypress cache in sync with the latest rendered content
433
+ // (the read is TTL-cached at ~500ms, so this stays cheap on each tick).
434
+ this.cached = data;
411
435
  return renderViewerBase(
412
436
  {
413
437
  theme: this.theme,
@@ -100,12 +100,26 @@ const onResize = (): void => {
100
100
  }, 120);
101
101
  };
102
102
 
103
- function installResizeListener(): void {
103
+ /**
104
+ * F-1: install the single, guarded terminal-resize listener (exported for
105
+ * testability). The stdout "resize" listener is only registered when it can
106
+ * ALSO be removed later (off()/removeListener()); otherwise it is skipped so
107
+ * it can never leak across widget reinstalls (UI-7).
108
+ */
109
+ export function installResizeListener(): void {
104
110
  if (resizeListenerInstalled) return;
105
111
  resizeListenerInstalled = true;
106
112
  process.on("SIGWINCH", onResize);
107
113
  // Windows has no SIGWINCH; Node emits "resize" on stdout instead.
108
- if (typeof process.stdout?.on === "function") process.stdout.on("resize", onResize);
114
+ // Guard (UI-7): only register when removal is possible too. Older runtimes
115
+ // or mock stdouts that expose only `on` would otherwise add a listener that
116
+ // uninstallResizeListener() can never address → leak on every reinstall.
117
+ if (
118
+ typeof process.stdout?.on === "function" &&
119
+ (typeof process.stdout?.off === "function" || typeof process.stdout?.removeListener === "function")
120
+ ) {
121
+ process.stdout.on("resize", onResize);
122
+ }
109
123
  }
110
124
 
111
125
  /**
@@ -123,15 +137,24 @@ export function uninstallResizeListener(): void {
123
137
  activeResizeTarget = undefined;
124
138
  process.off("SIGWINCH", onResize);
125
139
  // Windows has no SIGWINCH; Node emits "resize" on stdout instead.
126
- if (typeof process.stdout?.off === "function") process.stdout.off("resize", onResize);
140
+ // Remove via whichever API is present. The listener was only registered
141
+ // when removal was possible (UI-7), so at most one of these applies; both
142
+ // are no-ops if no listener was ever added.
143
+ if (typeof process.stdout?.off === "function") {
144
+ process.stdout.off("resize", onResize);
145
+ } else if (typeof process.stdout?.removeListener === "function") {
146
+ process.stdout.removeListener("resize", onResize);
147
+ }
127
148
  }
128
149
 
129
- // ── Widget Component ──────────────────────────────────────────────────
150
+ import type { CrewComponent } from "../component.ts";
130
151
 
131
- interface WidgetComponent {
132
- render(width: number): string[];
133
- invalidate(): void;
134
- }
152
+ // ── Widget Component ──────────────────────────────────────────────────────
153
+
154
+ // PR-G3 (UI-3): extend the shared CrewComponent contract (render +
155
+ // invalidate). Structurally identical to the previous declaration — no
156
+ // behavior change.
157
+ interface WidgetComponent extends CrewComponent {}
135
158
 
136
159
  class CrewWidgetComponent implements WidgetComponent {
137
160
  private readonly model: CrewWidgetModel;
@@ -302,7 +325,7 @@ class CrewWidgetComponent implements WidgetComponent {
302
325
 
303
326
  // ── Re-export listLiveAgents for buildSignature ───────────────────────
304
327
 
305
- import { listLiveAgents } from "../../runtime/live-agent-manager.ts";
328
+ import { listLiveAgents } from "../../runtime/live-session/live-agent-manager.ts";
306
329
 
307
330
  // ── Public API ────────────────────────────────────────────────────────
308
331
 
@@ -5,11 +5,65 @@
5
5
  */
6
6
 
7
7
  import type { CrewAgentRecord } from "../../runtime/crew-agent-runtime.ts";
8
- import type { LiveAgentHandle } from "../../runtime/live-agent-manager.ts";
8
+ import type { LiveAgentHandle } from "../../runtime/live-session/live-agent-manager.ts";
9
9
  import { getTaskUsage } from "../../runtime/usage-tracker.ts";
10
10
  import { visibleWidth } from "../../utils/visual.ts";
11
11
  import { computeLiveDurationMs } from "../live-duration.ts";
12
12
 
13
+ // ── No-color mode (UI-10) ─────────────────────────────────────────────
14
+ // Comprehensive color/emoji suppression. Color + emoji formatting is
15
+ // disabled when (a) NO_COLOR is set to a non-empty value (de-facto standard,
16
+ // https://bixense.com/clicolors/), OR (b) stdout is not a TTY (piped/
17
+ // redirected — escape codes & wide glyphs are noise there). Detected once at
18
+ // module init; `paint()` and the notification-badge emoji decision consult
19
+ // `colorEnabled`, so EVERY formatter in this file emits plain strings when the
20
+ // mode is active. Previously (line ~179) only the notification badge checked
21
+ // `NO_COLOR !== "1"` — a partial check that ignored the broader NO_COLOR-any-
22
+ // value standard and the non-TTY case, so color codes could still leak to
23
+ // pipes/logs. This makes the suppression uniform across the file.
24
+
25
+ const RESET = "\x1b[0m";
26
+ const ANSI_SGR_RE = /\u001b\[[0-9;]*m/g;
27
+
28
+ // Status foreground colors used by the activity formatters below.
29
+ const COLOR_RED = "\x1b[31m"; // failures
30
+ const COLOR_YELLOW = "\x1b[33m"; // needs-attention / warning
31
+
32
+ function computeColorEnabled(): boolean {
33
+ if (process.env.NO_COLOR !== undefined && process.env.NO_COLOR !== "") return false;
34
+ // Treat both `false` and `undefined` (piped/redirected/captured) as non-TTY:
35
+ // color is only enabled when stdout is positively a TTY.
36
+ if (process.stdout?.isTTY !== true) return false;
37
+ return true;
38
+ }
39
+
40
+ let colorEnabled = computeColorEnabled();
41
+
42
+ /**
43
+ * Wrap `text` in an ANSI SGR sequence (e.g. `\x1b[31m`) when color is active.
44
+ * In no-color mode the text is returned verbatim — no escape sequences — so the
45
+ * formatters are safe to feed into logs, pipes, and other non-TTY sinks.
46
+ */
47
+ function paint(text: string, sgr: string): string {
48
+ if (!colorEnabled || !text) return text;
49
+ return `${sgr}${text}${RESET}`;
50
+ }
51
+
52
+ /** Strip ANSI SGR escape sequences from a string. */
53
+ export function stripAnsi(text: string): string {
54
+ return text.replace(ANSI_SGR_RE, "");
55
+ }
56
+
57
+ /** Re-evaluate the color mode from the current env/stdout (repeat of init). */
58
+ export function __resetColorMode(): void {
59
+ colorEnabled = computeColorEnabled();
60
+ }
61
+
62
+ /** Test-only: force the color mode into a known state. */
63
+ export function __setColorModeForTest(enabled: boolean): void {
64
+ colorEnabled = enabled;
65
+ }
66
+
13
67
  // ── Token formatting ──────────────────────────────────────────────────
14
68
 
15
69
  // V-1: fixed visible widths for per-agent numeric metrics so columns don't
@@ -111,14 +165,14 @@ export function agentActivity(agent: CrewAgentRecord, liveHandle?: LiveAgentHand
111
165
  const cleaned = recent.replace(/\s+/g, " ").trim();
112
166
  return cleaned.length > 60 ? cleaned.slice(0, 60) + "…" : cleaned;
113
167
  }
114
- if (agent.progress?.activityState === "needs_attention") return "needs attention";
168
+ if (agent.progress?.activityState === "needs_attention") return paint("needs attention", COLOR_YELLOW);
115
169
  if (agent.status === "queued") return "queued";
116
170
  if (agent.status === "running") {
117
171
  const age = agent.startedAt ? Date.now() - new Date(agent.startedAt).getTime() : Infinity;
118
172
  if (age < 5000 && !agent.progress?.currentTool) return "spawning…";
119
173
  return "thinking…";
120
174
  }
121
- if (agent.status === "failed") return agent.error ?? "failed";
175
+ if (agent.status === "failed") return paint(agent.error ?? "failed", COLOR_RED);
122
176
  return "done";
123
177
  }
124
178
 
@@ -176,7 +230,11 @@ export const NOTIFICATION_BADGE_CAP = 99;
176
230
  export function notificationBadge(count: number | undefined, env: NodeJS.ProcessEnv = process.env): string {
177
231
  if (!count || count <= 0) return "";
178
232
  const term = `${env.TERM ?? ""} ${env.WT_SESSION ?? ""} ${env.TERM_PROGRAM ?? ""}`.toLowerCase();
179
- const supportsEmoji = !term.includes("dumb") && env.NO_COLOR !== "1";
233
+ // UI-10: emoji is formatting too — gate it on the comprehensive color mode
234
+ // (NO_COLOR / non-TTY) in addition to a per-call env NO_COLOR check (standard:
235
+ // any non-empty value disables) and the dumb-terminal fallback.
236
+ const envNoColor = env.NO_COLOR !== undefined && env.NO_COLOR !== "";
237
+ const supportsEmoji = colorEnabled && !envNoColor && !term.includes("dumb");
180
238
  const label = count > NOTIFICATION_BADGE_CAP ? `${NOTIFICATION_BADGE_CAP}+ alerts` : `${count} alerts`;
181
239
  return supportsEmoji ? ` · ${label}` : ` [${label}]`;
182
240
  }
@@ -5,12 +5,12 @@
5
5
  */
6
6
 
7
7
  import { listRecentRuns } from "../../extension/run-index.ts";
8
- import { reconcileAllStaleRuns } from "../../runtime/crash-recovery.ts";
9
8
  import { readCrewAgents } from "../../runtime/crew-agent-records.ts";
10
9
  import type { CrewAgentRecord } from "../../runtime/crew-agent-runtime.ts";
11
- import { evictStaleLiveAgentHandles } from "../../runtime/live-agent-manager.ts";
10
+ import { evictStaleLiveAgentHandles } from "../../runtime/live-session/live-agent-manager.ts";
12
11
  import type { ManifestCache } from "../../runtime/manifest-cache.ts";
13
12
  import { isDisplayActiveRun } from "../../runtime/process-status.ts";
13
+ import { reconcileAllStaleRuns } from "../../runtime/recovery/crash-recovery.ts";
14
14
  import type { TeamRunManifest } from "../../state/types.ts";
15
15
  import type { RunSnapshotCache } from "../snapshot-types.ts";
16
16
  import type { WidgetRun } from "./widget-types.ts";
@@ -4,7 +4,7 @@
4
4
  * Extracted from crew-widget.ts.
5
5
  */
6
6
 
7
- import { listLiveAgents } from "../../runtime/live-agent-manager.ts";
7
+ import { listLiveAgents } from "../../runtime/live-session/live-agent-manager.ts";
8
8
  import { isFinishedRunStatus } from "../../runtime/process-status.ts";
9
9
  import { truncate } from "../../utils/visual.ts";
10
10
  import { Box, Text } from "../layout-primitives.ts";