@pellux/goodvibes-agent 0.1.69 → 0.1.71
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 +12 -0
- package/package.json +42 -1
- package/src/agent/skill-discovery.ts +119 -0
- package/src/input/commands/delegation-runtime.ts +0 -8
- package/src/input/commands/experience-runtime.ts +0 -177
- package/src/input/commands/guidance-runtime.ts +9 -77
- package/src/input/commands/local-runtime.ts +1 -57
- package/src/input/commands/local-setup-review.ts +1 -1
- package/src/input/commands/operator-runtime.ts +1 -145
- package/src/input/commands/platform-access-runtime.ts +2 -195
- package/src/input/commands/product-runtime.ts +0 -116
- package/src/input/commands/security-runtime.ts +88 -0
- package/src/input/commands/session-content.ts +0 -97
- package/src/input/commands/shell-core.ts +1 -22
- package/src/input/commands.ts +2 -43
- package/src/panels/builtin/operations.ts +3 -184
- package/src/panels/index.ts +0 -11
- package/src/version.ts +1 -1
- package/src/input/commands/branch-runtime.ts +0 -72
- package/src/input/commands/control-room-runtime.ts +0 -234
- package/src/input/commands/discovery-runtime.ts +0 -61
- package/src/input/commands/hooks-runtime.ts +0 -207
- package/src/input/commands/incident-runtime.ts +0 -106
- package/src/input/commands/integration-runtime.ts +0 -437
- package/src/input/commands/local-setup.ts +0 -288
- package/src/input/commands/managed-runtime.ts +0 -240
- package/src/input/commands/marketplace-runtime.ts +0 -305
- package/src/input/commands/memory-product-runtime.ts +0 -148
- package/src/input/commands/operator-panel-runtime.ts +0 -146
- package/src/input/commands/platform-services-runtime.ts +0 -271
- package/src/input/commands/profile-sync-runtime.ts +0 -110
- package/src/input/commands/provider.ts +0 -363
- package/src/input/commands/remote-runtime-pool.ts +0 -89
- package/src/input/commands/remote-runtime-setup.ts +0 -226
- package/src/input/commands/remote-runtime.ts +0 -432
- package/src/input/commands/replay-runtime.ts +0 -25
- package/src/input/commands/services-runtime.ts +0 -220
- package/src/input/commands/settings-sync-runtime.ts +0 -197
- package/src/input/commands/share-runtime.ts +0 -127
- package/src/input/commands/skills-runtime.ts +0 -226
- package/src/input/commands/teleport-runtime.ts +0 -68
- package/src/panels/cockpit-panel.ts +0 -183
- package/src/panels/communication-panel.ts +0 -153
- package/src/panels/control-plane-panel.ts +0 -211
- package/src/panels/forensics-panel.ts +0 -364
- package/src/panels/hooks-panel.ts +0 -239
- package/src/panels/incident-review-panel.ts +0 -197
- package/src/panels/marketplace-panel.ts +0 -212
- package/src/panels/ops-control-panel.ts +0 -150
- package/src/panels/ops-strategy-panel.ts +0 -235
- package/src/panels/orchestration-panel.ts +0 -272
- package/src/panels/plugins-panel.ts +0 -178
- package/src/panels/remote-panel.ts +0 -449
- package/src/panels/routes-panel.ts +0 -178
- package/src/panels/services-panel.ts +0 -231
- package/src/panels/settings-sync-panel.ts +0 -120
- package/src/panels/skills-panel.ts +0 -431
- package/src/panels/watchers-panel.ts +0 -193
- package/src/verification/live-verifier.ts +0 -588
- package/src/verification/verification-ledger.ts +0 -239
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import type { Line } from '../types/grid.ts';
|
|
2
|
-
import type { ForensicsRegistry } from '@/runtime/index.ts';
|
|
3
|
-
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
4
|
-
import {
|
|
5
|
-
buildBodyText,
|
|
6
|
-
buildEmptyState,
|
|
7
|
-
buildGuidanceLine,
|
|
8
|
-
buildKeyValueLine,
|
|
9
|
-
buildPanelLine,
|
|
10
|
-
buildPanelWorkspace,
|
|
11
|
-
buildStatusPill,
|
|
12
|
-
DEFAULT_PANEL_PALETTE,
|
|
13
|
-
type PanelPalette,
|
|
14
|
-
} from './polish.ts';
|
|
15
|
-
import type { FailureReport } from '@/runtime/index.ts';
|
|
16
|
-
|
|
17
|
-
const C = {
|
|
18
|
-
...DEFAULT_PANEL_PALETTE,
|
|
19
|
-
header: '#cbd5e1',
|
|
20
|
-
headerBg: '#0f172a',
|
|
21
|
-
warn: '#f59e0b',
|
|
22
|
-
bad: '#ef4444',
|
|
23
|
-
selectBg: '#111827',
|
|
24
|
-
} as const;
|
|
25
|
-
|
|
26
|
-
function classificationColor(value: string): string {
|
|
27
|
-
switch (value) {
|
|
28
|
-
case 'cancelled':
|
|
29
|
-
return C.dim;
|
|
30
|
-
case 'max_tokens':
|
|
31
|
-
case 'unknown':
|
|
32
|
-
return C.warn;
|
|
33
|
-
default:
|
|
34
|
-
return C.bad;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export class IncidentReviewPanel extends ScrollableListPanel<FailureReport> {
|
|
39
|
-
private readonly registry?: ForensicsRegistry;
|
|
40
|
-
private readonly unsub: (() => void) | null;
|
|
41
|
-
|
|
42
|
-
public constructor(registry?: ForensicsRegistry) {
|
|
43
|
-
super('incident', 'Incident Review', 'N', 'monitoring');
|
|
44
|
-
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
45
|
-
this.registry = registry;
|
|
46
|
-
this.unsub = registry ? registry.subscribe(() => this.markDirty()) : null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
public override onDestroy(): void {
|
|
50
|
-
this.unsub?.();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
protected override getPalette(): PanelPalette {
|
|
54
|
-
return C;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
protected getItems(): readonly FailureReport[] {
|
|
58
|
-
return this.registry?.getAll() ?? [];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
protected renderItem(report: FailureReport, index: number, selected: boolean, width: number): Line {
|
|
62
|
-
const bg = selected ? C.selectBg : undefined;
|
|
63
|
-
return buildPanelLine(width, [
|
|
64
|
-
[' ', C.label, bg],
|
|
65
|
-
[report.id.slice(0, 8).padEnd(9), C.dim, bg],
|
|
66
|
-
[report.classification.padEnd(20), classificationColor(report.classification), bg],
|
|
67
|
-
[report.summary.slice(0, Math.max(0, width - 31)), C.value, bg],
|
|
68
|
-
]);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
protected override getEmptyStateMessage(): string {
|
|
72
|
-
return ' No incidents recorded yet.';
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
protected override getEmptyStateActions(): Array<{ command: string; summary: string }> {
|
|
76
|
-
return [
|
|
77
|
-
{ command: '/incident latest', summary: 'inspect the latest report once one exists' },
|
|
78
|
-
{ command: '/recall capture incident latest', summary: 'promote incident evidence into project knowledge' },
|
|
79
|
-
];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
public render(width: number, height: number): Line[] {
|
|
83
|
-
const intro = 'Failure bundles, replay mismatches, permission fallout, and exportable review evidence.';
|
|
84
|
-
|
|
85
|
-
if (!this.registry) {
|
|
86
|
-
return buildPanelWorkspace(width, height, {
|
|
87
|
-
title: 'Incident Review Workspace',
|
|
88
|
-
intro,
|
|
89
|
-
sections: [{
|
|
90
|
-
lines: buildEmptyState(
|
|
91
|
-
width,
|
|
92
|
-
' Forensics registry not wired into this panel yet.',
|
|
93
|
-
'Incident review needs the live forensics registry so it can inspect failure bundles, replay mismatches, and causal evidence.',
|
|
94
|
-
[
|
|
95
|
-
{ command: '/incident latest', summary: 'inspect the latest incident from the command surface' },
|
|
96
|
-
{ command: '/security', summary: 'open the broader trust and incident posture control room' },
|
|
97
|
-
],
|
|
98
|
-
C,
|
|
99
|
-
),
|
|
100
|
-
}],
|
|
101
|
-
palette: C,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const reports = this.getItems();
|
|
106
|
-
if (reports.length === 0) {
|
|
107
|
-
return this.renderList(width, height, { title: 'Incident Review Workspace' });
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
this.clampSelection();
|
|
111
|
-
const selected = reports[this.selectedIndex]!;
|
|
112
|
-
const bundle = this.registry.buildBundle(selected.id);
|
|
113
|
-
|
|
114
|
-
const headerLines: Line[] = [
|
|
115
|
-
buildKeyValueLine(width, [
|
|
116
|
-
{ label: 'incidents', value: String(reports.length), valueColor: C.value },
|
|
117
|
-
{ label: 'selected', value: `${this.selectedIndex + 1}/${reports.length}`, valueColor: C.info },
|
|
118
|
-
{ label: 'classification', value: selected.classification, valueColor: classificationColor(selected.classification) },
|
|
119
|
-
], C),
|
|
120
|
-
buildPanelLine(width, [[' Up/Down move Home/End jump selected incident drives the action rail below', C.dim]]),
|
|
121
|
-
];
|
|
122
|
-
|
|
123
|
-
const footerLines: Line[] = [];
|
|
124
|
-
if (bundle) {
|
|
125
|
-
footerLines.push(buildKeyValueLine(width, [
|
|
126
|
-
{ label: 'id', value: selected.id, valueColor: C.dim },
|
|
127
|
-
{ label: 'trace', value: selected.traceId, valueColor: C.dim },
|
|
128
|
-
], C));
|
|
129
|
-
footerLines.push(...buildBodyText(width, `Root cause: ${bundle.evidence.rootCause ?? 'n/a'}`, C, C.value));
|
|
130
|
-
footerLines.push(buildKeyValueLine(width, [
|
|
131
|
-
{ label: 'Permissions denied', value: String(bundle.evidence.deniedPermissionCount), valueColor: bundle.evidence.deniedPermissionCount > 0 ? C.warn : C.dim },
|
|
132
|
-
{ label: 'Budget breaches', value: String(bundle.evidence.budgetBreachCount), valueColor: bundle.evidence.budgetBreachCount > 0 ? C.warn : C.dim },
|
|
133
|
-
{ label: 'Replay mismatches', value: String(bundle.replay.mismatchCount), valueColor: bundle.replay.mismatchCount > 0 ? C.bad : C.dim },
|
|
134
|
-
], C));
|
|
135
|
-
footerLines.push(buildPanelLine(width, [
|
|
136
|
-
[' Related IDs: ', C.label],
|
|
137
|
-
[`turn=${bundle.evidence.relatedIds.turnId ?? 'n/a'} task=${bundle.evidence.relatedIds.taskId ?? 'n/a'} agent=${bundle.evidence.relatedIds.agentId ?? 'n/a'}`.slice(0, Math.max(0, width - 14)), C.info],
|
|
138
|
-
]));
|
|
139
|
-
if (bundle.evidence.slowPhases.length > 0) {
|
|
140
|
-
footerLines.push(buildPanelLine(width, [
|
|
141
|
-
[' Slow phases: ', C.label],
|
|
142
|
-
...buildStatusPill('warn', bundle.evidence.slowPhases.join(', ').slice(0, Math.max(0, width - 15))),
|
|
143
|
-
]));
|
|
144
|
-
}
|
|
145
|
-
const rootCause = selected.causalChain.find((entry) => entry.isRootCause);
|
|
146
|
-
if (rootCause) {
|
|
147
|
-
footerLines.push(buildPanelLine(width, [
|
|
148
|
-
[' Root event: ', C.label],
|
|
149
|
-
[`${rootCause.sourceEventType} - ${rootCause.description}`.slice(0, Math.max(0, width - 14)), C.dim],
|
|
150
|
-
]));
|
|
151
|
-
}
|
|
152
|
-
const denied = selected.permissionEvidence.find((entry) => entry.approved === false);
|
|
153
|
-
if (denied) {
|
|
154
|
-
footerLines.push(buildPanelLine(width, [
|
|
155
|
-
[' Permission: ', C.label],
|
|
156
|
-
[`${denied.tool} denied${denied.riskLevel ? ` (${denied.riskLevel})` : ''}${denied.summary ? ` - ${denied.summary}` : ''}`.slice(0, Math.max(0, width - 14)), C.warn],
|
|
157
|
-
]));
|
|
158
|
-
}
|
|
159
|
-
if (bundle.replay.relatedMismatches.length > 0) {
|
|
160
|
-
const mismatch = bundle.replay.relatedMismatches[0]!;
|
|
161
|
-
const ownerBreakdown = Object.entries(bundle.replay.mismatchBreakdown.byOwnerDomain)
|
|
162
|
-
.filter(([, count]) => count > 0)
|
|
163
|
-
.slice(0, 3)
|
|
164
|
-
.map(([domain, count]) => `${domain}:${count}`)
|
|
165
|
-
.join(', ');
|
|
166
|
-
const replayDetail = ownerBreakdown.length > 0
|
|
167
|
-
? `Replay link: ${mismatch.kind}${mismatch.ownerDomain ? `/${mismatch.ownerDomain}` : ''} - ${mismatch.description} Replay owners: ${ownerBreakdown}`
|
|
168
|
-
: `Replay link: ${mismatch.kind}${mismatch.ownerDomain ? `/${mismatch.ownerDomain}` : ''} - ${mismatch.description}`;
|
|
169
|
-
footerLines.push(buildPanelLine(width, [
|
|
170
|
-
[' ', C.label],
|
|
171
|
-
...buildStatusPill('bad', replayDetail.slice(0, Math.max(0, width - 2))),
|
|
172
|
-
]));
|
|
173
|
-
} else {
|
|
174
|
-
const ownerBreakdown = Object.entries(bundle.replay.mismatchBreakdown.byOwnerDomain)
|
|
175
|
-
.filter(([, count]) => count > 0)
|
|
176
|
-
.slice(0, 3)
|
|
177
|
-
.map(([domain, count]) => `${domain}:${count}`)
|
|
178
|
-
.join(', ');
|
|
179
|
-
if (ownerBreakdown.length > 0) {
|
|
180
|
-
footerLines.push(buildPanelLine(width, [
|
|
181
|
-
[' Replay owners: ', C.label],
|
|
182
|
-
[ownerBreakdown.slice(0, Math.max(0, width - 17)), C.info],
|
|
183
|
-
]));
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
footerLines.push(buildPanelLine(width, [[' Action Rail', C.label]]));
|
|
188
|
-
footerLines.push(buildPanelLine(width, [[` /incident latest /incident export ${selected.id} <path> --yes /incident capture ${selected.id} --yes`, C.info]]));
|
|
189
|
-
footerLines.push(buildGuidanceLine(width, '/security', 'open the broader trust and incident posture control room', C));
|
|
190
|
-
|
|
191
|
-
return this.renderList(width, height, {
|
|
192
|
-
title: 'Incident Review Workspace',
|
|
193
|
-
header: headerLines,
|
|
194
|
-
footer: footerLines,
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
}
|
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
import type { Line } from '../types/grid.ts';
|
|
2
|
-
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
3
|
-
import {
|
|
4
|
-
buildEmptyState,
|
|
5
|
-
buildGuidanceLine,
|
|
6
|
-
buildKeyValueLine,
|
|
7
|
-
buildPanelLine,
|
|
8
|
-
buildPanelWorkspace,
|
|
9
|
-
DEFAULT_PANEL_PALETTE,
|
|
10
|
-
type PanelWorkspaceSection,
|
|
11
|
-
} from './polish.ts';
|
|
12
|
-
import {
|
|
13
|
-
type EcosystemCatalogPathOptions,
|
|
14
|
-
listInstalledEcosystemEntries,
|
|
15
|
-
loadEcosystemCatalog,
|
|
16
|
-
reviewEcosystemCatalogEntry,
|
|
17
|
-
type EcosystemCatalogEntry,
|
|
18
|
-
type EcosystemEntryKind,
|
|
19
|
-
} from '@/runtime/index.ts';
|
|
20
|
-
import type { UiMarketplaceSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
|
|
21
|
-
|
|
22
|
-
const C = {
|
|
23
|
-
...DEFAULT_PANEL_PALETTE,
|
|
24
|
-
header: '#e2e8f0',
|
|
25
|
-
headerBg: '#1f2937',
|
|
26
|
-
} as const;
|
|
27
|
-
|
|
28
|
-
type MarketplaceRow = {
|
|
29
|
-
kind: EcosystemEntryKind;
|
|
30
|
-
entry: EcosystemCatalogEntry;
|
|
31
|
-
installed: boolean;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
function statusColor(installed: boolean): string {
|
|
35
|
-
return installed ? C.good : C.dim;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export class MarketplacePanel extends ScrollableListPanel<MarketplaceRow> {
|
|
39
|
-
private rows: MarketplaceRow[] = [];
|
|
40
|
-
private readonly unsub: (() => void) | null;
|
|
41
|
-
|
|
42
|
-
public constructor(
|
|
43
|
-
private readonly readModel?: UiReadModel<UiMarketplaceSnapshot>,
|
|
44
|
-
private readonly ecosystemPaths?: EcosystemCatalogPathOptions,
|
|
45
|
-
) {
|
|
46
|
-
super('marketplace', 'Marketplace', 'M', 'monitoring');
|
|
47
|
-
this.unsub = readModel ? readModel.subscribe(() => this.markDirty()) : null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
public override onDestroy(): void {
|
|
51
|
-
this.unsub?.();
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
public override onActivate(): void {
|
|
55
|
-
super.onActivate();
|
|
56
|
-
this.refresh();
|
|
57
|
-
}
|
|
58
|
-
|
|
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
|
-
];
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
private refresh(): void {
|
|
95
|
-
if (!this.ecosystemPaths) {
|
|
96
|
-
this.rows = [];
|
|
97
|
-
this.clampSelection();
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
try {
|
|
101
|
-
const installedPlugins = new Set(listInstalledEcosystemEntries('plugin', this.ecosystemPaths).map((receipt) => receipt.entry.id));
|
|
102
|
-
const installedSkills = new Set(listInstalledEcosystemEntries('skill', this.ecosystemPaths).map((receipt) => receipt.entry.id));
|
|
103
|
-
const installedHookPacks = new Set(listInstalledEcosystemEntries('hook-pack', this.ecosystemPaths).map((receipt) => receipt.entry.id));
|
|
104
|
-
const installedPolicyPacks = new Set(listInstalledEcosystemEntries('policy-pack', this.ecosystemPaths).map((receipt) => receipt.entry.id));
|
|
105
|
-
const rows: MarketplaceRow[] = [
|
|
106
|
-
...loadEcosystemCatalog('plugin', this.ecosystemPaths).map((entry) => ({ kind: 'plugin' as const, entry, installed: installedPlugins.has(entry.id) })),
|
|
107
|
-
...loadEcosystemCatalog('skill', this.ecosystemPaths).map((entry) => ({ kind: 'skill' as const, entry, installed: installedSkills.has(entry.id) })),
|
|
108
|
-
...loadEcosystemCatalog('hook-pack', this.ecosystemPaths).map((entry) => ({ kind: 'hook-pack' as const, entry, installed: installedHookPacks.has(entry.id) })),
|
|
109
|
-
...loadEcosystemCatalog('policy-pack', this.ecosystemPaths).map((entry) => ({ kind: 'policy-pack' as const, entry, installed: installedPolicyPacks.has(entry.id) })),
|
|
110
|
-
];
|
|
111
|
-
this.rows = rows.sort((a, b) => a.entry.name.localeCompare(b.entry.name));
|
|
112
|
-
this.clampSelection();
|
|
113
|
-
// I2: clear any previous catalog load error on successful refresh
|
|
114
|
-
this.clearError();
|
|
115
|
-
} catch (e) {
|
|
116
|
-
// I2: surface catalog load failure
|
|
117
|
-
this.setError(`Catalog load failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
public render(width: number, height: number): Line[] {
|
|
122
|
-
this.clampSelection();
|
|
123
|
-
this.refresh();
|
|
124
|
-
|
|
125
|
-
const intro = 'Curated local-first ecosystem with provenance, compatibility, rollback history, and receipt-aware lifecycle review.';
|
|
126
|
-
const installedCount = this.rows.filter((row) => row.installed).length;
|
|
127
|
-
const snapshot = this.readModel?.getSnapshot();
|
|
128
|
-
const recommendations = snapshot?.recommendations ?? [];
|
|
129
|
-
const startupIssues = snapshot?.startupIssues ?? [];
|
|
130
|
-
|
|
131
|
-
if (this.rows.length === 0) {
|
|
132
|
-
return buildPanelWorkspace(width, height, {
|
|
133
|
-
title: 'Marketplace Control Room',
|
|
134
|
-
intro,
|
|
135
|
-
sections: [{
|
|
136
|
-
lines: buildEmptyState(
|
|
137
|
-
width,
|
|
138
|
-
this.ecosystemPaths ? ' No curated marketplace entries found yet.' : ' Marketplace catalog paths are not wired into this panel yet.',
|
|
139
|
-
this.ecosystemPaths
|
|
140
|
-
? 'The marketplace is ready, but no plugin, skill, hook-pack, or policy-pack catalogs are available in this workspace.'
|
|
141
|
-
: 'The shell needs explicit marketplace catalog roots before this panel can inspect curated plugin, skill, hook-pack, or policy-pack entries.',
|
|
142
|
-
[
|
|
143
|
-
{ command: '/marketplace bundle import <path>', summary: 'import a curated marketplace bundle' },
|
|
144
|
-
{ command: '/marketplace catalog review', summary: 'inspect the current local catalog posture' },
|
|
145
|
-
{ command: '/marketplace publish <kind> <path>', summary: 'publish local ecosystem entries back into the curated catalog' },
|
|
146
|
-
],
|
|
147
|
-
C,
|
|
148
|
-
),
|
|
149
|
-
}],
|
|
150
|
-
palette: C,
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const postureLines = [
|
|
155
|
-
buildKeyValueLine(width, [
|
|
156
|
-
{ label: 'curated', value: String(this.rows.length), valueColor: C.value },
|
|
157
|
-
{ label: 'installed', value: String(installedCount), valueColor: installedCount > 0 ? C.good : C.dim },
|
|
158
|
-
{ label: 'plugins', value: String(this.rows.filter((row) => row.kind === 'plugin').length), valueColor: C.info },
|
|
159
|
-
{ label: 'skills', value: String(this.rows.filter((row) => row.kind === 'skill').length), valueColor: C.info },
|
|
160
|
-
{ label: 'hooks', value: String(this.rows.filter((row) => row.kind === 'hook-pack').length), valueColor: C.info },
|
|
161
|
-
{ label: 'policies', value: String(this.rows.filter((row) => row.kind === 'policy-pack').length), valueColor: C.info },
|
|
162
|
-
], C),
|
|
163
|
-
buildGuidanceLine(width, '/marketplace open', 'browse curated entries and inspect compatibility, provenance, and receipts', C),
|
|
164
|
-
];
|
|
165
|
-
|
|
166
|
-
const recommendationLines = recommendations.length > 0
|
|
167
|
-
? recommendations.slice(0, 4).map((recommendation) => buildPanelLine(width, [
|
|
168
|
-
[' ', C.label],
|
|
169
|
-
[`${recommendation.kind} ${recommendation.entry.id}`.slice(0, 28).padEnd(28), C.info],
|
|
170
|
-
[` ${recommendation.title}`.slice(0, Math.max(0, width - 31)), C.dim],
|
|
171
|
-
]))
|
|
172
|
-
: [buildPanelLine(width, [[' No contextual marketplace recommendations right now.', C.dim]])];
|
|
173
|
-
|
|
174
|
-
const startupIssueLines = startupIssues.length > 0
|
|
175
|
-
? startupIssues.slice(0, 4).map((issue) => buildPanelLine(width, [[' ', C.label], [issue.slice(0, Math.max(0, width - 2)), C.warn]]))
|
|
176
|
-
: [buildPanelLine(width, [[' No startup or lifecycle issues are currently pushing marketplace repair recommendations.', C.dim]])];
|
|
177
|
-
|
|
178
|
-
const selectedRow = this.rows[this.selectedIndex];
|
|
179
|
-
const selectedLines: Line[] = [];
|
|
180
|
-
if (selectedRow) {
|
|
181
|
-
const review = reviewEcosystemCatalogEntry(selectedRow.entry, this.ecosystemPaths!);
|
|
182
|
-
selectedLines.push(buildPanelLine(width, [
|
|
183
|
-
[' Provenance: ', C.label],
|
|
184
|
-
[(selectedRow.entry.provenance ?? '(none)').slice(0, Math.max(0, width - 15)), selectedRow.entry.provenance ? C.info : C.dim],
|
|
185
|
-
]));
|
|
186
|
-
selectedLines.push(buildPanelLine(width, [
|
|
187
|
-
[' Source: ', C.label],
|
|
188
|
-
[selectedRow.entry.source.slice(0, Math.max(0, width - 11)), C.value],
|
|
189
|
-
]));
|
|
190
|
-
selectedLines.push(buildKeyValueLine(width, [
|
|
191
|
-
{ label: 'Compatibility', value: review.compatibility.status, valueColor: review.compatibility.status === 'supported' ? C.good : C.warn },
|
|
192
|
-
{ label: 'Risk', value: review.riskLevel, valueColor: review.riskLevel === 'low' ? C.good : C.warn },
|
|
193
|
-
{ label: 'State', value: selectedRow.installed ? 'installed' : 'curated', valueColor: statusColor(selectedRow.installed) },
|
|
194
|
-
], C));
|
|
195
|
-
selectedLines.push(buildGuidanceLine(width, '/marketplace review <id>', 'inspect full compatibility and receipt detail for the selected entry', C));
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const postureSection: PanelWorkspaceSection = { title: 'Marketplace posture', lines: postureLines };
|
|
199
|
-
const startupIssuesSection: PanelWorkspaceSection = { title: 'Startup Issues', lines: startupIssueLines };
|
|
200
|
-
const recommendationsSection: PanelWorkspaceSection = { title: 'Recommendations', lines: recommendationLines };
|
|
201
|
-
|
|
202
|
-
return this.renderList(width, height, {
|
|
203
|
-
title: 'Marketplace Control Room',
|
|
204
|
-
header: [
|
|
205
|
-
...postureLines,
|
|
206
|
-
...startupIssueLines,
|
|
207
|
-
...recommendationLines,
|
|
208
|
-
],
|
|
209
|
-
footer: selectedLines.length > 0 && height >= 20 ? selectedLines : [],
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
}
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpsControlPanel — operator intervention log panel.
|
|
3
|
-
*
|
|
4
|
-
* Renders the ops audit log sourced from the OpsPanel diagnostics subscriber.
|
|
5
|
-
* Each entry shows: seq, timestamp, action, target, outcome, and optional note.
|
|
6
|
-
*
|
|
7
|
-
* Requires the `operator-control-plane` feature flag to be enabled.
|
|
8
|
-
* Open via Ctrl+O keybind or `/ops view` command.
|
|
9
|
-
*/
|
|
10
|
-
import type { Line } from '../types/grid.ts';
|
|
11
|
-
import type { OpsEvent } from '@/runtime/index.ts';
|
|
12
|
-
import type { UiEventFeed } from '../runtime/ui-events.ts';
|
|
13
|
-
import type { OpsAuditEntry } from '../runtime/diagnostics/panels/ops.ts';
|
|
14
|
-
import { OpsPanel } from '../runtime/diagnostics/panels/ops.ts';
|
|
15
|
-
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
16
|
-
import {
|
|
17
|
-
buildPanelLine,
|
|
18
|
-
DEFAULT_PANEL_PALETTE,
|
|
19
|
-
type PanelPalette,
|
|
20
|
-
} from './polish.ts';
|
|
21
|
-
|
|
22
|
-
// ── Colour palette ──────────────────────────────────────────────────────────
|
|
23
|
-
const C = {
|
|
24
|
-
...DEFAULT_PANEL_PALETTE,
|
|
25
|
-
header: '#94a3b8',
|
|
26
|
-
headerBg: '#1e293b',
|
|
27
|
-
success: '#22c55e',
|
|
28
|
-
rejected: '#f97316',
|
|
29
|
-
error: '#ef4444',
|
|
30
|
-
dim: '#4b5563',
|
|
31
|
-
label: '#64748b',
|
|
32
|
-
value: '#cbd5e1',
|
|
33
|
-
note: '#eab308',
|
|
34
|
-
seq: '#475569',
|
|
35
|
-
taskColor: '#22d3ee',
|
|
36
|
-
agentColor: '#a78bfa',
|
|
37
|
-
empty: '#334155',
|
|
38
|
-
selectBg: '#0f172a',
|
|
39
|
-
} as const;
|
|
40
|
-
|
|
41
|
-
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
42
|
-
|
|
43
|
-
function fmtTime(ts: number): string {
|
|
44
|
-
const d = new Date(ts);
|
|
45
|
-
const hh = String(d.getHours()).padStart(2, '0');
|
|
46
|
-
const mm = String(d.getMinutes()).padStart(2, '0');
|
|
47
|
-
const ss = String(d.getSeconds()).padStart(2, '0');
|
|
48
|
-
return `${hh}:${mm}:${ss}`;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function outcomeColor(outcome: OpsAuditEntry['outcome']): string {
|
|
52
|
-
switch (outcome) {
|
|
53
|
-
case 'success': return C.success;
|
|
54
|
-
case 'rejected': return C.rejected;
|
|
55
|
-
case 'error': return C.error;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function outcomeLabel(outcome: OpsAuditEntry['outcome']): string {
|
|
60
|
-
switch (outcome) {
|
|
61
|
-
case 'success': return 'OK ';
|
|
62
|
-
case 'rejected': return 'REJECT';
|
|
63
|
-
case 'error': return 'ERR ';
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function targetColor(kind: OpsAuditEntry['targetKind']): string {
|
|
68
|
-
return kind === 'task' ? C.taskColor : C.agentColor;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// ── OpsControlPanel ──────────────────────────────────────────────────────────
|
|
72
|
-
|
|
73
|
-
export class OpsControlPanel extends ScrollableListPanel<OpsAuditEntry> {
|
|
74
|
-
private readonly _opsPanel: OpsPanel;
|
|
75
|
-
private _unsub: (() => void) | null = null;
|
|
76
|
-
|
|
77
|
-
public constructor(eventFeed: UiEventFeed<OpsEvent>) {
|
|
78
|
-
super('ops-control', 'Ops Control', 'Q', 'agent');
|
|
79
|
-
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
80
|
-
this._opsPanel = new OpsPanel(eventFeed);
|
|
81
|
-
this._unsub = this._opsPanel.subscribe(() => this.markDirty());
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
public override onActivate(): void {
|
|
85
|
-
super.onActivate();
|
|
86
|
-
this.selectedIndex = 0;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
public override onDestroy(): void {
|
|
90
|
-
if (this._unsub) {
|
|
91
|
-
this._unsub();
|
|
92
|
-
this._unsub = null;
|
|
93
|
-
}
|
|
94
|
-
this._opsPanel.dispose();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
protected override getPalette(): PanelPalette {
|
|
98
|
-
return C;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
protected getItems(): readonly OpsAuditEntry[] {
|
|
102
|
-
// Return reversed so newest entries appear at top
|
|
103
|
-
return [...this._opsPanel.getSnapshot()].reverse();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
protected renderItem(entry: OpsAuditEntry, _index: number, _selected: boolean, width: number): Line {
|
|
107
|
-
const seqStr = String(entry.seq).padStart(4, ' ');
|
|
108
|
-
const timeStr = fmtTime(entry.ts);
|
|
109
|
-
const action = entry.action.slice(0, 15).padEnd(15, ' ');
|
|
110
|
-
const kindTag = entry.targetKind === 'task' ? 'T:' : 'A:';
|
|
111
|
-
// Truncation is intentional: TUI column width limits target ID display to 14 chars
|
|
112
|
-
const shortId = entry.targetId.slice(-10);
|
|
113
|
-
const target = (kindTag + shortId).slice(0, 14).padEnd(14, ' ');
|
|
114
|
-
const outLabel = outcomeLabel(entry.outcome);
|
|
115
|
-
const noteRaw = (entry.note ?? entry.errorMessage ?? '').slice(0, Math.max(0, width - 63));
|
|
116
|
-
|
|
117
|
-
const segs: Array<[string, string, string?]> = [
|
|
118
|
-
[` ${seqStr} `, C.seq],
|
|
119
|
-
[`${timeStr} `, C.dim],
|
|
120
|
-
[`${action} `, C.value],
|
|
121
|
-
[`${target} `, targetColor(entry.targetKind)],
|
|
122
|
-
[outLabel, outcomeColor(entry.outcome)],
|
|
123
|
-
];
|
|
124
|
-
if (noteRaw) segs.push([` ${noteRaw}`, C.note]);
|
|
125
|
-
return buildPanelLine(width, segs);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
protected override getEmptyStateMessage(): string {
|
|
129
|
-
return ' No operator interventions recorded.';
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
protected override getEmptyStateActions(): Array<{ command: string; summary: string }> {
|
|
133
|
-
return [{ command: '/cockpit', summary: 'open the cockpit and review runtime interventions' }];
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
public render(width: number, height: number): Line[] {
|
|
137
|
-
const headerLines: Line[] = [
|
|
138
|
-
buildPanelLine(width, [[' SEQ TIME ACTION TARGET OUT NOTE', C.label]]),
|
|
139
|
-
];
|
|
140
|
-
const footerLines: Line[] = [
|
|
141
|
-
buildPanelLine(width, [[' Up/Down scroll the intervention log', C.dim]]),
|
|
142
|
-
];
|
|
143
|
-
|
|
144
|
-
return this.renderList(width, height, {
|
|
145
|
-
title: 'Operator Interventions',
|
|
146
|
-
header: headerLines,
|
|
147
|
-
footer: footerLines,
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
}
|