@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.
- package/CHANGELOG.md +12 -0
- package/README.md +3 -3
- package/docs/README.md +2 -2
- package/docs/getting-started.md +1 -1
- package/docs/runtime-connection.md +37 -0
- package/package.json +43 -2
- package/src/agent/skill-discovery.ts +119 -0
- package/src/cli/config-overrides.ts +1 -5
- package/src/cli/entrypoint.ts +0 -6
- package/src/cli/help.ts +0 -43
- package/src/cli/index.ts +0 -2
- package/src/cli/management-commands.ts +1 -109
- package/src/cli/management.ts +1 -32
- package/src/cli/package-verification.ts +12 -4
- package/src/cli/parser.ts +0 -16
- package/src/cli/status.ts +1 -1
- package/src/cli/types.ts +0 -8
- package/src/input/commands/delegation-runtime.ts +0 -8
- package/src/input/commands/experience-runtime.ts +0 -177
- package/src/input/commands/guidance-runtime.ts +0 -69
- package/src/input/commands/local-runtime.ts +1 -57
- package/src/input/commands/local-setup-review.ts +1 -1
- package/src/input/commands/operator-runtime.ts +1 -145
- package/src/input/commands/platform-access-runtime.ts +2 -195
- package/src/input/commands/product-runtime.ts +0 -116
- package/src/input/commands/security-runtime.ts +88 -0
- package/src/input/commands/session-content.ts +0 -97
- package/src/input/commands/shell-core.ts +0 -13
- package/src/input/commands.ts +2 -95
- package/src/panels/builtin/operations.ts +3 -184
- package/src/panels/confirm-state.ts +1 -1
- package/src/panels/index.ts +0 -11
- package/src/version.ts +1 -1
- package/docs/deployment-and-services.md +0 -52
- package/src/cli/service-command.ts +0 -26
- package/src/cli/surface-command.ts +0 -247
- package/src/input/commands/branch-runtime.ts +0 -72
- package/src/input/commands/control-room-runtime.ts +0 -234
- package/src/input/commands/discovery-runtime.ts +0 -61
- package/src/input/commands/hooks-runtime.ts +0 -207
- package/src/input/commands/incident-runtime.ts +0 -106
- package/src/input/commands/integration-runtime.ts +0 -437
- package/src/input/commands/local-setup.ts +0 -288
- package/src/input/commands/managed-runtime.ts +0 -240
- package/src/input/commands/marketplace-runtime.ts +0 -305
- package/src/input/commands/memory-product-runtime.ts +0 -148
- package/src/input/commands/operator-panel-runtime.ts +0 -146
- package/src/input/commands/platform-services-runtime.ts +0 -271
- package/src/input/commands/profile-sync-runtime.ts +0 -110
- package/src/input/commands/provider.ts +0 -363
- package/src/input/commands/remote-runtime-pool.ts +0 -89
- package/src/input/commands/remote-runtime-setup.ts +0 -226
- package/src/input/commands/remote-runtime.ts +0 -432
- package/src/input/commands/replay-runtime.ts +0 -25
- package/src/input/commands/services-runtime.ts +0 -220
- package/src/input/commands/settings-sync-runtime.ts +0 -197
- package/src/input/commands/share-runtime.ts +0 -127
- package/src/input/commands/skills-runtime.ts +0 -226
- package/src/input/commands/teleport-runtime.ts +0 -68
- package/src/panels/cockpit-panel.ts +0 -183
- package/src/panels/communication-panel.ts +0 -153
- package/src/panels/control-plane-panel.ts +0 -211
- package/src/panels/forensics-panel.ts +0 -364
- package/src/panels/hooks-panel.ts +0 -239
- package/src/panels/incident-review-panel.ts +0 -197
- package/src/panels/marketplace-panel.ts +0 -212
- package/src/panels/ops-control-panel.ts +0 -150
- package/src/panels/ops-strategy-panel.ts +0 -235
- package/src/panels/orchestration-panel.ts +0 -272
- package/src/panels/plugins-panel.ts +0 -178
- package/src/panels/remote-panel.ts +0 -449
- package/src/panels/routes-panel.ts +0 -178
- package/src/panels/services-panel.ts +0 -231
- package/src/panels/settings-sync-panel.ts +0 -120
- package/src/panels/skills-panel.ts +0 -431
- package/src/panels/watchers-panel.ts +0 -193
- package/src/verification/live-verifier.ts +0 -588
- package/src/verification/verification-ledger.ts +0 -239
|
@@ -1,364 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ForensicsPanel — failure forensics TUI panel.
|
|
3
|
-
*
|
|
4
|
-
* Displays the most recent failure reports with auto-classified causes,
|
|
5
|
-
* causal chains, phase timings, and jump links to related panels.
|
|
6
|
-
*
|
|
7
|
-
* Open via /forensics or the panel picker.
|
|
8
|
-
*/
|
|
9
|
-
import type { Line } from '../types/grid.ts';
|
|
10
|
-
import type { ForensicsRegistry } from '@/runtime/index.ts';
|
|
11
|
-
import type { FailureReport, CausalChainEntry, PhaseTimingEntry } from '@/runtime/index.ts';
|
|
12
|
-
import { ForensicsDataPanel } from '@/runtime/index.ts';
|
|
13
|
-
import { BasePanel } from './base-panel.ts';
|
|
14
|
-
import { createEmptyLine } from '../types/grid.ts';
|
|
15
|
-
import {
|
|
16
|
-
buildEmptyState,
|
|
17
|
-
buildPanelLine,
|
|
18
|
-
buildPanelWorkspace,
|
|
19
|
-
resolveScrollablePanelSection,
|
|
20
|
-
DEFAULT_PANEL_PALETTE,
|
|
21
|
-
type PanelWorkspaceSection,
|
|
22
|
-
} from './polish.ts';
|
|
23
|
-
|
|
24
|
-
// ── Colour palette ────────────────────────────────────────────────────────────
|
|
25
|
-
const C = {
|
|
26
|
-
...DEFAULT_PANEL_PALETTE,
|
|
27
|
-
header: '#94a3b8',
|
|
28
|
-
headerBg: '#1e293b',
|
|
29
|
-
reportId: '#475569',
|
|
30
|
-
timestamp: '#64748b',
|
|
31
|
-
classification: '#f97316',
|
|
32
|
-
classOk: '#22c55e',
|
|
33
|
-
classCancelled: '#94a3b8',
|
|
34
|
-
classError: '#ef4444',
|
|
35
|
-
classWarn: '#eab308',
|
|
36
|
-
summaryText: '#cbd5e1',
|
|
37
|
-
label: '#64748b',
|
|
38
|
-
value: '#e2e8f0',
|
|
39
|
-
chainRoot: '#f97316',
|
|
40
|
-
chainEntry: '#94a3b8',
|
|
41
|
-
phaseOk: '#22c55e',
|
|
42
|
-
phaseFail: '#ef4444',
|
|
43
|
-
phasePending: '#64748b',
|
|
44
|
-
jumpLink: '#38bdf8',
|
|
45
|
-
separator: '#1e293b',
|
|
46
|
-
dim: '#334155',
|
|
47
|
-
empty: '#4b5563',
|
|
48
|
-
selectBg: '#1e3a5f',
|
|
49
|
-
} as const;
|
|
50
|
-
|
|
51
|
-
// ── Helpers ────────────────────────────────────────────────────────────────────
|
|
52
|
-
|
|
53
|
-
function fmtTime(ts: number): string {
|
|
54
|
-
const d = new Date(ts);
|
|
55
|
-
const hh = String(d.getHours()).padStart(2, '0');
|
|
56
|
-
const mm = String(d.getMinutes()).padStart(2, '0');
|
|
57
|
-
const ss = String(d.getSeconds()).padStart(2, '0');
|
|
58
|
-
return `${hh}:${mm}:${ss}`;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function fmtDuration(ms: number | undefined): string {
|
|
62
|
-
if (ms === undefined) return '?ms';
|
|
63
|
-
if (ms < 1000) return `${ms}ms`;
|
|
64
|
-
return `${(ms / 1000).toFixed(1)}s`;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function classificationColor(cls: FailureReport['classification']): string {
|
|
68
|
-
switch (cls) {
|
|
69
|
-
case 'cancelled': return C.classCancelled;
|
|
70
|
-
case 'max_tokens': return C.classWarn;
|
|
71
|
-
case 'unknown': return C.classWarn;
|
|
72
|
-
case 'llm_error': return C.classError;
|
|
73
|
-
case 'tool_failure': return C.classError;
|
|
74
|
-
case 'permission_denied':return C.classError;
|
|
75
|
-
case 'cascade_failure': return C.classError;
|
|
76
|
-
case 'turn_timeout': return C.classError;
|
|
77
|
-
case 'compaction_error': return C.classError;
|
|
78
|
-
default: return C.classification;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// ── ForensicsPanel ────────────────────────────────────────────────────────────
|
|
83
|
-
|
|
84
|
-
export class ForensicsPanel extends BasePanel {
|
|
85
|
-
private readonly _data: ForensicsDataPanel;
|
|
86
|
-
private _unsub: (() => void) | null = null;
|
|
87
|
-
// eslint-disable-next-line @typescript-eslint/prefer-readonly -- mutated in _renderDetail via direct assignment
|
|
88
|
-
private _scrollOffset = 0;
|
|
89
|
-
/** Index of the selected report in the all-reports list (newest-first). */
|
|
90
|
-
private _selectedIndex = 0;
|
|
91
|
-
/** View mode: 'list' shows report list; 'detail' shows a single report expanded. */
|
|
92
|
-
private _mode: 'list' | 'detail' = 'list';
|
|
93
|
-
|
|
94
|
-
public constructor(registry: ForensicsRegistry) {
|
|
95
|
-
super('forensics', 'Forensics', 'F', 'monitoring');
|
|
96
|
-
this._data = new ForensicsDataPanel(registry);
|
|
97
|
-
this._unsub = this._data.subscribe(() => this.markDirty());
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
public override onActivate(): void {
|
|
101
|
-
super.onActivate();
|
|
102
|
-
this._scrollOffset = 0;
|
|
103
|
-
this._selectedIndex = 0;
|
|
104
|
-
this._mode = 'list';
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
public handleInput(key: string): boolean {
|
|
108
|
-
if (this._mode === 'list') {
|
|
109
|
-
if (key === 'up' || key === 'k') {
|
|
110
|
-
this._selectedIndex = Math.max(0, this._selectedIndex - 1);
|
|
111
|
-
this.markDirty();
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
if (key === 'down' || key === 'j') {
|
|
115
|
-
const count = this._data.getAll().length;
|
|
116
|
-
this._selectedIndex = Math.min(count - 1, this._selectedIndex + 1);
|
|
117
|
-
this.markDirty();
|
|
118
|
-
return true;
|
|
119
|
-
}
|
|
120
|
-
if (key === 'return' || key === 'enter') {
|
|
121
|
-
this._mode = 'detail';
|
|
122
|
-
this._scrollOffset = 0;
|
|
123
|
-
this.markDirty();
|
|
124
|
-
return true;
|
|
125
|
-
}
|
|
126
|
-
} else {
|
|
127
|
-
if (key === 'escape' || key === 'q') {
|
|
128
|
-
this._mode = 'list';
|
|
129
|
-
this.markDirty();
|
|
130
|
-
return true;
|
|
131
|
-
}
|
|
132
|
-
if (key === 'up' || key === 'k') {
|
|
133
|
-
this._scrollOffset = Math.max(0, this._scrollOffset - 1);
|
|
134
|
-
this.markDirty();
|
|
135
|
-
return true;
|
|
136
|
-
}
|
|
137
|
-
if (key === 'down' || key === 'j') {
|
|
138
|
-
this._scrollOffset++;
|
|
139
|
-
this.markDirty();
|
|
140
|
-
return true;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return false;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
public override onDestroy(): void {
|
|
147
|
-
if (this._unsub) {
|
|
148
|
-
this._unsub();
|
|
149
|
-
this._unsub = null;
|
|
150
|
-
}
|
|
151
|
-
this._data.dispose();
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
public render(width: number, height: number): Line[] {
|
|
155
|
-
this.needsRender = false;
|
|
156
|
-
const intro = 'Recent failure reports, causal chains, phase timings, and cross-panel jump targets for incident investigation.';
|
|
157
|
-
const reports = this._data.getAll();
|
|
158
|
-
|
|
159
|
-
if (reports.length === 0) {
|
|
160
|
-
const workspace = buildPanelWorkspace(width, height, {
|
|
161
|
-
title: 'Failure Forensics',
|
|
162
|
-
intro,
|
|
163
|
-
sections: [{
|
|
164
|
-
lines: buildEmptyState(
|
|
165
|
-
width,
|
|
166
|
-
' No failure reports. All systems nominal.',
|
|
167
|
-
'Failures will appear here with classification, causal chains, phase timings, and jump links as runtime incidents are captured.',
|
|
168
|
-
[{ command: '/incident', summary: 'open the incident review workspace and forensics surfaces' }],
|
|
169
|
-
C,
|
|
170
|
-
),
|
|
171
|
-
}],
|
|
172
|
-
palette: C,
|
|
173
|
-
});
|
|
174
|
-
while (workspace.length < height) workspace.push(createEmptyLine(width));
|
|
175
|
-
return workspace;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const lines: Line[] = [];
|
|
179
|
-
if (this._mode === 'list') {
|
|
180
|
-
this._renderList(lines, reports, width, height, intro);
|
|
181
|
-
} else {
|
|
182
|
-
const report = reports[this._selectedIndex];
|
|
183
|
-
if (report) {
|
|
184
|
-
this._renderDetail(lines, report, width, height, intro);
|
|
185
|
-
} else {
|
|
186
|
-
this._mode = 'list';
|
|
187
|
-
this._renderList(lines, reports, width, height, intro);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
while (lines.length < height) lines.push(createEmptyLine(width));
|
|
192
|
-
return lines;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// ── List view ──────────────────────────────────────────────────────────────
|
|
196
|
-
|
|
197
|
-
private _renderList(lines: Line[], reports: FailureReport[], width: number, height: number, intro: string): void {
|
|
198
|
-
const reportRows: Line[] = [
|
|
199
|
-
buildPanelLine(width, [[' ID TIME CLASS SUMMARY', C.label]]),
|
|
200
|
-
];
|
|
201
|
-
|
|
202
|
-
for (let i = 0; i < reports.length; i++) {
|
|
203
|
-
const report = reports[i]!;
|
|
204
|
-
const isSelected = i === this._selectedIndex;
|
|
205
|
-
const bg = isSelected ? C.selectBg : undefined;
|
|
206
|
-
|
|
207
|
-
const idStr = report.id.slice(0, 8).padEnd(8, ' ');
|
|
208
|
-
const timeStr = fmtTime(report.generatedAt);
|
|
209
|
-
const cls = report.classification.slice(0, 20).padEnd(20, ' ');
|
|
210
|
-
const clsColor = classificationColor(report.classification);
|
|
211
|
-
const summaryMax = Math.max(0, width - 42);
|
|
212
|
-
const summaryStr = report.summary.slice(0, summaryMax);
|
|
213
|
-
|
|
214
|
-
const segs: Array<[string, string, string?]> = [
|
|
215
|
-
[isSelected ? '▸' : ' ', C.jumpLink, bg],
|
|
216
|
-
[`${idStr} `, C.reportId, bg],
|
|
217
|
-
[`${timeStr} `, C.timestamp, bg],
|
|
218
|
-
[`${cls} `, clsColor, bg],
|
|
219
|
-
[summaryStr, C.summaryText, bg],
|
|
220
|
-
];
|
|
221
|
-
reportRows.push(buildPanelLine(width, segs));
|
|
222
|
-
}
|
|
223
|
-
const reportsSection = resolveScrollablePanelSection(width, height, {
|
|
224
|
-
intro,
|
|
225
|
-
palette: C,
|
|
226
|
-
section: {
|
|
227
|
-
title: 'Reports',
|
|
228
|
-
scrollableLines: reportRows,
|
|
229
|
-
selectedIndex: this._selectedIndex + 1,
|
|
230
|
-
scrollOffset: this._scrollOffset,
|
|
231
|
-
minRows: 4,
|
|
232
|
-
appendWindowSummary: {
|
|
233
|
-
dimColor: C.label,
|
|
234
|
-
formatter: () => buildPanelLine(width, [[` [${this._selectedIndex + 1}/${reports.length}] Up/Down navigate Enter expand`, C.label]]),
|
|
235
|
-
},
|
|
236
|
-
},
|
|
237
|
-
});
|
|
238
|
-
this._scrollOffset = reportsSection.scrollOffset;
|
|
239
|
-
|
|
240
|
-
lines.push(...buildPanelWorkspace(width, height, {
|
|
241
|
-
title: 'Failure Forensics',
|
|
242
|
-
intro,
|
|
243
|
-
sections: [reportsSection.section],
|
|
244
|
-
palette: C,
|
|
245
|
-
}));
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// ── Detail view ────────────────────────────────────────────────────────────
|
|
249
|
-
|
|
250
|
-
private _renderDetail(lines: Line[], report: FailureReport, width: number, height: number, intro: string): void {
|
|
251
|
-
const detailLines: Line[] = [];
|
|
252
|
-
|
|
253
|
-
detailLines.push(buildPanelLine(width, [
|
|
254
|
-
[' Report: ', C.label],
|
|
255
|
-
[report.id, C.value],
|
|
256
|
-
[' Generated: ', C.label],
|
|
257
|
-
[fmtTime(report.generatedAt), C.timestamp],
|
|
258
|
-
]));
|
|
259
|
-
detailLines.push(buildPanelLine(width, [
|
|
260
|
-
[' Class: ', C.label],
|
|
261
|
-
[report.classification, classificationColor(report.classification)],
|
|
262
|
-
]));
|
|
263
|
-
detailLines.push(buildPanelLine(width, [
|
|
264
|
-
[' Summary: ', C.label],
|
|
265
|
-
[report.summary.slice(0, width - 11), C.summaryText],
|
|
266
|
-
]));
|
|
267
|
-
|
|
268
|
-
if (report.errorMessage) {
|
|
269
|
-
detailLines.push(buildPanelLine(width, [
|
|
270
|
-
[' Error: ', C.label],
|
|
271
|
-
[report.errorMessage.slice(0, width - 11), C.classError],
|
|
272
|
-
]));
|
|
273
|
-
}
|
|
274
|
-
if (report.stopReason) {
|
|
275
|
-
detailLines.push(buildPanelLine(width, [
|
|
276
|
-
[' Stop: ', C.label],
|
|
277
|
-
[report.stopReason, C.classWarn],
|
|
278
|
-
]));
|
|
279
|
-
}
|
|
280
|
-
if (report.taskId) {
|
|
281
|
-
detailLines.push(buildPanelLine(width, [[` Task: ${report.taskId}`, C.value]]));
|
|
282
|
-
}
|
|
283
|
-
if (report.turnId) {
|
|
284
|
-
detailLines.push(buildPanelLine(width, [[` Turn: ${report.turnId}`, C.value]]));
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Phase timings
|
|
288
|
-
if (report.phaseTimings.length > 0) {
|
|
289
|
-
detailLines.push(buildPanelLine(width, [[' Phase Timings:', C.label]]));
|
|
290
|
-
for (const pt of report.phaseTimings) {
|
|
291
|
-
this._renderPhase(detailLines, pt, width);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// Causal chain
|
|
296
|
-
if (report.causalChain.length > 0) {
|
|
297
|
-
detailLines.push(buildPanelLine(width, [[' Causal Chain:', C.label]]));
|
|
298
|
-
for (const entry of report.causalChain) {
|
|
299
|
-
this._renderCausal(detailLines, entry, width);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Jump links
|
|
304
|
-
if (report.jumpLinks.length > 0) {
|
|
305
|
-
detailLines.push(buildPanelLine(width, [[' Jump Links:', C.label]]));
|
|
306
|
-
for (const link of report.jumpLinks) {
|
|
307
|
-
const kindTag = link.kind === 'panel' ? '[panel]' : '[cmd] ';
|
|
308
|
-
detailLines.push(buildPanelLine(width, [
|
|
309
|
-
[' ', C.dim],
|
|
310
|
-
[kindTag, C.timestamp],
|
|
311
|
-
[` ${link.label}`, C.jumpLink],
|
|
312
|
-
[link.args ? ` (${link.args})` : '', C.dim],
|
|
313
|
-
]));
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
detailLines.push(buildPanelLine(width, [[' Esc/q: back to list Up/Down: scroll', C.dim]]));
|
|
318
|
-
|
|
319
|
-
const detailSection = resolveScrollablePanelSection(width, height, {
|
|
320
|
-
intro,
|
|
321
|
-
palette: C,
|
|
322
|
-
section: {
|
|
323
|
-
title: 'Report Detail',
|
|
324
|
-
scrollableLines: detailLines,
|
|
325
|
-
scrollOffset: this._scrollOffset,
|
|
326
|
-
minRows: 1,
|
|
327
|
-
},
|
|
328
|
-
});
|
|
329
|
-
this._scrollOffset = detailSection.scrollOffset;
|
|
330
|
-
lines.push(...buildPanelWorkspace(width, height, {
|
|
331
|
-
title: 'Failure Forensics',
|
|
332
|
-
intro,
|
|
333
|
-
sections: [detailSection.section],
|
|
334
|
-
palette: C,
|
|
335
|
-
}));
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
private _renderPhase(lines: Line[], pt: PhaseTimingEntry, width: number): void {
|
|
339
|
-
const statusChar = pt.success ? '✓' : '✕';
|
|
340
|
-
const statusColor = pt.success ? C.phaseOk : C.phaseFail;
|
|
341
|
-
const dur = fmtDuration(pt.durationMs);
|
|
342
|
-
const phaseLabel = pt.phase.slice(0, 14).padEnd(14, ' ');
|
|
343
|
-
const errPart = pt.error ? ` ${pt.error.slice(0, Math.max(0, width - 32))}` : '';
|
|
344
|
-
lines.push(buildPanelLine(width, [
|
|
345
|
-
[' ', C.dim],
|
|
346
|
-
[statusChar + ' ', statusColor],
|
|
347
|
-
[phaseLabel, C.value],
|
|
348
|
-
[dur.padStart(6, ' '), C.timestamp],
|
|
349
|
-
[errPart, C.classError],
|
|
350
|
-
]));
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
private _renderCausal(lines: Line[], entry: CausalChainEntry, width: number): void {
|
|
354
|
-
const prefix = entry.isRootCause ? ' * ' : ' - ';
|
|
355
|
-
const color = entry.isRootCause ? C.chainRoot : C.chainEntry;
|
|
356
|
-
const timeStr = fmtTime(entry.ts);
|
|
357
|
-
const descMax = Math.max(0, width - prefix.length - 9);
|
|
358
|
-
lines.push(buildPanelLine(width, [
|
|
359
|
-
[prefix, color],
|
|
360
|
-
[`${timeStr} `, C.timestamp],
|
|
361
|
-
[entry.description.slice(0, descMax), color],
|
|
362
|
-
]));
|
|
363
|
-
}
|
|
364
|
-
}
|
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
import type { Line } from '../types/grid.ts';
|
|
2
|
-
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
3
|
-
import { listHookPointContracts } from '@pellux/goodvibes-sdk/platform/hooks';
|
|
4
|
-
import type { HookDispatcher } from '@pellux/goodvibes-sdk/platform/hooks';
|
|
5
|
-
import type { HookPointContract } from '@pellux/goodvibes-sdk/platform/hooks';
|
|
6
|
-
import type { HookActivityRecord, HookActivityTracker } from '@pellux/goodvibes-sdk/platform/hooks';
|
|
7
|
-
import type { HookAuthoringAction, HookSimulationResult } from '@pellux/goodvibes-sdk/platform/hooks';
|
|
8
|
-
import type { HookChain, HookDefinition } from '@pellux/goodvibes-sdk/platform/hooks';
|
|
9
|
-
import type { HookWorkbench } from '@pellux/goodvibes-sdk/platform/hooks';
|
|
10
|
-
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
11
|
-
import {
|
|
12
|
-
buildPanelLine,
|
|
13
|
-
buildStatusPill,
|
|
14
|
-
DEFAULT_PANEL_PALETTE,
|
|
15
|
-
} from './polish.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
|
-
info: '#38bdf8',
|
|
25
|
-
selectBg: '#0f172a',
|
|
26
|
-
} as const;
|
|
27
|
-
|
|
28
|
-
export interface HooksPanelWorkbenchView {
|
|
29
|
-
listManagedHooks(): Array<{ pattern: string; hook: HookDefinition }>;
|
|
30
|
-
listManagedChains(): HookChain[];
|
|
31
|
-
listRecentActions(limit?: number): HookAuthoringAction[];
|
|
32
|
-
getLastSimulation(): HookSimulationResult | null;
|
|
33
|
-
getHooksFilePath(): string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface HooksPanelDataSource {
|
|
37
|
-
listContracts(): HookPointContract[];
|
|
38
|
-
listHooks(): Array<{ pattern: string; hook: HookDefinition }>;
|
|
39
|
-
listChains(): HookChain[];
|
|
40
|
-
listRecentActivity(limit?: number): readonly HookActivityRecord[];
|
|
41
|
-
getWorkbench(): HooksPanelWorkbenchView;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function createDefaultDataSource(
|
|
45
|
-
hookDispatcher: Pick<HookDispatcher, 'listHooks' | 'getChains'>,
|
|
46
|
-
hookWorkbench: HookWorkbench,
|
|
47
|
-
hookActivityTracker: Pick<HookActivityTracker, 'listRecent'>,
|
|
48
|
-
): HooksPanelDataSource {
|
|
49
|
-
return {
|
|
50
|
-
listContracts: () => listHookPointContracts(),
|
|
51
|
-
listHooks: () => hookDispatcher.listHooks(),
|
|
52
|
-
listChains: () => hookDispatcher.getChains(),
|
|
53
|
-
listRecentActivity: (limit = 3) => hookActivityTracker.listRecent(limit),
|
|
54
|
-
getWorkbench: () => hookWorkbench,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
type HookEntry = { pattern: string; hook: HookDefinition };
|
|
59
|
-
|
|
60
|
-
export class HooksPanel extends ScrollableListPanel<HookEntry> {
|
|
61
|
-
private readonly dataSource: HooksPanelDataSource;
|
|
62
|
-
|
|
63
|
-
public constructor(
|
|
64
|
-
hookDispatcher: Pick<HookDispatcher, 'listHooks' | 'getChains'>,
|
|
65
|
-
hookWorkbench: HookWorkbench,
|
|
66
|
-
hookActivityTracker: Pick<HookActivityTracker, 'listRecent'>,
|
|
67
|
-
dataSource: HooksPanelDataSource = createDefaultDataSource(hookDispatcher, hookWorkbench, hookActivityTracker),
|
|
68
|
-
) {
|
|
69
|
-
super('hooks', 'Hooks', 'H', 'monitoring');
|
|
70
|
-
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
71
|
-
this.dataSource = dataSource;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
protected override getPalette() { return C; }
|
|
75
|
-
protected override getEmptyStateMessage() { return ' No hooks are currently registered.'; }
|
|
76
|
-
protected override getEmptyStateActions() {
|
|
77
|
-
return [
|
|
78
|
-
{ command: '/hooks', summary: 'review hook contracts and managed authoring actions' },
|
|
79
|
-
{ command: '/settings', summary: 'review hook/runtime behavior in the settings surface' },
|
|
80
|
-
];
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
protected getItems(): readonly HookEntry[] {
|
|
84
|
-
return this.dataSource.listHooks();
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
protected renderItem(entry: HookEntry, index: number, selected: boolean, width: number): Line {
|
|
88
|
-
const bg = selected ? C.selectBg : undefined;
|
|
89
|
-
return buildPanelLine(width, [
|
|
90
|
-
[' ', C.label, bg],
|
|
91
|
-
[truncateDisplay(entry.hook.name ?? '(unnamed)', 20).padEnd(20), C.value, bg],
|
|
92
|
-
[` ${truncateDisplay(entry.pattern, 28).padEnd(28)}`, C.info, bg],
|
|
93
|
-
...buildStatusPill(entry.hook.enabled === false ? 'warn' : 'good', ` ${(entry.hook.enabled === false ? 'DISABLED' : 'ENABLED').padEnd(8)}`, { bg }),
|
|
94
|
-
[` ${entry.hook.type}`, C.dim, bg],
|
|
95
|
-
]);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
public handleInput(key: string): boolean {
|
|
99
|
-
if (key === 'r') {
|
|
100
|
-
this.markDirty();
|
|
101
|
-
return true;
|
|
102
|
-
}
|
|
103
|
-
return super.handleInput(key);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
public render(width: number, height: number): Line[] {
|
|
107
|
-
this.clampSelection();
|
|
108
|
-
const hooks = this.dataSource.listHooks();
|
|
109
|
-
const contracts = this.dataSource.listContracts();
|
|
110
|
-
const chains = this.dataSource.listChains();
|
|
111
|
-
const recentActivity = this.dataSource.listRecentActivity(3);
|
|
112
|
-
const workbench = this.dataSource.getWorkbench();
|
|
113
|
-
const managedHooks = workbench.listManagedHooks();
|
|
114
|
-
const managedChains = workbench.listManagedChains();
|
|
115
|
-
const recentAuthoring = workbench.listRecentActions(3);
|
|
116
|
-
const lastSimulation = workbench.getLastSimulation();
|
|
117
|
-
const intro = 'Hook contracts, active registrations, managed authoring, recent runtime activity, and simulation matches.';
|
|
118
|
-
|
|
119
|
-
const selected = hooks[this.selectedIndex];
|
|
120
|
-
const contract = selected ? contracts.find((c) => c.pattern === selected.pattern) : undefined;
|
|
121
|
-
|
|
122
|
-
const detailLines: Line[] = [];
|
|
123
|
-
if (selected) {
|
|
124
|
-
detailLines.push(buildPanelLine(width, [
|
|
125
|
-
[' Hook: ', C.label],
|
|
126
|
-
[selected.hook.name ?? '(unnamed)', C.value],
|
|
127
|
-
[' Type: ', C.label],
|
|
128
|
-
[selected.hook.type, C.info],
|
|
129
|
-
[' Match: ', C.label],
|
|
130
|
-
[selected.hook.matcher ?? selected.hook.match, C.value],
|
|
131
|
-
]));
|
|
132
|
-
detailLines.push(buildPanelLine(width, [
|
|
133
|
-
[' Pattern: ', C.label],
|
|
134
|
-
[truncateDisplay(selected.pattern, Math.max(0, width - 12)), C.value],
|
|
135
|
-
]));
|
|
136
|
-
if (contract) {
|
|
137
|
-
detailLines.push(buildPanelLine(width, [
|
|
138
|
-
[' Contract: ', C.label],
|
|
139
|
-
[`${contract.authority} / ${contract.executionMode}`, C.info],
|
|
140
|
-
[' Policy: ', C.label],
|
|
141
|
-
[contract.failurePolicy, C.value],
|
|
142
|
-
]));
|
|
143
|
-
detailLines.push(buildPanelLine(width, [
|
|
144
|
-
[' Capabilities: ', C.label],
|
|
145
|
-
[`deny=${contract.canDeny ? 'yes' : 'no'} mutate=${contract.canMutateInput ? 'yes' : 'no'} inject=${contract.canInjectContext ? 'yes' : 'no'}`, C.dim],
|
|
146
|
-
]));
|
|
147
|
-
} else {
|
|
148
|
-
detailLines.push(buildPanelLine(width, [[' Contract: No exact contract registered for this pattern.', C.warn]]));
|
|
149
|
-
}
|
|
150
|
-
detailLines.push(buildPanelLine(width, [
|
|
151
|
-
[' Summary: ', C.label],
|
|
152
|
-
[`hooks=${hooks.length} chains=${chains.length} contracts=${contracts.length} managed=${managedHooks.length}/${managedChains.length}`, C.dim],
|
|
153
|
-
]));
|
|
154
|
-
detailLines.push(buildPanelLine(width, [
|
|
155
|
-
[' Hooks file: ', C.label],
|
|
156
|
-
[truncateDisplay(workbench.getHooksFilePath(), Math.max(0, width - 15)), C.dim],
|
|
157
|
-
]));
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const activityLines: Line[] = recentActivity.length === 0
|
|
161
|
-
? [buildPanelLine(width, [[' No hook activity recorded yet.', C.empty]])]
|
|
162
|
-
: recentActivity.map((record) => {
|
|
163
|
-
const color = !record.ok ? C.error : record.decision === 'deny' ? C.warn : C.ok;
|
|
164
|
-
return buildPanelLine(width, [
|
|
165
|
-
[' ', C.label],
|
|
166
|
-
[truncateDisplay(record.hookName, 18).padEnd(18), C.value],
|
|
167
|
-
[' ', C.label],
|
|
168
|
-
[truncateDisplay(record.path, 26).padEnd(26), C.info],
|
|
169
|
-
[' ', C.label],
|
|
170
|
-
[record.ok ? (record.decision ?? 'ok') : 'error', color],
|
|
171
|
-
]);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
const authoringLines: Line[] = recentAuthoring.length === 0
|
|
175
|
-
? [buildPanelLine(width, [[' No managed hook authoring actions recorded yet.', C.empty]])]
|
|
176
|
-
: recentAuthoring.map((action) => buildPanelLine(width, [
|
|
177
|
-
[' ', C.label],
|
|
178
|
-
[truncateDisplay(action.kind, 14).padEnd(14), C.info],
|
|
179
|
-
[' ', C.label],
|
|
180
|
-
[truncateDisplay(action.target, Math.max(0, width - 20)), C.dim],
|
|
181
|
-
]));
|
|
182
|
-
if (lastSimulation) {
|
|
183
|
-
authoringLines.push(buildPanelLine(width, [
|
|
184
|
-
[' Last Simulation: ', C.label],
|
|
185
|
-
[truncateDisplay(lastSimulation.eventPath, Math.max(0, width - 20)), C.value],
|
|
186
|
-
]));
|
|
187
|
-
authoringLines.push(buildPanelLine(width, [
|
|
188
|
-
[' Matches: ', C.label],
|
|
189
|
-
[`hooks=${lastSimulation.matchedHooks.length} chains=${lastSimulation.matchedChains.length}`, C.dim],
|
|
190
|
-
]));
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Empty state: show extra context lines (hooks file, contracts, authoring) before base empty state
|
|
194
|
-
if (hooks.length === 0) {
|
|
195
|
-
const extraHeader: Line[] = [
|
|
196
|
-
buildPanelLine(width, [
|
|
197
|
-
[' Contracts: ', C.label],
|
|
198
|
-
[String(contracts.length), C.value],
|
|
199
|
-
[' Chains: ', C.label],
|
|
200
|
-
[String(chains.length), C.value],
|
|
201
|
-
[' Managed: ', C.label],
|
|
202
|
-
[String(managedHooks.length), C.info],
|
|
203
|
-
]),
|
|
204
|
-
buildPanelLine(width, [
|
|
205
|
-
[' Hooks file: ', C.label],
|
|
206
|
-
[truncateDisplay(workbench.getHooksFilePath(), Math.max(0, width - 15)), C.dim],
|
|
207
|
-
]),
|
|
208
|
-
];
|
|
209
|
-
if (recentAuthoring.length > 0) {
|
|
210
|
-
extraHeader.push(buildPanelLine(width, [
|
|
211
|
-
[' Authoring: ', C.label],
|
|
212
|
-
[truncateDisplay(`${recentAuthoring[0]!.kind} ${recentAuthoring[0]!.target}`, Math.max(0, width - 14)), C.info],
|
|
213
|
-
]));
|
|
214
|
-
}
|
|
215
|
-
if (lastSimulation) {
|
|
216
|
-
extraHeader.push(buildPanelLine(width, [
|
|
217
|
-
[' Last Simulation: ', C.label],
|
|
218
|
-
[truncateDisplay(lastSimulation.eventPath, Math.max(0, width - 20)), C.value],
|
|
219
|
-
]));
|
|
220
|
-
}
|
|
221
|
-
return this.renderList(width, height, {
|
|
222
|
-
title: 'Hooks Control Room',
|
|
223
|
-
header: extraHeader,
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return this.renderList(width, height, {
|
|
228
|
-
title: 'Hooks Control Room',
|
|
229
|
-
footer: [
|
|
230
|
-
...detailLines,
|
|
231
|
-
buildPanelLine(width, [[' Recent Activity', C.label]]),
|
|
232
|
-
...activityLines,
|
|
233
|
-
buildPanelLine(width, [[' Authoring', C.label]]),
|
|
234
|
-
...authoringLines,
|
|
235
|
-
buildPanelLine(width, [[' Up/Down move r refresh /hooks for full contract listing', C.dim]]),
|
|
236
|
-
],
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
}
|