@pellux/goodvibes-agent 1.4.4 → 1.5.1

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 (230) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +52 -7
  3. package/dist/package/main.js +7491 -12253
  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/live-verification/live-verification.json +13 -13
  11. package/release/live-verification/live-verification.md +15 -15
  12. package/release/performance-snapshot.json +2 -2
  13. package/release/release-notes.md +5 -7
  14. package/release/release-readiness.json +6 -6
  15. package/src/agent/behavior-discovery-summary.ts +4 -18
  16. package/src/agent/calendar-registry.ts +322 -0
  17. package/src/agent/competitive-feature-inventory.ts +268 -130
  18. package/src/agent/document-registry.ts +5 -1
  19. package/src/agent/email/email-service.ts +350 -0
  20. package/src/agent/email/imap-client.ts +596 -0
  21. package/src/agent/email/smtp-client.ts +453 -0
  22. package/src/agent/ics-calendar.ts +662 -0
  23. package/src/agent/ics-timezone.ts +172 -0
  24. package/src/agent/markdown-frontmatter.ts +31 -0
  25. package/src/agent/memory-safety.ts +1 -1
  26. package/src/agent/note-registry.ts +3 -9
  27. package/src/agent/persona-discovery.ts +3 -15
  28. package/src/agent/persona-registry.ts +1 -10
  29. package/src/agent/research-source-registry.ts +5 -1
  30. package/src/agent/routine-discovery.ts +3 -15
  31. package/src/agent/runtime-profile.ts +3 -0
  32. package/src/agent/skill-discovery.ts +3 -15
  33. package/src/agent/skill-draft-proposer.ts +203 -0
  34. package/src/agent/skill-draft-runner.ts +201 -0
  35. package/src/agent/skill-registry.ts +36 -1
  36. package/src/agent/skill-standard.ts +99 -0
  37. package/src/agent/vibe-file.ts +7 -22
  38. package/src/cli/completion.ts +1 -1
  39. package/src/cli/config-overrides.ts +10 -1
  40. package/src/cli/redaction.ts +17 -5
  41. package/src/config/provider-model.ts +2 -1
  42. package/src/config/secret-config.ts +13 -6
  43. package/src/core/activity-feed.ts +97 -0
  44. package/src/core/away-digest.ts +161 -0
  45. package/src/core/conversation-rendering.ts +7 -3
  46. package/src/core/conversation.ts +22 -15
  47. package/src/core/hardware-profile.ts +362 -0
  48. package/src/core/last-seen-store.ts +78 -0
  49. package/src/core/plain-language.ts +52 -0
  50. package/src/core/setup-incomplete-hint.ts +90 -0
  51. package/src/core/system-message-router.ts +38 -87
  52. package/src/input/agent-workspace-basic-command-editor-submission.ts +60 -220
  53. package/src/input/agent-workspace-basic-command-editors.ts +39 -141
  54. package/src/input/agent-workspace-categories.ts +40 -125
  55. package/src/input/agent-workspace-command-editor.ts +4 -0
  56. package/src/input/agent-workspace-host-category.ts +0 -5
  57. package/src/input/agent-workspace-local-editor-submission.ts +3 -1
  58. package/src/input/agent-workspace-navigation.ts +10 -3
  59. package/src/input/agent-workspace-onboarding-actions.ts +132 -0
  60. package/src/input/agent-workspace-onboarding-categories.ts +29 -26
  61. package/src/input/agent-workspace-onboarding-finish.ts +11 -4
  62. package/src/input/agent-workspace-onboarding-state.ts +111 -0
  63. package/src/input/agent-workspace-profile-editor-submission.ts +260 -0
  64. package/src/input/agent-workspace-profile-editors.ts +155 -0
  65. package/src/input/agent-workspace-subscription-editor.ts +7 -0
  66. package/src/input/agent-workspace-types.ts +5 -1
  67. package/src/input/agent-workspace.ts +65 -39
  68. package/src/input/command-registry.ts +18 -5
  69. package/src/input/commands/agent-runtime-profile-runtime.ts +2 -1
  70. package/src/input/commands/agent-skills-runtime.ts +60 -3
  71. package/src/input/commands/calendar-runtime.ts +209 -0
  72. package/src/input/commands/channels-runtime.ts +1 -0
  73. package/src/input/commands/command-error.ts +9 -0
  74. package/src/input/commands/compat-runtime.ts +1 -0
  75. package/src/input/commands/config.ts +1 -0
  76. package/src/input/commands/conversation-runtime.ts +1 -1
  77. package/src/input/commands/delegation-runtime.ts +5 -6
  78. package/src/input/commands/email-runtime.ts +387 -0
  79. package/src/input/commands/experience-runtime.ts +17 -4
  80. package/src/input/commands/guidance-runtime.ts +1 -1
  81. package/src/input/commands/health-runtime.ts +6 -1
  82. package/src/input/commands/knowledge-format.ts +73 -0
  83. package/src/input/commands/knowledge.ts +9 -74
  84. package/src/input/commands/local-provider-runtime.ts +2 -1
  85. package/src/input/commands/local-runtime.ts +10 -4
  86. package/src/input/commands/mcp-runtime.ts +7 -0
  87. package/src/input/commands/notify-runtime.ts +17 -1
  88. package/src/input/commands/onboarding-runtime.ts +1 -0
  89. package/src/input/commands/operator-actions-runtime.ts +1 -0
  90. package/src/input/commands/operator-runtime.ts +3 -0
  91. package/src/input/commands/personas-runtime.ts +1 -0
  92. package/src/input/commands/platform-access-runtime.ts +40 -17
  93. package/src/input/commands/product-runtime.ts +6 -1
  94. package/src/input/commands/provider-accounts-runtime.ts +3 -2
  95. package/src/input/commands/qrcode-runtime.ts +1 -0
  96. package/src/input/commands/routines-runtime.ts +1 -0
  97. package/src/input/commands/runtime-services.ts +0 -13
  98. package/src/input/commands/security-runtime.ts +1 -0
  99. package/src/input/commands/session-content.ts +1 -0
  100. package/src/input/commands/shell-core.ts +5 -2
  101. package/src/input/commands/subscription-runtime.ts +33 -9
  102. package/src/input/commands/support-bundle-runtime.ts +8 -1
  103. package/src/input/commands/tasks-runtime.ts +1 -0
  104. package/src/input/commands/tts-runtime.ts +1 -0
  105. package/src/input/commands/vibe-runtime.ts +1 -0
  106. package/src/input/commands.ts +4 -0
  107. package/src/input/feed-context-factory.ts +3 -12
  108. package/src/input/handler-command-route.ts +7 -60
  109. package/src/input/handler-feed-routes.ts +0 -194
  110. package/src/input/handler-feed.ts +2 -54
  111. package/src/input/handler-interactions.ts +0 -2
  112. package/src/input/handler-modal-stack.ts +0 -9
  113. package/src/input/handler-picker-routes.ts +24 -1
  114. package/src/input/handler-shortcuts.ts +11 -40
  115. package/src/input/handler-ui-state.ts +13 -0
  116. package/src/input/handler.ts +8 -35
  117. package/src/input/keybindings.ts +40 -17
  118. package/src/input/model-picker-local-fit.ts +130 -0
  119. package/src/input/shell-passthrough.ts +58 -0
  120. package/src/input/submission-router.ts +0 -5
  121. package/src/main.ts +129 -90
  122. package/src/renderer/activity-sidebar.ts +186 -0
  123. package/src/renderer/agent-workspace-context-lines.ts +5 -48
  124. package/src/renderer/agent-workspace-style.ts +1 -1
  125. package/src/renderer/agent-workspace.ts +14 -2
  126. package/src/renderer/compositor.ts +37 -125
  127. package/src/renderer/conversation-overlays.ts +3 -3
  128. package/src/renderer/help-overlay.ts +7 -5
  129. package/src/renderer/model-workspace.ts +101 -86
  130. package/src/{panels → renderer}/polish.ts +3 -3
  131. package/src/renderer/shell-surface.ts +4 -5
  132. package/src/renderer/status-token.ts +31 -11
  133. package/src/renderer/tab-strip.ts +1 -1
  134. package/src/renderer/tool-call.ts +7 -8
  135. package/src/renderer/tool-labels.ts +92 -0
  136. package/src/renderer/ui-factory.ts +48 -96
  137. package/src/runtime/bootstrap-command-context.ts +0 -5
  138. package/src/runtime/bootstrap-command-parts.ts +6 -15
  139. package/src/runtime/bootstrap-core.ts +34 -13
  140. package/src/runtime/bootstrap-hook-bridge.ts +3 -1
  141. package/src/runtime/bootstrap-shell.ts +3 -50
  142. package/src/runtime/bootstrap.ts +3 -4
  143. package/src/runtime/context.ts +1 -1
  144. package/src/runtime/diagnostics/panels/index.ts +0 -1
  145. package/src/runtime/diagnostics/panels/policy.ts +1 -1
  146. package/src/runtime/execution-ledger.ts +16 -1
  147. package/src/runtime/index.ts +6 -1
  148. package/src/runtime/onboarding/index.ts +1 -0
  149. package/src/runtime/onboarding/onboarding-state.ts +200 -0
  150. package/src/{panels → runtime}/provider-account-snapshot.ts +5 -2
  151. package/src/runtime/services.ts +16 -4
  152. package/src/runtime/terminal-output-guard.ts +7 -0
  153. package/src/runtime/tool-permission-safety.ts +1 -1
  154. package/src/runtime/ui/model-picker/data-provider.ts +1 -1
  155. package/src/runtime/ui/model-picker/health-enrichment.ts +2 -2
  156. package/src/runtime/ui/model-picker/types.ts +2 -2
  157. package/src/runtime/ui/provider-health/data-provider.ts +3 -3
  158. package/src/runtime/ui/provider-health/types.ts +2 -2
  159. package/src/runtime/ui-services.ts +0 -2
  160. package/src/shell/agent-workspace-fullscreen.ts +0 -1
  161. package/src/shell/autonomy-surfacing.ts +272 -0
  162. package/src/shell/session-continuity-hints.ts +0 -6
  163. package/src/shell/startup-wiring.ts +221 -0
  164. package/src/shell/ui-openers.ts +18 -29
  165. package/src/tools/agent-context-policy.ts +1 -1
  166. package/src/tools/agent-harness-background-processes.ts +4 -4
  167. package/src/tools/agent-harness-browser-cockpit-route.ts +3 -3
  168. package/src/tools/agent-harness-command-runner.ts +6 -1
  169. package/src/tools/agent-harness-document-ops.ts +26 -53
  170. package/src/tools/agent-harness-execution-history.ts +1 -13
  171. package/src/tools/agent-harness-execution-posture.ts +0 -15
  172. package/src/tools/agent-harness-keybinding-metadata.ts +28 -29
  173. package/src/tools/agent-harness-local-model-cookbook.ts +24 -1
  174. package/src/tools/agent-harness-metadata.ts +16 -0
  175. package/src/tools/agent-harness-mode-catalog.ts +2 -9
  176. package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
  177. package/src/tools/agent-harness-setup-posture-utils.ts +1 -1
  178. package/src/tools/agent-harness-tool-schema.ts +13 -14
  179. package/src/tools/agent-harness-tool.ts +27 -23
  180. package/src/tools/agent-harness-ui-surface-metadata.ts +10 -20
  181. package/src/tools/agent-harness-workspace-action-runner.ts +3 -4
  182. package/src/tools/agent-workspace-tool.ts +2 -33
  183. package/src/utils/terminal-width.ts +9 -3
  184. package/src/version.ts +1 -1
  185. package/src/input/agent-workspace-panel-route.ts +0 -44
  186. package/src/input/panel-integration-actions.ts +0 -26
  187. package/src/panels/approval-panel.ts +0 -149
  188. package/src/panels/automation-control-panel.ts +0 -212
  189. package/src/panels/base-panel.ts +0 -254
  190. package/src/panels/builtin/agent.ts +0 -58
  191. package/src/panels/builtin/knowledge.ts +0 -26
  192. package/src/panels/builtin/operations.ts +0 -121
  193. package/src/panels/builtin/session.ts +0 -138
  194. package/src/panels/builtin/shared.ts +0 -275
  195. package/src/panels/builtin/usage.ts +0 -21
  196. package/src/panels/builtin-panels.ts +0 -23
  197. package/src/panels/confirm-state.ts +0 -61
  198. package/src/panels/context-visualizer-panel.ts +0 -204
  199. package/src/panels/cost-tracker-panel.ts +0 -444
  200. package/src/panels/docs-panel.ts +0 -285
  201. package/src/panels/index.ts +0 -25
  202. package/src/panels/knowledge-panel.ts +0 -417
  203. package/src/panels/memory-panel.ts +0 -226
  204. package/src/panels/panel-list-panel.ts +0 -464
  205. package/src/panels/panel-manager.ts +0 -570
  206. package/src/panels/provider-accounts-panel.ts +0 -233
  207. package/src/panels/provider-health-domains.ts +0 -208
  208. package/src/panels/provider-health-panel.ts +0 -720
  209. package/src/panels/provider-health-tracker.ts +0 -115
  210. package/src/panels/provider-stats-panel.ts +0 -366
  211. package/src/panels/qr-panel.ts +0 -207
  212. package/src/panels/schedule-panel.ts +0 -321
  213. package/src/panels/scrollable-list-panel.ts +0 -491
  214. package/src/panels/search-focus.ts +0 -32
  215. package/src/panels/security-panel.ts +0 -295
  216. package/src/panels/session-browser-panel.ts +0 -395
  217. package/src/panels/session-maintenance.ts +0 -125
  218. package/src/panels/subscription-panel.ts +0 -263
  219. package/src/panels/system-messages-panel.ts +0 -230
  220. package/src/panels/tasks-panel.ts +0 -344
  221. package/src/panels/thinking-panel.ts +0 -304
  222. package/src/panels/token-budget-panel.ts +0 -475
  223. package/src/panels/tool-inspector-panel.ts +0 -436
  224. package/src/panels/types.ts +0 -54
  225. package/src/renderer/panel-composite.ts +0 -158
  226. package/src/renderer/panel-tab-bar.ts +0 -69
  227. package/src/renderer/panel-workspace-bar.ts +0 -42
  228. package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
  229. package/src/tools/agent-harness-panel-metadata.ts +0 -211
  230. /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
- }