@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,183 +0,0 @@
1
- import type { Line } from '../types/grid.ts';
2
- import { createEmptyLine } from '../types/grid.ts';
3
- import { BasePanel } from './base-panel.ts';
4
- import type { UiCockpitSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
5
- import {
6
- buildGuidanceLine,
7
- buildKeyValueLine,
8
- buildPanelLine,
9
- buildPanelWorkspace,
10
- buildStatPill,
11
- DEFAULT_PANEL_PALETTE,
12
- type PanelWorkspaceSection,
13
- } from './polish.ts';
14
-
15
- const C = {
16
- ...DEFAULT_PANEL_PALETTE,
17
- header: '#cbd5e1',
18
- headerBg: '#0f172a',
19
- } as const;
20
-
21
- function pickColor(value: number, warnAt = 1, badAt = 3): string {
22
- if (value >= badAt) return C.bad;
23
- if (value >= warnAt) return C.warn;
24
- return C.good;
25
- }
26
-
27
- const WORKSPACE_IDS = ['flow', 'governance', 'health', 'domains'] as const;
28
-
29
- export class CockpitPanel extends BasePanel {
30
- private readonly unsub: (() => void) | null;
31
- private selectedWorkspaceIndex = 0;
32
-
33
- public constructor(private readonly readModel?: UiReadModel<UiCockpitSnapshot>) {
34
- super('cockpit', 'Cockpit', 'O', 'monitoring');
35
- this.unsub = readModel ? readModel.subscribe(() => this.markDirty()) : null;
36
- }
37
-
38
- public override onDestroy(): void {
39
- this.unsub?.();
40
- }
41
-
42
- public handleInput(key: string): boolean {
43
- if (key === 'left' || key === 'h') {
44
- this.selectedWorkspaceIndex = Math.max(0, this.selectedWorkspaceIndex - 1);
45
- this.markDirty();
46
- return true;
47
- }
48
- if (key === 'right' || key === 'l') {
49
- this.selectedWorkspaceIndex = Math.min(WORKSPACE_IDS.length - 1, this.selectedWorkspaceIndex + 1);
50
- this.markDirty();
51
- return true;
52
- }
53
- if (key === 'home') {
54
- this.selectedWorkspaceIndex = 0;
55
- this.markDirty();
56
- return true;
57
- }
58
- if (key === 'end') {
59
- this.selectedWorkspaceIndex = WORKSPACE_IDS.length - 1;
60
- this.markDirty();
61
- return true;
62
- }
63
- return false;
64
- }
65
-
66
- public render(width: number, height: number): Line[] {
67
- this.needsRender = false;
68
-
69
- if (!this.readModel) {
70
- const lines: Line[] = [buildPanelLine(width, [[' Runtime read model not wired into this panel yet.', C.empty]])];
71
- const workspace = buildPanelWorkspace(width, height, {
72
- title: 'Operator Cockpit',
73
- sections: [{ lines }],
74
- palette: C,
75
- });
76
- while (workspace.length < height) workspace.push(createEmptyLine(width));
77
- return workspace;
78
- }
79
-
80
- const snapshot = this.readModel.getSnapshot();
81
- const selectedWorkspace = WORKSPACE_IDS[this.selectedWorkspaceIndex] ?? 'flow';
82
-
83
- const flowLines: Line[] = [
84
- buildPanelLine(width, [
85
- ...buildStatPill('graphs', String(snapshot.activeGraphs), C.label, pickColor(snapshot.activeGraphs, 1, 4)),
86
- ...buildStatPill('running', String(snapshot.runningTasks), C.label, C.value),
87
- ...buildStatPill('blocked', String(snapshot.blockedTasks), C.label, pickColor(snapshot.blockedTasks)),
88
- ...buildStatPill('failed', String(snapshot.failedTasks), C.label, pickColor(snapshot.failedTasks)),
89
- ...buildStatPill('guards', String(snapshot.guardTrips), C.label, pickColor(snapshot.guardTrips)),
90
- ]),
91
- buildPanelLine(width, [
92
- ...buildStatPill('blocked comms', String(snapshot.blockedMessages), C.label, pickColor(snapshot.blockedMessages)),
93
- ...buildStatPill('pending approvals', String(snapshot.pendingPermissions), C.label, pickColor(snapshot.pendingPermissions)),
94
- ...buildStatPill('denied', String(snapshot.deniedPermissions), C.label, pickColor(snapshot.deniedPermissions)),
95
- ]),
96
- ];
97
- const governanceLines: Line[] = [
98
- buildPanelLine(width, [
99
- ...buildStatPill('preflight', snapshot.preflightStatus.toUpperCase(), C.label, snapshot.preflightStatus === 'block' ? C.bad : snapshot.preflightStatus === 'warn' ? C.warn : snapshot.preflightStatus === 'pass' ? C.good : C.dim),
100
- ...buildStatPill('issues', String(snapshot.preflightIssueCount), C.label, pickColor(snapshot.preflightIssueCount)),
101
- ...buildStatPill('lint', String(snapshot.lintFindingCount), C.label, pickColor(snapshot.lintFindingCount)),
102
- ...buildStatPill('allow-all MCP', String(snapshot.elevatedMcp), C.label, pickColor(snapshot.elevatedMcp)),
103
- ...buildStatPill('unhealthy MCP', String(snapshot.unhealthyMcp), C.label, pickColor(snapshot.unhealthyMcp)),
104
- ]),
105
- buildPanelLine(width, [
106
- ...buildStatPill('token blocked', String(snapshot.tokenBlockedCount), C.label, pickColor(snapshot.tokenBlockedCount)),
107
- ...buildStatPill('overdue', String(snapshot.tokenRotationOverdueCount), C.label, pickColor(snapshot.tokenRotationOverdueCount)),
108
- ...buildStatPill('scope violations', String(snapshot.tokenScopeViolationCount), C.label, pickColor(snapshot.tokenScopeViolationCount)),
109
- ...buildStatPill('warnings', String(snapshot.tokenRotationWarningCount), C.label, pickColor(snapshot.tokenRotationWarningCount)),
110
- ]),
111
- ];
112
- const healthLines: Line[] = [
113
- buildPanelLine(width, [
114
- ...buildStatPill('incidents', String(snapshot.incidentCount), C.label, pickColor(snapshot.incidentCount)),
115
- ...buildStatPill('plugins', String(snapshot.erroredPlugins), C.label, pickColor(snapshot.erroredPlugins)),
116
- ...buildStatPill('integrations', String(snapshot.failingIntegrations), C.label, pickColor(snapshot.failingIntegrations)),
117
- ]),
118
- ];
119
- if (snapshot.latestIncident) {
120
- healthLines.push(buildPanelLine(width, [
121
- [' latest incident ', C.label],
122
- [snapshot.latestIncident.classification, C.bad],
123
- [' ', C.label],
124
- [snapshot.latestIncident.summary.slice(0, Math.max(0, width - 19 - snapshot.latestIncident.classification.length)), C.dim],
125
- ]));
126
- }
127
- const domainLines: Line[] = [buildPanelLine(width, [[
128
- `tasks:${snapshot.taskCount} agents:${snapshot.agentCount} graphs:${snapshot.totalGraphs} comms:${snapshot.communicationCount} mcp:${snapshot.mcpServerCount} plugins:${snapshot.pluginCount}`,
129
- C.dim,
130
- ]])];
131
- const workspaceLines: Line[] = [];
132
- if (selectedWorkspace === 'flow') {
133
- workspaceLines.push(buildKeyValueLine(width, [
134
- { label: 'running', value: String(snapshot.runningTasks), valueColor: C.value },
135
- { label: 'blocked', value: String(snapshot.blockedTasks), valueColor: pickColor(snapshot.blockedTasks) },
136
- { label: 'graphs', value: String(snapshot.activeGraphs), valueColor: pickColor(snapshot.activeGraphs, 1, 4) },
137
- ], C));
138
- workspaceLines.push(buildGuidanceLine(width, '/orchestration', 'inspect graph state, retries, and subtree controls', C));
139
- workspaceLines.push(buildGuidanceLine(width, '/tasks', 'review active task pressure and task-specific output', C));
140
- } else if (selectedWorkspace === 'governance') {
141
- workspaceLines.push(buildKeyValueLine(width, [
142
- { label: 'preflight', value: snapshot.preflightStatus.toUpperCase(), valueColor: snapshot.preflightStatus === 'block' ? C.bad : snapshot.preflightStatus === 'warn' ? C.warn : snapshot.preflightStatus === 'pass' ? C.good : C.dim },
143
- { label: 'lint', value: String(snapshot.lintFindingCount), valueColor: pickColor(snapshot.lintFindingCount) },
144
- { label: 'allow-all mcp', value: String(snapshot.elevatedMcp), valueColor: pickColor(snapshot.elevatedMcp) },
145
- ], C));
146
- workspaceLines.push(buildGuidanceLine(width, '/policy', 'run simulation, preflight, and bundle review', C));
147
- workspaceLines.push(buildGuidanceLine(width, '/security', 'inspect trust, tokens, quarantines, and incident pressure', C));
148
- } else if (selectedWorkspace === 'health') {
149
- workspaceLines.push(buildKeyValueLine(width, [
150
- { label: 'incidents', value: String(snapshot.incidentCount), valueColor: pickColor(snapshot.incidentCount) },
151
- { label: 'plugins', value: String(snapshot.erroredPlugins), valueColor: pickColor(snapshot.erroredPlugins) },
152
- { label: 'integrations', value: String(snapshot.failingIntegrations), valueColor: pickColor(snapshot.failingIntegrations) },
153
- ], C));
154
- workspaceLines.push(buildGuidanceLine(width, '/incident latest', 'inspect the latest incident bundle and replay fallout', C));
155
- workspaceLines.push(buildGuidanceLine(width, '/plugins', 'review errored plugins and provenance posture', C));
156
- } else {
157
- workspaceLines.push(buildKeyValueLine(width, [
158
- { label: 'tasks', value: String(snapshot.taskCount), valueColor: C.value },
159
- { label: 'comms', value: String(snapshot.communicationCount), valueColor: C.value },
160
- { label: 'mcp', value: String(snapshot.mcpServerCount), valueColor: C.value },
161
- ], C));
162
- workspaceLines.push(buildGuidanceLine(width, '/mcp', 'inspect trust, quarantine, and risky server posture', C));
163
- workspaceLines.push(buildGuidanceLine(width, '/communication', 'review blocked lanes and agent message flow', C));
164
- }
165
-
166
- const sections: PanelWorkspaceSection[] = [
167
- { title: 'Flow', lines: flowLines },
168
- { title: 'Governance', lines: governanceLines },
169
- { title: 'Health', lines: healthLines },
170
- { title: 'Domains', lines: domainLines },
171
- { title: 'Selected Workspace', lines: workspaceLines },
172
- ];
173
- const lines = buildPanelWorkspace(width, height, {
174
- title: 'Operator Cockpit',
175
- intro: 'Live runtime pressure across orchestration, approvals, governance, integrations, and provider trust posture.',
176
- sections,
177
- footerLines: [buildPanelLine(width, [[` Left/Right move workspace focus Home/End jump focus=${selectedWorkspace}`, C.dim]])],
178
- palette: C,
179
- });
180
- while (lines.length < height) lines.push(createEmptyLine(width));
181
- return lines.slice(0, height);
182
- }
183
- }
@@ -1,153 +0,0 @@
1
- import type { Line } from '../types/grid.ts';
2
- import { ScrollableListPanel } from './scrollable-list-panel.ts';
3
- import type { UiCommunicationSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
4
- import { truncateDisplay } from '../utils/terminal-width.ts';
5
- import {
6
- buildBodyText,
7
- buildEmptyState,
8
- buildGuidanceLine,
9
- buildKeyValueLine,
10
- buildPanelLine,
11
- buildPanelWorkspace,
12
- DEFAULT_PANEL_PALETTE,
13
- type PanelPalette,
14
- } from './polish.ts';
15
- import { createEmptyLine } from '../types/grid.ts';
16
-
17
- const C = {
18
- ...DEFAULT_PANEL_PALETTE,
19
- header: '#94a3b8',
20
- headerBg: '#1e293b',
21
- ok: '#22c55e',
22
- warn: '#eab308',
23
- error: '#ef4444',
24
- selectBg: '#0f172a',
25
- } as const;
26
-
27
- type CommunicationRecord = UiCommunicationSnapshot['records'][number];
28
-
29
- export class CommunicationPanel extends ScrollableListPanel<CommunicationRecord> {
30
- private readonly readModel?: UiReadModel<UiCommunicationSnapshot>;
31
- private readonly unsub: (() => void) | null;
32
-
33
- public constructor(readModel?: UiReadModel<UiCommunicationSnapshot>) {
34
- super('communication', 'Communication', 'Y', 'monitoring');
35
- this.showSelectionGutter = true; // I5: non-color selection affordance
36
- this.readModel = readModel;
37
- this.unsub = readModel ? readModel.subscribe(() => this.markDirty()) : null;
38
- }
39
-
40
- public override onDestroy(): void {
41
- this.unsub?.();
42
- }
43
-
44
- protected override getPalette(): PanelPalette {
45
- return C;
46
- }
47
-
48
- protected getItems(): readonly CommunicationRecord[] {
49
- if (!this.readModel) return [];
50
- return this.readModel.getSnapshot().records;
51
- }
52
-
53
- protected renderItem(record: CommunicationRecord, index: number, selected: boolean, width: number): Line {
54
- const bg = selected ? C.selectBg : undefined;
55
- const color = record.status === 'blocked' ? C.error : record.status === 'delivered' ? C.ok : C.info;
56
- return buildPanelLine(width, [
57
- [' ', C.label, bg],
58
- [record.status.padEnd(10), color, bg],
59
- [` ${record.kind.padEnd(10)}`, C.info, bg],
60
- [` ${truncateDisplay(`${record.fromId} -> ${record.toId}`, 28).padEnd(28)}`, C.value, bg],
61
- [` ${truncateDisplay(record.content, Math.max(0, width - 53))}`, C.dim, bg],
62
- ]);
63
- }
64
-
65
- protected override getEmptyStateMessage(): string {
66
- return ' No structured communication recorded yet.';
67
- }
68
-
69
- protected override getEmptyStateActions(): Array<{ command: string; summary: string }> {
70
- return [
71
- { command: '/orchestration', summary: 'review graphs and recursive agent activity' },
72
- { command: '/communication', summary: 'reopen this workspace once the runtime emits message traffic' },
73
- ];
74
- }
75
-
76
- public render(width: number, height: number): Line[] {
77
- const intro = 'Structured agent communication, routing policy outcomes, and delivery status across orchestration trees.';
78
-
79
- if (!this.readModel) {
80
- const workspace = buildPanelWorkspace(width, height, {
81
- title: 'Communication Control Room',
82
- intro,
83
- sections: [{
84
- lines: buildEmptyState(
85
- width,
86
- ' Runtime store not wired into this panel yet.',
87
- 'This workspace needs the live runtime store before it can show communication history and policy outcomes.',
88
- [{ command: '/communication', summary: 'reopen the workspace from the shell-owned runtime' }],
89
- C,
90
- ),
91
- }],
92
- palette: C,
93
- });
94
- while (workspace.length < height) workspace.push(createEmptyLine(width));
95
- return workspace;
96
- }
97
-
98
- const snapshot = this.readModel.getSnapshot();
99
- const records = this.getItems();
100
-
101
- const postureLines: Line[] = [
102
- buildPanelLine(width, [[' Communication posture', C.label]]),
103
- buildKeyValueLine(width, [
104
- { label: 'sent', value: String(snapshot.totalSent), valueColor: snapshot.totalSent > 0 ? C.info : C.dim },
105
- { label: 'delivered', value: String(snapshot.totalDelivered), valueColor: snapshot.totalDelivered > 0 ? C.ok : C.dim },
106
- { label: 'blocked', value: String(snapshot.totalBlocked), valueColor: snapshot.totalBlocked > 0 ? C.error : C.dim },
107
- ], C),
108
- buildGuidanceLine(width, '/orchestration', 'inspect recursive routing, message handoff, and blocked broadcast posture', C),
109
- ];
110
-
111
- if (records.length === 0) {
112
- return this.renderList(width, height, {
113
- title: 'Communication Control Room',
114
- header: postureLines,
115
- });
116
- }
117
-
118
- this.clampSelection();
119
- const selected = records[this.selectedIndex];
120
-
121
- // Update posture with selected info
122
- const postureWithSelected: Line[] = [
123
- buildPanelLine(width, [[' Communication posture', C.label]]),
124
- buildKeyValueLine(width, [
125
- { label: 'sent', value: String(snapshot.totalSent), valueColor: snapshot.totalSent > 0 ? C.info : C.dim },
126
- { label: 'delivered', value: String(snapshot.totalDelivered), valueColor: snapshot.totalDelivered > 0 ? C.ok : C.dim },
127
- { label: 'blocked', value: String(snapshot.totalBlocked), valueColor: snapshot.totalBlocked > 0 ? C.error : C.dim },
128
- { label: 'selected', value: `${selected?.fromId ?? 'n/a'} -> ${selected?.toId ?? 'n/a'}`, valueColor: C.value },
129
- ], C),
130
- buildGuidanceLine(width, '/orchestration', 'inspect recursive routing, message handoff, and blocked broadcast posture', C),
131
- ];
132
-
133
- const footerLines: Line[] = [];
134
- if (selected) {
135
- footerLines.push(
136
- buildPanelLine(width, [[' Route: ', C.label], [`${selected.scope} / ${selected.kind}`, C.value], [' Status: ', C.label], [selected.status, selected.status === 'blocked' ? C.error : C.ok]]),
137
- buildPanelLine(width, [[' From: ', C.label], [selected.fromId, C.value], [' To: ', C.label], [selected.toId, C.value]]),
138
- buildPanelLine(width, [[' Roles: ', C.label], [`${selected.fromRole ?? 'unknown'} -> ${selected.toRole ?? 'unknown'}`, C.dim]]),
139
- );
140
- if (selected.reason) {
141
- footerLines.push(buildPanelLine(width, [[' Reason: ', C.label], [truncateDisplay(selected.reason, Math.max(0, width - 11)), C.warn]]));
142
- }
143
- footerLines.push(...buildBodyText(width, ` Content: ${selected.content}`, C));
144
- }
145
- footerLines.push(buildPanelLine(width, [[' Up/Down move through messages', C.dim]]));
146
-
147
- return this.renderList(width, height, {
148
- title: 'Communication Control Room',
149
- header: postureWithSelected,
150
- footer: footerLines,
151
- });
152
- }
153
- }
@@ -1,211 +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 { UiControlPlaneSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
5
- import { truncateDisplay } from '../utils/terminal-width.ts';
6
- import {
7
- buildEmptyState,
8
- buildGuidanceLine,
9
- buildKeyValueLine,
10
- buildPanelLine,
11
- buildPanelWorkspace,
12
- DEFAULT_PANEL_PALETTE,
13
- type PanelPalette,
14
- } from './polish.ts';
15
-
16
- const C = {
17
- ...DEFAULT_PANEL_PALETTE,
18
- header: '#94a3b8',
19
- headerBg: '#1e293b',
20
- ok: '#22c55e',
21
- warn: '#eab308',
22
- error: '#ef4444',
23
- info: '#38bdf8',
24
- selectBg: '#0f172a',
25
- } as const;
26
-
27
- function formatTime(value?: number): string {
28
- if (!value) return 'n/a';
29
- return new Date(value).toLocaleString();
30
- }
31
-
32
- function connectionColor(state: string): string {
33
- if (state === 'connected' || state === 'healthy') return C.ok;
34
- if (state === 'degraded' || state === 'connecting' || state === 'initializing') return C.warn;
35
- if (state === 'terminal_failure') return C.error;
36
- return C.dim;
37
- }
38
-
39
- type ControlPlaneClient = UiControlPlaneSnapshot['clients'][number];
40
-
41
- export class ControlPlanePanel extends ScrollableListPanel<ControlPlaneClient> {
42
- private readonly unsub: (() => void) | null;
43
-
44
- public constructor(private readonly readModel?: UiReadModel<UiControlPlaneSnapshot>) {
45
- super('control-plane', 'Runtime Status', 'C', 'monitoring');
46
- this.showSelectionGutter = true; // I5: non-color selection affordance
47
- this.unsub = readModel ? readModel.subscribe(() => this.markDirty()) : null;
48
- }
49
-
50
- public override onDestroy(): void {
51
- this.unsub?.();
52
- }
53
-
54
- protected override getPalette(): PanelPalette {
55
- return C;
56
- }
57
-
58
- protected getItems(): readonly ControlPlaneClient[] {
59
- if (!this.readModel) return [];
60
- return this.readModel.getSnapshot().clients;
61
- }
62
-
63
- protected renderItem(client: ControlPlaneClient, _index: number, selected: boolean, width: number): Line {
64
- const bg = selected ? C.selectBg : undefined;
65
- return buildPanelLine(width, [
66
- [' ', C.label, bg],
67
- [client.kind.padEnd(10), C.info, bg],
68
- [` ${truncateDisplay(client.label, 20).padEnd(20)}`, C.value, bg],
69
- [` ${client.transport.padEnd(12)}`, C.dim, bg],
70
- [` ${truncateDisplay(formatTime(client.lastSeenAt), Math.max(0, width - 46))}`, C.dim, bg],
71
- ]);
72
- }
73
-
74
- protected override getEmptyStateMessage(): string {
75
- return ' No runtime activity recorded.';
76
- }
77
-
78
- protected override getEmptyStateActions(): Array<{ command: string; summary: string }> {
79
- return [
80
- { command: '/cockpit', summary: 'watch operator posture from the terminal' },
81
- { command: '/schedule list', summary: 'inspect externally managed automation without starting jobs here' },
82
- ];
83
- }
84
-
85
- public render(width: number, height: number): Line[] {
86
- const intro = 'Shared runtime state, live clients, approval pressure, and recent channel session posture.';
87
-
88
- if (!this.readModel) {
89
- const workspace = buildPanelWorkspace(width, height, {
90
- title: 'Runtime Status',
91
- intro,
92
- sections: [{
93
- lines: buildEmptyState(
94
- width,
95
- ' Runtime read model not wired.',
96
- 'This panel needs the shared runtime read model to inspect clients, requests, and approvals.',
97
- [{ command: '/cockpit', summary: 'use the cockpit while runtime wiring is unavailable' }],
98
- C,
99
- ),
100
- }],
101
- palette: C,
102
- });
103
- while (workspace.length < height) workspace.push(createEmptyLine(width));
104
- return workspace;
105
- }
106
-
107
- const snapshot = this.readModel.getSnapshot();
108
- const approvals = snapshot.approvals;
109
- const sessions = snapshot.sessions;
110
- const recentEvents = snapshot.recentEvents;
111
- const clients = this.getItems();
112
-
113
- const headerLines: Line[] = [
114
- buildKeyValueLine(width, [
115
- { label: 'state', value: snapshot.connectionState, valueColor: connectionColor(snapshot.connectionState) },
116
- { label: 'clients', value: String(snapshot.activeClientIds.length), valueColor: snapshot.activeClientIds.length > 0 ? C.ok : C.dim },
117
- { label: 'requests', value: String(snapshot.requestCount), valueColor: snapshot.requestCount > 0 ? C.info : C.dim },
118
- { label: 'errors', value: String(snapshot.errorCount), valueColor: snapshot.errorCount > 0 ? C.error : C.dim },
119
- ], C),
120
- buildKeyValueLine(width, [
121
- { label: 'host', value: `${snapshot.host}:${snapshot.port}`, valueColor: C.value },
122
- { label: 'approvals', value: String(approvals.filter((entry) => entry.status === 'pending').length), valueColor: approvals.some((entry) => entry.status === 'pending') ? C.warn : C.dim },
123
- { label: 'sessions', value: String(sessions.length), valueColor: sessions.length > 0 ? C.info : C.dim },
124
- { label: 'events', value: String(recentEvents.length), valueColor: recentEvents.length > 0 ? C.info : C.dim },
125
- ], C),
126
- buildGuidanceLine(width, '/cockpit', 'use confirmed runtime actions for interventions while this panel tracks overall posture', C),
127
- ];
128
-
129
- if (clients.length === 0 && approvals.length === 0 && sessions.length === 0) {
130
- return this.renderList(width, height, {
131
- title: 'Runtime Status',
132
- header: headerLines,
133
- emptyMessage: ' No runtime activity recorded.',
134
- });
135
- }
136
-
137
- this.clampSelection();
138
- const selected = clients[this.selectedIndex];
139
-
140
- const footerLines: Line[] = [];
141
- if (selected) {
142
- footerLines.push(
143
- buildPanelLine(width, [
144
- [' Client: ', C.label],
145
- [selected.label, C.value],
146
- [' Kind: ', C.label],
147
- [selected.kind, C.info],
148
- ]),
149
- buildPanelLine(width, [
150
- [' Transport: ', C.label],
151
- [selected.transport, C.value],
152
- [' Connected: ', C.label],
153
- [selected.connected ? 'yes' : 'no', selected.connected ? C.ok : C.warn],
154
- ]),
155
- buildPanelLine(width, [
156
- [' Route: ', C.label],
157
- [selected.routeId ?? 'n/a', C.dim],
158
- [' Session: ', C.label],
159
- [selected.sessionId ?? 'n/a', C.dim],
160
- ]),
161
- buildPanelLine(width, [
162
- [' Last seen: ', C.label],
163
- [formatTime(selected.lastSeenAt), C.dim],
164
- [' Remote: ', C.label],
165
- [truncateDisplay(selected.remoteAddress ?? 'n/a', Math.max(0, width - 36)), C.dim],
166
- ]),
167
- );
168
- } else {
169
- footerLines.push(buildPanelLine(width, [[' No connected client selected.', C.dim]]));
170
- }
171
-
172
- if (approvals.length > 0) {
173
- footerLines.push(
174
- ...approvals.slice(0, 6).map((approval) => buildPanelLine(width, [
175
- [' ', C.label],
176
- [approval.status.padEnd(10), approval.status === 'pending' ? C.warn : approval.status === 'approved' ? C.ok : approval.status === 'denied' ? C.error : C.dim],
177
- [` ${truncateDisplay(approval.request.tool, 16).padEnd(16)}`, C.value],
178
- [` ${truncateDisplay(approval.sessionId ?? approval.id, Math.max(0, width - 30))}`, C.dim],
179
- ])),
180
- );
181
- }
182
-
183
- if (sessions.length > 0) {
184
- footerLines.push(
185
- ...sessions.slice(0, 6).map((session) => buildPanelLine(width, [
186
- [' ', C.label],
187
- [session.status.padEnd(10), session.status === 'active' ? C.ok : C.dim],
188
- [` ${truncateDisplay(session.title, 20).padEnd(20)}`, C.value],
189
- [` ${truncateDisplay(session.activeAgentId ?? session.id, Math.max(0, width - 34))}`, C.dim],
190
- ])),
191
- );
192
- }
193
-
194
- if (recentEvents.length > 0) {
195
- footerLines.push(
196
- ...recentEvents.slice(0, 6).map((event) => buildPanelLine(width, [
197
- [' ', C.label],
198
- [truncateDisplay(event.event, 16).padEnd(16), C.info],
199
- [` ${truncateDisplay(typeof event.payload === 'string' ? event.payload : JSON.stringify(event.payload) ?? '', Math.max(0, width - 19))}`, C.dim],
200
- ])),
201
- );
202
- }
203
- footerLines.push(buildPanelLine(width, [[' Up/Down move through connected clients', C.dim]]));
204
-
205
- return this.renderList(width, height, {
206
- title: 'Runtime Status',
207
- header: headerLines,
208
- footer: footerLines,
209
- });
210
- }
211
- }