@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.
Files changed (60) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +42 -1
  3. package/src/agent/skill-discovery.ts +119 -0
  4. package/src/input/commands/delegation-runtime.ts +0 -8
  5. package/src/input/commands/experience-runtime.ts +0 -177
  6. package/src/input/commands/guidance-runtime.ts +9 -77
  7. package/src/input/commands/local-runtime.ts +1 -57
  8. package/src/input/commands/local-setup-review.ts +1 -1
  9. package/src/input/commands/operator-runtime.ts +1 -145
  10. package/src/input/commands/platform-access-runtime.ts +2 -195
  11. package/src/input/commands/product-runtime.ts +0 -116
  12. package/src/input/commands/security-runtime.ts +88 -0
  13. package/src/input/commands/session-content.ts +0 -97
  14. package/src/input/commands/shell-core.ts +1 -22
  15. package/src/input/commands.ts +2 -43
  16. package/src/panels/builtin/operations.ts +3 -184
  17. package/src/panels/index.ts +0 -11
  18. package/src/version.ts +1 -1
  19. package/src/input/commands/branch-runtime.ts +0 -72
  20. package/src/input/commands/control-room-runtime.ts +0 -234
  21. package/src/input/commands/discovery-runtime.ts +0 -61
  22. package/src/input/commands/hooks-runtime.ts +0 -207
  23. package/src/input/commands/incident-runtime.ts +0 -106
  24. package/src/input/commands/integration-runtime.ts +0 -437
  25. package/src/input/commands/local-setup.ts +0 -288
  26. package/src/input/commands/managed-runtime.ts +0 -240
  27. package/src/input/commands/marketplace-runtime.ts +0 -305
  28. package/src/input/commands/memory-product-runtime.ts +0 -148
  29. package/src/input/commands/operator-panel-runtime.ts +0 -146
  30. package/src/input/commands/platform-services-runtime.ts +0 -271
  31. package/src/input/commands/profile-sync-runtime.ts +0 -110
  32. package/src/input/commands/provider.ts +0 -363
  33. package/src/input/commands/remote-runtime-pool.ts +0 -89
  34. package/src/input/commands/remote-runtime-setup.ts +0 -226
  35. package/src/input/commands/remote-runtime.ts +0 -432
  36. package/src/input/commands/replay-runtime.ts +0 -25
  37. package/src/input/commands/services-runtime.ts +0 -220
  38. package/src/input/commands/settings-sync-runtime.ts +0 -197
  39. package/src/input/commands/share-runtime.ts +0 -127
  40. package/src/input/commands/skills-runtime.ts +0 -226
  41. package/src/input/commands/teleport-runtime.ts +0 -68
  42. package/src/panels/cockpit-panel.ts +0 -183
  43. package/src/panels/communication-panel.ts +0 -153
  44. package/src/panels/control-plane-panel.ts +0 -211
  45. package/src/panels/forensics-panel.ts +0 -364
  46. package/src/panels/hooks-panel.ts +0 -239
  47. package/src/panels/incident-review-panel.ts +0 -197
  48. package/src/panels/marketplace-panel.ts +0 -212
  49. package/src/panels/ops-control-panel.ts +0 -150
  50. package/src/panels/ops-strategy-panel.ts +0 -235
  51. package/src/panels/orchestration-panel.ts +0 -272
  52. package/src/panels/plugins-panel.ts +0 -178
  53. package/src/panels/remote-panel.ts +0 -449
  54. package/src/panels/routes-panel.ts +0 -178
  55. package/src/panels/services-panel.ts +0 -231
  56. package/src/panels/settings-sync-panel.ts +0 -120
  57. package/src/panels/skills-panel.ts +0 -431
  58. package/src/panels/watchers-panel.ts +0 -193
  59. package/src/verification/live-verifier.ts +0 -588
  60. package/src/verification/verification-ledger.ts +0 -239
