@pellux/goodvibes-tui 0.21.0 → 0.22.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 (54) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +1 -1
  3. package/package.json +2 -1
  4. package/src/cli/completions/generate.ts +4 -8
  5. package/src/cli/entrypoint.ts +6 -0
  6. package/src/cli/parser.ts +17 -0
  7. package/src/cli/types.ts +2 -0
  8. package/src/config/goodvibes-home-audit.ts +2 -0
  9. package/src/core/context-auto-compact.ts +77 -0
  10. package/src/core/turn-event-wiring.ts +124 -0
  11. package/src/daemon/cli.ts +5 -0
  12. package/src/input/command-registry.ts +1 -0
  13. package/src/input/commands/control-room-runtime.ts +5 -5
  14. package/src/input/commands/provider.ts +57 -3
  15. package/src/input/commands/session-workflow.ts +8 -16
  16. package/src/input/commands/session.ts +70 -20
  17. package/src/input/commands.ts +0 -2
  18. package/src/input/handler-modal-routes.ts +37 -0
  19. package/src/input/handler-modal-token-routes.ts +19 -5
  20. package/src/input/handler-onboarding.ts +18 -0
  21. package/src/input/handler.ts +1 -0
  22. package/src/input/onboarding/onboarding-wizard-apply.ts +10 -0
  23. package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +14 -0
  24. package/src/input/onboarding/onboarding-wizard-steps.ts +6 -0
  25. package/src/input/onboarding/onboarding-wizard-validation.ts +77 -0
  26. package/src/input/settings-modal-behavior.ts +5 -0
  27. package/src/input/settings-modal-data.ts +77 -3
  28. package/src/input/settings-modal-mutations.ts +3 -0
  29. package/src/input/settings-modal-reset.ts +154 -0
  30. package/src/input/settings-modal.ts +55 -13
  31. package/src/main.ts +36 -28
  32. package/src/panels/agent-inspector-panel.ts +120 -18
  33. package/src/panels/agent-inspector-shared.ts +29 -0
  34. package/src/panels/builtin/development.ts +1 -0
  35. package/src/panels/builtin/knowledge.ts +14 -13
  36. package/src/panels/builtin/operations.ts +22 -1
  37. package/src/panels/builtin/shared.ts +7 -0
  38. package/src/panels/cockpit-panel.ts +123 -3
  39. package/src/panels/cockpit-read-model.ts +232 -0
  40. package/src/panels/index.ts +1 -1
  41. package/src/panels/knowledge-graph-panel.ts +84 -0
  42. package/src/panels/memory-panel.ts +370 -40
  43. package/src/panels/session-maintenance.ts +66 -15
  44. package/src/renderer/agent-detail-modal.ts +107 -3
  45. package/src/renderer/context-status-hint.ts +54 -0
  46. package/src/renderer/settings-modal.ts +14 -3
  47. package/src/renderer/shell-surface.ts +10 -0
  48. package/src/runtime/bootstrap-command-parts.ts +4 -0
  49. package/src/runtime/bootstrap-core.ts +24 -0
  50. package/src/runtime/bootstrap-shell.ts +11 -0
  51. package/src/runtime/bootstrap.ts +7 -0
  52. package/src/runtime/services.ts +6 -1
  53. package/src/version.ts +1 -1
  54. package/src/panels/knowledge-panel.ts +0 -343
