@kal-elsam/kairo-runtime 0.8.0 → 0.10.0
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/README.md +41 -15
- package/package.json +1 -1
- package/scripts/cockpit-smoke.mjs +6 -6
- package/scripts/ux-prototype-tty.mjs +9 -0
- package/src/cli.js +24 -1
- package/src/global/control-plane-proposals.js +2 -1
- package/src/global/control-plane-snapshot.js +1 -1
- package/src/global/global-doctor.js +2 -0
- package/src/global/ink/cockpit/primitives.js +127 -50
- package/src/global/ink/cockpit-alerts.js +36 -0
- package/src/global/ink/cockpit-changes.js +61 -37
- package/src/global/ink/cockpit-control-center.js +79 -53
- package/src/global/ink/cockpit-controller.js +98 -15
- package/src/global/ink/cockpit-enter.js +1 -0
- package/src/global/ink/cockpit-focus.js +4 -2
- package/src/global/ink/cockpit-models.js +100 -51
- package/src/global/ink/cockpit-palette.js +109 -0
- package/src/global/ink/cockpit-path-label.js +19 -0
- package/src/global/ink/cockpit-recovery.js +84 -18
- package/src/global/ink/cockpit-reviews.js +14 -10
- package/src/global/ink/cockpit-runs.js +13 -4
- package/src/global/ink/cockpit-settings.js +194 -0
- package/src/global/ink/cockpit-usage.js +111 -0
- package/src/global/ink/cockpit-views.js +119 -116
- package/src/global/ink/orchestrator-app.js +169 -46
- package/src/global/ink/orchestrator-state.js +24 -14
- package/src/global/ink/setup-app.js +55 -72
- package/src/global/ink/setup-state.js +16 -0
- package/src/global/ink/theme.js +27 -0
- package/src/global/ink/use-orchestrator-data.js +58 -0
- package/src/global/ink/ux/live-activity.js +194 -0
- package/src/global/ink/ux/live-alerts.js +159 -0
- package/src/global/ink/ux/live-governance.js +195 -0
- package/src/global/ink/ux/live-orchestration.js +188 -0
- package/src/global/ink/ux/live-overview.js +125 -0
- package/src/global/ink/ux/live-settings.js +189 -0
- package/src/global/ink/ux/live-setup.js +160 -0
- package/src/global/ink/ux/live-usage.js +51 -0
- package/src/global/ink/ux/semantic.js +84 -0
- package/src/global/ink/ux/task-flow-app.js +85 -0
- package/src/global/ink/ux/task-flow.js +173 -0
- package/src/global/orchestrator.js +37 -20
- package/src/global/paths.js +3 -0
- package/src/global/runtime/alerts/alert-store.js +216 -0
- package/src/global/runtime/alerts/alert-types.js +59 -0
- package/src/global/runtime/alerts/alert-validate.js +117 -0
- package/src/global/runtime/monitor/monitor-cli.js +62 -0
- package/src/global/runtime/monitor/monitor-platform.js +95 -0
- package/src/global/runtime/monitor/monitor.js +249 -0
|
@@ -7,6 +7,7 @@ import { buildHomeMissionModel, formatHomeRecentRun } from "./cockpit-home.js";
|
|
|
7
7
|
import { isRunsBranchView } from "./cockpit-runs.js";
|
|
8
8
|
import { buildChangesFooterParts } from "./cockpit-changes.js";
|
|
9
9
|
import { buildRecoveryFooterParts } from "./cockpit-recovery.js";
|
|
10
|
+
import { buildSettingsFooterParts, SETTINGS_PHASE } from "./cockpit-settings.js";
|
|
10
11
|
|
|
11
12
|
export const COCKPIT_REGIONS = {
|
|
12
13
|
NAV: "nav",
|
|
@@ -17,53 +18,45 @@ export const COCKPIT_REGIONS = {
|
|
|
17
18
|
export const COCKPIT_NAV = [
|
|
18
19
|
{
|
|
19
20
|
id: "overview",
|
|
20
|
-
label: "
|
|
21
|
+
label: "Overview",
|
|
21
22
|
view: ORCHESTRATOR_VIEWS.HOME,
|
|
22
|
-
description: "
|
|
23
|
+
description: "Status, next action, activity, alerts, and tokens."
|
|
23
24
|
},
|
|
24
25
|
{
|
|
25
|
-
id: "
|
|
26
|
-
label: "
|
|
27
|
-
view: ORCHESTRATOR_VIEWS.IDES,
|
|
28
|
-
description: "Detected agents, auth signals, capabilities, and recommended policy."
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
id: "modules",
|
|
32
|
-
label: "Harness modules",
|
|
33
|
-
view: ORCHESTRATOR_VIEWS.MODULES,
|
|
34
|
-
description: "Orchestrator, SDD/TDD, and external Engram/Graphify integrations."
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
id: "changes",
|
|
38
|
-
label: "Changes",
|
|
26
|
+
id: "governance",
|
|
27
|
+
label: "Governance",
|
|
39
28
|
view: ORCHESTRATOR_VIEWS.CHANGES,
|
|
40
|
-
description: "
|
|
29
|
+
description: "Repair drift and apply governed changes."
|
|
41
30
|
},
|
|
42
31
|
{
|
|
43
32
|
id: "activity",
|
|
44
|
-
label: "Activity
|
|
33
|
+
label: "Activity",
|
|
45
34
|
view: ORCHESTRATOR_VIEWS.ACTIVITY,
|
|
46
|
-
description: "
|
|
35
|
+
description: "Operations, backups, and recovery."
|
|
47
36
|
},
|
|
48
37
|
{
|
|
49
|
-
id: "
|
|
50
|
-
label: "
|
|
51
|
-
view: ORCHESTRATOR_VIEWS.
|
|
52
|
-
description: "
|
|
38
|
+
id: "orchestration",
|
|
39
|
+
label: "Orchestration",
|
|
40
|
+
view: ORCHESTRATOR_VIEWS.RUNS,
|
|
41
|
+
description: "Runs, reviews, and supervised execution."
|
|
53
42
|
},
|
|
54
43
|
{
|
|
55
|
-
id: "
|
|
56
|
-
label: "
|
|
57
|
-
view: ORCHESTRATOR_VIEWS.
|
|
58
|
-
description: "
|
|
44
|
+
id: "usage",
|
|
45
|
+
label: "Usage",
|
|
46
|
+
view: ORCHESTRATOR_VIEWS.USAGE,
|
|
47
|
+
description: "Token and context pressure when auditable."
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "settings",
|
|
51
|
+
label: "Settings",
|
|
52
|
+
view: ORCHESTRATOR_VIEWS.PROFILE,
|
|
53
|
+
description: "Profile, policy, and guided configuration."
|
|
59
54
|
}
|
|
60
55
|
];
|
|
61
56
|
|
|
62
57
|
export function regionsForLayout(layoutMode) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
if (layoutMode === LAYOUT_MODES.COMPACT) {
|
|
58
|
+
// Single main panel: nav strip + content. SYSTEM column retired from the shell.
|
|
59
|
+
if (layoutMode === LAYOUT_MODES.WIDE || layoutMode === LAYOUT_MODES.COMPACT) {
|
|
67
60
|
return [COCKPIT_REGIONS.NAV, COCKPIT_REGIONS.CONTENT];
|
|
68
61
|
}
|
|
69
62
|
return [COCKPIT_REGIONS.CONTENT];
|
|
@@ -71,8 +64,8 @@ export function regionsForLayout(layoutMode) {
|
|
|
71
64
|
|
|
72
65
|
export function navIndexForView(view, items = COCKPIT_NAV) {
|
|
73
66
|
if (isRunsBranchView(view)) {
|
|
74
|
-
const
|
|
75
|
-
return
|
|
67
|
+
const orchIndex = items.findIndex((item) => item.id === "orchestration");
|
|
68
|
+
return orchIndex >= 0 ? orchIndex : 0;
|
|
76
69
|
}
|
|
77
70
|
const index = items.findIndex((item) => item.view === view);
|
|
78
71
|
return index >= 0 ? index : 0;
|
|
@@ -119,18 +112,40 @@ export function resolveNavStatusSummary(item, {
|
|
|
119
112
|
diagnostics,
|
|
120
113
|
dashboard
|
|
121
114
|
}).label;
|
|
115
|
+
case "governance":
|
|
116
|
+
return changes > 0 ? `${changes} pending` : "Clean";
|
|
117
|
+
case "activity":
|
|
118
|
+
return backups > 0 ? `${backups} backups` : "No backups";
|
|
119
|
+
case "orchestration":
|
|
120
|
+
return active === 0 ? "Idle" : `${active} active`;
|
|
121
|
+
case "usage": {
|
|
122
|
+
if (Number.isFinite(snapshot?.budgets?.stableUsedTokens)) return "Auditable";
|
|
123
|
+
const resolved = dashboard?.profile?.profile ?? {};
|
|
124
|
+
if (Number.isFinite(resolved.tokenBudget)
|
|
125
|
+
|| Number.isFinite(resolved.stableContextBudget)
|
|
126
|
+
|| Number.isFinite(resolved.requestContextBudget)) {
|
|
127
|
+
return "Auditable";
|
|
128
|
+
}
|
|
129
|
+
const runs = [...(dashboard?.activeRuns ?? []), ...(dashboard?.recentRuns ?? [])];
|
|
130
|
+
return runs.some((run) => {
|
|
131
|
+
const u = run?.tokenUsage;
|
|
132
|
+
return u && typeof u === "object"
|
|
133
|
+
&& (Number.isFinite(u.total) || Number.isFinite(u.input) || Number.isFinite(u.output));
|
|
134
|
+
})
|
|
135
|
+
? "Auditable"
|
|
136
|
+
: "n/a";
|
|
137
|
+
}
|
|
138
|
+
case "settings":
|
|
139
|
+
return snapshot?.policy?.profile ?? "defaults";
|
|
122
140
|
case "ides":
|
|
123
141
|
return `${governed}/${detected} governed`;
|
|
124
142
|
case "modules":
|
|
125
143
|
return `${snapshot?.coverage?.components ?? 0} modules`;
|
|
126
144
|
case "changes":
|
|
127
145
|
return changes > 0 ? `${changes} pending` : "Clean";
|
|
128
|
-
case "activity":
|
|
129
|
-
return backups > 0 ? `${backups} backups` : "No backups";
|
|
130
146
|
case "profile":
|
|
131
147
|
return snapshot?.policy?.profile ?? "defaults";
|
|
132
148
|
case "runs":
|
|
133
|
-
return active === 0 ? "Idle" : `${active} active`;
|
|
134
149
|
case "active":
|
|
135
150
|
return active === 0 ? "Idle" : `${active} active`;
|
|
136
151
|
case "providers":
|
|
@@ -159,7 +174,7 @@ export function buildNavModel({
|
|
|
159
174
|
const mapped = items.map((item, index) => {
|
|
160
175
|
const isSelected = index === navIndex;
|
|
161
176
|
const isCurrent = item.view === currentView
|
|
162
|
-
|| (item.id === "
|
|
177
|
+
|| (item.id === "orchestration" && isRunsBranchView(currentView));
|
|
163
178
|
return {
|
|
164
179
|
...item,
|
|
165
180
|
marker: isSelected ? glyphs.focus : (isCurrent ? glyphs.bullet : " "),
|
|
@@ -222,45 +237,72 @@ export function buildFooterModel({
|
|
|
222
237
|
region = COCKPIT_REGIONS.NAV,
|
|
223
238
|
navIndex = 0,
|
|
224
239
|
helpOpen = false,
|
|
240
|
+
paletteOpen = false,
|
|
225
241
|
canCancel = false,
|
|
226
242
|
unicode = true,
|
|
227
243
|
hasError = false,
|
|
228
244
|
changesPhase = null,
|
|
229
|
-
recoveryPhase = null
|
|
245
|
+
recoveryPhase = null,
|
|
246
|
+
recoveryHasPreview = false,
|
|
247
|
+
settingsPhase = null,
|
|
248
|
+
columns = 80
|
|
230
249
|
} = {}) {
|
|
231
250
|
const glyphs = resolveGlyphs(unicode);
|
|
232
251
|
const parts = [];
|
|
252
|
+
const footerColumns = Math.max(24, Math.min(Number(columns) || 80, 120));
|
|
233
253
|
|
|
234
254
|
if (hasError) {
|
|
235
255
|
parts.push("R Retry");
|
|
236
256
|
parts.push("Esc Exit");
|
|
237
|
-
return { text: parts.join(` ${glyphs.bullet} `) };
|
|
257
|
+
return { text: parts.join(` ${glyphs.bullet} `), columns: footerColumns };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (paletteOpen) {
|
|
261
|
+
return {
|
|
262
|
+
text: ["↑↓ Select", "Enter Run", "Esc Close"].join(` ${glyphs.bullet} `),
|
|
263
|
+
columns: footerColumns
|
|
264
|
+
};
|
|
238
265
|
}
|
|
239
266
|
|
|
240
267
|
if (helpOpen || view === ORCHESTRATOR_VIEWS.HELP) {
|
|
241
268
|
parts.push("Esc close help");
|
|
242
269
|
parts.push("? Help");
|
|
243
|
-
return { text: parts.join(` ${glyphs.bullet} `) };
|
|
270
|
+
return { text: parts.join(` ${glyphs.bullet} `), columns: footerColumns };
|
|
244
271
|
}
|
|
245
272
|
|
|
246
273
|
if (view === ORCHESTRATOR_VIEWS.RUN_DETAIL) {
|
|
247
274
|
parts.push("R refresh");
|
|
248
275
|
if (canCancel) parts.push("C cancel");
|
|
249
276
|
parts.push("Esc Back");
|
|
250
|
-
return { text: parts.join(` ${glyphs.bullet} `) };
|
|
277
|
+
return { text: parts.join(` ${glyphs.bullet} `), columns: footerColumns };
|
|
251
278
|
}
|
|
252
279
|
|
|
253
280
|
if (view === ORCHESTRATOR_VIEWS.REVIEW_DETAIL) {
|
|
254
281
|
parts.push("Esc Back");
|
|
255
|
-
return { text: parts.join(` ${glyphs.bullet} `) };
|
|
282
|
+
return { text: parts.join(` ${glyphs.bullet} `), columns: footerColumns };
|
|
256
283
|
}
|
|
257
284
|
|
|
258
285
|
if (view === ORCHESTRATOR_VIEWS.CHANGES) {
|
|
259
|
-
return {
|
|
286
|
+
return {
|
|
287
|
+
text: buildChangesFooterParts(changesPhase).join(` ${glyphs.bullet} `),
|
|
288
|
+
columns: footerColumns
|
|
289
|
+
};
|
|
260
290
|
}
|
|
261
291
|
|
|
262
292
|
if (view === ORCHESTRATOR_VIEWS.ACTIVITY) {
|
|
263
|
-
return {
|
|
293
|
+
return {
|
|
294
|
+
text: buildRecoveryFooterParts(recoveryPhase, { hasPreview: recoveryHasPreview })
|
|
295
|
+
.join(` ${glyphs.bullet} `),
|
|
296
|
+
columns: footerColumns
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (view === ORCHESTRATOR_VIEWS.PROFILE) {
|
|
301
|
+
return {
|
|
302
|
+
text: buildSettingsFooterParts(settingsPhase ?? SETTINGS_PHASE.BROWSE)
|
|
303
|
+
.join(` ${glyphs.bullet} `),
|
|
304
|
+
columns: footerColumns
|
|
305
|
+
};
|
|
264
306
|
}
|
|
265
307
|
|
|
266
308
|
parts.push("↑↓ Navigate");
|
|
@@ -275,15 +317,21 @@ export function buildFooterModel({
|
|
|
275
317
|
parts.push("Tab Region");
|
|
276
318
|
}
|
|
277
319
|
|
|
278
|
-
|
|
279
|
-
if (
|
|
280
|
-
|
|
281
|
-
|
|
320
|
+
// HOME footer must stay on one line at 80 cols (frame already near 24 rows).
|
|
321
|
+
if (view === ORCHESTRATOR_VIEWS.HOME) {
|
|
322
|
+
return {
|
|
323
|
+
text: ["↑↓", "Enter", "Space", "R", "?", "/", "Esc"].join(` ${glyphs.bullet} `),
|
|
324
|
+
columns: footerColumns
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (region === COCKPIT_REGIONS.NAV
|
|
329
|
+
|| view === ORCHESTRATOR_VIEWS.IDES
|
|
282
330
|
|| view === ORCHESTRATOR_VIEWS.MODULES
|
|
283
331
|
|| view === ORCHESTRATOR_VIEWS.PROFILE
|
|
284
332
|
|| view === ORCHESTRATOR_VIEWS.PROVIDERS
|
|
285
333
|
|| view === ORCHESTRATOR_VIEWS.DIAGNOSTICS
|
|
286
|
-
||
|
|
334
|
+
|| view === ORCHESTRATOR_VIEWS.USAGE
|
|
287
335
|
|| view === ORCHESTRATOR_VIEWS.RUNS
|
|
288
336
|
|| view === ORCHESTRATOR_VIEWS.ACTIVE_RUNS
|
|
289
337
|
|| view === ORCHESTRATOR_VIEWS.RECENT_RUNS
|
|
@@ -295,9 +343,10 @@ export function buildFooterModel({
|
|
|
295
343
|
}
|
|
296
344
|
|
|
297
345
|
parts.push("? Help");
|
|
298
|
-
parts.push(
|
|
346
|
+
parts.push("/ Actions");
|
|
347
|
+
parts.push("Esc Back");
|
|
299
348
|
|
|
300
|
-
return { text: parts.join(` ${glyphs.bullet} `) };
|
|
349
|
+
return { text: parts.join(` ${glyphs.bullet} `), columns: footerColumns };
|
|
301
350
|
}
|
|
302
351
|
|
|
303
352
|
export function windowLinesForLayout(lines = [], layoutMode = LAYOUT_MODES.COMPACT, contentRows = 12) {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { COCKPIT_NAV } from "./cockpit-models.js";
|
|
2
|
+
import { ORCHESTRATOR_VIEWS } from "./orchestrator-state.js";
|
|
3
|
+
|
|
4
|
+
export const PALETTE_KINDS = Object.freeze({
|
|
5
|
+
NAVIGATE: "navigate",
|
|
6
|
+
REFRESH: "refresh",
|
|
7
|
+
HELP: "help",
|
|
8
|
+
SETUP: "setup"
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const DESTINATION_VIEWS = Object.freeze({
|
|
12
|
+
changes: ORCHESTRATOR_VIEWS.CHANGES,
|
|
13
|
+
governance: ORCHESTRATOR_VIEWS.CHANGES,
|
|
14
|
+
"control-center": ORCHESTRATOR_VIEWS.HOME,
|
|
15
|
+
ides: ORCHESTRATOR_VIEWS.IDES,
|
|
16
|
+
modules: ORCHESTRATOR_VIEWS.MODULES,
|
|
17
|
+
activity: ORCHESTRATOR_VIEWS.ACTIVITY,
|
|
18
|
+
profile: ORCHESTRATOR_VIEWS.PROFILE,
|
|
19
|
+
runs: ORCHESTRATOR_VIEWS.RUNS,
|
|
20
|
+
orchestration: ORCHESTRATOR_VIEWS.RUNS,
|
|
21
|
+
usage: ORCHESTRATOR_VIEWS.USAGE,
|
|
22
|
+
alerts: ORCHESTRATOR_VIEWS.ALERTS
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export function resolvePaletteDestination(key) {
|
|
26
|
+
return DESTINATION_VIEWS[key] ?? null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function canOpenPalette({ loading = false, busy = false, confirming = false } = {}) {
|
|
30
|
+
return !loading && !busy && !confirming;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Optional CTA + six destinations + Refresh + Help. No write shortcuts. */
|
|
34
|
+
export function buildPaletteActions({
|
|
35
|
+
ctaDestination = null,
|
|
36
|
+
ctaTitle = null,
|
|
37
|
+
ctaDetail = null,
|
|
38
|
+
navItems = COCKPIT_NAV
|
|
39
|
+
} = {}) {
|
|
40
|
+
const actions = [];
|
|
41
|
+
if (ctaDestination === "setup") {
|
|
42
|
+
actions.push({
|
|
43
|
+
id: "recommended",
|
|
44
|
+
kind: PALETTE_KINDS.SETUP,
|
|
45
|
+
label: ctaTitle?.trim() || "Finish local setup",
|
|
46
|
+
view: null,
|
|
47
|
+
description: ctaDetail?.trim() || "Open the local setup wizard."
|
|
48
|
+
});
|
|
49
|
+
} else {
|
|
50
|
+
const recommendedView = resolvePaletteDestination(ctaDestination);
|
|
51
|
+
if (recommendedView) {
|
|
52
|
+
actions.push({
|
|
53
|
+
id: "recommended",
|
|
54
|
+
kind: PALETTE_KINDS.NAVIGATE,
|
|
55
|
+
label: ctaTitle?.trim() || "Recommended next action",
|
|
56
|
+
view: recommendedView,
|
|
57
|
+
description: ctaDetail?.trim() || "Overview recommended destination."
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
for (const item of navItems) {
|
|
62
|
+
actions.push({
|
|
63
|
+
id: item.id,
|
|
64
|
+
kind: PALETTE_KINDS.NAVIGATE,
|
|
65
|
+
label: item.label,
|
|
66
|
+
view: item.view,
|
|
67
|
+
description: item.description
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
actions.push({
|
|
71
|
+
id: "alerts",
|
|
72
|
+
kind: PALETTE_KINDS.NAVIGATE,
|
|
73
|
+
label: "Alerts",
|
|
74
|
+
view: ORCHESTRATOR_VIEWS.ALERTS,
|
|
75
|
+
description: "Pending alert inbox."
|
|
76
|
+
});
|
|
77
|
+
actions.push({
|
|
78
|
+
id: "refresh",
|
|
79
|
+
kind: PALETTE_KINDS.REFRESH,
|
|
80
|
+
label: "Refresh",
|
|
81
|
+
view: null,
|
|
82
|
+
description: "Reload the read-only cockpit scan."
|
|
83
|
+
});
|
|
84
|
+
actions.push({
|
|
85
|
+
id: "help",
|
|
86
|
+
kind: PALETTE_KINDS.HELP,
|
|
87
|
+
label: "Help",
|
|
88
|
+
view: ORCHESTRATOR_VIEWS.HELP,
|
|
89
|
+
description: "Keyboard reference and primary flow."
|
|
90
|
+
});
|
|
91
|
+
return actions;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function buildPaletteModel({ actions = buildPaletteActions(), index = 0, unicode = true } = {}) {
|
|
95
|
+
const max = Math.max(0, actions.length - 1);
|
|
96
|
+
const safeIndex = Math.min(Math.max(0, index), max);
|
|
97
|
+
const focus = unicode ? "›" : ">";
|
|
98
|
+
return {
|
|
99
|
+
title: "ACTIONS",
|
|
100
|
+
hint: "Navigate · Refresh · Help — no writes",
|
|
101
|
+
items: actions.map((action, i) => ({
|
|
102
|
+
...action,
|
|
103
|
+
marker: i === safeIndex ? focus : " ",
|
|
104
|
+
selected: i === safeIndex
|
|
105
|
+
})),
|
|
106
|
+
selected: actions[safeIndex] ?? null,
|
|
107
|
+
index: safeIndex
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Distinguishable path for confirmation / detail disclosure.
|
|
3
|
+
* Prefer home-relative `~/...`; otherwise keep parent/basename.
|
|
4
|
+
*/
|
|
5
|
+
export function formatConfirmPath(target, homeDir = null) {
|
|
6
|
+
const raw = String(target ?? "").trim();
|
|
7
|
+
if (!raw) return "target";
|
|
8
|
+
const normalized = raw.replaceAll("\\", "/");
|
|
9
|
+
if (normalized === "~" || normalized.startsWith("~/")) return normalized;
|
|
10
|
+
|
|
11
|
+
const home = homeDir ? String(homeDir).replaceAll("\\", "/").replace(/\/$/, "") : "";
|
|
12
|
+
if (home && (normalized === home || normalized.startsWith(`${home}/`))) {
|
|
13
|
+
return `~${normalized.slice(home.length)}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
17
|
+
if (parts.length >= 2) return parts.slice(-2).join("/");
|
|
18
|
+
return parts[0] || normalized;
|
|
19
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { formatConfirmPath } from "./cockpit-path-label.js";
|
|
2
|
+
|
|
1
3
|
export const RECOVERY_PHASE = Object.freeze({
|
|
2
4
|
IDLE: "idle",
|
|
3
5
|
PREVIEWING: "previewing",
|
|
@@ -73,48 +75,112 @@ export function listRecoverySnapshots(snapshot) {
|
|
|
73
75
|
return snapshot?.backups?.snapshots ?? [];
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
export function
|
|
78
|
+
export function formatWhen(timestamp) {
|
|
79
|
+
if (!timestamp) return "unknown time";
|
|
80
|
+
const raw = String(timestamp);
|
|
81
|
+
if (/^\d{4}-\d{2}-\d{2}/.test(raw)) return raw.slice(0, 16).replace("T", " ");
|
|
82
|
+
const date = new Date(raw);
|
|
83
|
+
if (Number.isNaN(date.getTime())) return raw.slice(0, 16);
|
|
84
|
+
return date.toISOString().slice(0, 16).replace("T", " ");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function formatResult(action) {
|
|
88
|
+
const value = String(action ?? "").toLowerCase();
|
|
89
|
+
if (!value) return "done";
|
|
90
|
+
if (value === "applied" || value === "ok" || value === "success") return "ok";
|
|
91
|
+
if (value === "cancelled" || value === "canceled") return "cancelled";
|
|
92
|
+
if (value.includes("fail")) return "failed";
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function shortName(name) {
|
|
97
|
+
const raw = String(name ?? "").trim();
|
|
98
|
+
if (!raw) return "snapshot";
|
|
99
|
+
const parts = raw.split(/[/\\]/).filter(Boolean);
|
|
100
|
+
return parts[parts.length - 1] || raw;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Activity surface: what agents did, when, and result.
|
|
105
|
+
* Snapshot restore stays secondary; paths only under DETAILS.
|
|
106
|
+
*/
|
|
107
|
+
export function formatRecoveryLines({
|
|
108
|
+
snapshot,
|
|
109
|
+
recoveryAction,
|
|
110
|
+
listIndex = 0,
|
|
111
|
+
dashboard = null,
|
|
112
|
+
detail = false,
|
|
113
|
+
homeDir = null
|
|
114
|
+
} = {}) {
|
|
77
115
|
const backups = listRecoverySnapshots(snapshot);
|
|
78
116
|
const events = snapshot?.history?.events ?? [];
|
|
79
117
|
const phase = recoveryAction?.phase ?? RECOVERY_PHASE.IDLE;
|
|
80
|
-
const lines = [
|
|
81
|
-
`Activity & recovery · ${phase}`,
|
|
82
|
-
`History: ${events.length} recent event(s)`,
|
|
83
|
-
...events.slice(0, 3).map((event) => `${event.command ?? event.type ?? "event"} · ${event.action ?? ""} · ${event.timestamp ?? ""}`),
|
|
84
|
-
"",
|
|
85
|
-
`Snapshots: ${snapshot?.backups?.count ?? backups.length}`
|
|
86
|
-
];
|
|
118
|
+
const lines = ["RECENT"];
|
|
87
119
|
|
|
120
|
+
const recent = [];
|
|
121
|
+
for (const event of events.slice(0, 3)) {
|
|
122
|
+
recent.push(
|
|
123
|
+
`${formatWhen(event.timestamp)} · ${event.command ?? event.type ?? "event"} · ${formatResult(event.action)}`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
for (const run of (dashboard?.recentRuns ?? []).slice(0, 2)) {
|
|
127
|
+
recent.push(
|
|
128
|
+
`${formatWhen(run.updatedAt ?? run.endedAt ?? run.startedAt)} · ${run.agentId ?? "agent"} · ${formatResult(run.state)}`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
if (recent.length === 0) lines.push("No recent agent activity.");
|
|
132
|
+
else lines.push(...recent.slice(0, 4));
|
|
133
|
+
|
|
134
|
+
lines.push("", "SNAPSHOTS");
|
|
88
135
|
if (backups.length === 0) {
|
|
89
136
|
lines.push("No global snapshots yet.");
|
|
90
137
|
} else {
|
|
91
138
|
backups.forEach((entry, index) => {
|
|
92
139
|
const mark = index === listIndex ? "›" : " ";
|
|
93
|
-
lines.push(`${mark} ${entry.name} · ${entry.fileCount ?? "?"} files`);
|
|
140
|
+
lines.push(`${mark} ${shortName(entry.name)} · ${entry.fileCount ?? "?"} files`);
|
|
94
141
|
});
|
|
95
142
|
}
|
|
96
143
|
|
|
97
144
|
if (recoveryAction?.message) lines.push("", recoveryAction.message);
|
|
145
|
+
|
|
98
146
|
const preview = recoveryAction?.preview;
|
|
99
147
|
if (preview) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (
|
|
148
|
+
const fileCount = preview.files?.length ?? 0;
|
|
149
|
+
lines.push(`Restore preview · ${fileCount} file(s)`);
|
|
150
|
+
if (detail || phase === RECOVERY_PHASE.CONFIRMING) {
|
|
151
|
+
lines.push("DETAILS");
|
|
152
|
+
const limit = detail ? 12 : 3;
|
|
153
|
+
for (const file of (preview.files ?? []).slice(0, limit)) {
|
|
154
|
+
lines.push(formatConfirmPath(file.displayPath ?? file.path, homeDir));
|
|
155
|
+
}
|
|
156
|
+
if ((preview.files?.length ?? 0) > limit) {
|
|
157
|
+
lines.push(`… ${preview.files.length - limit} more`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
103
160
|
}
|
|
161
|
+
|
|
104
162
|
const receipt = recoveryAction?.receipt;
|
|
105
163
|
if (receipt) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
164
|
+
const restored = receipt.restored?.length ?? 0;
|
|
165
|
+
lines.push("", `Result · ${receipt.action ?? "rollback"} · ${restored} restored`);
|
|
166
|
+
if (detail && receipt.safetyBackup) {
|
|
167
|
+
lines.push("DETAILS", `Safety backup retained`);
|
|
168
|
+
} else if (receipt.safetyBackup) {
|
|
169
|
+
lines.push("Safety backup retained");
|
|
170
|
+
}
|
|
109
171
|
}
|
|
172
|
+
|
|
110
173
|
if (phase === RECOVERY_PHASE.IDLE || phase === RECOVERY_PHASE.COMPLETED || phase === RECOVERY_PHASE.FAILED) {
|
|
111
174
|
lines.push("", "Enter preview · Y restore · N/Esc cancel · R re-scan");
|
|
112
175
|
}
|
|
113
176
|
return lines;
|
|
114
177
|
}
|
|
115
178
|
|
|
116
|
-
export function buildRecoveryFooterParts(phase) {
|
|
179
|
+
export function buildRecoveryFooterParts(phase, { hasPreview = false } = {}) {
|
|
117
180
|
if (phase === RECOVERY_PHASE.PREVIEWING || phase === RECOVERY_PHASE.APPLYING) return ["Working…", "Esc Back"];
|
|
118
|
-
if (phase === RECOVERY_PHASE.CONFIRMING) return ["Y Restore", "N/Esc Cancel"];
|
|
119
|
-
|
|
181
|
+
if (phase === RECOVERY_PHASE.CONFIRMING) return ["Y Restore", "N/Esc Cancel", "Space"];
|
|
182
|
+
const parts = ["↑↓ Select", "Enter Preview", "R Re-scan"];
|
|
183
|
+
if (hasPreview) parts.push("Space");
|
|
184
|
+
parts.push("Esc Back");
|
|
185
|
+
return parts;
|
|
120
186
|
}
|
|
@@ -22,8 +22,10 @@ export function formatReviewListLines(receipts = []) {
|
|
|
22
22
|
return receipts.map((receipt) => {
|
|
23
23
|
const counts = countFindingsBySeverity(receipt.findings);
|
|
24
24
|
const findingTotal = (receipt.findings ?? []).length;
|
|
25
|
-
const created = String(receipt.createdAt ?? "").slice(0,
|
|
26
|
-
|
|
25
|
+
const created = String(receipt.createdAt ?? "").slice(0, 16).replace("T", " ");
|
|
26
|
+
const agent = String(receipt.agentId ?? "agent");
|
|
27
|
+
const state = String(receipt.state ?? "unknown");
|
|
28
|
+
return `${agent} · ${state} · ${findingTotal} findings (h${counts.high}/m${counts.medium}/l${counts.low}) · ${created || "unknown time"}`;
|
|
27
29
|
});
|
|
28
30
|
}
|
|
29
31
|
|
|
@@ -33,15 +35,17 @@ export function formatReviewDetailLines(receipt) {
|
|
|
33
35
|
const snapshot = receipt.snapshot ?? {};
|
|
34
36
|
const totals = snapshot.totals ?? {};
|
|
35
37
|
const lines = [
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
`
|
|
39
|
-
`Created
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
"SUMMARY",
|
|
39
|
+
`${receipt.agentId}${receipt.model ? ` · ${receipt.model}` : ""} · ${receipt.state}`,
|
|
40
|
+
`Findings · ${(receipt.findings ?? []).length} (high ${counts.high}, medium ${counts.medium}, low ${counts.low})`,
|
|
41
|
+
`Created · ${receipt.createdAt ?? "n/a"}`,
|
|
42
|
+
"",
|
|
43
|
+
"DETAILS",
|
|
44
|
+
`Id · ${receipt.reviewId}`,
|
|
45
|
+
`Snapshot · ${snapshot.mode ?? "n/a"} · ${totals.fileCount ?? 0} files`
|
|
42
46
|
];
|
|
43
47
|
if ((receipt.warnings ?? []).length > 0) {
|
|
44
|
-
lines.push(`Warnings
|
|
48
|
+
lines.push(`Warnings · ${receipt.warnings.length}`);
|
|
45
49
|
}
|
|
46
50
|
const findings = receipt.findings ?? [];
|
|
47
51
|
if (findings.length === 0) {
|
|
@@ -56,7 +60,7 @@ export function formatReviewDetailLines(receipt) {
|
|
|
56
60
|
);
|
|
57
61
|
}
|
|
58
62
|
if (findings.length > 20) {
|
|
59
|
-
lines.push(` … ${findings.length - 20} more
|
|
63
|
+
lines.push(` … ${findings.length - 20} more`);
|
|
60
64
|
}
|
|
61
65
|
return lines;
|
|
62
66
|
}
|
|
@@ -5,19 +5,19 @@ export const RUNS_HUB_ITEMS = [
|
|
|
5
5
|
id: "active",
|
|
6
6
|
label: "Active runs",
|
|
7
7
|
view: ORCHESTRATOR_VIEWS.ACTIVE_RUNS,
|
|
8
|
-
description: "Inspect and cancel supervised runs
|
|
8
|
+
description: "Inspect and cancel supervised runs in flight."
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
id: "history",
|
|
12
12
|
label: "History",
|
|
13
13
|
view: ORCHESTRATOR_VIEWS.RECENT_RUNS,
|
|
14
|
-
description: "
|
|
14
|
+
description: "Completed and failed outcomes."
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
17
|
id: "reviews",
|
|
18
18
|
label: "Reviews",
|
|
19
19
|
view: ORCHESTRATOR_VIEWS.REVIEWS,
|
|
20
|
-
description: "
|
|
20
|
+
description: "Secret-free review receipts (read-only)."
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
id: "launch",
|
|
@@ -51,6 +51,15 @@ export function resolveRunsHubItem(listIndex = 0, items = RUNS_HUB_ITEMS) {
|
|
|
51
51
|
return items[index] ?? null;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/** Selectable hub labels only — counts belong in the panel title/hint. */
|
|
54
55
|
export function formatRunsHubLines(items = RUNS_HUB_ITEMS) {
|
|
55
|
-
return items.map((item) =>
|
|
56
|
+
return items.map((item) => item.label);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function formatOrchestrationStatus({
|
|
60
|
+
active = 0,
|
|
61
|
+
recent = 0,
|
|
62
|
+
reviews = 0
|
|
63
|
+
} = {}) {
|
|
64
|
+
return `${active} active · ${recent} recent · ${reviews} reviews`;
|
|
56
65
|
}
|