@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.
- package/CHANGELOG.md +23 -0
- package/README.md +1 -1
- package/package.json +2 -1
- package/src/cli/completions/generate.ts +4 -8
- package/src/cli/entrypoint.ts +6 -0
- package/src/cli/parser.ts +17 -0
- package/src/cli/types.ts +2 -0
- package/src/config/goodvibes-home-audit.ts +2 -0
- package/src/core/context-auto-compact.ts +77 -0
- package/src/core/turn-event-wiring.ts +124 -0
- package/src/daemon/cli.ts +5 -0
- package/src/input/command-registry.ts +1 -0
- package/src/input/commands/control-room-runtime.ts +5 -5
- package/src/input/commands/provider.ts +57 -3
- package/src/input/commands/session-workflow.ts +8 -16
- package/src/input/commands/session.ts +70 -20
- package/src/input/commands.ts +0 -2
- package/src/input/handler-modal-routes.ts +37 -0
- package/src/input/handler-modal-token-routes.ts +19 -5
- package/src/input/handler-onboarding.ts +18 -0
- package/src/input/handler.ts +1 -0
- package/src/input/onboarding/onboarding-wizard-apply.ts +10 -0
- package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +14 -0
- package/src/input/onboarding/onboarding-wizard-steps.ts +6 -0
- package/src/input/onboarding/onboarding-wizard-validation.ts +77 -0
- package/src/input/settings-modal-behavior.ts +5 -0
- package/src/input/settings-modal-data.ts +77 -3
- package/src/input/settings-modal-mutations.ts +3 -0
- package/src/input/settings-modal-reset.ts +154 -0
- package/src/input/settings-modal.ts +55 -13
- package/src/main.ts +36 -28
- package/src/panels/agent-inspector-panel.ts +120 -18
- package/src/panels/agent-inspector-shared.ts +29 -0
- package/src/panels/builtin/development.ts +1 -0
- package/src/panels/builtin/knowledge.ts +14 -13
- package/src/panels/builtin/operations.ts +22 -1
- package/src/panels/builtin/shared.ts +7 -0
- package/src/panels/cockpit-panel.ts +123 -3
- package/src/panels/cockpit-read-model.ts +232 -0
- package/src/panels/index.ts +1 -1
- package/src/panels/knowledge-graph-panel.ts +84 -0
- package/src/panels/memory-panel.ts +370 -40
- package/src/panels/session-maintenance.ts +66 -15
- package/src/renderer/agent-detail-modal.ts +107 -3
- package/src/renderer/context-status-hint.ts +54 -0
- package/src/renderer/settings-modal.ts +14 -3
- package/src/renderer/shell-surface.ts +10 -0
- package/src/runtime/bootstrap-command-parts.ts +4 -0
- package/src/runtime/bootstrap-core.ts +24 -0
- package/src/runtime/bootstrap-shell.ts +11 -0
- package/src/runtime/bootstrap.ts +7 -0
- package/src/runtime/services.ts +6 -1
- package/src/version.ts +1 -1
- 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
|
-
}
|