@pellux/goodvibes-agent 1.4.3 → 1.5.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 (227) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +7 -7
  3. package/dist/package/main.js +7450 -12285
  4. package/docs/README.md +2 -2
  5. package/docs/channels-remote-and-api.md +1 -1
  6. package/docs/getting-started.md +2 -2
  7. package/docs/release-and-publishing.md +1 -1
  8. package/docs/tools-and-commands.md +5 -5
  9. package/package.json +4 -2
  10. package/release/performance-snapshot.json +2 -2
  11. package/release/release-notes.md +11 -7
  12. package/release/release-readiness.json +6 -6
  13. package/src/agent/behavior-discovery-summary.ts +4 -18
  14. package/src/agent/calendar-registry.ts +322 -0
  15. package/src/agent/competitive-feature-inventory.ts +268 -130
  16. package/src/agent/document-registry.ts +5 -1
  17. package/src/agent/email/email-service.ts +350 -0
  18. package/src/agent/email/imap-client.ts +596 -0
  19. package/src/agent/email/smtp-client.ts +453 -0
  20. package/src/agent/ics-calendar.ts +662 -0
  21. package/src/agent/ics-timezone.ts +172 -0
  22. package/src/agent/markdown-frontmatter.ts +31 -0
  23. package/src/agent/memory-safety.ts +1 -1
  24. package/src/agent/note-registry.ts +3 -9
  25. package/src/agent/persona-discovery.ts +3 -15
  26. package/src/agent/persona-registry.ts +1 -10
  27. package/src/agent/research-source-registry.ts +5 -1
  28. package/src/agent/routine-discovery.ts +3 -15
  29. package/src/agent/runtime-profile.ts +3 -0
  30. package/src/agent/skill-discovery.ts +3 -15
  31. package/src/agent/skill-draft-proposer.ts +203 -0
  32. package/src/agent/skill-draft-runner.ts +201 -0
  33. package/src/agent/skill-registry.ts +36 -1
  34. package/src/agent/skill-standard.ts +99 -0
  35. package/src/agent/vibe-file.ts +7 -22
  36. package/src/cli/completion.ts +1 -1
  37. package/src/cli/config-overrides.ts +10 -1
  38. package/src/cli/redaction.ts +17 -5
  39. package/src/config/provider-model.ts +2 -1
  40. package/src/config/secret-config.ts +13 -6
  41. package/src/core/activity-feed.ts +97 -0
  42. package/src/core/away-digest.ts +161 -0
  43. package/src/core/conversation-rendering.ts +7 -3
  44. package/src/core/conversation.ts +22 -15
  45. package/src/core/hardware-profile.ts +362 -0
  46. package/src/core/last-seen-store.ts +78 -0
  47. package/src/core/plain-language.ts +52 -0
  48. package/src/core/setup-incomplete-hint.ts +90 -0
  49. package/src/core/system-message-router.ts +38 -87
  50. package/src/input/agent-workspace-basic-command-editor-submission.ts +60 -220
  51. package/src/input/agent-workspace-basic-command-editors.ts +39 -141
  52. package/src/input/agent-workspace-categories.ts +63 -158
  53. package/src/input/agent-workspace-command-editor.ts +4 -0
  54. package/src/input/agent-workspace-host-category.ts +0 -5
  55. package/src/input/agent-workspace-local-editor-submission.ts +3 -1
  56. package/src/input/agent-workspace-navigation.ts +10 -3
  57. package/src/input/agent-workspace-onboarding-actions.ts +132 -0
  58. package/src/input/agent-workspace-onboarding-categories.ts +29 -26
  59. package/src/input/agent-workspace-onboarding-finish.ts +11 -4
  60. package/src/input/agent-workspace-onboarding-state.ts +111 -0
  61. package/src/input/agent-workspace-profile-editor-submission.ts +260 -0
  62. package/src/input/agent-workspace-profile-editors.ts +155 -0
  63. package/src/input/agent-workspace-subscription-editor.ts +7 -0
  64. package/src/input/agent-workspace-types.ts +5 -1
  65. package/src/input/agent-workspace.ts +65 -39
  66. package/src/input/command-registry.ts +18 -5
  67. package/src/input/commands/agent-runtime-profile-runtime.ts +2 -1
  68. package/src/input/commands/agent-skills-runtime.ts +60 -3
  69. package/src/input/commands/calendar-runtime.ts +209 -0
  70. package/src/input/commands/channels-runtime.ts +1 -0
  71. package/src/input/commands/command-error.ts +9 -0
  72. package/src/input/commands/compat-runtime.ts +1 -0
  73. package/src/input/commands/config.ts +1 -0
  74. package/src/input/commands/conversation-runtime.ts +1 -1
  75. package/src/input/commands/delegation-runtime.ts +5 -6
  76. package/src/input/commands/email-runtime.ts +387 -0
  77. package/src/input/commands/experience-runtime.ts +17 -4
  78. package/src/input/commands/guidance-runtime.ts +1 -1
  79. package/src/input/commands/health-runtime.ts +6 -1
  80. package/src/input/commands/knowledge-format.ts +73 -0
  81. package/src/input/commands/knowledge.ts +9 -74
  82. package/src/input/commands/local-provider-runtime.ts +2 -1
  83. package/src/input/commands/local-runtime.ts +10 -4
  84. package/src/input/commands/mcp-runtime.ts +7 -0
  85. package/src/input/commands/notify-runtime.ts +17 -1
  86. package/src/input/commands/onboarding-runtime.ts +1 -0
  87. package/src/input/commands/operator-actions-runtime.ts +1 -0
  88. package/src/input/commands/operator-runtime.ts +3 -0
  89. package/src/input/commands/personas-runtime.ts +1 -0
  90. package/src/input/commands/platform-access-runtime.ts +40 -17
  91. package/src/input/commands/product-runtime.ts +6 -1
  92. package/src/input/commands/provider-accounts-runtime.ts +3 -2
  93. package/src/input/commands/qrcode-runtime.ts +1 -0
  94. package/src/input/commands/routines-runtime.ts +1 -0
  95. package/src/input/commands/runtime-services.ts +0 -13
  96. package/src/input/commands/security-runtime.ts +1 -0
  97. package/src/input/commands/session-content.ts +1 -0
  98. package/src/input/commands/shell-core.ts +5 -2
  99. package/src/input/commands/subscription-runtime.ts +33 -9
  100. package/src/input/commands/support-bundle-runtime.ts +8 -1
  101. package/src/input/commands/tasks-runtime.ts +1 -0
  102. package/src/input/commands/tts-runtime.ts +1 -0
  103. package/src/input/commands/vibe-runtime.ts +1 -0
  104. package/src/input/commands.ts +4 -0
  105. package/src/input/feed-context-factory.ts +3 -12
  106. package/src/input/handler-command-route.ts +7 -60
  107. package/src/input/handler-feed-routes.ts +0 -194
  108. package/src/input/handler-feed.ts +2 -54
  109. package/src/input/handler-interactions.ts +0 -2
  110. package/src/input/handler-modal-stack.ts +0 -9
  111. package/src/input/handler-picker-routes.ts +24 -1
  112. package/src/input/handler-shortcuts.ts +11 -40
  113. package/src/input/handler-ui-state.ts +13 -0
  114. package/src/input/handler.ts +8 -35
  115. package/src/input/keybindings.ts +40 -17
  116. package/src/input/model-picker-local-fit.ts +130 -0
  117. package/src/input/submission-router.ts +0 -5
  118. package/src/main.ts +111 -89
  119. package/src/renderer/activity-sidebar.ts +186 -0
  120. package/src/renderer/agent-workspace-context-lines.ts +5 -48
  121. package/src/renderer/agent-workspace-style.ts +1 -1
  122. package/src/renderer/agent-workspace.ts +14 -2
  123. package/src/renderer/compositor.ts +37 -125
  124. package/src/renderer/conversation-overlays.ts +3 -3
  125. package/src/renderer/help-overlay.ts +7 -5
  126. package/src/renderer/model-workspace.ts +101 -86
  127. package/src/{panels → renderer}/polish.ts +3 -3
  128. package/src/renderer/shell-surface.ts +4 -5
  129. package/src/renderer/status-token.ts +31 -11
  130. package/src/renderer/tab-strip.ts +1 -1
  131. package/src/renderer/tool-call.ts +7 -8
  132. package/src/renderer/tool-labels.ts +92 -0
  133. package/src/renderer/ui-factory.ts +48 -96
  134. package/src/runtime/bootstrap-command-context.ts +0 -5
  135. package/src/runtime/bootstrap-command-parts.ts +6 -15
  136. package/src/runtime/bootstrap-core.ts +34 -13
  137. package/src/runtime/bootstrap-hook-bridge.ts +3 -1
  138. package/src/runtime/bootstrap-shell.ts +3 -50
  139. package/src/runtime/bootstrap.ts +3 -4
  140. package/src/runtime/context.ts +1 -1
  141. package/src/runtime/diagnostics/panels/index.ts +0 -1
  142. package/src/runtime/diagnostics/panels/policy.ts +1 -1
  143. package/src/runtime/execution-ledger.ts +16 -1
  144. package/src/runtime/index.ts +6 -1
  145. package/src/runtime/onboarding/index.ts +1 -0
  146. package/src/runtime/onboarding/onboarding-state.ts +200 -0
  147. package/src/{panels → runtime}/provider-account-snapshot.ts +5 -2
  148. package/src/runtime/services.ts +16 -4
  149. package/src/runtime/terminal-output-guard.ts +7 -0
  150. package/src/runtime/tool-permission-safety.ts +1 -1
  151. package/src/runtime/ui/model-picker/data-provider.ts +1 -1
  152. package/src/runtime/ui/model-picker/health-enrichment.ts +2 -2
  153. package/src/runtime/ui/model-picker/types.ts +2 -2
  154. package/src/runtime/ui/provider-health/data-provider.ts +3 -3
  155. package/src/runtime/ui/provider-health/types.ts +2 -2
  156. package/src/runtime/ui-services.ts +0 -2
  157. package/src/shell/agent-workspace-fullscreen.ts +0 -1
  158. package/src/shell/autonomy-surfacing.ts +272 -0
  159. package/src/shell/session-continuity-hints.ts +0 -6
  160. package/src/shell/startup-wiring.ts +221 -0
  161. package/src/shell/ui-openers.ts +18 -29
  162. package/src/tools/agent-context-policy.ts +1 -1
  163. package/src/tools/agent-harness-background-processes.ts +4 -4
  164. package/src/tools/agent-harness-browser-cockpit-route.ts +3 -3
  165. package/src/tools/agent-harness-command-runner.ts +6 -1
  166. package/src/tools/agent-harness-document-ops.ts +26 -53
  167. package/src/tools/agent-harness-execution-history.ts +1 -13
  168. package/src/tools/agent-harness-execution-posture.ts +0 -15
  169. package/src/tools/agent-harness-keybinding-metadata.ts +28 -29
  170. package/src/tools/agent-harness-local-model-cookbook.ts +24 -1
  171. package/src/tools/agent-harness-metadata.ts +16 -0
  172. package/src/tools/agent-harness-mode-catalog.ts +2 -9
  173. package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
  174. package/src/tools/agent-harness-setup-posture-utils.ts +1 -1
  175. package/src/tools/agent-harness-tool-schema.ts +13 -14
  176. package/src/tools/agent-harness-tool.ts +27 -23
  177. package/src/tools/agent-harness-ui-surface-metadata.ts +10 -20
  178. package/src/tools/agent-harness-workspace-action-runner.ts +3 -4
  179. package/src/tools/agent-workspace-tool.ts +2 -33
  180. package/src/utils/terminal-width.ts +9 -3
  181. package/src/version.ts +1 -1
  182. package/src/input/agent-workspace-panel-route.ts +0 -44
  183. package/src/input/panel-integration-actions.ts +0 -26
  184. package/src/panels/approval-panel.ts +0 -149
  185. package/src/panels/automation-control-panel.ts +0 -212
  186. package/src/panels/base-panel.ts +0 -254
  187. package/src/panels/builtin/agent.ts +0 -58
  188. package/src/panels/builtin/knowledge.ts +0 -26
  189. package/src/panels/builtin/operations.ts +0 -121
  190. package/src/panels/builtin/session.ts +0 -138
  191. package/src/panels/builtin/shared.ts +0 -275
  192. package/src/panels/builtin/usage.ts +0 -21
  193. package/src/panels/builtin-panels.ts +0 -23
  194. package/src/panels/confirm-state.ts +0 -61
  195. package/src/panels/context-visualizer-panel.ts +0 -204
  196. package/src/panels/cost-tracker-panel.ts +0 -444
  197. package/src/panels/docs-panel.ts +0 -285
  198. package/src/panels/index.ts +0 -25
  199. package/src/panels/knowledge-panel.ts +0 -417
  200. package/src/panels/memory-panel.ts +0 -226
  201. package/src/panels/panel-list-panel.ts +0 -464
  202. package/src/panels/panel-manager.ts +0 -570
  203. package/src/panels/provider-accounts-panel.ts +0 -233
  204. package/src/panels/provider-health-domains.ts +0 -208
  205. package/src/panels/provider-health-panel.ts +0 -720
  206. package/src/panels/provider-health-tracker.ts +0 -115
  207. package/src/panels/provider-stats-panel.ts +0 -366
  208. package/src/panels/qr-panel.ts +0 -207
  209. package/src/panels/schedule-panel.ts +0 -321
  210. package/src/panels/scrollable-list-panel.ts +0 -491
  211. package/src/panels/search-focus.ts +0 -32
  212. package/src/panels/security-panel.ts +0 -295
  213. package/src/panels/session-browser-panel.ts +0 -395
  214. package/src/panels/session-maintenance.ts +0 -125
  215. package/src/panels/subscription-panel.ts +0 -263
  216. package/src/panels/system-messages-panel.ts +0 -230
  217. package/src/panels/tasks-panel.ts +0 -344
  218. package/src/panels/thinking-panel.ts +0 -304
  219. package/src/panels/token-budget-panel.ts +0 -475
  220. package/src/panels/tool-inspector-panel.ts +0 -436
  221. package/src/panels/types.ts +0 -54
  222. package/src/renderer/panel-composite.ts +0 -158
  223. package/src/renderer/panel-tab-bar.ts +0 -69
  224. package/src/renderer/panel-workspace-bar.ts +0 -42
  225. package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
  226. package/src/tools/agent-harness-panel-metadata.ts +0 -211
  227. /package/src/runtime/perf/{panel-health-monitor.ts → component-health.ts} +0 -0
