@pellux/goodvibes-agent 0.1.70 → 0.1.72

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 (78) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +3 -3
  3. package/docs/README.md +2 -2
  4. package/docs/getting-started.md +1 -1
  5. package/docs/runtime-connection.md +37 -0
  6. package/package.json +43 -2
  7. package/src/agent/skill-discovery.ts +119 -0
  8. package/src/cli/config-overrides.ts +1 -5
  9. package/src/cli/entrypoint.ts +0 -6
  10. package/src/cli/help.ts +0 -43
  11. package/src/cli/index.ts +0 -2
  12. package/src/cli/management-commands.ts +1 -109
  13. package/src/cli/management.ts +1 -32
  14. package/src/cli/package-verification.ts +12 -4
  15. package/src/cli/parser.ts +0 -16
  16. package/src/cli/status.ts +1 -1
  17. package/src/cli/types.ts +0 -8
  18. package/src/input/commands/delegation-runtime.ts +0 -8
  19. package/src/input/commands/experience-runtime.ts +0 -177
  20. package/src/input/commands/guidance-runtime.ts +0 -69
  21. package/src/input/commands/local-runtime.ts +1 -57
  22. package/src/input/commands/local-setup-review.ts +1 -1
  23. package/src/input/commands/operator-runtime.ts +1 -145
  24. package/src/input/commands/platform-access-runtime.ts +2 -195
  25. package/src/input/commands/product-runtime.ts +0 -116
  26. package/src/input/commands/security-runtime.ts +88 -0
  27. package/src/input/commands/session-content.ts +0 -97
  28. package/src/input/commands/shell-core.ts +0 -13
  29. package/src/input/commands.ts +2 -95
  30. package/src/panels/builtin/operations.ts +3 -184
  31. package/src/panels/confirm-state.ts +1 -1
  32. package/src/panels/index.ts +0 -11
  33. package/src/version.ts +1 -1
  34. package/docs/deployment-and-services.md +0 -52
  35. package/src/cli/service-command.ts +0 -26
  36. package/src/cli/surface-command.ts +0 -247
  37. package/src/input/commands/branch-runtime.ts +0 -72
  38. package/src/input/commands/control-room-runtime.ts +0 -234
  39. package/src/input/commands/discovery-runtime.ts +0 -61
  40. package/src/input/commands/hooks-runtime.ts +0 -207
  41. package/src/input/commands/incident-runtime.ts +0 -106
  42. package/src/input/commands/integration-runtime.ts +0 -437
  43. package/src/input/commands/local-setup.ts +0 -288
  44. package/src/input/commands/managed-runtime.ts +0 -240
  45. package/src/input/commands/marketplace-runtime.ts +0 -305
  46. package/src/input/commands/memory-product-runtime.ts +0 -148
  47. package/src/input/commands/operator-panel-runtime.ts +0 -146
  48. package/src/input/commands/platform-services-runtime.ts +0 -271
  49. package/src/input/commands/profile-sync-runtime.ts +0 -110
  50. package/src/input/commands/provider.ts +0 -363
  51. package/src/input/commands/remote-runtime-pool.ts +0 -89
  52. package/src/input/commands/remote-runtime-setup.ts +0 -226
  53. package/src/input/commands/remote-runtime.ts +0 -432
  54. package/src/input/commands/replay-runtime.ts +0 -25
  55. package/src/input/commands/services-runtime.ts +0 -220
  56. package/src/input/commands/settings-sync-runtime.ts +0 -197
  57. package/src/input/commands/share-runtime.ts +0 -127
  58. package/src/input/commands/skills-runtime.ts +0 -226
  59. package/src/input/commands/teleport-runtime.ts +0 -68
  60. package/src/panels/cockpit-panel.ts +0 -183
  61. package/src/panels/communication-panel.ts +0 -153
  62. package/src/panels/control-plane-panel.ts +0 -211
  63. package/src/panels/forensics-panel.ts +0 -364
  64. package/src/panels/hooks-panel.ts +0 -239
  65. package/src/panels/incident-review-panel.ts +0 -197
  66. package/src/panels/marketplace-panel.ts +0 -212
  67. package/src/panels/ops-control-panel.ts +0 -150
  68. package/src/panels/ops-strategy-panel.ts +0 -235
  69. package/src/panels/orchestration-panel.ts +0 -272
  70. package/src/panels/plugins-panel.ts +0 -178
  71. package/src/panels/remote-panel.ts +0 -449
  72. package/src/panels/routes-panel.ts +0 -178
  73. package/src/panels/services-panel.ts +0 -231
  74. package/src/panels/settings-sync-panel.ts +0 -120
  75. package/src/panels/skills-panel.ts +0 -431
  76. package/src/panels/watchers-panel.ts +0 -193
  77. package/src/verification/live-verifier.ts +0 -588
  78. 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
- }