@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,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
- }