@@ -1,344 +0,0 @@
1
- import type { Line } from '../types/grid.ts';
2
- import { createEmptyLine } from '../types/grid.ts';
3
- import { ScrollableListPanel } from './scrollable-list-panel.ts';
4
- import type { RuntimeTask, TaskLifecycleState } from '@/runtime/index.ts';
5
- import type { UiReadModel, UiTasksSnapshot } from '../runtime/ui-read-models.ts';
6
- import {
7
- buildDetailBlock,
8
- buildEmptyState,
9
- buildGuidanceLine,
10
- buildPanelListRow,
11
- buildPanelLine,
12
- buildSummaryBlock,
13
- buildPanelWorkspace,
14
- DEFAULT_PANEL_PALETTE,
15
- } from './polish.ts';
16
-
17
- const C = {
18
- ...DEFAULT_PANEL_PALETTE,
19
- header: '#94a3b8',
20
- headerBg: '#1e293b',
21
- queued: '#38bdf8',
22
- running: '#22c55e',
23
- blocked: '#f59e0b',
24
- failed: '#ef4444',
25
- completed: '#a78bfa',
26
- selectBg: '#0f172a',
27
- } as const;
28
-
29
- const STATUS_ORDER: TaskLifecycleState[] = ['queued', 'running', 'blocked', 'failed', 'completed'];
30
-
31
- function formatWhen(value?: number): string {
32
- return value ? new Date(value).toLocaleString() : 'n/a';
33
- }
34
-
35
- function formatDuration(startedAt?: number, endedAt?: number): string {
36
- if (!startedAt) return 'n/a';
37
- const end = endedAt ?? Date.now();
38
- const ms = Math.max(0, end - startedAt);
39
- if (ms < 1_000) return `${ms}ms`;
40
- if (ms < 60_000) return `${(ms / 1_000).toFixed(ms < 10_000 ? 1 : 0)}s`;
41
- const mins = Math.floor(ms / 60_000);
42
- const secs = Math.floor((ms % 60_000) / 1_000);
43
- return `${mins}m ${secs}s`;
44
- }
45
-
46
- function kindLabel(kind: RuntimeTask['kind']): string {
47
- switch (kind) {
48
- case 'exec': return 'exec';
49
- case 'agent': return 'task';
50
- case 'acp': return 'delegate';
51
- case 'scheduler': return 'scheduler';
52
- case 'daemon': return 'host';
53
- case 'mcp': return 'mcp';
54
- case 'plugin': return 'plugin';
55
- case 'integration': return 'integration';
56
- }
57
- }
58
-
59
- function descriptorOriginLabel(source: string | undefined): string {
60
- const normalized = source?.trim();
61
- if (!normalized || normalized === 'builtin/runtime') return 'runtime';
62
- if (normalized === 'daemon' || normalized === 'daemon-runtime') return 'host';
63
- return normalized.replace(/[-_/]+/g, ' ');
64
- }
65
-
66
- function statusColor(status: TaskLifecycleState): string {
67
- switch (status) {
68
- case 'queued':
69
- return C.queued;
70
- case 'running':
71
- return C.running;
72
- case 'blocked':
73
- return C.blocked;
74
- case 'failed':
75
- return C.failed;
76
- case 'completed':
77
- return C.completed;
78
- case 'cancelled':
79
- return C.dim;
80
- }
81
- }
82
-
83
- function sortTasks(tasks: RuntimeTask[]): RuntimeTask[] {
84
- const order = new Map(STATUS_ORDER.map((status, index) => [status, index] as const));
85
- return [...tasks].sort((a, b) => {
86
- const statusDelta = (order.get(a.status) ?? 99) - (order.get(b.status) ?? 99);
87
- if (statusDelta !== 0) return statusDelta;
88
- const aTime = a.startedAt ?? a.queuedAt;
89
- const bTime = b.startedAt ?? b.queuedAt;
90
- return bTime - aTime || a.title.localeCompare(b.title);
91
- });
92
- }
93
-
94
- function safeJson(value: unknown): string {
95
- try {
96
- return JSON.stringify(value);
97
- } catch {
98
- return String(value);
99
- }
100
- }
101
-
102
- interface TaskDescriptorMeta {
103
- readonly mode?: string;
104
- readonly template?: string;
105
- readonly reviewMode?: string;
106
- readonly executionProtocol?: string;
107
- readonly source?: string;
108
- readonly family?: string;
109
- }
110
-
111
- function parseTaskDescriptor(description: string): TaskDescriptorMeta | null {
112
- try {
113
- const parsed = JSON.parse(description) as TaskDescriptorMeta;
114
- if (!parsed || typeof parsed !== 'object') return null;
115
- return parsed;
116
- } catch {
117
- return null;
118
- }
119
- }
120
-
121
- export class TasksPanel extends ScrollableListPanel<RuntimeTask> {
122
- private readonly readModel?: UiReadModel<UiTasksSnapshot>;
123
- private readonly unsubscribers: readonly (() => void)[];
124
-
125
- public constructor(readModel: UiReadModel<UiTasksSnapshot> | undefined) {
126
- super('tasks', 'Tasks', 'J', 'monitoring');
127
- this.showSelectionGutter = true; // I5: non-color selection affordance
128
- this.readModel = readModel;
129
- this.unsubscribers = [
130
- readModel?.subscribe(() => this.markDirty()),
131
- ].filter((unsubscribe): unsubscribe is () => void => Boolean(unsubscribe));
132
- }
133
-
134
- public override onActivate(): void {
135
- super.onActivate();
136
- this.selectedIndex = 0;
137
- }
138
-
139
- public override onDestroy(): void {
140
- for (const unsubscribe of this.unsubscribers) unsubscribe();
141
- }
142
-
143
- protected override getPalette() { return C; }
144
- protected override getEmptyStateMessage() { return ' No tasks recorded yet.'; }
145
- protected override getEmptyStateActions() {
146
- return [
147
- { command: '/workplan', summary: 'track visible Agent work in the workspace' },
148
- { command: '/delegate <task>', summary: 'send explicit build/fix/review work to GoodVibes TUI' },
149
- ];
150
- }
151
-
152
- protected getItems(): readonly RuntimeTask[] {
153
- if (!this.readModel) return [];
154
- return sortTasks([...this.readModel.getSnapshot().tasks]);
155
- }
156
-
157
- protected renderItem(task: RuntimeTask, index: number, selected: boolean, width: number): Line {
158
- return buildPanelListRow(width, [
159
- { text: task.status.padEnd(10), fg: statusColor(task.status) },
160
- { text: ` ${kindLabel(task.kind).padEnd(12)}`, fg: C.value },
161
- { text: ` ${task.id.slice(0, 8)} `, fg: C.dim },
162
- { text: task.title.slice(0, Math.max(0, width - 37)), fg: C.value },
163
- ], C, { selected });
164
- }
165
-
166
- public handleInput(key: string): boolean {
167
- if (key === 'home') {
168
- this.selectedIndex = 0;
169
- this.markDirty();
170
- return true;
171
- }
172
- if (key === 'end') {
173
- const tasks = this.getItems();
174
- this.selectedIndex = Math.max(0, tasks.length - 1);
175
- this.markDirty();
176
- return true;
177
- }
178
- return super.handleInput(key);
179
- }
180
-
181
- public render(width: number, height: number): Line[] {
182
- this.clampSelection();
183
- const intro = 'Connected-host task lifecycle, ownership, retries, and result/error details for operator visibility.';
184
- const footerLines = [buildPanelLine(width, [[' Up/Down move Home/End jump', C.dim]])];
185
-
186
- if (!this.readModel) {
187
- const workspace = buildPanelWorkspace(width, height, {
188
- title: 'Task Control Room',
189
- intro,
190
- sections: [{
191
- lines: buildEmptyState(
192
- width,
193
- ' Connected-host task state is unavailable.',
194
- 'This operator view is read-only. Use /tasks list for compact transcript output; task execution stays unchanged.',
195
- [{ command: '/tasks', summary: 'review connected-host tasks without changing execution' }],
196
- C,
197
- ),
198
- }],
199
- palette: C,
200
- });
201
- while (workspace.length < height) workspace.push(createEmptyLine(width));
202
- return workspace;
203
- }
204
-
205
- const tasks = this.getItems();
206
- const counts = STATUS_ORDER.map((status) => ({
207
- status,
208
- count: tasks.filter((task) => task.status === status).length,
209
- }));
210
- const blockedCount = counts.find((entry) => entry.status === 'blocked')?.count ?? 0;
211
- const failedCount = counts.find((entry) => entry.status === 'failed')?.count ?? 0;
212
- const runningCount = counts.find((entry) => entry.status === 'running')?.count ?? 0;
213
- const queuedCount = counts.find((entry) => entry.status === 'queued')?.count ?? 0;
214
- const completedCount = counts.find((entry) => entry.status === 'completed')?.count ?? 0;
215
-
216
- const postureLines: Line[] = [
217
- buildPanelLine(width, [
218
- [' queued ', C.label],
219
- [String(queuedCount), queuedCount > 0 ? C.queued : C.dim],
220
- [' running ', C.label],
221
- [String(runningCount), runningCount > 0 ? C.running : C.dim],
222
- [' blocked ', C.label],
223
- [String(blockedCount), blockedCount > 0 ? C.blocked : C.dim],
224
- [' failed ', C.label],
225
- [String(failedCount), failedCount > 0 ? C.failed : C.dim],
226
- [' completed ', C.label],
227
- [String(completedCount), completedCount > 0 ? C.completed : C.dim],
228
- ]),
229
- ];
230
-
231
- const selected = tasks[this.selectedIndex];
232
- if (selected) {
233
- postureLines.push(buildPanelLine(width, [
234
- [' selected ', C.label],
235
- [selected.id, C.info],
236
- [' status ', C.label],
237
- [selected.status, statusColor(selected.status)],
238
- [' kind ', C.label],
239
- [kindLabel(selected.kind), C.value],
240
- [' owner ', C.label],
241
- [selected.owner.slice(0, Math.max(0, width - 46)), C.dim],
242
- ]));
243
- }
244
- postureLines.push(
245
- buildGuidanceLine(width, '/tasks', 'review connected-host task posture without changing execution', C),
246
- buildGuidanceLine(width, '/delegate <task>', 'delegate explicit build/fix/review work to GoodVibes TUI when code execution is required', C),
247
- );
248
-
249
- const detailRows: Line[] = [];
250
- if (selected) {
251
- const descriptor = selected.description ? parseTaskDescriptor(selected.description) : null;
252
- detailRows.push(buildPanelLine(width, [
253
- [' Title: ', C.label],
254
- [selected.title, C.value],
255
- [' Status: ', C.label],
256
- [selected.status, statusColor(selected.status)],
257
- [' Kind: ', C.label],
258
- [kindLabel(selected.kind), C.value],
259
- ]));
260
- detailRows.push(buildPanelLine(width, [
261
- [' Owner: ', C.label],
262
- [selected.owner, C.value],
263
- [' Cancellable: ', C.label],
264
- [selected.cancellable ? 'yes' : 'no', selected.cancellable ? C.running : C.failed],
265
- [' Queue: ', C.label],
266
- [formatWhen(selected.queuedAt), C.dim],
267
- ]));
268
- detailRows.push(buildPanelLine(width, [
269
- [' Started: ', C.label],
270
- [formatWhen(selected.startedAt), C.dim],
271
- [' Ended: ', C.label],
272
- [formatWhen(selected.endedAt), C.dim],
273
- [' Duration: ', C.label],
274
- [formatDuration(selected.startedAt, selected.endedAt), C.dim],
275
- ]));
276
- if (descriptor?.mode || descriptor?.family || descriptor?.source) {
277
- detailRows.push(buildPanelLine(width, [
278
- [' Mode: ', C.label],
279
- [descriptor?.mode ?? 'n/a', C.value],
280
- [' Family: ', C.label],
281
- [descriptor?.family ?? 'n/a', C.info],
282
- [' Origin: ', C.label],
283
- [descriptorOriginLabel(descriptor?.source), C.dim],
284
- ]));
285
- }
286
- if (descriptor?.reviewMode || descriptor?.executionProtocol || descriptor?.template) {
287
- detailRows.push(buildPanelLine(width, [
288
- [' Review: ', C.label],
289
- [descriptor?.reviewMode ?? 'n/a', C.value],
290
- [' Protocol: ', C.label],
291
- [descriptor?.executionProtocol ?? 'n/a', C.value],
292
- [' Template: ', C.label],
293
- [descriptor?.template ?? 'n/a', C.dim],
294
- ]));
295
- }
296
- if (selected.correlationId || selected.turnId) {
297
- detailRows.push(buildPanelLine(width, [
298
- [' Correlation: ', C.label],
299
- [selected.correlationId ?? 'n/a', C.dim],
300
- [' Turn: ', C.label],
301
- [selected.turnId ?? 'n/a', C.dim],
302
- ]));
303
- }
304
- if (selected.parentTaskId || selected.childTaskIds.length > 0) {
305
- detailRows.push(buildPanelLine(width, [
306
- [' Parent: ', C.label],
307
- [selected.parentTaskId ?? 'none', C.dim],
308
- [' Children: ', C.label],
309
- [selected.childTaskIds.length > 0 ? selected.childTaskIds.join(', ') : 'none', C.dim],
310
- ]));
311
- }
312
- if (selected.retryPolicy) {
313
- detailRows.push(buildPanelLine(width, [
314
- [' Retry: ', C.label],
315
- [`${selected.retryPolicy.currentAttempt}/${selected.retryPolicy.maxAttempts} ${selected.retryPolicy.backoff}`, C.value],
316
- ]));
317
- }
318
- if (selected.error) {
319
- detailRows.push(buildPanelLine(width, [
320
- [' Error ', C.label],
321
- [selected.error.slice(0, Math.max(0, width - 10)), C.failed],
322
- ]));
323
- }
324
- if (selected.result !== undefined) {
325
- const resultText = safeJson(selected.result);
326
- detailRows.push(buildPanelLine(width, [
327
- [' Result: ', C.label],
328
- [resultText.slice(0, Math.max(0, width - 11)), C.dim],
329
- ]));
330
- }
331
- }
332
-
333
- const headerLines: Line[] = buildSummaryBlock(width, 'Task posture', postureLines, C);
334
-
335
- return this.renderList(width, height, {
336
- title: 'Task Control Room',
337
- header: headerLines,
338
- footer: [
339
- ...buildDetailBlock(width, 'Selected task', detailRows, C),
340
- ...footerLines,
341
- ],
342
- });
343
- }
344
- }
@@ -1,304 +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
- DEFAULT_PANEL_PALETTE,
16
- type PanelWorkspaceSection,
17
- } from './polish.ts';
18
- import { truncateDisplay } from '../utils/terminal-width.ts';
19
-
20
- const C = {
21
- headerBg: '#1a1a2e',
22
- headerFg: '#ffffff',
23
- statusBar: '#222233',
24
- statusFg: '#aaaaaa',
25
- reasoningFg: '#aa88ff',
26
- activeFg: '#cc99ff',
27
- dimFg: '#555566',
28
- turnLabel: '#7766bb',
29
- activeLabel: '#00ffff',
30
- collapsedFg: '#888899',
31
- selected: '#00ffff',
32
- selectedBg: '#1a2a3a',
33
- } as const;
34
-
35
- interface ReasoningBlock {
36
- turnId: number;
37
- content: string;
38
- active: boolean; // true = currently streaming
39
- collapsed: boolean;
40
- }
41
-
42
- function wrapLines(text: string, width: number): string[] {
43
- const result: string[] = [];
44
- const raw = text.split('\n');
45
- for (const line of raw) {
46
- if (line.length <= width) {
47
- result.push(line);
48
- } else {
49
- let start = 0;
50
- while (start < line.length) {
51
- result.push(line.slice(start, start + width));
52
- start += width;
53
- }
54
- }
55
- }
56
- return result;
57
- }
58
-
59
- type FlatRow = { kind: 'header'; blockIndex: number; text: string } | { kind: 'content'; text: string };
60
-
61
- export class ThinkingPanel extends BasePanel {
62
- private blocks: ReasoningBlock[] = [];
63
- private nextTurnId = 1;
64
- private unsubs: Array<() => void> = [];
65
- private cursorIndex = 0;
66
- private scrollOffset = 0;
67
- private autoScroll = true;
68
- private lastWidth = 80;
69
- private static readonly MAX_BLOCKS = 100;
70
-
71
- constructor(private readonly turnEvents: UiEventFeed<TurnEvent>) {
72
- super('thinking', 'Thinking', 'T', 'ai');
73
- this._attachBus();
74
- }
75
-
76
- override onActivate(): void {
77
- this.needsRender = true;
78
- }
79
-
80
- override onDeactivate(): void {
81
- super.onDeactivate();
82
- }
83
-
84
- override onDestroy(): void {
85
- this._detachBus();
86
- }
87
-
88
- handleInput(key: string): boolean {
89
- switch (key) {
90
- case 'up': this._move(-1); this.autoScroll = false; return true;
91
- case 'down': this._move(1); return true;
92
- case 'pageup': this._move(-10); this.autoScroll = false; return true;
93
- case 'pagedown': this._move(10); return true;
94
- case 'return': this._toggleCollapse(); return true;
95
- case 'g': this.autoScroll = true; this.markDirty(); return true;
96
- default: return false;
97
- }
98
- }
99
-
100
- render(width: number, height: number): Line[] {
101
- if (height <= 0 || width <= 0) return [];
102
- this.lastWidth = width;
103
-
104
- const hasActive = this.blocks.some(b => b.active);
105
- const flat = this._buildFlat(width);
106
- const title = hasActive ? ' Thinking [streaming]' : ` Thinking [${this.blocks.length} blocks]`;
107
- const footerLines = [
108
- buildPanelLine(width, [
109
- [' Up/Down', DEFAULT_PANEL_PALETTE.info],
110
- [' scroll', DEFAULT_PANEL_PALETTE.dim],
111
- [' Enter', DEFAULT_PANEL_PALETTE.info],
112
- [' collapse', DEFAULT_PANEL_PALETTE.dim],
113
- [' g', DEFAULT_PANEL_PALETTE.info],
114
- [' jump to end', DEFAULT_PANEL_PALETTE.dim],
115
- [this.autoScroll ? ' auto-scroll ON' : ' manual', this.autoScroll ? DEFAULT_PANEL_PALETTE.good : DEFAULT_PANEL_PALETTE.warn],
116
- ]),
117
- ];
118
-
119
- if (flat.length === 0) {
120
- return buildPanelWorkspace(width, height, {
121
- title,
122
- intro: 'Live reasoning blocks stream here while the model is actively thinking.',
123
- sections: [
124
- {
125
- title: 'Reasoning',
126
- lines: buildEmptyState(
127
- width,
128
- ' No reasoning content yet',
129
- 'When the model emits thinking or reasoning deltas, they accumulate here in expandable blocks.',
130
- [],
131
- DEFAULT_PANEL_PALETTE,
132
- ),
133
- },
134
- ],
135
- footerLines,
136
- palette: DEFAULT_PANEL_PALETTE,
137
- });
138
- }
139
-
140
- if (this.autoScroll) {
141
- this.cursorIndex = Math.max(0, flat.length - 1);
142
- }
143
-
144
- this.cursorIndex = Math.max(0, Math.min(this.cursorIndex, Math.max(0, flat.length - 1)));
145
-
146
- const summary: PanelWorkspaceSection = {
147
- title: 'Summary',
148
- lines: [
149
- buildPanelLine(width, [
150
- [' Blocks ', DEFAULT_PANEL_PALETTE.label],
151
- [String(this.blocks.length), DEFAULT_PANEL_PALETTE.value],
152
- [' Active ', DEFAULT_PANEL_PALETTE.label],
153
- [String(this.blocks.filter((block) => block.active).length), hasActive ? DEFAULT_PANEL_PALETTE.warn : DEFAULT_PANEL_PALETTE.dim],
154
- [' Mode ', DEFAULT_PANEL_PALETTE.label],
155
- [this.autoScroll ? 'auto-scroll' : 'manual', this.autoScroll ? DEFAULT_PANEL_PALETTE.good : DEFAULT_PANEL_PALETTE.info],
156
- ]),
157
- ],
158
- };
159
-
160
- const selectedRow = flat[this.cursorIndex];
161
- const selectedSection: PanelWorkspaceSection = {
162
- title: 'Selected',
163
- lines: [
164
- buildPanelLine(width, [[' Row Type ', DEFAULT_PANEL_PALETTE.label], [selectedRow?.kind ?? 'none', DEFAULT_PANEL_PALETTE.value]]),
165
- ],
166
- };
167
-
168
- const reasoningSection = resolveScrollablePanelSection(width, height, {
169
- intro: 'Live reasoning blocks stream here while the model is actively thinking.',
170
- footerLines,
171
- palette: DEFAULT_PANEL_PALETTE,
172
- beforeSections: [summary],
173
- section: {
174
- title: 'Reasoning',
175
- scrollableLines: flat.map((row, index) => this._renderRow(width, row, index === this.cursorIndex)),
176
- selectedIndex: this.cursorIndex,
177
- scrollOffset: this.scrollOffset,
178
- minRows: 8,
179
- },
180
- afterSections: [selectedSection],
181
- });
182
- this.scrollOffset = reasoningSection.scrollOffset;
183
-
184
- return buildPanelWorkspace(width, height, {
185
- title,
186
- intro: 'Live reasoning blocks stream here while the model is actively thinking.',
187
- sections: [
188
- summary,
189
- reasoningSection.section,
190
- selectedSection,
191
- ],
192
- footerLines,
193
- palette: DEFAULT_PANEL_PALETTE,
194
- });
195
- }
196
-
197
- private _renderRow(width: number, row: FlatRow, isCursor: boolean): Line {
198
- const bg = isCursor ? C.selectedBg : '';
199
- if (row.kind === 'header') {
200
- const indicator = this.blocks[row.blockIndex]?.collapsed ? '▸' : '▾';
201
- const active = this.blocks[row.blockIndex]?.active;
202
- const bullet = active ? '\u25cf ' : '\u25e6 ';
203
- return buildStyledPanelLine(width, [
204
- { text: truncateDisplay(` ${bullet}${row.text} ${indicator}`, width), fg: active ? C.activeLabel : C.turnLabel, bg, bold: true },
205
- ]);
206
- }
207
- return buildStyledPanelLine(width, [
208
- { text: ' ', fg: C.reasoningFg, bg },
209
- { text: truncateDisplay(row.text, Math.max(0, width - 2)), fg: isCursor ? C.activeFg : C.reasoningFg, bg },
210
- ]);
211
- }
212
-
213
- private _buildFlat(width: number): FlatRow[] {
214
- const rows: FlatRow[] = [];
215
- for (let i = 0; i < this.blocks.length; i++) {
216
- const block = this.blocks[i]!;
217
- const turnLabel = `Turn ${block.turnId}${block.active ? ' (streaming)' : ''}`;
218
- rows.push({ kind: 'header', blockIndex: i, text: turnLabel });
219
- if (!block.collapsed) {
220
- const wrapped = wrapLines(block.content || '(empty)', Math.max(1, width - 2));
221
- for (const line of wrapped) {
222
- rows.push({ kind: 'content', text: line });
223
- }
224
- }
225
- }
226
- return rows;
227
- }
228
-
229
- private _toggleCollapse(): void {
230
- // Find the header row at or before cursorIndex
231
- const flat = this._buildFlat(this.lastWidth);
232
- for (let i = this.cursorIndex; i >= 0; i--) {
233
- const row = flat[i];
234
- if (row?.kind === 'header') {
235
- const block = this.blocks[row.blockIndex];
236
- if (block) {
237
- block.collapsed = !block.collapsed;
238
- this.markDirty();
239
- return;
240
- }
241
- }
242
- }
243
- }
244
-
245
- private _move(delta: number): void {
246
- const flat = this._buildFlat(this.lastWidth);
247
- if (flat.length === 0) return;
248
- this.cursorIndex = Math.max(0, Math.min(flat.length - 1, this.cursorIndex + delta));
249
- this.markDirty();
250
- }
251
-
252
- private _attachBus(): void {
253
- if (this.unsubs.length > 0) return;
254
-
255
- let currentBlock: ReasoningBlock | null = null;
256
-
257
- this.unsubs.push(this.turnEvents.on('STREAM_START', () => {
258
- const block: ReasoningBlock = {
259
- turnId: this.nextTurnId++,
260
- content: '',
261
- active: true,
262
- collapsed: false,
263
- };
264
- // Cap block count to prevent unbounded growth
265
- if (this.blocks.length >= ThinkingPanel.MAX_BLOCKS) {
266
- this.blocks.shift();
267
- }
268
- this.blocks.push(block);
269
- currentBlock = block;
270
- this.autoScroll = true;
271
- this.markDirty();
272
- }));
273
-
274
- this.unsubs.push(this.turnEvents.on('STREAM_DELTA', (env) => {
275
- const reasoning = env.reasoning;
276
- if (reasoning && currentBlock) {
277
- currentBlock.content += reasoning;
278
- this.autoScroll = true;
279
- this.markDirty();
280
- }
281
- }));
282
-
283
- this.unsubs.push(this.turnEvents.on('STREAM_END', () => {
284
- if (currentBlock) {
285
- currentBlock.active = false;
286
- currentBlock = null;
287
- this.markDirty();
288
- }
289
- }));
290
-
291
- this.unsubs.push(this.turnEvents.on('TURN_COMPLETED', () => {
292
- if (currentBlock) {
293
- currentBlock.active = false;
294
- currentBlock = null;
295
- this.markDirty();
296
- }
297
- }));
298
- }
299
-
300
- private _detachBus(): void {
301
- for (const unsub of this.unsubs) unsub();
302
- this.unsubs = [];
303
- }
304
- }