@pellux/goodvibes-tui 1.1.0 → 1.7.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 (194) hide show
  1. package/CHANGELOG.md +85 -21
  2. package/README.md +20 -11
  3. package/docs/foundation-artifacts/operator-contract.json +1 -1
  4. package/package.json +2 -2
  5. package/src/core/alert-gating.ts +67 -0
  6. package/src/core/approval-alert.ts +72 -0
  7. package/src/core/budget-breach-notifier.ts +106 -0
  8. package/src/core/conversation-rendering.ts +19 -0
  9. package/src/core/conversation.ts +18 -0
  10. package/src/core/focus-tracker.ts +41 -0
  11. package/src/core/long-task-notifier.ts +33 -6
  12. package/src/core/system-message-router.ts +37 -51
  13. package/src/core/turn-cancellation.ts +20 -0
  14. package/src/core/turn-event-wiring.ts +64 -3
  15. package/src/export/cost-utils.ts +36 -0
  16. package/src/input/command-registry.ts +38 -1
  17. package/src/input/commands/checkpoint-runtime.ts +280 -0
  18. package/src/input/commands/codebase-runtime.ts +192 -0
  19. package/src/input/commands/eval.ts +1 -1
  20. package/src/input/commands/health-runtime.ts +1 -1
  21. package/src/input/commands/image-runtime.ts +112 -0
  22. package/src/input/commands/intelligence-runtime.ts +11 -1
  23. package/src/input/commands/local-auth-runtime.ts +4 -1
  24. package/src/input/commands/local-runtime.ts +9 -3
  25. package/src/input/commands/marketplace-runtime.ts +2 -2
  26. package/src/input/commands/memory.ts +6 -1
  27. package/src/input/commands/operator-panel-runtime.ts +40 -24
  28. package/src/input/commands/planning-runtime.ts +26 -3
  29. package/src/input/commands/platform-sandbox-runtime.ts +1 -6
  30. package/src/input/commands/plugin-runtime.ts +2 -2
  31. package/src/input/commands/policy-dispatch.ts +21 -0
  32. package/src/input/commands/provider-accounts-runtime.ts +1 -1
  33. package/src/input/commands/qrcode-runtime.ts +3 -3
  34. package/src/input/commands/recall-review.ts +35 -0
  35. package/src/input/commands/remote-runtime.ts +3 -3
  36. package/src/input/commands/runtime-services.ts +54 -13
  37. package/src/input/commands/services-runtime.ts +1 -1
  38. package/src/input/commands/session-workflow.ts +16 -1
  39. package/src/input/commands/settings-sync-runtime.ts +1 -1
  40. package/src/input/commands/shell-core.ts +15 -7
  41. package/src/input/commands/skills-runtime.ts +2 -8
  42. package/src/input/commands/subscription-runtime.ts +2 -2
  43. package/src/input/commands/test-runtime.ts +277 -0
  44. package/src/input/commands/websearch-runtime.ts +101 -0
  45. package/src/input/commands/work-plan-runtime.ts +36 -10
  46. package/src/input/commands/workstream-runtime.ts +338 -0
  47. package/src/input/commands.ts +12 -0
  48. package/src/input/config-modal-types.ts +161 -0
  49. package/src/input/config-modal.ts +377 -0
  50. package/src/input/feed-context-factory.ts +7 -8
  51. package/src/input/handler-command-route.ts +27 -17
  52. package/src/input/handler-feed-routes.ts +61 -23
  53. package/src/input/handler-feed.ts +47 -23
  54. package/src/input/handler-interactions.ts +1 -3
  55. package/src/input/handler-modal-routes.ts +65 -0
  56. package/src/input/handler-modal-stack.ts +3 -5
  57. package/src/input/handler-modal-token-routes.ts +16 -49
  58. package/src/input/handler-picker-routes.ts +11 -94
  59. package/src/input/handler-shortcuts.ts +24 -14
  60. package/src/input/handler-types.ts +2 -6
  61. package/src/input/handler-ui-state.ts +10 -22
  62. package/src/input/handler.ts +6 -28
  63. package/src/input/keybindings.ts +22 -8
  64. package/src/input/model-picker-types.ts +24 -6
  65. package/src/input/model-picker.ts +58 -0
  66. package/src/input/panel-integration-actions.ts +9 -170
  67. package/src/input/settings-modal-data.ts +95 -0
  68. package/src/input/settings-modal-mutations.ts +6 -4
  69. package/src/main.ts +24 -27
  70. package/src/panels/agent-inspector-shared.ts +2 -1
  71. package/src/panels/base-panel.ts +22 -1
  72. package/src/panels/builtin/agent.ts +21 -107
  73. package/src/panels/builtin/development.ts +15 -61
  74. package/src/panels/builtin/knowledge.ts +8 -32
  75. package/src/panels/builtin/operations.ts +84 -428
  76. package/src/panels/builtin/session.ts +21 -112
  77. package/src/panels/builtin/shared.ts +9 -43
  78. package/src/panels/builtin-modals.ts +218 -0
  79. package/src/panels/builtin-panels.ts +5 -0
  80. package/src/panels/cost-tracker-panel.ts +36 -10
  81. package/src/panels/diff-panel.ts +12 -43
  82. package/src/panels/eval-registry.ts +60 -0
  83. package/src/panels/fleet-panel.ts +800 -0
  84. package/src/panels/fleet-read-model.ts +477 -0
  85. package/src/panels/fleet-steer.ts +92 -0
  86. package/src/panels/fleet-stop.ts +125 -0
  87. package/src/panels/fleet-tabs.ts +230 -0
  88. package/src/panels/fleet-transcript.ts +356 -0
  89. package/src/panels/index.ts +7 -31
  90. package/src/panels/modals/hooks-modal.ts +187 -0
  91. package/src/panels/modals/keybindings-modal.ts +151 -0
  92. package/src/panels/modals/knowledge-modal.ts +158 -0
  93. package/src/panels/modals/local-auth-modal.ts +132 -0
  94. package/src/panels/modals/marketplace-modal.ts +218 -0
  95. package/src/panels/modals/memory-modal.ts +180 -0
  96. package/src/panels/modals/modal-surface-helpers.ts +54 -0
  97. package/src/panels/modals/modal-theme.ts +36 -0
  98. package/src/panels/modals/pairing-modal.ts +144 -0
  99. package/src/panels/modals/planning-modal.ts +282 -0
  100. package/src/panels/modals/plugins-modal.ts +174 -0
  101. package/src/panels/modals/policy-modal.ts +256 -0
  102. package/src/panels/modals/provider-health-modal.ts +136 -0
  103. package/src/panels/modals/remote-modal.ts +115 -0
  104. package/src/panels/modals/sandbox-modal.ts +150 -0
  105. package/src/panels/modals/security-modal.ts +217 -0
  106. package/src/panels/modals/services-modal.ts +179 -0
  107. package/src/panels/modals/settings-sync-modal.ts +142 -0
  108. package/src/panels/modals/skills-modal.ts +189 -0
  109. package/src/panels/modals/subscription-modal.ts +172 -0
  110. package/src/panels/modals/work-plan-modal.ts +149 -0
  111. package/src/panels/panel-confirm-overlay.ts +72 -0
  112. package/src/panels/panel-manager.ts +108 -5
  113. package/src/panels/project-planning-answer-actions.ts +4 -2
  114. package/src/panels/skills-panel.ts +7 -51
  115. package/src/panels/types.ts +13 -0
  116. package/src/permissions/hunk-selection.ts +179 -0
  117. package/src/permissions/prompt.ts +60 -4
  118. package/src/renderer/compaction-history-modal.ts +19 -3
  119. package/src/renderer/compaction-preview.ts +13 -2
  120. package/src/renderer/compaction-quality.ts +100 -0
  121. package/src/renderer/config-modal.ts +81 -0
  122. package/src/renderer/conversation-overlays.ts +10 -17
  123. package/src/renderer/fleet-tab-strip.ts +56 -0
  124. package/src/renderer/footer-tips.ts +10 -2
  125. package/src/renderer/git-status.ts +40 -0
  126. package/src/renderer/help-overlay.ts +2 -2
  127. package/src/renderer/model-workspace.ts +44 -1
  128. package/src/renderer/panel-workspace-bar.ts +8 -2
  129. package/src/renderer/turn-injection.ts +114 -0
  130. package/src/runtime/bootstrap-command-context.ts +21 -1
  131. package/src/runtime/bootstrap-command-parts.ts +34 -7
  132. package/src/runtime/bootstrap-core.ts +14 -4
  133. package/src/runtime/bootstrap-shell.ts +29 -16
  134. package/src/runtime/bootstrap.ts +11 -17
  135. package/src/runtime/code-index-services.ts +112 -0
  136. package/src/runtime/orchestrator-core-services.ts +49 -0
  137. package/src/runtime/process-lifecycle.ts +3 -1
  138. package/src/runtime/services.ts +91 -43
  139. package/src/runtime/ui-services.ts +8 -0
  140. package/src/runtime/workstream-services.ts +195 -0
  141. package/src/shell/blocking-input.ts +22 -1
  142. package/src/shell/ui-openers.ts +129 -17
  143. package/src/utils/format-duration.ts +8 -18
  144. package/src/utils/splash-lines.ts +1 -1
  145. package/src/version.ts +1 -1
  146. package/src/panels/agent-inspector-panel.ts +0 -786
  147. package/src/panels/approval-panel.ts +0 -252
  148. package/src/panels/automation-control-panel.ts +0 -479
  149. package/src/panels/cockpit-panel.ts +0 -481
  150. package/src/panels/cockpit-read-model.ts +0 -238
  151. package/src/panels/communication-panel.ts +0 -256
  152. package/src/panels/control-plane-panel.ts +0 -470
  153. package/src/panels/debug-panel.ts +0 -615
  154. package/src/panels/docs-panel.ts +0 -384
  155. package/src/panels/eval-panel.ts +0 -627
  156. package/src/panels/file-explorer-panel.ts +0 -673
  157. package/src/panels/file-preview-panel.ts +0 -517
  158. package/src/panels/hooks-panel.ts +0 -401
  159. package/src/panels/incident-review-panel.ts +0 -406
  160. package/src/panels/intelligence-panel.ts +0 -383
  161. package/src/panels/knowledge-graph-panel.ts +0 -515
  162. package/src/panels/marketplace-panel.ts +0 -399
  163. package/src/panels/memory-panel.ts +0 -558
  164. package/src/panels/ops-control-panel.ts +0 -329
  165. package/src/panels/ops-strategy-panel.ts +0 -321
  166. package/src/panels/orchestration-panel.ts +0 -465
  167. package/src/panels/panel-list-panel.ts +0 -566
  168. package/src/panels/plan-dashboard-panel.ts +0 -707
  169. package/src/panels/policy-panel.ts +0 -517
  170. package/src/panels/project-planning-panel.ts +0 -731
  171. package/src/panels/provider-health-panel.ts +0 -678
  172. package/src/panels/provider-health-tracker.ts +0 -310
  173. package/src/panels/provider-health-views.ts +0 -567
  174. package/src/panels/qr-panel.ts +0 -280
  175. package/src/panels/remote-panel.ts +0 -534
  176. package/src/panels/routes-panel.ts +0 -241
  177. package/src/panels/sandbox-panel.ts +0 -456
  178. package/src/panels/security-panel.ts +0 -447
  179. package/src/panels/services-panel.ts +0 -329
  180. package/src/panels/session-browser-panel.ts +0 -496
  181. package/src/panels/settings-sync-panel.ts +0 -398
  182. package/src/panels/subscription-panel.ts +0 -342
  183. package/src/panels/symbol-outline-panel.ts +0 -619
  184. package/src/panels/system-messages-panel.ts +0 -364
  185. package/src/panels/tasks-panel.ts +0 -608
  186. package/src/panels/thinking-panel.ts +0 -333
  187. package/src/panels/tool-inspector-panel.ts +0 -537
  188. package/src/panels/work-plan-panel.ts +0 -540
  189. package/src/panels/worktree-panel.ts +0 -360
  190. package/src/panels/wrfc-panel.ts +0 -790
  191. package/src/renderer/agent-detail-modal.ts +0 -466
  192. package/src/renderer/live-tail-modal.ts +0 -156
  193. package/src/renderer/process-modal.ts +0 -671
  194. package/src/renderer/qr-renderer.ts +0 -120
