@pellux/goodvibes-agent 0.1.70 → 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 +6 -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 +0 -69
  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 +0 -13
  15. package/src/input/commands.ts +2 -95
  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,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
- }