@@ -1,343 +0,0 @@
1
- import type { Line } from '../types/grid.ts';
2
- import { ScrollableListPanel } from './scrollable-list-panel.ts';
3
- import type { KeyName } from './types.ts';
4
- import { type ConfirmState, handleConfirmInput, renderConfirmLines } from './confirm-state.ts';
5
- import type { MemoryClass, MemoryRecord, MemoryRegistry, MemoryReviewState } from '@pellux/goodvibes-sdk/platform/state';
6
- import {
7
- buildBodyText,
8
- buildEmptyState,
9
- buildGuidanceLine,
10
- buildKeyValueLine,
11
- buildPanelLine,
12
- buildPanelWorkspace,
13
- DEFAULT_PANEL_PALETTE,
14
- } from './polish.ts';
15
-
16
- function summarize(records: MemoryRecord[], cls: MemoryClass): MemoryRecord[] {
17
- return records.filter((record) => record.cls === cls).slice(0, 3);
18
- }
19
-
20
- const C = {
21
- ...DEFAULT_PANEL_PALETTE,
22
- header: '#94a3b8',
23
- headerBg: '#1e293b',
24
- } as const;
25
-
26
- function reviewStateColor(state: MemoryReviewState): string {
27
- switch (state) {
28
- case 'reviewed':
29
- return C.good;
30
- case 'stale':
31
- return C.warn;
32
- case 'contradicted':
33
- return C.bad;
34
- case 'fresh':
35
- default:
36
- return C.info;
37
- }
38
- }
39
-
40
- function formatConfidence(confidence: number): string {
41
- return `${confidence.toString().padStart(3, ' ')}%`;
42
- }
43
-
44
- export class KnowledgePanel extends ScrollableListPanel<MemoryRecord> {
45
- private readonly registry: MemoryRegistry;
46
- private unsubscribe?: () => void;
47
- private records: MemoryRecord[] = [];
48
- // I1: confirm for destructive review-state mutations
49
- private confirm: ConfirmState<{ id: string; action: 'stale' | 'contradicted' }> | null = null;
50
-
51
- public constructor(registry: MemoryRegistry) {
52
- super('knowledge', 'Knowledge', 'K', 'agent');
53
- this.registry = registry;
54
- }
55
-
56
- public override onActivate(): void {
57
- super.onActivate();
58
- this.refresh();
59
- this.unsubscribe = this.registry.subscribe(() => {
60
- this.refresh();
61
- this.markDirty();
62
- });
63
- }
64
-
65
- public override onDeactivate(): void {
66
- super.onDeactivate();
67
- }
68
-
69
- public override onDestroy(): void {
70
- this.unsubscribe?.();
71
- this.unsubscribe = undefined;
72
- }
73
-
74
- // ---------------------------------------------------------------------------
75
- // ScrollableListPanel implementation
76
- // ---------------------------------------------------------------------------
77
-
78
- protected getItems(): readonly MemoryRecord[] {
79
- return this.records;
80
- }
81
-
82
- protected renderItem(record: MemoryRecord, index: number, selected: boolean, width: number): Line {
83
- const bg = selected ? C.selectBg : undefined;
84
- return buildPanelLine(width, [
85
- [' ', C.label, bg],
86
- [record.reviewState.padEnd(13), reviewStateColor(record.reviewState), bg],
87
- [` ${formatConfidence(record.confidence)} `, C.value, bg],
88
- [record.summary.slice(0, Math.max(0, width - 26)), C.value, bg],
89
- ]);
90
- }
91
-
92
- protected override getPalette() { return C; }
93
- protected override getEmptyStateMessage() { return 'No durable project knowledge'; }
94
- protected override getEmptyStateActions() {
95
- return [
96
- { command: '/recall add fact <summary>', summary: 'capture a durable fact directly' },
97
- { command: '/recall capture incident latest', summary: 'promote the latest incident into project memory' },
98
- { command: '/recall capture policy', summary: 'store the current policy posture as durable evidence' },
99
- ];
100
- }
101
-
102
- // ---------------------------------------------------------------------------
103
- // Input
104
- // ---------------------------------------------------------------------------
105
-
106
- public handleInput(key: KeyName): boolean {
107
- // I1: y/n confirm for stale/contradict
108
- if (this.confirm) {
109
- const result = handleConfirmInput(this.confirm, key);
110
- if (result === 'confirmed') {
111
- const { id, action } = this.confirm.subject;
112
- this.confirm = null;
113
- const selected = this.records.find((r) => r.id === id);
114
- if (selected) {
115
- try {
116
- if (action === 'stale') {
117
- this.registry.review(id, {
118
- state: 'stale',
119
- confidence: Math.min(selected.confidence, 40),
120
- reviewedBy: 'operator',
121
- staleReason: 'marked stale from the knowledge panel',
122
- });
123
- } else {
124
- this.registry.review(id, {
125
- state: 'contradicted',
126
- confidence: 0,
127
- reviewedBy: 'operator',
128
- staleReason: 'marked contradicted from the knowledge panel',
129
- });
130
- }
131
- } catch (e) {
132
- // I2: surface async failure
133
- this.setError(`Review update failed: ${e instanceof Error ? e.message : String(e)}`);
134
- }
135
- }
136
- this.refresh();
137
- this.markDirty();
138
- return true;
139
- }
140
- if (result === 'cancelled') {
141
- this.confirm = null;
142
- this.markDirty();
143
- return true;
144
- }
145
- if (result === 'absorbed') return true;
146
- }
147
-
148
- // I2: auto-clear error on next keypress (inherited via super.handleInput)
149
- if (this.records.length === 0) return super.handleInput(key);
150
-
151
- const selected = this.records[this.selectedIndex];
152
-
153
- if (key === 'enter' || key === 'return' || key === 'r') {
154
- if (!selected) return false;
155
- this.registry.review(selected.id, {
156
- state: 'reviewed',
157
- confidence: Math.max(selected.confidence, 85),
158
- reviewedBy: 'operator',
159
- });
160
- this.refresh();
161
- this.markDirty();
162
- return true;
163
- }
164
- if (key === 's') {
165
- if (!selected) return false;
166
- // I1: prompt confirm before marking stale
167
- this.confirm = { subject: { id: selected.id, action: 'stale' }, label: selected.summary.slice(0, 40) };
168
- this.markDirty();
169
- return true;
170
- }
171
- if (key === 'c') {
172
- if (!selected) return false;
173
- // I1: prompt confirm before marking contradicted
174
- this.confirm = { subject: { id: selected.id, action: 'contradicted' }, label: selected.summary.slice(0, 40) };
175
- this.markDirty();
176
- return true;
177
- }
178
- if (key === 'f') {
179
- if (!selected) return false;
180
- this.registry.review(selected.id, {
181
- state: 'fresh',
182
- confidence: Math.max(selected.confidence, 60),
183
- reviewedBy: 'operator',
184
- });
185
- this.refresh();
186
- this.markDirty();
187
- return true;
188
- }
189
-
190
- return super.handleInput(key);
191
- }
192
-
193
- private refresh(): void {
194
- const queue = this.registry.reviewQueue(24);
195
- this.records = queue.length > 0 ? queue : this.registry.search({ limit: 24 });
196
- this.clampSelection();
197
- }
198
-
199
- public render(width: number, height: number): Line[] {
200
- this.clampSelection();
201
-
202
- // I1: show confirm dialog in place of normal content
203
- if (this.confirm) {
204
- return buildPanelWorkspace(width, height, {
205
- title: 'Knowledge Control Room',
206
- intro: '',
207
- sections: [{ title: 'Confirmation', lines: renderConfirmLines(width, this.confirm) }],
208
- footerLines: [buildPanelLine(width, [[' y confirm n / Esc cancel', C.dim]])],
209
- palette: C,
210
- });
211
- }
212
-
213
- if (this.records.length === 0) this.refresh();
214
-
215
- const intro = 'Typed project knowledge, reviewed evidence, and operator-governed memory across session, project, and team scopes.';
216
- const records = this.registry.search({ limit: 200 });
217
-
218
- if (records.length === 0) {
219
- return this.renderList(width, height, {
220
- title: 'Knowledge Control Room',
221
- footer: [buildPanelLine(width, [[' Review keys: Up/Down move r/Enter review s stale c contradicted f fresh', C.dim]])],
222
- });
223
- }
224
-
225
- const queue = this.registry.reviewQueue(24);
226
- const byClass = new Map<MemoryClass, number>();
227
- const byReview = new Map<MemoryReviewState, number>();
228
- const byScope = new Map<string, number>();
229
- for (const record of records) {
230
- byClass.set(record.cls, (byClass.get(record.cls) ?? 0) + 1);
231
- byReview.set(record.reviewState, (byReview.get(record.reviewState) ?? 0) + 1);
232
- byScope.set(record.scope, (byScope.get(record.scope) ?? 0) + 1);
233
- }
234
-
235
- const classLines: Line[] = [
236
- buildPanelLine(width, [
237
- [' facts ', C.label], [String(byClass.get('fact') ?? 0), C.good],
238
- [' risks ', C.label], [String(byClass.get('risk') ?? 0), (byClass.get('risk') ?? 0) > 0 ? C.warn : C.good],
239
- [' runbooks ', C.label], [String(byClass.get('runbook') ?? 0), C.info],
240
- [' architecture ', C.label], [String(byClass.get('architecture') ?? 0), C.info],
241
- [' incidents ', C.label], [String(byClass.get('incident') ?? 0), (byClass.get('incident') ?? 0) > 0 ? C.bad : C.good],
242
- ]),
243
- buildPanelLine(width, [
244
- [' decisions ', C.label], [String(byClass.get('decision') ?? 0), C.value],
245
- [' constraints ', C.label], [String(byClass.get('constraint') ?? 0), C.value],
246
- [' ownership ', C.label], [String(byClass.get('ownership') ?? 0), C.value],
247
- [' patterns ', C.label], [String(byClass.get('pattern') ?? 0), C.value],
248
- [' total ', C.label], [String(records.length), C.value],
249
- ]),
250
- ];
251
-
252
- const reviewLines: Line[] = [
253
- buildPanelLine(width, [
254
- [' reviewed ', C.label], [String(byReview.get('reviewed') ?? 0), C.good],
255
- [' fresh ', C.label], [String(byReview.get('fresh') ?? 0), C.info],
256
- [' stale ', C.label], [String(byReview.get('stale') ?? 0), C.warn],
257
- [' contradicted ', C.label], [String(byReview.get('contradicted') ?? 0), C.bad],
258
- [' Review Queue ', C.label], [String(queue.length), queue.length > 0 ? C.warn : C.good],
259
- ]),
260
- buildPanelLine(width, [
261
- [' session ', C.label], [String(byScope.get('session') ?? 0), C.info],
262
- [' project ', C.label], [String(byScope.get('project') ?? 0), C.value],
263
- [' team ', C.label], [String(byScope.get('team') ?? 0), C.good],
264
- ]),
265
- buildGuidanceLine(width, '/recall review', 'work the stale and contradicted queue from the command surface', C),
266
- ];
267
-
268
- const recentSummaryLines: Line[] = [];
269
- for (const [title, items, color] of [
270
- ['Recent Risks', summarize(records, 'risk'), C.warn],
271
- ['Runbooks', summarize(records, 'runbook'), C.info],
272
- ['Architecture Notes', summarize(records, 'architecture'), C.info],
273
- ['Recent Incidents', summarize(records, 'incident'), C.bad],
274
- ] as const) {
275
- if (recentSummaryLines.length > 0) {
276
- recentSummaryLines.push(buildPanelLine(width, [['', C.dim]]));
277
- }
278
- recentSummaryLines.push(buildPanelLine(width, [[` ${title}`, C.label]]));
279
- if (items.length === 0) {
280
- recentSummaryLines.push(buildPanelLine(width, [[' none recorded', C.dim]]));
281
- continue;
282
- }
283
- for (const item of items) {
284
- recentSummaryLines.push(buildPanelLine(width, [
285
- [' ', C.label],
286
- [item.summary.slice(0, Math.max(0, width - 2)), color],
287
- ]));
288
- }
289
- }
290
-
291
- const selectedRecord = this.records[this.selectedIndex];
292
- const selectedLines: Line[] = [];
293
- if (selectedRecord) {
294
- selectedLines.push(buildPanelLine(width, [[' Selected', C.label]]));
295
- selectedLines.push(buildKeyValueLine(width, [
296
- { label: 'Class', value: selectedRecord.cls, valueColor: C.value },
297
- { label: 'Scope', value: selectedRecord.scope, valueColor: C.info },
298
- { label: 'Review', value: selectedRecord.reviewState, valueColor: reviewStateColor(selectedRecord.reviewState) },
299
- { label: 'Confidence', value: formatConfidence(selectedRecord.confidence), valueColor: C.value },
300
- ], C));
301
- selectedLines.push(...buildBodyText(width, `Summary: ${selectedRecord.summary}`, C, C.value));
302
- if (selectedRecord.detail) selectedLines.push(...buildBodyText(width, `Detail: ${selectedRecord.detail}`, C, C.dim));
303
- if (selectedRecord.provenance.length) {
304
- selectedLines.push(...buildBodyText(
305
- width,
306
- `Provenance: ${selectedRecord.provenance.map((p) => `${p.kind}:${p.ref}`).join(', ')}`,
307
- C,
308
- C.dim,
309
- ));
310
- }
311
- if (selectedRecord.staleReason) {
312
- selectedLines.push(...buildBodyText(
313
- width,
314
- `Stale reason: ${selectedRecord.staleReason}`,
315
- C,
316
- selectedRecord.reviewState === 'contradicted' ? C.bad : C.warn,
317
- ));
318
- }
319
- if (selectedRecord.reviewedAt) {
320
- selectedLines.push(buildPanelLine(width, [
321
- [' Reviewed: ', C.label],
322
- [new Date(selectedRecord.reviewedAt).toLocaleString(), C.dim],
323
- ]));
324
- if (selectedRecord.reviewedBy) {
325
- selectedLines.push(buildPanelLine(width, [
326
- [' Reviewer: ', C.label],
327
- [selectedRecord.reviewedBy, C.dim],
328
- ]));
329
- }
330
- }
331
- }
332
-
333
- return this.renderList(width, height, {
334
- title: 'Knowledge Control Room',
335
- header: [...classLines, ...reviewLines],
336
- footer: [
337
- ...(selectedLines.length > 0 ? selectedLines : []),
338
- ...recentSummaryLines,
339
- buildPanelLine(width, [[' Up/Down move r/Enter reviewed s stale c contradicted f fresh', C.dim]]),
340
- ],
341
- });
342
- }
343
- }