@@ -1,333 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // ThinkingPanel — streams model reasoning tokens in real-time.
3
- // ---------------------------------------------------------------------------
4
-
5
- import type { Line } from '../types/grid.ts';
6
- import { BasePanel } from './base-panel.ts';
7
- import type { TurnEvent } from '@/runtime/index.ts';
8
- import type { UiEventFeed } from '../runtime/ui-events.ts';
9
- import {
10
- buildEmptyState,
11
- buildPanelLine,
12
- buildStyledPanelLine,
13
- buildPanelWorkspace,
14
- resolveScrollablePanelSection,
15
- extendPalette,
16
- DEFAULT_PANEL_PALETTE,
17
- type PanelWorkspaceSection,
18
- } from './polish.ts';
19
- import { truncateDisplay, wrapText } from '../utils/terminal-width.ts';
20
-
21
- const C = extendPalette(DEFAULT_PANEL_PALETTE, {
22
- // Domain-specific reasoning tones (purples) and the active-stream cyan have no
23
- // clean shared equivalent; selection background maps to the shared selectBg.
24
- reasoningFg: '#aa88ff',
25
- activeFg: '#cc99ff',
26
- turnLabel: '#7766bb',
27
- activeLabel: '#00ffff',
28
- });
29
-
30
- interface ReasoningBlock {
31
- // Real turn id from the TURN_*/STREAM_* event envelope — joins this block
32
- // to the same turn's rows in TasksPanel and the tool inspector, which key
33
- // off the identical turnId string.
34
- turnId: string;
35
- // Distributed-tracing correlation id (EventEnvelope.traceId), when the
36
- // originating event carried one — same field RuntimeTask.correlationId
37
- // joins against.
38
- correlationId?: string;
39
- content: string;
40
- active: boolean; // true = currently streaming
41
- collapsed: boolean;
42
- startedAt: number;
43
- updatedAt: number;
44
- }
45
-
46
- function fmtClock(ts: number): string {
47
- const d = new Date(ts);
48
- const h = String(d.getHours()).padStart(2, '0');
49
- const m = String(d.getMinutes()).padStart(2, '0');
50
- const s = String(d.getSeconds()).padStart(2, '0');
51
- return `${h}:${m}:${s}`;
52
- }
53
-
54
- type FlatRow = { kind: 'header'; blockIndex: number; text: string } | { kind: 'content'; text: string };
55
-
56
- export class ThinkingPanel extends BasePanel {
57
- private blocks: ReasoningBlock[] = [];
58
- private unsubs: Array<() => void> = [];
59
- private cursorIndex = 0;
60
- private scrollOffset = 0;
61
- private autoScroll = true;
62
- private lastWidth = 80;
63
- private static readonly MAX_BLOCKS = 100;
64
-
65
- constructor(private readonly turnEvents: UiEventFeed<TurnEvent>) {
66
- super('thinking', 'Thinking', 'T', 'ai');
67
- this._attachBus();
68
- }
69
-
70
- override onActivate(): void {
71
- this.needsRender = true;
72
- }
73
-
74
- override onDeactivate(): void {
75
- super.onDeactivate();
76
- }
77
-
78
- override onDestroy(): void {
79
- this._detachBus();
80
- }
81
-
82
- handleInput(key: string): boolean {
83
- switch (key) {
84
- case 'up': this._move(-1); this.autoScroll = false; return true;
85
- case 'down': this._move(1); return true;
86
- case 'pageup': this._move(-10); this.autoScroll = false; return true;
87
- case 'pagedown': this._move(10); return true;
88
- case 'return': this._toggleCollapse(); return true;
89
- case 'g': this.autoScroll = true; this.markDirty(); return true;
90
- default: return false;
91
- }
92
- }
93
-
94
- render(width: number, height: number): Line[] {
95
- if (height <= 0 || width <= 0) return [];
96
- this.lastWidth = width;
97
-
98
- const hasActive = this.blocks.some(b => b.active);
99
- const flat = this._buildFlat(width);
100
- const title = hasActive ? ' Thinking [streaming]' : ` Thinking [${this.blocks.length} blocks]`;
101
- const footerLines = [
102
- buildPanelLine(width, [
103
- [' Up/Down', DEFAULT_PANEL_PALETTE.info],
104
- [' scroll', DEFAULT_PANEL_PALETTE.dim],
105
- [' Enter', DEFAULT_PANEL_PALETTE.info],
106
- [' collapse', DEFAULT_PANEL_PALETTE.dim],
107
- [' g', DEFAULT_PANEL_PALETTE.info],
108
- [' jump to end', DEFAULT_PANEL_PALETTE.dim],
109
- [this.autoScroll ? ' auto-scroll ON' : ' manual', this.autoScroll ? DEFAULT_PANEL_PALETTE.good : DEFAULT_PANEL_PALETTE.warn],
110
- ]),
111
- ];
112
-
113
- if (flat.length === 0) {
114
- return buildPanelWorkspace(width, height, {
115
- title,
116
- intro: 'Live reasoning blocks stream here while the model is actively thinking.',
117
- sections: [
118
- {
119
- title: 'Reasoning',
120
- lines: buildEmptyState(
121
- width,
122
- ' No reasoning content yet',
123
- 'When the model emits thinking or reasoning deltas during a turn, they accumulate here in expandable per-turn blocks.',
124
- [{ command: '/chat <prompt>', summary: 'start a turn with a reasoning-capable model to stream thinking here' }],
125
- DEFAULT_PANEL_PALETTE,
126
- ),
127
- },
128
- ],
129
- footerLines,
130
- palette: DEFAULT_PANEL_PALETTE,
131
- });
132
- }
133
-
134
- if (this.autoScroll) {
135
- this.cursorIndex = Math.max(0, flat.length - 1);
136
- }
137
-
138
- this.cursorIndex = Math.max(0, Math.min(this.cursorIndex, Math.max(0, flat.length - 1)));
139
-
140
- const summary: PanelWorkspaceSection = {
141
- title: 'Summary',
142
- lines: [
143
- buildPanelLine(width, [
144
- [' Blocks ', DEFAULT_PANEL_PALETTE.label],
145
- [String(this.blocks.length), DEFAULT_PANEL_PALETTE.value],
146
- [' Active ', DEFAULT_PANEL_PALETTE.label],
147
- [String(this.blocks.filter((block) => block.active).length), hasActive ? DEFAULT_PANEL_PALETTE.warn : DEFAULT_PANEL_PALETTE.dim],
148
- [' Mode ', DEFAULT_PANEL_PALETTE.label],
149
- [this.autoScroll ? 'auto-scroll' : 'manual', this.autoScroll ? DEFAULT_PANEL_PALETTE.good : DEFAULT_PANEL_PALETTE.info],
150
- ]),
151
- ],
152
- };
153
-
154
- // Resolve the reasoning block that owns the cursor row (walk back to its
155
- // header) so the detail block describes the actual turn, not just row kind.
156
- let selectedBlockIndex = -1;
157
- for (let i = Math.min(this.cursorIndex, flat.length - 1); i >= 0; i--) {
158
- const row = flat[i];
159
- if (row?.kind === 'header') { selectedBlockIndex = row.blockIndex; break; }
160
- }
161
- const selectedBlock = selectedBlockIndex >= 0 ? this.blocks[selectedBlockIndex] : undefined;
162
- const selectedSection: PanelWorkspaceSection = {
163
- title: 'Selected',
164
- lines: selectedBlock
165
- ? [
166
- buildPanelLine(width, [
167
- [' Turn ', DEFAULT_PANEL_PALETTE.label],
168
- [selectedBlock.turnId, DEFAULT_PANEL_PALETTE.value],
169
- [' State ', DEFAULT_PANEL_PALETTE.label],
170
- [selectedBlock.active ? 'streaming' : 'complete', selectedBlock.active ? DEFAULT_PANEL_PALETTE.warn : DEFAULT_PANEL_PALETTE.good],
171
- [' View ', DEFAULT_PANEL_PALETTE.label],
172
- [selectedBlock.collapsed ? 'collapsed' : 'expanded', DEFAULT_PANEL_PALETTE.info],
173
- [' Chars ', DEFAULT_PANEL_PALETTE.label],
174
- [String(selectedBlock.content.length), DEFAULT_PANEL_PALETTE.dim],
175
- ]),
176
- buildPanelLine(width, [
177
- [' Correlation ', DEFAULT_PANEL_PALETTE.label],
178
- [selectedBlock.correlationId ?? 'n/a', DEFAULT_PANEL_PALETTE.dim],
179
- [' Started ', DEFAULT_PANEL_PALETTE.label],
180
- [fmtClock(selectedBlock.startedAt), DEFAULT_PANEL_PALETTE.dim],
181
- [' Updated ', DEFAULT_PANEL_PALETTE.label],
182
- [fmtClock(selectedBlock.updatedAt), DEFAULT_PANEL_PALETTE.dim],
183
- ]),
184
- ]
185
- : [buildPanelLine(width, [[' No block selected', DEFAULT_PANEL_PALETTE.dim]])],
186
- };
187
-
188
- const reasoningSection = resolveScrollablePanelSection(width, height, {
189
- intro: 'Live reasoning blocks stream here while the model is actively thinking.',
190
- footerLines,
191
- palette: DEFAULT_PANEL_PALETTE,
192
- beforeSections: [summary],
193
- section: {
194
- title: 'Reasoning',
195
- scrollableLines: flat.map((row, index) => this._renderRow(width, row, index === this.cursorIndex)),
196
- selectedIndex: this.cursorIndex,
197
- scrollOffset: this.scrollOffset,
198
- minRows: 8,
199
- },
200
- afterSections: [selectedSection],
201
- });
202
- this.scrollOffset = reasoningSection.scrollOffset;
203
-
204
- return buildPanelWorkspace(width, height, {
205
- title,
206
- intro: 'Live reasoning blocks stream here while the model is actively thinking.',
207
- sections: [
208
- summary,
209
- reasoningSection.section,
210
- selectedSection,
211
- ],
212
- footerLines,
213
- palette: DEFAULT_PANEL_PALETTE,
214
- });
215
- }
216
-
217
- private _renderRow(width: number, row: FlatRow, isCursor: boolean): Line {
218
- const bg = isCursor ? C.selectBg : '';
219
- if (row.kind === 'header') {
220
- const indicator = this.blocks[row.blockIndex]?.collapsed ? '▸' : '▾';
221
- const active = this.blocks[row.blockIndex]?.active;
222
- const bullet = active ? '\u25cf ' : '\u25e6 ';
223
- return buildStyledPanelLine(width, [
224
- { text: truncateDisplay(` ${bullet}${row.text} ${indicator}`, width), fg: active ? C.activeLabel : C.turnLabel, bg, bold: true },
225
- ]);
226
- }
227
- return buildStyledPanelLine(width, [
228
- { text: ' ', fg: C.reasoningFg, bg },
229
- { text: truncateDisplay(row.text, Math.max(0, width - 2)), fg: isCursor ? C.activeFg : C.reasoningFg, bg },
230
- ]);
231
- }
232
-
233
- private _buildFlat(width: number): FlatRow[] {
234
- const rows: FlatRow[] = [];
235
- for (let i = 0; i < this.blocks.length; i++) {
236
- const block = this.blocks[i]!;
237
- const turnLabel = `Turn ${block.turnId} ${fmtClock(block.startedAt)}${block.active ? ' (streaming)' : ''}`;
238
- rows.push({ kind: 'header', blockIndex: i, text: turnLabel });
239
- if (!block.collapsed) {
240
- const wrapped = wrapText(block.content || '(empty)', Math.max(1, width - 2));
241
- for (const line of wrapped) {
242
- rows.push({ kind: 'content', text: line });
243
- }
244
- }
245
- }
246
- return rows;
247
- }
248
-
249
- private _toggleCollapse(): void {
250
- // Find the header row at or before cursorIndex
251
- const flat = this._buildFlat(this.lastWidth);
252
- for (let i = this.cursorIndex; i >= 0; i--) {
253
- const row = flat[i];
254
- if (row?.kind === 'header') {
255
- const block = this.blocks[row.blockIndex];
256
- if (block) {
257
- block.collapsed = !block.collapsed;
258
- this.markDirty();
259
- return;
260
- }
261
- }
262
- }
263
- }
264
-
265
- private _move(delta: number): void {
266
- const flat = this._buildFlat(this.lastWidth);
267
- if (flat.length === 0) return;
268
- this.cursorIndex = Math.max(0, Math.min(flat.length - 1, this.cursorIndex + delta));
269
- this.markDirty();
270
- }
271
-
272
- private _attachBus(): void {
273
- if (this.unsubs.length > 0) return;
274
-
275
- let currentBlock: ReasoningBlock | null = null;
276
-
277
- // onEnvelope (not on) so real turnId/traceId/timestamp — the same fields
278
- // TasksPanel and the tool inspector join their own rows on — stamp each
279
- // block instead of a locally-incrementing counter.
280
- this.unsubs.push(this.turnEvents.onEnvelope('STREAM_START', (envelope) => {
281
- const block: ReasoningBlock = {
282
- turnId: envelope.turnId ?? envelope.payload.turnId,
283
- correlationId: envelope.traceId,
284
- content: '',
285
- active: true,
286
- collapsed: false,
287
- startedAt: envelope.ts,
288
- updatedAt: envelope.ts,
289
- };
290
- // Cap block count to prevent unbounded growth
291
- if (this.blocks.length >= ThinkingPanel.MAX_BLOCKS) {
292
- this.blocks.shift();
293
- }
294
- this.blocks.push(block);
295
- currentBlock = block;
296
- this.autoScroll = true;
297
- this.markDirty();
298
- }));
299
-
300
- this.unsubs.push(this.turnEvents.onEnvelope('STREAM_DELTA', (envelope) => {
301
- const reasoning = envelope.payload.reasoning;
302
- if (reasoning && currentBlock) {
303
- currentBlock.content += reasoning;
304
- currentBlock.updatedAt = envelope.ts;
305
- this.autoScroll = true;
306
- this.markDirty();
307
- }
308
- }));
309
-
310
- this.unsubs.push(this.turnEvents.onEnvelope('STREAM_END', (envelope) => {
311
- if (currentBlock) {
312
- currentBlock.active = false;
313
- currentBlock.updatedAt = envelope.ts;
314
- currentBlock = null;
315
- this.markDirty();
316
- }
317
- }));
318
-
319
- this.unsubs.push(this.turnEvents.onEnvelope('TURN_COMPLETED', (envelope) => {
320
- if (currentBlock) {
321
- currentBlock.active = false;
322
- currentBlock.updatedAt = envelope.ts;
323
- currentBlock = null;
324
- this.markDirty();
325
- }
326
- }));
327
- }
328
-
329
- private _detachBus(): void {
330
- for (const unsub of this.unsubs) unsub();
331
- this.unsubs = [];
332
- }
333
- }