@@ -1,235 +0,0 @@
1
- /**
2
- * Ops Strategy Timeline Panel.
3
- *
4
- * Renders the Adaptive Execution Planner state: current strategy, reason
5
- * code, mode, override status, and a scrollable history of past decisions.
6
- *
7
- * Registered as panel id 'ops' in builtin-panels.
8
- */
9
-
10
- import { BasePanel } from './base-panel.ts';
11
- import type { Line } from '../types/grid.ts';
12
- import type { PlannerDecision, ExecutionStrategy } from '@pellux/goodvibes-sdk/platform/core';
13
- import type { PlannerEvent } from '@/runtime/index.ts';
14
- import type { UiEventFeed } from '../runtime/ui-events.ts';
15
- import type { OpsStrategyQuery } from '../runtime/ui-service-queries.ts';
16
- import {
17
- buildEmptyState,
18
- buildPanelLine,
19
- buildStyledPanelLine,
20
- buildPanelWorkspace,
21
- resolveScrollablePanelSection,
22
- DEFAULT_PANEL_PALETTE,
23
- } from './polish.ts';
24
-
25
- const STRATEGY_FG: Record<ExecutionStrategy, string> = {
26
- auto: '#00cccc',
27
- single: '#00cc66',
28
- cohort: '#cccc00',
29
- background: '#cc66cc',
30
- remote: '#cccccc',
31
- };
32
-
33
- const STRATEGY_ICON: Record<ExecutionStrategy, string> = {
34
- auto: '~',
35
- single: '▸',
36
- cohort: '◆',
37
- background: '.',
38
- remote: '▸',
39
- };
40
-
41
- // ---------------------------------------------------------------------------
42
- // OpsStrategyPanel
43
- // ---------------------------------------------------------------------------
44
-
45
- export class OpsStrategyPanel extends BasePanel {
46
- private unsubscribers: Array<() => void> = [];
47
- private scrollOffset = 0;
48
- private history: PlannerDecision[] = [];
49
- private readonly adaptivePlanner: OpsStrategyQuery;
50
-
51
- constructor(
52
- private readonly plannerEvents: UiEventFeed<PlannerEvent>,
53
- adaptivePlanner: OpsStrategyQuery,
54
- ) {
55
- super('ops', 'Ops', 'O', 'agent');
56
- this.adaptivePlanner = adaptivePlanner;
57
- }
58
-
59
- override onActivate(): void {
60
- super.onActivate();
61
- this._syncHistory();
62
- this.unsubscribers.push(
63
- this.plannerEvents.on('PLAN_STRATEGY_SELECTED', () => {
64
- this._syncHistory();
65
- this.markDirty();
66
- }),
67
- this.plannerEvents.on('PLAN_STRATEGY_OVERRIDDEN', () => {
68
- this._syncHistory();
69
- this.markDirty();
70
- }),
71
- );
72
- }
73
-
74
- override onDestroy(): void {
75
- for (const unsub of this.unsubscribers) unsub();
76
- this.unsubscribers = [];
77
- }
78
-
79
- handleInput(key: string): boolean {
80
- if (key === 'up' || key === 'k') {
81
- this.scrollOffset = Math.max(0, this.scrollOffset - 1);
82
- this.markDirty();
83
- return true;
84
- }
85
- if (key === 'down' || key === 'j') {
86
- this.scrollOffset++;
87
- this.markDirty();
88
- return true;
89
- }
90
- if (key === 'g') {
91
- this.scrollOffset = 0;
92
- this.markDirty();
93
- return true;
94
- }
95
- if (key === 'G') {
96
- this.scrollOffset = Math.max(0, this.history.length - 1);
97
- this.markDirty();
98
- return true;
99
- }
100
- return false;
101
- }
102
-
103
- render(width: number, height: number): Line[] {
104
- const latest = this.adaptivePlanner.getLatest();
105
- const mode = this.adaptivePlanner.getMode();
106
- const override = this.adaptivePlanner.getOverride();
107
- const statusLines: Line[] = [
108
- buildPanelLine(width, [
109
- [' Mode ', DEFAULT_PANEL_PALETTE.label],
110
- [mode.toUpperCase(), DEFAULT_PANEL_PALETTE.value],
111
- [' Override ', DEFAULT_PANEL_PALETTE.label],
112
- [override ? `${override.toUpperCase()} [ACTIVE]` : 'none', override ? DEFAULT_PANEL_PALETTE.warn : DEFAULT_PANEL_PALETTE.dim],
113
- ]),
114
- ];
115
-
116
- if (latest) {
117
- statusLines.push(buildPanelLine(width, [
118
- [' Last ', DEFAULT_PANEL_PALETTE.label],
119
- [`${STRATEGY_ICON[latest.selected]} ${latest.selected.toUpperCase()}`, STRATEGY_FG[latest.selected]],
120
- [' Reason ', DEFAULT_PANEL_PALETTE.label],
121
- [latest.reasonCode, DEFAULT_PANEL_PALETTE.dim],
122
- ]));
123
- }
124
-
125
- if (this.history.length === 0) {
126
- return buildPanelWorkspace(width, height, {
127
- title: ' Ops Strategy',
128
- intro: 'Review adaptive execution planner decisions, overrides, and recent strategy history.',
129
- sections: [
130
- { title: 'Status', lines: statusLines },
131
- {
132
- lines: buildEmptyState(
133
- width,
134
- ' No decisions recorded yet',
135
- 'Adaptive planner decisions appear here once the planner begins selecting strategies.',
136
- [],
137
- DEFAULT_PANEL_PALETTE,
138
- ),
139
- },
140
- ],
141
- footerLines: [
142
- buildPanelLine(width, [[' Up/Down', DEFAULT_PANEL_PALETTE.info], [' scroll history', DEFAULT_PANEL_PALETTE.dim], [' g/G', DEFAULT_PANEL_PALETTE.info], [' top/bottom', DEFAULT_PANEL_PALETTE.dim]]),
143
- ],
144
- palette: DEFAULT_PANEL_PALETTE,
145
- });
146
- }
147
-
148
- const historyLines = this._renderHistory(width);
149
- const statusSection = { title: 'Status', lines: statusLines } as const;
150
- const historySection = resolveScrollablePanelSection(width, height, {
151
- intro: 'Review adaptive execution planner decisions, overrides, and recent strategy history.',
152
- footerLines: [
153
- buildPanelLine(width, [[' Up/Down', DEFAULT_PANEL_PALETTE.info], [' scroll history', DEFAULT_PANEL_PALETTE.dim], [' g/G', DEFAULT_PANEL_PALETTE.info], [' top/bottom', DEFAULT_PANEL_PALETTE.dim]]),
154
- ],
155
- palette: DEFAULT_PANEL_PALETTE,
156
- beforeSections: [statusSection],
157
- section: {
158
- title: 'Decision History',
159
- scrollableLines: historyLines,
160
- scrollOffset: Math.min(this.scrollOffset, Math.max(0, historyLines.length - 1)),
161
- minRows: 8,
162
- },
163
- });
164
- this.scrollOffset = historySection.scrollOffset;
165
-
166
- return buildPanelWorkspace(width, height, {
167
- title: ' Ops Strategy',
168
- intro: 'Review adaptive execution planner decisions, overrides, and recent strategy history.',
169
- sections: [
170
- statusSection,
171
- historySection.section,
172
- ],
173
- footerLines: [
174
- buildPanelLine(width, [[' Up/Down', DEFAULT_PANEL_PALETTE.info], [' scroll history', DEFAULT_PANEL_PALETTE.dim], [' g/G', DEFAULT_PANEL_PALETTE.info], [' top/bottom', DEFAULT_PANEL_PALETTE.dim]]),
175
- ],
176
- palette: DEFAULT_PANEL_PALETTE,
177
- });
178
- }
179
-
180
- // -------------------------------------------------------------------------
181
- // Private helpers
182
- // -------------------------------------------------------------------------
183
-
184
- private _syncHistory(): void {
185
- this.history = this.adaptivePlanner.getHistory(50);
186
- }
187
-
188
- private _renderHistory(width: number): Line[] {
189
- if (this.history.length === 0) {
190
- return [buildStyledPanelLine(width, [{ text: ' No history yet.', fg: DEFAULT_PANEL_PALETTE.dim, dim: true }])];
191
- }
192
-
193
- const lines: Line[] = [];
194
- lines.push(buildStyledPanelLine(width, [{ text: ' Decision History', fg: DEFAULT_PANEL_PALETTE.value, bold: true }]));
195
-
196
- const reversed = [...this.history].reverse();
197
- for (let i = 0; i < reversed.length; i++) {
198
- const d = reversed[i]!;
199
- const ts = new Date(d.timestamp).toLocaleTimeString();
200
- const fg = STRATEGY_FG[d.selected];
201
- const icon = STRATEGY_ICON[d.selected];
202
- const num = String(i + 1).padStart(3);
203
- const overrideMark = d.overrideActive ? ' [O]' : '';
204
-
205
- // Row 1: index + icon + strategy + timestamp (right-aligned)
206
- const leftBase = ` ${num}. ${icon} ${d.selected.toUpperCase()}${overrideMark}`;
207
- const rightText = ` ${ts}`;
208
- const pad = Math.max(1, width - leftBase.length - rightText.length);
209
- lines.push(buildStyledPanelLine(width, [
210
- { text: ` ${num}. `, fg: DEFAULT_PANEL_PALETTE.dim, dim: true },
211
- { text: `${icon} ${d.selected.toUpperCase()}`, fg, bold: true },
212
- { text: overrideMark, fg: DEFAULT_PANEL_PALETTE.warn },
213
- { text: ' '.repeat(pad), fg: DEFAULT_PANEL_PALETTE.dim },
214
- { text: rightText, fg: DEFAULT_PANEL_PALETTE.dim, dim: true },
215
- ]));
216
-
217
- // Row 2: reason code
218
- lines.push(buildStyledPanelLine(width, [{ text: ` ${d.reasonCode}`, fg: DEFAULT_PANEL_PALETTE.dim, dim: true }]));
219
-
220
- // Row 3+: top-2 scored candidates (auto mode only)
221
- if (!d.overrideActive && d.candidates.length > 1) {
222
- const top2 = d.candidates.slice(0, 2);
223
- for (const c of top2) {
224
- lines.push(buildStyledPanelLine(width, [
225
- { text: ' ', fg: DEFAULT_PANEL_PALETTE.dim, dim: true },
226
- { text: c.strategy.padEnd(12), fg: STRATEGY_FG[c.strategy] },
227
- { text: ` score ${c.score}`, fg: DEFAULT_PANEL_PALETTE.dim, dim: true },
228
- ]));
229
- }
230
- }
231
- }
232
-
233
- return lines;
234
- }
235
- }
@@ -1,272 +0,0 @@
1
- /**
2
- * OrchestrationPanel — displays task graphs, node contracts, and recursion guards.
3
- *
4
- * Migrated (Wave B2): extends ScrollableListPanel<OrchestrationGraphRecord>.
5
- * Navigation (up/down/j/k) is handled by the base class.
6
- */
7
-
8
- import type { Line } from '../types/grid.ts';
9
- import { createEmptyLine } from '../types/grid.ts';
10
- import { ScrollableListPanel } from './scrollable-list-panel.ts';
11
- import type { UiOrchestrationSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
12
- import type { OrchestrationGraphRecord } from '@/runtime/index.ts';
13
- import {
14
- buildEmptyState,
15
- buildGuidanceLine,
16
- buildKeyValueLine,
17
- buildPanelLine,
18
- buildPanelWorkspace,
19
- resolveScrollablePanelSection,
20
- DEFAULT_PANEL_PALETTE,
21
- extendPalette,
22
- type PanelPalette,
23
- type PanelWorkspaceSection,
24
- } from './polish.ts';
25
-
26
- const C = extendPalette(DEFAULT_PANEL_PALETTE, {
27
- header: '#94a3b8',
28
- headerBg: '#1e293b',
29
- running: '#22c55e',
30
- ready: '#38bdf8',
31
- blocked: '#f59e0b',
32
- failed: '#ef4444',
33
- completed: '#a78bfa',
34
- selectBg: '#0f172a',
35
- } as const);
36
-
37
- function statusColor(status: string): string {
38
- switch (status) {
39
- case 'ready': return C.ready;
40
- case 'running': return C.running;
41
- case 'blocked': return C.blocked;
42
- case 'failed': return C.failed;
43
- case 'completed': return C.completed;
44
- default: return C.dim;
45
- }
46
- }
47
-
48
- export class OrchestrationPanel extends ScrollableListPanel<OrchestrationGraphRecord> {
49
- private readonly readModel?: UiReadModel<UiOrchestrationSnapshot>;
50
- private readonly unsub: (() => void) | null;
51
-
52
- public constructor(readModel?: UiReadModel<UiOrchestrationSnapshot>) {
53
- super('orchestration', 'Orchestration', 'Q', 'monitoring');
54
- this.readModel = readModel;
55
- this.unsub = readModel ? readModel.subscribe(() => this.markDirty()) : null;
56
- }
57
-
58
- public override onDestroy(): void {
59
- this.unsub?.();
60
- }
61
-
62
- // ---------------------------------------------------------------------------
63
- // ScrollableListPanel contract
64
- // ---------------------------------------------------------------------------
65
-
66
- protected getItems(): readonly OrchestrationGraphRecord[] {
67
- if (!this.readModel) return [];
68
- return [...this.readModel.getSnapshot().graphs].sort((a, b) => b.createdAt - a.createdAt);
69
- }
70
-
71
- protected renderItem(
72
- graph: OrchestrationGraphRecord,
73
- index: number,
74
- selected: boolean,
75
- width: number,
76
- ): Line {
77
- const bg = selected ? C.selectBg : undefined;
78
- return buildPanelLine(width, [
79
- [' ', C.label, bg],
80
- [graph.status.padEnd(10), statusColor(graph.status), bg],
81
- [` ${graph.mode.padEnd(17)}`, C.value, bg],
82
- [` ${graph.id.slice(0, 8)} `, C.dim, bg],
83
- [graph.title.slice(0, Math.max(0, width - 39)), C.value, bg],
84
- ]);
85
- }
86
-
87
- protected override getPalette(): PanelPalette {
88
- return C;
89
- }
90
-
91
- protected override getEmptyStateMessage(): string {
92
- return ' No orchestration graphs recorded yet.';
93
- }
94
-
95
- // ---------------------------------------------------------------------------
96
- // Input — base class handles all navigation (up/down/j/k/pageup/pagedown/g/G)
97
- // ---------------------------------------------------------------------------
98
-
99
- // ---------------------------------------------------------------------------
100
- // Render — multi-section layout (posture + scrollable graphs + detail + nodes)
101
- // ---------------------------------------------------------------------------
102
-
103
- public render(width: number, height: number): Line[] {
104
- const intro = 'Read-only task graph posture, node contracts, and recursion guard state. Agent does not start local worker chains.';
105
-
106
- if (!this.readModel) {
107
- this.needsRender = false;
108
- const workspace = buildPanelWorkspace(width, height, {
109
- title: 'Orchestration Control Room',
110
- intro,
111
- sections: [{
112
- lines: buildEmptyState(
113
- width,
114
- ' Runtime store not wired into this panel yet.',
115
- 'The orchestration workspace needs the live runtime store before it can show graphs, nodes, and recursion guard events.',
116
- [{ command: '/orchestration', summary: 'reopen from the shell-owned runtime once orchestration is active' }],
117
- C,
118
- ),
119
- }],
120
- palette: C,
121
- });
122
- while (workspace.length < height) workspace.push(createEmptyLine(width));
123
- return workspace;
124
- }
125
-
126
- const snapshot = this.readModel.getSnapshot();
127
- const graphs = this.getItems();
128
- const postureLines = [
129
- buildKeyValueLine(width, [
130
- { label: 'graphs', value: String(snapshot.totalGraphs), valueColor: snapshot.totalGraphs > 0 ? C.value : C.dim },
131
- { label: 'active', value: String(snapshot.activeGraphIds.length), valueColor: snapshot.activeGraphIds.length > 0 ? C.running : C.dim },
132
- { label: 'completed', value: String(snapshot.totalCompletedGraphs), valueColor: snapshot.totalCompletedGraphs > 0 ? C.completed : C.dim },
133
- { label: 'failed', value: String(snapshot.totalFailedGraphs), valueColor: snapshot.totalFailedGraphs > 0 ? C.failed : C.dim },
134
- { label: 'guards', value: String(snapshot.recursionGuardTrips), valueColor: snapshot.recursionGuardTrips > 0 ? C.blocked : C.dim },
135
- ], C),
136
- buildGuidanceLine(width, '/orchestration', 'inspect graph health without spawning local workers', C),
137
- ];
138
- if (graphs.length === 0) {
139
- this.needsRender = false;
140
- const workspace = buildPanelWorkspace(width, height, {
141
- title: 'Orchestration Control Room',
142
- intro,
143
- sections: [{
144
- title: 'Orchestration posture',
145
- lines: [
146
- ...postureLines,
147
- ...buildEmptyState(
148
- width,
149
- this.getEmptyStateMessage(),
150
- 'Graphs, nodes, child contracts, and recursion guard trips appear here only if a runtime record already exists.',
151
- [
152
- { command: '/tasks', summary: 'create or inspect task flows that feed orchestration graphs' },
153
- { command: '/communication', summary: 'review structured agent communication alongside graph execution' },
154
- ],
155
- C,
156
- ),
157
- ],
158
- }],
159
- palette: C,
160
- });
161
- while (workspace.length < height) workspace.push(createEmptyLine(width));
162
- return workspace;
163
- }
164
-
165
- this.clampSelection();
166
- const selected = graphs[this.selectedIndex]!;
167
- const detailLines: Line[] = [
168
- buildPanelLine(width, [
169
- [' Title: ', C.label],
170
- [selected.title, C.value],
171
- [' Status: ', C.label],
172
- [selected.status, statusColor(selected.status)],
173
- [' Mode: ', C.label],
174
- [selected.mode, C.value],
175
- ]),
176
- buildPanelLine(width, [
177
- [' Nodes: ', C.label],
178
- [String(selected.nodeOrder.length), C.value],
179
- [' Started: ', C.label],
180
- [selected.startedAt ? new Date(selected.startedAt).toLocaleTimeString() : 'n/a', C.dim],
181
- [' Ended: ', C.label],
182
- [selected.endedAt ? new Date(selected.endedAt).toLocaleTimeString() : 'n/a', C.dim],
183
- ]),
184
- ];
185
- if (selected.lastRecursionGuard) {
186
- detailLines.push(buildPanelLine(width, [
187
- [' Recursion guard: ', C.label],
188
- [`depth ${selected.lastRecursionGuard.depth} active ${selected.lastRecursionGuard.activeAgents} ${selected.lastRecursionGuard.reason}`, C.blocked],
189
- ]));
190
- }
191
-
192
- const nodes = selected.nodeOrder
193
- .map((nodeId) => selected.nodes.get(nodeId))
194
- .filter((node): node is NonNullable<typeof node> => Boolean(node));
195
- const focusNode = nodes[0];
196
- if (focusNode?.contract) {
197
- const toolCount = focusNode.contract.allowedTools?.length ?? 0;
198
- const evidenceCount = focusNode.contract.requiredEvidence?.length ?? 0;
199
- const scopeCount = focusNode.contract.writeScope?.length ?? 0;
200
- detailLines.push(buildPanelLine(width, [
201
- [' Contract: ', C.label],
202
- [`tools ${toolCount}`, C.value],
203
- [' evidence ', C.label],
204
- [String(evidenceCount), C.value],
205
- [' write scope ', C.label],
206
- [String(scopeCount), C.value],
207
- ]));
208
- detailLines.push(buildPanelLine(width, [
209
- [' Flow: ', C.label],
210
- [focusNode.contract.executionProtocol ?? 'direct', C.value],
211
- [' Review: ', C.label],
212
- [focusNode.contract.reviewMode ?? 'none', C.value],
213
- [' Lane: ', C.label],
214
- [focusNode.contract.communicationLane ?? 'default', C.value],
215
- [' Inherits: ', C.label],
216
- [focusNode.contract.inheritsParentConstraints ? 'yes' : 'no', C.value],
217
- ]));
218
- }
219
-
220
- const nodeLines: Line[] = nodes.length === 0
221
- ? [buildPanelLine(width, [[' No nodes recorded yet.', C.dim]])]
222
- : nodes.map((node) => {
223
- const depends = node.dependencyNodeIds.length > 0 ? ` deps:${node.dependencyNodeIds.length}` : '';
224
- return buildPanelLine(width, [
225
- [' ', C.label],
226
- [node.status.padEnd(10), statusColor(node.status)],
227
- [` ${node.role.padEnd(10)}`, C.value],
228
- [` ${node.id.slice(0, 8)} `, C.dim],
229
- [`${node.title}${depends}`.slice(0, Math.max(0, width - 34)), C.value],
230
- ]);
231
- });
232
-
233
- const scrollableLines: Line[] = graphs.map((graph, index) =>
234
- this.renderItem(graph, index, index === this.selectedIndex, width),
235
- );
236
-
237
- const postureSection: PanelWorkspaceSection = { title: 'Orchestration posture', lines: postureLines };
238
- const selectedGraphSection: PanelWorkspaceSection = { title: 'Selected Graph', lines: detailLines };
239
- const nodesSection: PanelWorkspaceSection = { title: 'Nodes', lines: nodeLines };
240
- const graphsSection = resolveScrollablePanelSection(width, height, {
241
- intro,
242
- palette: C,
243
- beforeSections: [postureSection],
244
- section: {
245
- title: 'Graphs',
246
- scrollableLines,
247
- selectedIndex: this.selectedIndex,
248
- scrollOffset: this.scrollStart,
249
- minRows: 4,
250
- appendWindowSummary: { dimColor: C.dim },
251
- },
252
- afterSections: [selectedGraphSection, nodesSection],
253
- });
254
- this.scrollStart = graphsSection.scrollOffset;
255
-
256
- const sections: PanelWorkspaceSection[] = [
257
- postureSection,
258
- graphsSection.section,
259
- selectedGraphSection,
260
- nodesSection,
261
- ];
262
- this.needsRender = false;
263
- const lines = buildPanelWorkspace(width, height, {
264
- title: 'Orchestration Control Room',
265
- intro,
266
- sections,
267
- palette: C,
268
- });
269
- while (lines.length < height) lines.push(createEmptyLine(width));
270
- return lines.slice(0, height);
271
- }
272
- }
@@ -1,178 +0,0 @@
1
- import type { Line } from '../types/grid.ts';
2
- import { createEmptyLine } from '../types/grid.ts';
3
- import { ScrollableListPanel } from './scrollable-list-panel.ts';
4
- import type { PluginManagerObserver, PluginStatus } from '@pellux/goodvibes-sdk/platform/plugins';
5
- import {
6
- buildEmptyState,
7
- buildPanelLine,
8
- buildPanelWorkspace,
9
- DEFAULT_PANEL_PALETTE,
10
- type PanelPalette,
11
- } from './polish.ts';
12
-
13
- const C = {
14
- ...DEFAULT_PANEL_PALETTE,
15
- header: '#94a3b8',
16
- headerBg: '#1e293b',
17
- ok: '#22c55e',
18
- warn: '#eab308',
19
- error: '#ef4444',
20
- info: '#38bdf8',
21
- selectBg: '#0f172a',
22
- } as const;
23
-
24
- function trustColor(tier: PluginStatus['trustTier']): string {
25
- switch (tier) {
26
- case 'trusted':
27
- return C.ok;
28
- case 'limited':
29
- return C.warn;
30
- case 'untrusted':
31
- return C.error;
32
- }
33
- }
34
-
35
- function statusColor(status: PluginStatus): string {
36
- if (status.quarantined) return C.error;
37
- if (status.active) return C.ok;
38
- if (status.enabled) return C.warn;
39
- return C.dim;
40
- }
41
-
42
- function statusLabel(status: PluginStatus): string {
43
- if (status.quarantined) return 'QUARANTINED';
44
- if (status.active) return 'ACTIVE';
45
- if (status.enabled) return 'ENABLED';
46
- return 'DISABLED';
47
- }
48
-
49
- export class PluginsPanel extends ScrollableListPanel<PluginStatus> {
50
- private readonly manager: PluginManagerObserver;
51
- private readonly unsub: (() => void) | null;
52
-
53
- public constructor(manager: PluginManagerObserver) {
54
- super('plugins', 'Plugins', 'P', 'monitoring');
55
- this.showSelectionGutter = true; // I5: non-color selection affordance
56
- this.manager = manager;
57
- this.unsub = manager.subscribe(() => this.markDirty());
58
- }
59
-
60
- public override onActivate(): void {
61
- super.onActivate();
62
- this.selectedIndex = 0;
63
- }
64
-
65
- public override onDestroy(): void {
66
- this.unsub?.();
67
- }
68
-
69
- protected override getPalette(): PanelPalette {
70
- return C;
71
- }
72
-
73
- protected getItems(): readonly PluginStatus[] {
74
- return this.manager.list();
75
- }
76
-
77
- protected renderItem(plugin: PluginStatus, _index: number, selected: boolean, width: number): Line {
78
- const bg = selected ? C.selectBg : undefined;
79
- return buildPanelLine(width, [
80
- [' ', C.label, bg],
81
- [plugin.name.padEnd(22), C.value, bg],
82
- [` ${statusLabel(plugin).padEnd(11)}`, statusColor(plugin), bg],
83
- [` ${plugin.trustTier.toUpperCase().padEnd(10)}`, trustColor(plugin.trustTier), bg],
84
- [` ${plugin.version}`, C.dim, bg],
85
- ]);
86
- }
87
-
88
- protected override getEmptyStateMessage(): string {
89
- return ' No plugins discovered.';
90
- }
91
-
92
- protected override getEmptyStateActions(): Array<{ command: string; summary: string }> {
93
- return [
94
- { command: '/plugin list', summary: 'inspect plugin discovery paths and current registry state' },
95
- { command: '/marketplace', summary: 'review curated ecosystem entries and provenance posture' },
96
- ];
97
- }
98
-
99
- public render(width: number, height: number): Line[] {
100
- const intro = 'Plugin trust, capabilities, signatures, and quarantine posture for the active ecosystem surface.';
101
- const plugins = this.getItems();
102
-
103
- if (plugins.length === 0) {
104
- const workspace = buildPanelWorkspace(width, height, {
105
- title: 'Plugin Control Room',
106
- intro,
107
- sections: [{
108
- lines: buildEmptyState(
109
- width,
110
- ' No plugins discovered.',
111
- 'Use /plugin list for search paths, install hints, and trust review before activating plugin-backed flows.',
112
- [
113
- { command: '/plugin list', summary: 'inspect plugin discovery paths and current registry state' },
114
- { command: '/marketplace', summary: 'review curated ecosystem entries and provenance posture' },
115
- ],
116
- C,
117
- ),
118
- }],
119
- palette: C,
120
- });
121
- while (workspace.length < height) workspace.push(createEmptyLine(width));
122
- return workspace;
123
- }
124
-
125
- this.clampSelection();
126
- const selected = plugins[this.selectedIndex]!;
127
- const selectedCaps = this.manager.capabilities(selected.name);
128
- const trustRecord = this.manager.getTrustRecord(selected.name);
129
- const quarantineRecord = this.manager.getQuarantineRecord(selected.name);
130
- const detailLines: Line[] = [
131
- buildPanelLine(width, [
132
- [' Plugin: ', C.label],
133
- [selected.name, C.value],
134
- [' State: ', C.label],
135
- [statusLabel(selected), statusColor(selected)],
136
- [' Trust: ', C.label],
137
- [selected.trustTier, trustColor(selected.trustTier)],
138
- ]),
139
- buildPanelLine(width, [
140
- [' Description: ', C.label],
141
- [selected.description.slice(0, Math.max(0, width - 15)), C.dim],
142
- ]),
143
- ];
144
-
145
- if (selectedCaps) {
146
- detailLines.push(buildPanelLine(width, [
147
- [' Capabilities: ', C.label],
148
- [String(selectedCaps.requested.length), C.value],
149
- [' High-risk: ', C.label],
150
- [String(selectedCaps.highRisk.length), selectedCaps.highRisk.length > 0 ? C.warn : C.ok],
151
- [' Blocked: ', C.label],
152
- [String(selectedCaps.blocked.length), selectedCaps.blocked.length > 0 ? C.error : C.ok],
153
- ]));
154
- }
155
-
156
- if (trustRecord?.signatureFingerprint) {
157
- detailLines.push(buildPanelLine(width, [
158
- [' Signature: ', C.label],
159
- [trustRecord.signatureFingerprint, C.info],
160
- ]));
161
- }
162
-
163
- if (quarantineRecord) {
164
- detailLines.push(buildPanelLine(width, [
165
- [' Quarantine: ', C.label],
166
- [quarantineRecord.reason.slice(0, Math.max(0, width - 14)), C.error],
167
- ]));
168
- }
169
-
170
- detailLines.push(buildPanelLine(width, [[' Inspect trust and capability state here, then use /plugin to take action.', C.dim]]));
171
- detailLines.push(buildPanelLine(width, [[' Up/Down move through discovered plugins', C.dim]]));
172
-
173
- return this.renderList(width, height, {
174
- title: 'Plugin Control Room',
175
- footer: detailLines,
176
- });
177
- }
178
- }