@pellux/goodvibes-tui 1.1.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/CHANGELOG.md +85 -21
  2. package/README.md +28 -18
  3. package/docs/foundation-artifacts/operator-contract.json +4045 -1763
  4. package/package.json +2 -2
  5. package/src/audio/spoken-turn-controller.ts +12 -2
  6. package/src/audio/spoken-turn-wiring.ts +2 -1
  7. package/src/cli/help.ts +8 -1
  8. package/src/cli/service-posture.ts +2 -1
  9. package/src/cli/status.ts +2 -1
  10. package/src/cli/surface-command.ts +2 -2
  11. package/src/core/alert-gating.ts +67 -0
  12. package/src/core/approval-alert.ts +72 -0
  13. package/src/core/budget-breach-notifier.ts +106 -0
  14. package/src/core/composer-state.ts +11 -3
  15. package/src/core/conversation-line-cache.ts +27 -3
  16. package/src/core/conversation-rendering.ts +90 -14
  17. package/src/core/conversation.ts +18 -0
  18. package/src/core/focus-tracker.ts +41 -0
  19. package/src/core/long-task-notifier.ts +33 -6
  20. package/src/core/stream-event-wiring.ts +20 -1
  21. package/src/core/system-message-noise.ts +87 -0
  22. package/src/core/system-message-router.ts +104 -51
  23. package/src/core/turn-cancellation.ts +25 -0
  24. package/src/core/turn-event-wiring.ts +72 -3
  25. package/src/daemon/cli.ts +29 -2
  26. package/src/daemon/handlers/register.ts +8 -1
  27. package/src/daemon/service-commands.ts +329 -0
  28. package/src/export/cost-utils.ts +36 -0
  29. package/src/input/autocomplete.ts +27 -1
  30. package/src/input/command-registry.ts +84 -5
  31. package/src/input/commands/checkpoint-runtime.ts +280 -0
  32. package/src/input/commands/codebase-runtime.ts +232 -0
  33. package/src/input/commands/config.ts +43 -3
  34. package/src/input/commands/eval.ts +1 -1
  35. package/src/input/commands/health-runtime.ts +10 -2
  36. package/src/input/commands/image-runtime.ts +112 -0
  37. package/src/input/commands/intelligence-runtime.ts +11 -1
  38. package/src/input/commands/local-auth-runtime.ts +4 -1
  39. package/src/input/commands/local-runtime.ts +9 -3
  40. package/src/input/commands/marketplace-runtime.ts +2 -2
  41. package/src/input/commands/memory.ts +73 -35
  42. package/src/input/commands/operator-panel-runtime.ts +71 -31
  43. package/src/input/commands/planning-runtime.ts +116 -4
  44. package/src/input/commands/platform-sandbox-runtime.ts +1 -6
  45. package/src/input/commands/plugin-runtime.ts +2 -2
  46. package/src/input/commands/policy-dispatch.ts +21 -0
  47. package/src/input/commands/provider-accounts-runtime.ts +1 -1
  48. package/src/input/commands/qrcode-runtime.ts +26 -6
  49. package/src/input/commands/recall-review.ts +35 -0
  50. package/src/input/commands/remote-runtime-setup.ts +5 -3
  51. package/src/input/commands/remote-runtime.ts +3 -3
  52. package/src/input/commands/runtime-services.ts +54 -13
  53. package/src/input/commands/services-runtime.ts +1 -1
  54. package/src/input/commands/session-content.ts +20 -9
  55. package/src/input/commands/session-workflow.ts +16 -1
  56. package/src/input/commands/settings-sync-runtime.ts +16 -4
  57. package/src/input/commands/shell-core.ts +25 -8
  58. package/src/input/commands/skills-runtime.ts +2 -8
  59. package/src/input/commands/subscription-runtime.ts +2 -2
  60. package/src/input/commands/test-runtime.ts +277 -0
  61. package/src/input/commands/websearch-runtime.ts +101 -0
  62. package/src/input/commands/work-plan-runtime.ts +36 -10
  63. package/src/input/commands/workstream-runtime.ts +488 -0
  64. package/src/input/commands.ts +12 -0
  65. package/src/input/config-modal-types.ts +175 -0
  66. package/src/input/config-modal.ts +592 -0
  67. package/src/input/feed-context-factory.ts +10 -8
  68. package/src/input/handler-command-route.ts +37 -20
  69. package/src/input/handler-content-actions.ts +17 -2
  70. package/src/input/handler-feed-routes.ts +74 -114
  71. package/src/input/handler-feed.ts +79 -27
  72. package/src/input/handler-interactions.ts +1 -3
  73. package/src/input/handler-modal-routes.ts +136 -6
  74. package/src/input/handler-modal-stack.ts +14 -7
  75. package/src/input/handler-modal-token-routes.ts +16 -49
  76. package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
  77. package/src/input/handler-onboarding.ts +71 -59
  78. package/src/input/handler-picker-routes.ts +26 -102
  79. package/src/input/handler-shortcuts.ts +74 -14
  80. package/src/input/handler-types.ts +2 -6
  81. package/src/input/handler-ui-state.ts +10 -22
  82. package/src/input/handler.ts +12 -29
  83. package/src/input/keybindings.ts +23 -8
  84. package/src/input/model-picker-types.ts +24 -6
  85. package/src/input/model-picker.ts +77 -2
  86. package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
  87. package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
  88. package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
  89. package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
  90. package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
  91. package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
  92. package/src/input/panel-integration-actions.ts +9 -170
  93. package/src/input/panel-mouse-geometry.ts +97 -0
  94. package/src/input/panel-paste-flood-guard.ts +86 -0
  95. package/src/input/selection-modal.ts +11 -0
  96. package/src/input/session-picker-modal.ts +44 -4
  97. package/src/input/settings-modal-data.ts +146 -2
  98. package/src/input/settings-modal-mutations.ts +6 -4
  99. package/src/input/settings-modal-types.ts +4 -2
  100. package/src/main.ts +49 -51
  101. package/src/panels/agent-inspector-shared.ts +2 -1
  102. package/src/panels/base-panel.ts +22 -1
  103. package/src/panels/builtin/agent.ts +21 -107
  104. package/src/panels/builtin/development.ts +15 -61
  105. package/src/panels/builtin/knowledge.ts +8 -32
  106. package/src/panels/builtin/operations.ts +84 -428
  107. package/src/panels/builtin/session.ts +21 -112
  108. package/src/panels/builtin/shared.ts +18 -46
  109. package/src/panels/builtin-modals.ts +218 -0
  110. package/src/panels/builtin-panels.ts +5 -0
  111. package/src/panels/cost-tracker-panel.ts +36 -10
  112. package/src/panels/diff-panel.ts +12 -43
  113. package/src/panels/eval-registry.ts +60 -0
  114. package/src/panels/fleet-deep-link.ts +31 -0
  115. package/src/panels/fleet-panel-format.ts +62 -0
  116. package/src/panels/fleet-panel-worktree-detail.ts +48 -0
  117. package/src/panels/fleet-panel.ts +758 -0
  118. package/src/panels/fleet-read-model.ts +520 -0
  119. package/src/panels/fleet-steer.ts +125 -0
  120. package/src/panels/fleet-stop.ts +153 -0
  121. package/src/panels/fleet-tabs.ts +230 -0
  122. package/src/panels/fleet-transcript.ts +356 -0
  123. package/src/panels/index.ts +7 -31
  124. package/src/panels/modals/hooks-modal.ts +187 -0
  125. package/src/panels/modals/keybindings-modal.ts +166 -0
  126. package/src/panels/modals/knowledge-modal.ts +158 -0
  127. package/src/panels/modals/local-auth-modal.ts +132 -0
  128. package/src/panels/modals/marketplace-modal.ts +218 -0
  129. package/src/panels/modals/memory-modal.ts +180 -0
  130. package/src/panels/modals/modal-surface-helpers.ts +54 -0
  131. package/src/panels/modals/modal-theme.ts +42 -0
  132. package/src/panels/modals/pairing-modal.ts +163 -0
  133. package/src/panels/modals/planning-modal.ts +304 -0
  134. package/src/panels/modals/plugins-modal.ts +174 -0
  135. package/src/panels/modals/policy-modal.ts +256 -0
  136. package/src/panels/modals/provider-health-modal.ts +136 -0
  137. package/src/panels/modals/remote-modal.ts +115 -0
  138. package/src/panels/modals/sandbox-modal.ts +150 -0
  139. package/src/panels/modals/security-modal.ts +217 -0
  140. package/src/panels/modals/services-modal.ts +179 -0
  141. package/src/panels/modals/settings-sync-modal.ts +142 -0
  142. package/src/panels/modals/skills-modal.ts +189 -0
  143. package/src/panels/modals/subscription-modal.ts +172 -0
  144. package/src/panels/modals/work-plan-modal.ts +177 -0
  145. package/src/panels/panel-confirm-overlay.ts +72 -0
  146. package/src/panels/panel-manager.ts +123 -9
  147. package/src/panels/polish-core.ts +38 -25
  148. package/src/panels/project-planning-answer-actions.ts +8 -11
  149. package/src/panels/skills-panel.ts +7 -51
  150. package/src/panels/types.ts +37 -0
  151. package/src/permissions/hunk-selection.ts +179 -0
  152. package/src/permissions/prompt.ts +216 -13
  153. package/src/renderer/autocomplete-overlay.ts +28 -2
  154. package/src/renderer/compaction-history-modal.ts +19 -3
  155. package/src/renderer/compaction-preview.ts +13 -2
  156. package/src/renderer/compaction-quality.ts +100 -0
  157. package/src/renderer/compositor.ts +2 -3
  158. package/src/renderer/config-modal.ts +95 -0
  159. package/src/renderer/conversation-overlays.ts +10 -17
  160. package/src/renderer/fleet-tab-strip.ts +56 -0
  161. package/src/renderer/footer-tips.ts +15 -3
  162. package/src/renderer/fullscreen-primitives.ts +32 -22
  163. package/src/renderer/git-status.ts +43 -1
  164. package/src/renderer/help-overlay.ts +2 -2
  165. package/src/renderer/layout.ts +0 -4
  166. package/src/renderer/markdown.ts +7 -3
  167. package/src/renderer/modal-factory.ts +25 -20
  168. package/src/renderer/model-workspace.ts +62 -4
  169. package/src/renderer/overlay-box.ts +21 -17
  170. package/src/renderer/panel-workspace-bar.ts +8 -2
  171. package/src/renderer/process-indicator.ts +14 -3
  172. package/src/renderer/selection-modal-overlay.ts +6 -1
  173. package/src/renderer/session-picker-modal.ts +196 -3
  174. package/src/renderer/settings-modal-helpers.ts +2 -0
  175. package/src/renderer/settings-modal.ts +7 -0
  176. package/src/renderer/shell-surface.ts +8 -1
  177. package/src/renderer/status-glyphs.ts +14 -15
  178. package/src/renderer/system-message.ts +15 -3
  179. package/src/renderer/terminal-bg-probe.ts +339 -0
  180. package/src/renderer/terminal-escapes.ts +20 -0
  181. package/src/renderer/theme-mode-config.ts +67 -0
  182. package/src/renderer/theme.ts +91 -1
  183. package/src/renderer/thinking.ts +11 -3
  184. package/src/renderer/tool-call.ts +15 -9
  185. package/src/renderer/tool-result-summary.ts +148 -0
  186. package/src/renderer/turn-injection.ts +133 -0
  187. package/src/renderer/ui-factory.ts +154 -85
  188. package/src/renderer/ui-primitives.ts +30 -129
  189. package/src/runtime/bootstrap-command-context.ts +27 -1
  190. package/src/runtime/bootstrap-command-parts.ts +41 -10
  191. package/src/runtime/bootstrap-core.ts +47 -15
  192. package/src/runtime/bootstrap-hook-bridge.ts +7 -0
  193. package/src/runtime/bootstrap-shell.ts +48 -17
  194. package/src/runtime/bootstrap.ts +129 -22
  195. package/src/runtime/code-index-services.ts +135 -0
  196. package/src/runtime/legacy-daemon-migration.ts +516 -0
  197. package/src/runtime/memory-fold.ts +26 -0
  198. package/src/runtime/onboarding/derivation.ts +7 -2
  199. package/src/runtime/onboarding/snapshot.ts +27 -1
  200. package/src/runtime/onboarding/types.ts +31 -1
  201. package/src/runtime/operator-token-cleanup.ts +82 -1
  202. package/src/runtime/orchestrator-core-services.ts +59 -0
  203. package/src/runtime/process-lifecycle.ts +3 -1
  204. package/src/runtime/resume-notice.ts +209 -0
  205. package/src/runtime/services.ts +98 -46
  206. package/src/runtime/session-inbound-inputs.ts +252 -0
  207. package/src/runtime/session-spine-transport.ts +64 -0
  208. package/src/runtime/terminal-output-guard.ts +15 -8
  209. package/src/runtime/ui-services.ts +27 -3
  210. package/src/runtime/workstream-services.ts +327 -0
  211. package/src/runtime/wrfc-persistence.ts +124 -17
  212. package/src/shell/blocking-input.ts +68 -4
  213. package/src/shell/recovery-input-helpers.ts +170 -1
  214. package/src/shell/ui-openers.ts +171 -26
  215. package/src/utils/format-duration.ts +8 -18
  216. package/src/utils/splash-lines.ts +1 -1
  217. package/src/utils/terminal-width.ts +52 -0
  218. package/src/version.ts +1 -1
  219. package/src/panels/agent-inspector-panel.ts +0 -786
  220. package/src/panels/approval-panel.ts +0 -252
  221. package/src/panels/automation-control-panel.ts +0 -479
  222. package/src/panels/cockpit-panel.ts +0 -481
  223. package/src/panels/cockpit-read-model.ts +0 -238
  224. package/src/panels/communication-panel.ts +0 -256
  225. package/src/panels/control-plane-panel.ts +0 -470
  226. package/src/panels/debug-panel.ts +0 -615
  227. package/src/panels/docs-panel.ts +0 -384
  228. package/src/panels/eval-panel.ts +0 -627
  229. package/src/panels/file-explorer-panel.ts +0 -673
  230. package/src/panels/file-preview-panel.ts +0 -517
  231. package/src/panels/hooks-panel.ts +0 -401
  232. package/src/panels/incident-review-panel.ts +0 -406
  233. package/src/panels/intelligence-panel.ts +0 -383
  234. package/src/panels/knowledge-graph-panel.ts +0 -515
  235. package/src/panels/marketplace-panel.ts +0 -399
  236. package/src/panels/memory-panel.ts +0 -558
  237. package/src/panels/ops-control-panel.ts +0 -329
  238. package/src/panels/ops-strategy-panel.ts +0 -321
  239. package/src/panels/orchestration-panel.ts +0 -465
  240. package/src/panels/panel-list-panel.ts +0 -566
  241. package/src/panels/plan-dashboard-panel.ts +0 -707
  242. package/src/panels/policy-panel.ts +0 -517
  243. package/src/panels/project-planning-panel.ts +0 -731
  244. package/src/panels/provider-health-panel.ts +0 -678
  245. package/src/panels/provider-health-tracker.ts +0 -310
  246. package/src/panels/provider-health-views.ts +0 -567
  247. package/src/panels/qr-panel.ts +0 -280
  248. package/src/panels/remote-panel.ts +0 -534
  249. package/src/panels/routes-panel.ts +0 -241
  250. package/src/panels/sandbox-panel.ts +0 -456
  251. package/src/panels/security-panel.ts +0 -447
  252. package/src/panels/services-panel.ts +0 -329
  253. package/src/panels/session-browser-panel.ts +0 -496
  254. package/src/panels/settings-sync-panel.ts +0 -398
  255. package/src/panels/subscription-panel.ts +0 -342
  256. package/src/panels/symbol-outline-panel.ts +0 -619
  257. package/src/panels/system-messages-panel.ts +0 -364
  258. package/src/panels/tasks-panel.ts +0 -608
  259. package/src/panels/thinking-panel.ts +0 -333
  260. package/src/panels/tool-inspector-panel.ts +0 -537
  261. package/src/panels/work-plan-panel.ts +0 -540
  262. package/src/panels/worktree-panel.ts +0 -360
  263. package/src/panels/wrfc-panel.ts +0 -790
  264. package/src/renderer/agent-detail-modal.ts +0 -466
  265. package/src/renderer/live-tail-modal.ts +0 -156
  266. package/src/renderer/process-modal.ts +0 -671
  267. package/src/renderer/qr-renderer.ts +0 -120
