@pellux/goodvibes-tui 0.18.23 → 0.19.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.
- package/CHANGELOG.md +71 -0
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +7 -3
- package/src/core/conversation-rendering.ts +8 -6
- package/src/core/orchestrator.ts +1 -1
- package/src/daemon/cli.ts +54 -0
- package/src/input/commands/diff-runtime.ts +6 -5
- package/src/input/commands/guidance-runtime.ts +1 -1
- package/src/input/commands/health-runtime.ts +2 -2
- package/src/input/commands/local-setup-review.ts +1 -1
- package/src/input/commands/session-content.ts +1 -1
- package/src/input/commands/shell-core.ts +3 -2
- package/src/input/commands/skills-runtime.ts +2 -2
- package/src/input/commands/subscription-runtime.ts +4 -4
- package/src/input/handler.ts +8 -10
- package/src/input/model-picker.ts +6 -2
- package/src/input/panel-integration-actions.ts +2 -1
- package/src/input/settings-modal-types.ts +60 -0
- package/src/input/settings-modal.ts +83 -65
- package/src/main.ts +52 -0
- package/src/panels/agent-inspector-panel.ts +10 -9
- package/src/panels/agent-logs-panel.ts +26 -6
- package/src/panels/approval-panel.ts +1 -0
- package/src/panels/automation-control-panel.ts +1 -0
- package/src/panels/base-panel.ts +108 -3
- package/src/panels/communication-panel.ts +1 -0
- package/src/panels/context-visualizer-panel.ts +2 -0
- package/src/panels/control-plane-panel.ts +1 -0
- package/src/panels/diff-panel.ts +2 -0
- package/src/panels/file-explorer-panel.ts +51 -31
- package/src/panels/file-preview-panel.ts +57 -35
- package/src/panels/git-panel.ts +12 -13
- package/src/panels/hooks-panel.ts +3 -1
- package/src/panels/incident-review-panel.ts +4 -2
- package/src/panels/knowledge-panel.ts +75 -107
- package/src/panels/local-auth-panel.ts +1 -0
- package/src/panels/marketplace-panel.ts +51 -69
- package/src/panels/mcp-panel.ts +3 -1
- package/src/panels/memory-panel.ts +90 -158
- package/src/panels/ops-control-panel.ts +1 -0
- package/src/panels/orchestration-panel.ts +70 -51
- package/src/panels/panel-list-panel.ts +5 -4
- package/src/panels/panel-manager.ts +3 -0
- package/src/panels/plan-dashboard-panel.ts +2 -0
- package/src/panels/plugins-panel.ts +1 -0
- package/src/panels/polish.ts +51 -2
- package/src/panels/provider-accounts-panel.ts +1 -0
- package/src/panels/provider-health-panel.ts +6 -8
- package/src/panels/routes-panel.ts +3 -1
- package/src/panels/schedule-panel.ts +7 -6
- package/src/panels/scrollable-list-panel.ts +19 -2
- package/src/panels/security-panel.ts +17 -15
- package/src/panels/services-panel.ts +6 -4
- package/src/panels/session-browser-panel.ts +19 -18
- package/src/panels/settings-sync-panel.ts +3 -1
- package/src/panels/skills-panel.ts +114 -230
- package/src/panels/subscription-panel.ts +1 -0
- package/src/panels/system-messages-panel.ts +147 -141
- package/src/panels/tasks-panel.ts +1 -0
- package/src/panels/token-budget-panel.ts +2 -0
- package/src/panels/watchers-panel.ts +1 -0
- package/src/panels/worktree-panel.ts +1 -0
- package/src/panels/wrfc-panel.ts +2 -0
- package/src/renderer/agent-detail-modal.ts +2 -2
- package/src/renderer/ansi-sanitize.ts +76 -0
- package/src/renderer/buffer.ts +12 -1
- package/src/renderer/help-overlay.ts +14 -3
- package/src/renderer/model-picker-overlay.ts +9 -2
- package/src/renderer/settings-modal-helpers.ts +27 -0
- package/src/renderer/settings-modal.ts +18 -1
- package/src/renderer/status-glyphs.ts +21 -0
- package/src/renderer/status-token.ts +4 -8
- package/src/renderer/tool-call.ts +4 -3
- package/src/runtime/bootstrap-core.ts +1 -1
- package/src/runtime/bootstrap-hook-bridge.ts +1 -1
- package/src/runtime/bootstrap.ts +7 -8
- package/src/runtime/diagnostics/panels/policy.ts +2 -1
- package/src/shell/ui-openers.ts +44 -3
- package/src/version.ts +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Line } from '../types/grid.ts';
|
|
2
|
-
import {
|
|
2
|
+
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
3
3
|
import { type ConfirmState, handleConfirmInput, renderConfirmLines } from './confirm-state.ts';
|
|
4
4
|
import type { MemoryClass, MemoryRecord, MemoryRegistry, MemoryReviewState } from '@pellux/goodvibes-sdk/platform/state/memory-store';
|
|
5
5
|
import {
|
|
@@ -9,9 +9,7 @@ import {
|
|
|
9
9
|
buildKeyValueLine,
|
|
10
10
|
buildPanelLine,
|
|
11
11
|
buildPanelWorkspace,
|
|
12
|
-
resolveScrollablePanelSection,
|
|
13
12
|
DEFAULT_PANEL_PALETTE,
|
|
14
|
-
type PanelWorkspaceSection,
|
|
15
13
|
} from './polish.ts';
|
|
16
14
|
|
|
17
15
|
function summarize(records: MemoryRecord[], cls: MemoryClass): MemoryRecord[] {
|
|
@@ -42,11 +40,9 @@ function formatConfidence(confidence: number): string {
|
|
|
42
40
|
return `${confidence.toString().padStart(3, ' ')}%`;
|
|
43
41
|
}
|
|
44
42
|
|
|
45
|
-
export class KnowledgePanel extends
|
|
43
|
+
export class KnowledgePanel extends ScrollableListPanel<MemoryRecord> {
|
|
46
44
|
private readonly registry: MemoryRegistry;
|
|
47
45
|
private unsubscribe?: () => void;
|
|
48
|
-
private selectedIndex = 0;
|
|
49
|
-
private scrollOffset = 0;
|
|
50
46
|
private records: MemoryRecord[] = [];
|
|
51
47
|
// I1: confirm for destructive review-state mutations
|
|
52
48
|
private confirm: ConfirmState<{ id: string; action: 'stale' | 'contradicted' }> | null = null;
|
|
@@ -74,6 +70,38 @@ export class KnowledgePanel extends BasePanel {
|
|
|
74
70
|
this.unsubscribe = undefined;
|
|
75
71
|
}
|
|
76
72
|
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// ScrollableListPanel implementation
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
protected getItems(): readonly MemoryRecord[] {
|
|
78
|
+
return this.records;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
protected renderItem(record: MemoryRecord, index: number, selected: boolean, width: number): Line {
|
|
82
|
+
const bg = selected ? C.selectBg : undefined;
|
|
83
|
+
return buildPanelLine(width, [
|
|
84
|
+
[' ', C.label, bg],
|
|
85
|
+
[record.reviewState.padEnd(13), reviewStateColor(record.reviewState), bg],
|
|
86
|
+
[` ${formatConfidence(record.confidence)} `, C.value, bg],
|
|
87
|
+
[record.summary.slice(0, Math.max(0, width - 26)), C.value, bg],
|
|
88
|
+
]);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
protected override getPalette() { return C; }
|
|
92
|
+
protected override getEmptyStateMessage() { return 'No durable project knowledge'; }
|
|
93
|
+
protected override getEmptyStateActions() {
|
|
94
|
+
return [
|
|
95
|
+
{ command: '/recall add fact <summary>', summary: 'capture a durable fact directly' },
|
|
96
|
+
{ command: '/recall capture incident latest', summary: 'promote the latest incident into project memory' },
|
|
97
|
+
{ command: '/recall capture policy', summary: 'store the current policy posture as durable evidence' },
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// Input
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
|
|
77
105
|
public handleInput(key: string): boolean {
|
|
78
106
|
// I1: y/n confirm for stale/contradict
|
|
79
107
|
if (this.confirm) {
|
|
@@ -116,25 +144,13 @@ export class KnowledgePanel extends BasePanel {
|
|
|
116
144
|
if (result === 'absorbed') return true;
|
|
117
145
|
}
|
|
118
146
|
|
|
119
|
-
// I2: auto-clear error on next keypress
|
|
120
|
-
if (this.
|
|
121
|
-
|
|
122
|
-
if (this.records.length === 0) return false;
|
|
123
|
-
if (key === 'ArrowUp' || key === 'k') {
|
|
124
|
-
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
|
|
125
|
-
this.markDirty();
|
|
126
|
-
return true;
|
|
127
|
-
}
|
|
128
|
-
if (key === 'ArrowDown' || key === 'j') {
|
|
129
|
-
this.selectedIndex = Math.min(this.records.length - 1, this.selectedIndex + 1);
|
|
130
|
-
this.markDirty();
|
|
131
|
-
return true;
|
|
132
|
-
}
|
|
147
|
+
// I2: auto-clear error on next keypress (inherited via super.handleInput)
|
|
148
|
+
if (this.records.length === 0) return super.handleInput(key);
|
|
133
149
|
|
|
134
150
|
const selected = this.records[this.selectedIndex];
|
|
135
|
-
if (!selected) return false;
|
|
136
151
|
|
|
137
|
-
if (key === 'Enter' || key === 'r') {
|
|
152
|
+
if (key === 'Enter' || key === 'return' || key === 'r') {
|
|
153
|
+
if (!selected) return false;
|
|
138
154
|
this.registry.review(selected.id, {
|
|
139
155
|
state: 'reviewed',
|
|
140
156
|
confidence: Math.max(selected.confidence, 85),
|
|
@@ -145,18 +161,21 @@ export class KnowledgePanel extends BasePanel {
|
|
|
145
161
|
return true;
|
|
146
162
|
}
|
|
147
163
|
if (key === 's') {
|
|
164
|
+
if (!selected) return false;
|
|
148
165
|
// I1: prompt confirm before marking stale
|
|
149
166
|
this.confirm = { subject: { id: selected.id, action: 'stale' }, label: selected.summary.slice(0, 40) };
|
|
150
167
|
this.markDirty();
|
|
151
168
|
return true;
|
|
152
169
|
}
|
|
153
170
|
if (key === 'c') {
|
|
171
|
+
if (!selected) return false;
|
|
154
172
|
// I1: prompt confirm before marking contradicted
|
|
155
173
|
this.confirm = { subject: { id: selected.id, action: 'contradicted' }, label: selected.summary.slice(0, 40) };
|
|
156
174
|
this.markDirty();
|
|
157
175
|
return true;
|
|
158
176
|
}
|
|
159
177
|
if (key === 'f') {
|
|
178
|
+
if (!selected) return false;
|
|
160
179
|
this.registry.review(selected.id, {
|
|
161
180
|
state: 'fresh',
|
|
162
181
|
confidence: Math.max(selected.confidence, 60),
|
|
@@ -167,17 +186,20 @@ export class KnowledgePanel extends BasePanel {
|
|
|
167
186
|
return true;
|
|
168
187
|
}
|
|
169
188
|
|
|
170
|
-
|
|
189
|
+
// Normalize arrow keys to base class format
|
|
190
|
+
if (key === 'ArrowUp') return super.handleInput('up');
|
|
191
|
+
if (key === 'ArrowDown') return super.handleInput('down');
|
|
192
|
+
return super.handleInput(key);
|
|
171
193
|
}
|
|
172
194
|
|
|
173
195
|
private refresh(): void {
|
|
174
196
|
const queue = this.registry.reviewQueue(24);
|
|
175
197
|
this.records = queue.length > 0 ? queue : this.registry.search({ limit: 24 });
|
|
176
|
-
this.
|
|
198
|
+
this.clampSelection();
|
|
177
199
|
}
|
|
178
200
|
|
|
179
201
|
public render(width: number, height: number): Line[] {
|
|
180
|
-
this.
|
|
202
|
+
this.clampSelection();
|
|
181
203
|
|
|
182
204
|
// I1: show confirm dialog in place of normal content
|
|
183
205
|
if (this.confirm) {
|
|
@@ -196,26 +218,9 @@ export class KnowledgePanel extends BasePanel {
|
|
|
196
218
|
const records = this.registry.search({ limit: 200 });
|
|
197
219
|
|
|
198
220
|
if (records.length === 0) {
|
|
199
|
-
return
|
|
221
|
+
return this.renderList(width, height, {
|
|
200
222
|
title: 'Knowledge Control Room',
|
|
201
|
-
|
|
202
|
-
sections: [{
|
|
203
|
-
lines: buildEmptyState(
|
|
204
|
-
width,
|
|
205
|
-
' No durable project knowledge has been recorded yet.',
|
|
206
|
-
'The knowledge system is empty. It becomes useful once session, incident, task, and operator evidence are promoted into durable records.',
|
|
207
|
-
[
|
|
208
|
-
{ command: '/recall add fact <summary>', summary: 'capture a durable fact directly' },
|
|
209
|
-
{ command: '/recall capture incident latest', summary: 'promote the latest incident into project memory' },
|
|
210
|
-
{ command: '/recall capture policy', summary: 'store the current policy posture as durable evidence' },
|
|
211
|
-
],
|
|
212
|
-
C,
|
|
213
|
-
),
|
|
214
|
-
}],
|
|
215
|
-
footerLines: [
|
|
216
|
-
buildPanelLine(width, [[' Review keys: Up/Down move r/Enter review s stale c contradicted f fresh', C.dim]]),
|
|
217
|
-
],
|
|
218
|
-
palette: C,
|
|
223
|
+
footer: [buildPanelLine(width, [[' Review keys: Up/Down move r/Enter review s stale c contradicted f fresh', C.dim]])],
|
|
219
224
|
});
|
|
220
225
|
}
|
|
221
226
|
|
|
@@ -252,7 +257,7 @@ export class KnowledgePanel extends BasePanel {
|
|
|
252
257
|
[' fresh ', C.label], [String(byReview.get('fresh') ?? 0), C.info],
|
|
253
258
|
[' stale ', C.label], [String(byReview.get('stale') ?? 0), C.warn],
|
|
254
259
|
[' contradicted ', C.label], [String(byReview.get('contradicted') ?? 0), C.bad],
|
|
255
|
-
['
|
|
260
|
+
[' Review Queue ', C.label], [String(queue.length), queue.length > 0 ? C.warn : C.good],
|
|
256
261
|
]),
|
|
257
262
|
buildPanelLine(width, [
|
|
258
263
|
[' session ', C.label], [String(byScope.get('session') ?? 0), C.info],
|
|
@@ -285,93 +290,56 @@ export class KnowledgePanel extends BasePanel {
|
|
|
285
290
|
}
|
|
286
291
|
}
|
|
287
292
|
|
|
288
|
-
const
|
|
293
|
+
const selectedRecord = this.records[this.selectedIndex];
|
|
289
294
|
const selectedLines: Line[] = [];
|
|
290
|
-
if (
|
|
295
|
+
if (selectedRecord) {
|
|
296
|
+
selectedLines.push(buildPanelLine(width, [[' Selected', C.label]]));
|
|
291
297
|
selectedLines.push(buildKeyValueLine(width, [
|
|
292
|
-
{ label: 'Class', value:
|
|
293
|
-
{ label: 'Scope', value:
|
|
294
|
-
{ label: 'Review', value:
|
|
295
|
-
{ label: 'Confidence', value: formatConfidence(
|
|
298
|
+
{ label: 'Class', value: selectedRecord.cls, valueColor: C.value },
|
|
299
|
+
{ label: 'Scope', value: selectedRecord.scope, valueColor: C.info },
|
|
300
|
+
{ label: 'Review', value: selectedRecord.reviewState, valueColor: reviewStateColor(selectedRecord.reviewState) },
|
|
301
|
+
{ label: 'Confidence', value: formatConfidence(selectedRecord.confidence), valueColor: C.value },
|
|
296
302
|
], C));
|
|
297
|
-
selectedLines.push(...buildBodyText(width, `Summary: ${
|
|
298
|
-
if (
|
|
299
|
-
if (
|
|
303
|
+
selectedLines.push(...buildBodyText(width, `Summary: ${selectedRecord.summary}`, C, C.value));
|
|
304
|
+
if (selectedRecord.detail) selectedLines.push(...buildBodyText(width, `Detail: ${selectedRecord.detail}`, C, C.dim));
|
|
305
|
+
if (selectedRecord.provenance.length) {
|
|
300
306
|
selectedLines.push(...buildBodyText(
|
|
301
307
|
width,
|
|
302
|
-
`Provenance: ${
|
|
308
|
+
`Provenance: ${selectedRecord.provenance.map((p) => `${p.kind}:${p.ref}`).join(', ')}`,
|
|
303
309
|
C,
|
|
304
310
|
C.dim,
|
|
305
311
|
));
|
|
306
312
|
}
|
|
307
|
-
if (
|
|
313
|
+
if (selectedRecord.staleReason) {
|
|
308
314
|
selectedLines.push(...buildBodyText(
|
|
309
315
|
width,
|
|
310
|
-
`Stale reason: ${
|
|
316
|
+
`Stale reason: ${selectedRecord.staleReason}`,
|
|
311
317
|
C,
|
|
312
|
-
|
|
318
|
+
selectedRecord.reviewState === 'contradicted' ? C.bad : C.warn,
|
|
313
319
|
));
|
|
314
320
|
}
|
|
315
|
-
if (
|
|
321
|
+
if (selectedRecord.reviewedAt) {
|
|
316
322
|
selectedLines.push(buildPanelLine(width, [
|
|
317
323
|
[' Reviewed: ', C.label],
|
|
318
|
-
[new Date(
|
|
324
|
+
[new Date(selectedRecord.reviewedAt).toLocaleString(), C.dim],
|
|
319
325
|
]));
|
|
320
|
-
if (
|
|
326
|
+
if (selectedRecord.reviewedBy) {
|
|
321
327
|
selectedLines.push(buildPanelLine(width, [
|
|
322
328
|
[' Reviewer: ', C.label],
|
|
323
|
-
[
|
|
329
|
+
[selectedRecord.reviewedBy, C.dim],
|
|
324
330
|
]));
|
|
325
331
|
}
|
|
326
332
|
}
|
|
327
333
|
}
|
|
328
334
|
|
|
329
|
-
|
|
330
|
-
buildPanelLine(width, [[' Up/Down move r/Enter reviewed s stale c contradicted f fresh', C.dim]]),
|
|
331
|
-
];
|
|
332
|
-
const classesSection: PanelWorkspaceSection = { title: 'Classes', lines: classLines };
|
|
333
|
-
const reviewStateSection: PanelWorkspaceSection = { title: 'Review State', lines: reviewLines };
|
|
334
|
-
const selectedSection: PanelWorkspaceSection = selectedLines.length > 0 ? { title: 'Selected', lines: selectedLines } : { title: 'Selected', lines: [] };
|
|
335
|
-
const recentSection: PanelWorkspaceSection = { title: 'Recent Risks / Runbooks / Architecture Notes', lines: recentSummaryLines };
|
|
336
|
-
const queueSection = resolveScrollablePanelSection(width, height, {
|
|
337
|
-
intro,
|
|
338
|
-
footerLines,
|
|
339
|
-
palette: C,
|
|
340
|
-
beforeSections: [classesSection, reviewStateSection],
|
|
341
|
-
section: {
|
|
342
|
-
title: 'Review Queue',
|
|
343
|
-
scrollableLines: this.records.map((record, globalIndex) => {
|
|
344
|
-
const bg = globalIndex === this.selectedIndex ? C.selectBg : undefined;
|
|
345
|
-
return buildPanelLine(width, [
|
|
346
|
-
[' ', C.label, bg],
|
|
347
|
-
[record.reviewState.padEnd(13), reviewStateColor(record.reviewState), bg],
|
|
348
|
-
[` ${formatConfidence(record.confidence)} `, C.value, bg],
|
|
349
|
-
[record.summary.slice(0, Math.max(0, width - 26)), C.value, bg],
|
|
350
|
-
]);
|
|
351
|
-
}),
|
|
352
|
-
selectedIndex: this.selectedIndex,
|
|
353
|
-
scrollOffset: this.scrollOffset,
|
|
354
|
-
minRows: 4,
|
|
355
|
-
appendWindowSummary: { dimColor: C.dim },
|
|
356
|
-
},
|
|
357
|
-
afterSections: selectedLines.length > 0 ? [selectedSection, recentSection] : [recentSection],
|
|
358
|
-
});
|
|
359
|
-
this.scrollOffset = queueSection.scrollOffset;
|
|
360
|
-
|
|
361
|
-
const sections: PanelWorkspaceSection[] = [
|
|
362
|
-
classesSection,
|
|
363
|
-
reviewStateSection,
|
|
364
|
-
queueSection.section,
|
|
365
|
-
];
|
|
366
|
-
if (selectedLines.length > 0) sections.push(selectedSection);
|
|
367
|
-
sections.push(recentSection);
|
|
368
|
-
|
|
369
|
-
return buildPanelWorkspace(width, height, {
|
|
335
|
+
return this.renderList(width, height, {
|
|
370
336
|
title: 'Knowledge Control Room',
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
337
|
+
header: [...classLines, ...reviewLines],
|
|
338
|
+
footer: [
|
|
339
|
+
...(selectedLines.length > 0 ? selectedLines : []),
|
|
340
|
+
...recentSummaryLines,
|
|
341
|
+
buildPanelLine(width, [[' Up/Down move r/Enter reviewed s stale c contradicted f fresh', C.dim]]),
|
|
342
|
+
],
|
|
375
343
|
});
|
|
376
344
|
}
|
|
377
345
|
}
|
|
@@ -33,6 +33,7 @@ export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
|
|
|
33
33
|
|
|
34
34
|
public constructor(authManager: LocalAuthInspectionQuery) {
|
|
35
35
|
super('local-auth', 'Local Auth', 'U', 'monitoring');
|
|
36
|
+
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
36
37
|
this.authManager = authManager;
|
|
37
38
|
}
|
|
38
39
|
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import type { Line } from '../types/grid.ts';
|
|
2
|
-
import {
|
|
2
|
+
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
3
3
|
import {
|
|
4
4
|
buildEmptyState,
|
|
5
5
|
buildGuidanceLine,
|
|
6
6
|
buildKeyValueLine,
|
|
7
7
|
buildPanelLine,
|
|
8
8
|
buildPanelWorkspace,
|
|
9
|
-
resolveScrollablePanelSection,
|
|
10
9
|
DEFAULT_PANEL_PALETTE,
|
|
11
10
|
type PanelWorkspaceSection,
|
|
12
11
|
} from './polish.ts';
|
|
@@ -36,10 +35,8 @@ function statusColor(installed: boolean): string {
|
|
|
36
35
|
return installed ? C.good : C.dim;
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
export class MarketplacePanel extends
|
|
38
|
+
export class MarketplacePanel extends ScrollableListPanel<MarketplaceRow> {
|
|
40
39
|
private rows: MarketplaceRow[] = [];
|
|
41
|
-
private selectedIndex = 0;
|
|
42
|
-
private scrollOffset = 0;
|
|
43
40
|
private readonly unsub: (() => void) | null;
|
|
44
41
|
|
|
45
42
|
public constructor(
|
|
@@ -59,26 +56,45 @@ export class MarketplacePanel extends BasePanel {
|
|
|
59
56
|
this.refresh();
|
|
60
57
|
}
|
|
61
58
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// ScrollableListPanel implementation
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
protected getItems(): readonly MarketplaceRow[] {
|
|
64
|
+
return this.rows;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
protected renderItem(row: MarketplaceRow, index: number, selected: boolean, width: number): Line {
|
|
68
|
+
const bg = selected ? C.selectBg : undefined;
|
|
69
|
+
const provenance = row.entry.provenance ?? 'local';
|
|
70
|
+
return buildPanelLine(width, [
|
|
71
|
+
[' ', C.label, bg],
|
|
72
|
+
[row.kind.padEnd(11), C.info, bg],
|
|
73
|
+
[row.entry.name.slice(0, 20).padEnd(20), C.value, bg],
|
|
74
|
+
[` ${provenance.slice(0, 16).padEnd(16)}`, provenance === 'local' ? C.dim : C.info, bg],
|
|
75
|
+
[` ${(row.installed ? 'INSTALLED' : 'CURATED').padEnd(9)} `, statusColor(row.installed), bg],
|
|
76
|
+
[` ${row.entry.version ?? 'n/a'}`, C.dim, bg],
|
|
77
|
+
]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
protected override getPalette() { return C; }
|
|
81
|
+
protected override getEmptyStateMessage() {
|
|
82
|
+
return this.ecosystemPaths
|
|
83
|
+
? ' No curated marketplace entries found yet.'
|
|
84
|
+
: ' Marketplace catalog paths are not wired into this panel yet.';
|
|
85
|
+
}
|
|
86
|
+
protected override getEmptyStateActions() {
|
|
87
|
+
return [
|
|
88
|
+
{ command: '/marketplace bundle import <path>', summary: 'import a curated marketplace bundle' },
|
|
89
|
+
{ command: '/marketplace catalog review', summary: 'inspect the current local catalog posture' },
|
|
90
|
+
{ command: '/marketplace publish <kind> <path>', summary: 'publish local ecosystem entries back into the curated catalog' },
|
|
91
|
+
];
|
|
75
92
|
}
|
|
76
93
|
|
|
77
94
|
private refresh(): void {
|
|
78
95
|
if (!this.ecosystemPaths) {
|
|
79
96
|
this.rows = [];
|
|
80
|
-
this.
|
|
81
|
-
this.scrollOffset = 0;
|
|
97
|
+
this.clampSelection();
|
|
82
98
|
return;
|
|
83
99
|
}
|
|
84
100
|
try {
|
|
@@ -93,7 +109,7 @@ export class MarketplacePanel extends BasePanel {
|
|
|
93
109
|
...loadEcosystemCatalog('policy-pack', this.ecosystemPaths).map((entry) => ({ kind: 'policy-pack' as const, entry, installed: installedPolicyPacks.has(entry.id) })),
|
|
94
110
|
];
|
|
95
111
|
this.rows = rows.sort((a, b) => a.entry.name.localeCompare(b.entry.name));
|
|
96
|
-
this.
|
|
112
|
+
this.clampSelection();
|
|
97
113
|
// I2: clear any previous catalog load error on successful refresh
|
|
98
114
|
this.clearError();
|
|
99
115
|
} catch (e) {
|
|
@@ -103,7 +119,7 @@ export class MarketplacePanel extends BasePanel {
|
|
|
103
119
|
}
|
|
104
120
|
|
|
105
121
|
public render(width: number, height: number): Line[] {
|
|
106
|
-
this.
|
|
122
|
+
this.clampSelection();
|
|
107
123
|
this.refresh();
|
|
108
124
|
|
|
109
125
|
const intro = 'Curated local-first ecosystem with provenance, compatibility, rollback history, and receipt-aware lifecycle review.';
|
|
@@ -159,22 +175,22 @@ export class MarketplacePanel extends BasePanel {
|
|
|
159
175
|
? startupIssues.slice(0, 4).map((issue) => buildPanelLine(width, [[' ', C.label], [issue.slice(0, Math.max(0, width - 2)), C.warn]]))
|
|
160
176
|
: [buildPanelLine(width, [[' No startup or lifecycle issues are currently pushing marketplace repair recommendations.', C.dim]])];
|
|
161
177
|
|
|
162
|
-
const
|
|
178
|
+
const selectedRow = this.rows[this.selectedIndex];
|
|
163
179
|
const selectedLines: Line[] = [];
|
|
164
|
-
if (
|
|
165
|
-
const review = reviewEcosystemCatalogEntry(
|
|
180
|
+
if (selectedRow) {
|
|
181
|
+
const review = reviewEcosystemCatalogEntry(selectedRow.entry, this.ecosystemPaths!);
|
|
166
182
|
selectedLines.push(buildPanelLine(width, [
|
|
167
183
|
[' Provenance: ', C.label],
|
|
168
|
-
[(
|
|
184
|
+
[(selectedRow.entry.provenance ?? '(none)').slice(0, Math.max(0, width - 15)), selectedRow.entry.provenance ? C.info : C.dim],
|
|
169
185
|
]));
|
|
170
186
|
selectedLines.push(buildPanelLine(width, [
|
|
171
187
|
[' Source: ', C.label],
|
|
172
|
-
[
|
|
188
|
+
[selectedRow.entry.source.slice(0, Math.max(0, width - 11)), C.value],
|
|
173
189
|
]));
|
|
174
190
|
selectedLines.push(buildKeyValueLine(width, [
|
|
175
191
|
{ label: 'Compatibility', value: review.compatibility.status, valueColor: review.compatibility.status === 'compatible' ? C.good : C.warn },
|
|
176
192
|
{ label: 'Risk', value: review.riskLevel, valueColor: review.riskLevel === 'low' ? C.good : C.warn },
|
|
177
|
-
{ label: 'State', value:
|
|
193
|
+
{ label: 'State', value: selectedRow.installed ? 'installed' : 'curated', valueColor: statusColor(selectedRow.installed) },
|
|
178
194
|
], C));
|
|
179
195
|
selectedLines.push(buildGuidanceLine(width, '/marketplace review <id>', 'inspect full compatibility and receipt detail for the selected entry', C));
|
|
180
196
|
}
|
|
@@ -182,49 +198,15 @@ export class MarketplacePanel extends BasePanel {
|
|
|
182
198
|
const postureSection: PanelWorkspaceSection = { title: 'Marketplace posture', lines: postureLines };
|
|
183
199
|
const startupIssuesSection: PanelWorkspaceSection = { title: 'Startup Issues', lines: startupIssueLines };
|
|
184
200
|
const recommendationsSection: PanelWorkspaceSection = { title: 'Recommendations', lines: recommendationLines };
|
|
185
|
-
const selectedSection: PanelWorkspaceSection = { title: 'Selected', lines: selectedLines };
|
|
186
|
-
const catalogSection = resolveScrollablePanelSection(width, height, {
|
|
187
|
-
intro,
|
|
188
|
-
palette: C,
|
|
189
|
-
beforeSections: [postureSection, startupIssuesSection, recommendationsSection],
|
|
190
|
-
section: {
|
|
191
|
-
title: 'Catalog',
|
|
192
|
-
scrollableLines: this.rows.map((row, globalIndex) => {
|
|
193
|
-
const bg = globalIndex === this.selectedIndex ? C.selectBg : undefined;
|
|
194
|
-
const provenance = row.entry.provenance ?? 'local';
|
|
195
|
-
return buildPanelLine(width, [
|
|
196
|
-
[' ', C.label, bg],
|
|
197
|
-
[row.kind.padEnd(11), C.info, bg],
|
|
198
|
-
[row.entry.name.slice(0, 20).padEnd(20), C.value, bg],
|
|
199
|
-
[` ${provenance.slice(0, 16).padEnd(16)}`, provenance === 'local' ? C.dim : C.info, bg],
|
|
200
|
-
[` ${(row.installed ? 'INSTALLED' : 'CURATED').padEnd(9)} `, statusColor(row.installed), bg],
|
|
201
|
-
[` ${row.entry.version ?? 'n/a'}`, C.dim, bg],
|
|
202
|
-
]);
|
|
203
|
-
}),
|
|
204
|
-
selectedIndex: this.selectedIndex,
|
|
205
|
-
scrollOffset: this.scrollOffset,
|
|
206
|
-
minRows: 4,
|
|
207
|
-
appendWindowSummary: { dimColor: C.dim },
|
|
208
|
-
},
|
|
209
|
-
afterSections: selectedLines.length > 0 && height >= 20 ? [selectedSection] : [],
|
|
210
|
-
});
|
|
211
|
-
this.scrollOffset = catalogSection.scrollOffset;
|
|
212
|
-
|
|
213
|
-
const sections: PanelWorkspaceSection[] = [
|
|
214
|
-
postureSection,
|
|
215
|
-
startupIssuesSection,
|
|
216
|
-
recommendationsSection,
|
|
217
|
-
catalogSection.section,
|
|
218
|
-
];
|
|
219
|
-
if (selectedLines.length > 0 && height >= 20) {
|
|
220
|
-
sections.push(selectedSection);
|
|
221
|
-
}
|
|
222
201
|
|
|
223
|
-
return
|
|
202
|
+
return this.renderList(width, height, {
|
|
224
203
|
title: 'Marketplace Control Room',
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
204
|
+
header: [
|
|
205
|
+
...postureLines,
|
|
206
|
+
...startupIssueLines,
|
|
207
|
+
...recommendationLines,
|
|
208
|
+
],
|
|
209
|
+
footer: selectedLines.length > 0 && height >= 20 ? selectedLines : [],
|
|
228
210
|
});
|
|
229
211
|
}
|
|
230
212
|
}
|
package/src/panels/mcp-panel.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
buildGuidanceLine,
|
|
10
10
|
buildKeyValueLine,
|
|
11
11
|
buildPanelLine,
|
|
12
|
+
buildStatusPill,
|
|
12
13
|
DEFAULT_PANEL_PALETTE,
|
|
13
14
|
} from './polish.ts';
|
|
14
15
|
|
|
@@ -62,6 +63,7 @@ export class McpPanel extends ScrollableListPanel<McpServerSecurityEntry> {
|
|
|
62
63
|
|
|
63
64
|
public constructor(registry: McpRegistry) {
|
|
64
65
|
super('mcp', 'MCP', 'Z', 'monitoring');
|
|
66
|
+
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
65
67
|
this.registry = registry;
|
|
66
68
|
}
|
|
67
69
|
|
|
@@ -83,7 +85,7 @@ export class McpPanel extends ScrollableListPanel<McpServerSecurityEntry> {
|
|
|
83
85
|
return buildPanelLine(width, [
|
|
84
86
|
[' ', C.label, bg],
|
|
85
87
|
[entry.name.padEnd(20), C.value, bg],
|
|
86
|
-
|
|
88
|
+
...buildStatusPill(entry.connected ? 'good' : 'bad', ` ${(entry.connected ? 'CONNECTED' : 'DISCONNECTED').padEnd(13)}`, { bg }),
|
|
87
89
|
[` ${entry.trustMode.padEnd(12)}`, modeColor(entry.trustMode), bg],
|
|
88
90
|
[` ${entry.role.padEnd(10)}`, C.info, bg],
|
|
89
91
|
[` ${entry.schemaFreshness}`, freshnessColor(entry.schemaFreshness), bg],
|