@@ -1,466 +0,0 @@
1
- import { readFile } from 'fs/promises';
2
- import { type Line } from '../types/grid.ts';
3
- import { ModalFactory } from './modal-factory.ts';
4
- import type { AgentManager } from '@pellux/goodvibes-sdk/platform/tools';
5
- import type { AgentMessageBus } from '@pellux/goodvibes-sdk/platform/agents';
6
- import type { WrfcController } from '@pellux/goodvibes-sdk/platform/agents';
7
- import { formatElapsed } from '../utils/format-elapsed.ts';
8
- import { logger } from '@pellux/goodvibes-sdk/platform/utils';
9
- import { getOverlaySurfaceMetrics, getStableOverlayContentRows } from './overlay-viewport.ts';
10
- import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
11
- import { handleConfirmInput, type ConfirmState } from '../panels/confirm-state.ts';
12
- import { AGENT_TERMINAL_STATUSES as MODAL_TERMINAL_STATUSES, AGENT_STALL_THRESHOLD_MS as MODAL_STALL_THRESHOLD_MS, hasReportedUsage } from '../panels/agent-inspector-shared.ts';
13
- import { UI_TONES } from './ui-primitives.ts';
14
-
15
- /**
16
- * Agent-detail-modal neon accent palette — WRFC/streaming panel-native hues
17
- * with no UI_TONES.state/accent role. Preserved byte-exact as named
18
- * accents per WO-207b (2026-07-02-renderer-plan.md decisions: "Agent-detail
19
- * neon palette: preserved byte-exact as named accents, zero visual change").
20
- */
21
- const AGENT_DETAIL_NEON = {
22
- /** Addendum note + streaming content text. */
23
- mint: '#aaffee',
24
- /** Unsatisfied WRFC findings + agent error text. */
25
- bad: '#ff6666',
26
- /** Satisfied WRFC findings. */
27
- good: '#44ff88',
28
- /** Progress line + streaming label. */
29
- accent: '#00ffcc',
30
- } as const;
31
-
32
- // ─── Constants ────────────────────────────────────────────────────────────────
33
-
34
- const MAX_LOG_ENTRIES = 10;
35
- const AGENT_ID_DISPLAY_LENGTH = 16;
36
-
37
- // MODAL_TERMINAL_STATUSES and MODAL_STALL_THRESHOLD_MS are re-exported aliases
38
- // from agent-inspector-shared.ts (imported above alongside ConfirmState).
39
-
40
- /**
41
- * formatStalledLabel — the "[STALLED — N+ min no activity]" suffix appended
42
- * to the Status line for a stalled agent. The minute count is derived from
43
- * the stall threshold (in ms) rather than hardcoded, so the label can never
44
- * drift out of sync with the actual threshold that decided isStalled.
45
- */
46
- export function formatStalledLabel(thresholdMs: number): string {
47
- const minutes = Math.floor(thresholdMs / 60000);
48
- return ` [STALLED — ${minutes}+ min no activity]`;
49
- }
50
-
51
- export interface AgentDetailModalDeps {
52
- readonly agentManager: Pick<AgentManager, 'getStatus' | 'list'>;
53
- readonly agentMessageBus: Pick<AgentMessageBus, 'getMessages'>;
54
- readonly sessionLogPathResolver: (agentId: string) => string;
55
- /** Optional — when supplied, constraint data from the agent's WRFC chain is shown (SDK 0.23.0). */
56
- readonly wrfcController?: Pick<WrfcController, 'getChain'>;
57
- /** Cancel the agent by id using the same orphan-free path as WRFC. Returns true if cancelled. */
58
- readonly cancelAgent: (agentId: string) => boolean;
59
- }
60
-
61
- // ─── AgentDetailModal ─────────────────────────────────────────────────────────
62
-
63
- /**
64
- * AgentDetailModal — deep-view modal for a single running/completed agent.
65
- *
66
- * Displays task description, template, model, status, duration, tool-call
67
- * count, estimated token usage, recent messages from AgentMessageBus, and
68
- * the agent's current progress note.
69
- */
70
- export class AgentDetailModal {
71
- public active = false;
72
- public agentId: string | null = null;
73
-
74
- /** Cached JSONL log entries, loaded on open(). */
75
- public logEntries: Record<string, unknown>[] = [];
76
- public logTotal = 0;
77
-
78
- /** Pending cancel confirmation. Subject is the agent id to cancel. */
79
- public confirmCancel: ConfirmState<string> | null = null;
80
-
81
- private refreshTimer: ReturnType<typeof setInterval> | null = null;
82
- private onRefresh: (() => void) | null = null;
83
-
84
- constructor(readonly deps: AgentDetailModalDeps) {}
85
-
86
- /** Set a callback to trigger re-render when log data updates. */
87
- setOnRefresh(fn: () => void): void {
88
- this.onRefresh = fn;
89
- }
90
-
91
- open(agentId: string): void {
92
- this.agentId = agentId;
93
- this.active = true;
94
- this.logEntries = [];
95
- this.logTotal = 0;
96
- this.loadLog().catch((err) => { logger.debug('agent detail log load failed', { err }); });
97
- // Auto-refresh log every 500ms while modal is open
98
- if (this.refreshTimer) clearInterval(this.refreshTimer);
99
- this.refreshTimer = setInterval(() => {
100
- this.loadLog().then(() => this.onRefresh?.()).catch((err) => { logger.debug('agent detail log refresh tick failed', { err }); });
101
- }, 500);
102
- }
103
-
104
- close(): void {
105
- this.active = false;
106
- this.agentId = null;
107
- this.logEntries = [];
108
- this.logTotal = 0;
109
- this.confirmCancel = null;
110
- if (this.refreshTimer) {
111
- clearInterval(this.refreshTimer);
112
- this.refreshTimer = null;
113
- }
114
- }
115
-
116
- /**
117
- * Handle a key press while the modal is active.
118
- * Must be called BEFORE the Esc handler closes the modal.
119
- *
120
- * Routes:
121
- * - 'c' initiates cancel confirm (if agent is non-terminal)
122
- * - confirm keys (Enter/y/n/Esc) are forwarded to handleConfirmInput
123
- *
124
- * Returns true when the key was consumed (caller should NOT propagate).
125
- */
126
- handleKey(key: string): boolean {
127
- if (!this.active) return false;
128
-
129
- if (this.confirmCancel) {
130
- const result = handleConfirmInput(this.confirmCancel, key);
131
- if (result === 'confirmed') {
132
- if (this.agentId) {
133
- const rec = this.deps.agentManager.getStatus(this.agentId);
134
- if (rec && !MODAL_TERMINAL_STATUSES.has(rec.status)) {
135
- this.deps.cancelAgent(rec.id);
136
- }
137
- }
138
- this.confirmCancel = null;
139
- this.onRefresh?.();
140
- return true;
141
- }
142
- if (result === 'cancelled') {
143
- this.confirmCancel = null;
144
- this.onRefresh?.();
145
- return true;
146
- }
147
- // absorbed — key swallowed while confirm is pending
148
- return true;
149
- }
150
-
151
- if (key === 'c') {
152
- if (this.agentId) {
153
- const rec = this.deps.agentManager.getStatus(this.agentId);
154
- if (rec && !MODAL_TERMINAL_STATUSES.has(rec.status)) {
155
- const label = rec.task.split('\n')[0]?.slice(0, 40) ?? rec.id.slice(-8);
156
- this.confirmCancel = { subject: rec.id, label };
157
- this.onRefresh?.();
158
- return true;
159
- }
160
- }
161
- // Non-cancellable — absorb key silently
162
- return true;
163
- }
164
-
165
- return false;
166
- }
167
-
168
- /**
169
- * Returns whether the current agent is considered stalled.
170
- * Non-terminal agent with elapsed time exceeding MODAL_STALL_THRESHOLD_MS.
171
- */
172
- isCurrentAgentStalled(): boolean {
173
- if (!this.agentId) return false;
174
- const rec = this.deps.agentManager.getStatus(this.agentId);
175
- if (!rec || MODAL_TERMINAL_STATUSES.has(rec.status)) return false;
176
- return (Date.now() - rec.startedAt) >= MODAL_STALL_THRESHOLD_MS;
177
- }
178
-
179
- /**
180
- * Count of all stalled agents across the agentManager list.
181
- * Non-terminal agents with elapsed time >= MODAL_STALL_THRESHOLD_MS.
182
- */
183
- getStalledAgentCount(): number {
184
- const now = Date.now();
185
- return this.deps.agentManager.list().filter(rec => {
186
- if (MODAL_TERMINAL_STATUSES.has(rec.status)) return false;
187
- return (now - rec.startedAt) >= MODAL_STALL_THRESHOLD_MS;
188
- }).length;
189
- }
190
-
191
- async loadLog(): Promise<void> {
192
- if (!this.agentId) { this.logEntries = []; this.logTotal = 0; return; }
193
- try {
194
- const sessionFile = this.deps.sessionLogPathResolver(this.agentId);
195
- const logContent = await readFile(sessionFile, 'utf-8');
196
- const logLines = logContent.trim().split('\n');
197
- this.logTotal = logLines.length;
198
- const parsed = logLines.slice(-MAX_LOG_ENTRIES).map(line => {
199
- try { return JSON.parse(line) as Record<string, unknown>; } catch { return null; }
200
- });
201
- const failedCount = parsed.filter(e => e === null).length;
202
- if (failedCount > 0) {
203
- logger.debug('AgentDetailModal: skipped malformed JSONL lines', { count: failedCount });
204
- }
205
- this.logEntries = parsed.filter((e): e is Record<string, unknown> => e !== null);
206
- } catch (err) {
207
- const code = (err as NodeJS.ErrnoException).code;
208
- if (code !== 'ENOENT') {
209
- logger.debug('AgentDetailModal: failed to load session log', { error: summarizeError(err) });
210
- }
211
- this.logEntries = [];
212
- this.logTotal = 0;
213
- }
214
- }
215
- }
216
-
217
- // ─── renderAgentDetailModal ───────────────────────────────────────────────────
218
-
219
- /**
220
- * Render the agent detail modal as Line[] for overlay in the viewport.
221
- *
222
- * Shows a deep view of the selected agent: task, template, model, status,
223
- * duration, tool call count, token estimate, recent bus messages, and
224
- * progress text.
225
- *
226
- * @param modal AgentDetailModal state
227
- * @param width Terminal width
228
- */
229
- export function renderAgentDetailModal(
230
- modal: AgentDetailModal,
231
- width: number,
232
- viewportHeight = 24,
233
- ): Line[] {
234
- if (!modal.agentId) return [];
235
- const metrics = getOverlaySurfaceMetrics(width, viewportHeight, {
236
- margin: 2,
237
- maxWidth: width - 4,
238
- chromeRows: 6,
239
- minContentRows: 10,
240
- maxContentRows: 22,
241
- });
242
- const targetContentRows = Math.max(18, Math.min(22, getStableOverlayContentRows(metrics.contentRows, 12) + 8));
243
-
244
- const rec = modal.deps.agentManager.getStatus(modal.agentId);
245
- if (!rec) {
246
- return ModalFactory.createModal({
247
- title: 'Agent Detail',
248
- width: metrics.boxWidth,
249
- margin: metrics.margin,
250
- targetContentRows,
251
- sections: [
252
- { type: 'text', content: '(agent not found)' },
253
- ],
254
- hints: ['[Esc] Close'],
255
- }, width);
256
- }
257
-
258
- const now = Date.now();
259
- const elapsedMs = (rec.completedAt ?? now) - rec.startedAt;
260
-
261
- // ── Build sections ────────────────────────────────────────────────────────
262
-
263
- const sections: import('./modal-factory.ts').ModalSection[] = [];
264
-
265
- // Task — show first line only, capped at 120 chars
266
- const taskFirstLine = rec.task.split('\n')[0].replace(/^(WRFC\s+(Fix|Review)\s+Request\s*)/i, '').trim();
267
- const taskDisplay = taskFirstLine.length > 120 ? taskFirstLine.slice(0, 117) + '\u2026' : taskFirstLine;
268
- sections.push({
269
- type: 'text',
270
- content: `Task: ${taskDisplay}`,
271
- style: { bold: true },
272
- });
273
- sections.push({ type: 'separator' });
274
-
275
- // Metadata grid
276
- const modelStr = rec.model ? `${rec.provider ?? ''}/${rec.model}` : (rec.provider ?? '(default)');
277
- sections.push({ type: 'text', content: `Template : ${rec.template}` });
278
- sections.push({ type: 'text', content: `Model : ${modelStr}` });
279
- const isStalled = !MODAL_TERMINAL_STATUSES.has(rec.status) && (now - rec.startedAt) >= MODAL_STALL_THRESHOLD_MS;
280
- sections.push({ type: 'text', content: `Status : ${rec.status}${isStalled ? formatStalledLabel(MODAL_STALL_THRESHOLD_MS) : ''}` });
281
- sections.push({ type: 'text', content: `Duration : ${formatElapsed(elapsedMs)}` });
282
- sections.push({ type: 'separator' });
283
-
284
- // Metrics
285
- sections.push({ type: 'text', content: `Tool calls : ${rec.toolCallCount}` });
286
- if (hasReportedUsage(rec.usage)) {
287
- const usage = rec.usage;
288
- const totalIn = usage.inputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0);
289
- sections.push({ type: 'text', content: `Tokens in : ${totalIn.toLocaleString()}` });
290
- sections.push({ type: 'text', content: `Tokens out : ${usage.outputTokens.toLocaleString()}` });
291
- } else {
292
- sections.push({
293
- type: 'text',
294
- content: 'Tokens : n/a (agent running)',
295
- style: { dim: true },
296
- });
297
- }
298
-
299
- // SDK 0.23.0: systemPromptAddendum indicator — confirms WRFC constraint addendum was injected
300
- if (rec.systemPromptAddendum) {
301
- sections.push({
302
- type: 'text',
303
- content: 'Addendum : yes (WRFC constraint layer injected)',
304
- style: { fg: AGENT_DETAIL_NEON.mint },
305
- });
306
- }
307
-
308
- // SDK 0.23.0: constraint data from WRFC chain (engineer constraints + reviewer findings)
309
- if (rec.wrfcId && modal.deps.wrfcController) {
310
- try {
311
- const chain = modal.deps.wrfcController.getChain(rec.wrfcId);
312
- if (chain && chain.constraints.length > 0) {
313
- sections.push({ type: 'separator' });
314
- sections.push({
315
- type: 'text',
316
- content: `Constraints (${chain.constraints.length}):`,
317
- style: { dim: true },
318
- });
319
- for (const c of chain.constraints) {
320
- const text = c.text.length > 80 ? c.text.slice(0, 77) + '…' : c.text;
321
- sections.push({
322
- type: 'text',
323
- content: ` [${c.id}] ${text}`,
324
- style: { fg: '246' },
325
- });
326
- }
327
- // Reviewer constraint findings (if review has completed)
328
- const findings = chain.reviewerReport?.constraintFindings;
329
- if (findings && findings.length > 0) {
330
- const unsatisfied = findings.filter((f) => !f.satisfied);
331
- sections.push({
332
- type: 'text',
333
- content: `Findings : ${findings.length} checked, ${unsatisfied.length} unsatisfied`,
334
- style: { fg: unsatisfied.length > 0 ? AGENT_DETAIL_NEON.bad : AGENT_DETAIL_NEON.good },
335
- });
336
- }
337
- }
338
- } catch {
339
- // wrfcController.getChain throws when chain not found — normal during teardown
340
- }
341
- }
342
-
343
- // Progress
344
- if (rec.progress) {
345
- sections.push({ type: 'separator' });
346
- sections.push({
347
- type: 'text',
348
- content: `Progress: ${rec.progress}`,
349
- style: { fg: AGENT_DETAIL_NEON.accent },
350
- });
351
- }
352
-
353
- // Error
354
- if (rec.error) {
355
- sections.push({ type: 'separator' });
356
- sections.push({
357
- type: 'text',
358
- content: `Error: ${rec.error}`,
359
- style: { fg: AGENT_DETAIL_NEON.bad },
360
- });
361
- }
362
-
363
- // Recent messages from AgentMessageBus
364
- const recentMessages = modal.deps.agentMessageBus
365
- .getMessages(modal.agentId)
366
- .slice(-4); // last 4 messages
367
-
368
- if (recentMessages.length > 0) {
369
- sections.push({ type: 'separator' });
370
- sections.push({
371
- type: 'text',
372
- content: `Recent messages (${recentMessages.length}):`,
373
- style: { dim: true },
374
- });
375
- for (const msg of recentMessages) {
376
- const fromLabel = msg.from === '*' ? 'broadcast' : msg.from.slice(0, 12);
377
- const preview = msg.content.length > 50
378
- ? msg.content.slice(0, 47) + '\u2026'
379
- : msg.content;
380
- sections.push({
381
- type: 'text',
382
- content: ` [${fromLabel}] ${preview}`,
383
- style: { fg: '246' },
384
- });
385
- }
386
- }
387
-
388
- // Execution history from cached JSONL session log (loaded on open())
389
- if (modal.logEntries.length > 0) {
390
- sections.push({ type: 'separator' });
391
- sections.push({
392
- type: 'text',
393
- content: `Execution Log (${modal.logTotal} events, showing last ${modal.logEntries.length}):`,
394
- style: { dim: true },
395
- });
396
- for (const entry of modal.logEntries) {
397
- const entryType = String(entry.type ?? 'unknown');
398
- const rawTs = entry.timestamp;
399
- const ts = typeof rawTs === 'string' && rawTs.length >= 19
400
- ? rawTs.slice(11, 19)
401
- : '';
402
- let detail = '';
403
- if (entryType === 'tool_execution') detail = ` ${entry.toolName}`;
404
- if (entryType === 'llm_response') detail = ` (${entry.toolCallCount} tools, ${entry.contentLength} chars)`;
405
- if (entryType === 'session_end') detail = ` [${entry.status}]`;
406
- sections.push({
407
- type: 'text',
408
- content: ` ${ts} ${entryType}${detail}`,
409
- style: { fg: '246' },
410
- });
411
- }
412
- }
413
-
414
- // Streaming content — show live output when agent is actively streaming
415
- const STREAMING_MAX_CHARS = 500;
416
- if (rec.status === 'running' && rec.streamingContent) {
417
- const content = rec.streamingContent;
418
- const truncated = content.length > STREAMING_MAX_CHARS;
419
- const display = truncated ? content.slice(-STREAMING_MAX_CHARS) : content;
420
- sections.push({ type: 'separator' });
421
- sections.push({
422
- type: 'text',
423
- content: truncated
424
- ? `Streaming (last ${STREAMING_MAX_CHARS} of ${content.length} chars \u2191 scroll for more):`
425
- : 'Streaming:',
426
- style: { fg: AGENT_DETAIL_NEON.accent, dim: true },
427
- });
428
- // Split into display lines, capped at width for readability
429
- const maxLineWidth = Math.max(width - 10, 40);
430
- const streamLines = display.split('\n');
431
- for (const line of streamLines) {
432
- const trimmed = line.length > maxLineWidth ? line.slice(0, maxLineWidth - 1) + '\u2026' : line;
433
- sections.push({
434
- type: 'text',
435
- content: ` ${trimmed}`,
436
- style: { fg: AGENT_DETAIL_NEON.mint },
437
- });
438
- }
439
- }
440
-
441
- // Cancel confirm overlay (when pending)
442
- const cancellable = !MODAL_TERMINAL_STATUSES.has(rec.status);
443
- if (modal.confirmCancel) {
444
- sections.push({ type: 'separator' });
445
- sections.push({
446
- type: 'text',
447
- content: `Cancel agent "${modal.confirmCancel.label}"?`,
448
- style: { fg: UI_TONES.state.warn },
449
- });
450
- sections.push({
451
- type: 'text',
452
- content: 'y / Enter confirm n / Esc cancel',
453
- style: { dim: true },
454
- });
455
- }
456
-
457
- const cancelHint = cancellable ? '[c] Cancel ' : '';
458
- return ModalFactory.createModal({
459
- title: `Agent: ${rec.id.slice(0, AGENT_ID_DISPLAY_LENGTH)}`,
460
- width: metrics.boxWidth,
461
- margin: metrics.margin,
462
- targetContentRows,
463
- sections,
464
- hints: [cancelHint + '[Esc] Close'],
465
- }, width);
466
- }
@@ -1,156 +0,0 @@
1
- import { type Line } from '../types/grid.ts';
2
- import { ModalFactory } from './modal-factory.ts';
3
- import type { ProcessManager } from '@pellux/goodvibes-sdk/platform/tools';
4
- import type { AgentManager } from '@pellux/goodvibes-sdk/platform/tools';
5
- import type { ProcessEntry } from './process-modal.ts';
6
- import { getOverlaySurfaceMetrics, getStableOverlayContentRows } from './overlay-viewport.ts';
7
-
8
- export interface LiveTailModalDeps {
9
- readonly agentManager: Pick<AgentManager, 'cancel' | 'getStatus'>;
10
- readonly processManager: Pick<ProcessManager, 'stop' | 'getOutput'>;
11
- }
12
-
13
- // ─── LiveTailModal ────────────────────────────────────────────────────────────
14
-
15
- /**
16
- * LiveTailModal — manages state for the live output peek modal.
17
- *
18
- * Shows streaming stdout/stderr from a selected background process or agent
19
- * progress notes. Auto-scrolls to the bottom unless the user scrolled up.
20
- */
21
- export class LiveTailModal {
22
- public active = false;
23
- public entry: ProcessEntry | null = null;
24
-
25
- /** Number of lines scrolled up from the bottom (0 = at bottom). */
26
- public scrollOffset = 0;
27
-
28
- constructor(private readonly deps: LiveTailModalDeps) {}
29
-
30
- open(entry: ProcessEntry): void {
31
- this.entry = entry;
32
- this.scrollOffset = 0;
33
- this.active = true;
34
- }
35
-
36
- close(): void {
37
- this.active = false;
38
- this.entry = null;
39
- this.scrollOffset = 0;
40
- }
41
-
42
- scrollUp(): void {
43
- this.scrollOffset++;
44
- }
45
-
46
- scrollDown(): void {
47
- this.scrollOffset = Math.max(0, this.scrollOffset - 1);
48
- }
49
-
50
- /**
51
- * Kill the current process.
52
- * Returns true if the process was found and stopped.
53
- */
54
- killProcess(): boolean {
55
- if (!this.entry) return false;
56
-
57
- if (this.entry.type === 'exec') {
58
- return this.deps.processManager.stop(this.entry.id);
59
- } else {
60
- return this.deps.agentManager.cancel(this.entry.id);
61
- }
62
- }
63
-
64
- /** Retrieve the current output text for the watched process. */
65
- getOutput(): string {
66
- if (!this.entry) return '';
67
-
68
- if (this.entry.type === 'exec') {
69
- const out = this.deps.processManager.getOutput(this.entry.id);
70
- if (!out) return '';
71
- const combined = [out.stdout, out.stderr].filter(Boolean).join('\n').trim();
72
- return combined || '(no output yet)';
73
- } else {
74
- // For agents, show progress note and status
75
- const rec = this.deps.agentManager.getStatus(this.entry.id);
76
- if (!rec) return '(process not found)';
77
- const parts: string[] = [
78
- `Task: ${rec.task}`,
79
- `Status: ${rec.status}`,
80
- `Tool calls: ${rec.toolCallCount}`,
81
- ];
82
- if (rec.progress) parts.push(`Progress: ${rec.progress}`);
83
- if (rec.error) parts.push(`Error: ${rec.error}`);
84
- return parts.join('\n');
85
- }
86
- }
87
- }
88
-
89
- // ─── renderLiveTailModal ──────────────────────────────────────────────────────
90
-
91
- /**
92
- * Render the live-tail peek modal as Line[] for overlay in the viewport.
93
- *
94
- * Shows a scrollable view of the process output. scrollOffset=0 means the
95
- * bottom of the output is visible (auto-scroll behaviour).
96
- *
97
- * @param modal LiveTailModal state
98
- * @param width Terminal width
99
- * @param maxOutputLines Maximum lines to show inside the box (default: 16)
100
- */
101
- export function renderLiveTailModal(
102
- modal: LiveTailModal,
103
- width: number,
104
- viewportHeight = 24,
105
- ): Line[] {
106
- const entry = modal.entry;
107
- if (!entry) return [];
108
- const metrics = getOverlaySurfaceMetrics(width, viewportHeight, {
109
- chromeRows: 4,
110
- minContentRows: 6,
111
- maxContentRows: 10,
112
- });
113
- const maxOutputLines = metrics.contentRows;
114
- const targetContentRows = getStableOverlayContentRows(metrics.contentRows, 8);
115
-
116
- const output = modal.getOutput();
117
- const allLines = output.split('\n');
118
-
119
- // Apply scroll: scrollOffset=0 → show tail; larger → scroll toward head
120
- const totalLines = allLines.length;
121
- // Clamp scrollOffset so we never scroll past the top of the content
122
- const maxScroll = Math.max(0, totalLines - maxOutputLines);
123
- const clampedOffset = Math.min(modal.scrollOffset, maxScroll);
124
- const endIdx = Math.max(maxOutputLines, totalLines - clampedOffset);
125
- const startIdx = Math.max(0, endIdx - maxOutputLines);
126
- const visibleLines = allLines.slice(startIdx, endIdx);
127
-
128
- const typeTag = entry.type === 'agent' ? '[agent]' : '[exec]';
129
- const maxLabelW = Math.max(20, width - 30);
130
- const title = `${typeTag} ${entry.label.slice(0, maxLabelW)}`;
131
-
132
- // Build scroll indicator for text section header
133
- const scrollInfo = totalLines > maxOutputLines
134
- ? ` Lines ${startIdx + 1}-${Math.min(endIdx, totalLines)} of ${totalLines} [Up/Down] Scroll`
135
- : '';
136
-
137
- const contentText = visibleLines.join('\n') || '(no output yet)';
138
-
139
- const sections: import('./modal-factory.ts').ModalSection[] = [];
140
-
141
- if (scrollInfo) {
142
- sections.push({ type: 'text', content: scrollInfo });
143
- sections.push({ type: 'separator' });
144
- }
145
-
146
- sections.push({ type: 'text', content: contentText });
147
-
148
- return ModalFactory.createModal({
149
- title,
150
- width: metrics.boxWidth,
151
- margin: 2,
152
- targetContentRows,
153
- sections,
154
- hints: ['[Up/Down] Scroll', '[k] Kill', '[Esc] Back'],
155
- }, width);
156
- }