@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.
Files changed (49) hide show
  1. package/README.md +41 -15
  2. package/package.json +1 -1
  3. package/scripts/cockpit-smoke.mjs +6 -6
  4. package/scripts/ux-prototype-tty.mjs +9 -0
  5. package/src/cli.js +24 -1
  6. package/src/global/control-plane-proposals.js +2 -1
  7. package/src/global/control-plane-snapshot.js +1 -1
  8. package/src/global/global-doctor.js +2 -0
  9. package/src/global/ink/cockpit/primitives.js +127 -50
  10. package/src/global/ink/cockpit-alerts.js +36 -0
  11. package/src/global/ink/cockpit-changes.js +61 -37
  12. package/src/global/ink/cockpit-control-center.js +79 -53
  13. package/src/global/ink/cockpit-controller.js +98 -15
  14. package/src/global/ink/cockpit-enter.js +1 -0
  15. package/src/global/ink/cockpit-focus.js +4 -2
  16. package/src/global/ink/cockpit-models.js +100 -51
  17. package/src/global/ink/cockpit-palette.js +109 -0
  18. package/src/global/ink/cockpit-path-label.js +19 -0
  19. package/src/global/ink/cockpit-recovery.js +84 -18
  20. package/src/global/ink/cockpit-reviews.js +14 -10
  21. package/src/global/ink/cockpit-runs.js +13 -4
  22. package/src/global/ink/cockpit-settings.js +194 -0
  23. package/src/global/ink/cockpit-usage.js +111 -0
  24. package/src/global/ink/cockpit-views.js +119 -116
  25. package/src/global/ink/orchestrator-app.js +169 -46
  26. package/src/global/ink/orchestrator-state.js +24 -14
  27. package/src/global/ink/setup-app.js +55 -72
  28. package/src/global/ink/setup-state.js +16 -0
  29. package/src/global/ink/theme.js +27 -0
  30. package/src/global/ink/use-orchestrator-data.js +58 -0
  31. package/src/global/ink/ux/live-activity.js +194 -0
  32. package/src/global/ink/ux/live-alerts.js +159 -0
  33. package/src/global/ink/ux/live-governance.js +195 -0
  34. package/src/global/ink/ux/live-orchestration.js +188 -0
  35. package/src/global/ink/ux/live-overview.js +125 -0
  36. package/src/global/ink/ux/live-settings.js +189 -0
  37. package/src/global/ink/ux/live-setup.js +160 -0
  38. package/src/global/ink/ux/live-usage.js +51 -0
  39. package/src/global/ink/ux/semantic.js +84 -0
  40. package/src/global/ink/ux/task-flow-app.js +85 -0
  41. package/src/global/ink/ux/task-flow.js +173 -0
  42. package/src/global/orchestrator.js +37 -20
  43. package/src/global/paths.js +3 -0
  44. package/src/global/runtime/alerts/alert-store.js +216 -0
  45. package/src/global/runtime/alerts/alert-types.js +59 -0
  46. package/src/global/runtime/alerts/alert-validate.js +117 -0
  47. package/src/global/runtime/monitor/monitor-cli.js +62 -0
  48. package/src/global/runtime/monitor/monitor-platform.js +95 -0
  49. 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: "Control center",
21
+ label: "Overview",
21
22
  view: ORCHESTRATOR_VIEWS.HOME,
22
- description: "Coverage, integrity, and the next governance action."
23
+ description: "Status, next action, activity, alerts, and tokens."
23
24
  },
24
25
  {
25
- id: "ides",
26
- label: "IDEs & models",
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: "Findings, drift, and exact preview before any write."
29
+ description: "Repair drift and apply governed changes."
41
30
  },
42
31
  {
43
32
  id: "activity",
44
- label: "Activity & recovery",
33
+ label: "Activity",
45
34
  view: ORCHESTRATOR_VIEWS.ACTIVITY,
46
- description: "Kairo operations, backups, and rollback readiness."
35
+ description: "Operations, backups, and recovery."
47
36
  },
48
37
  {
49
- id: "profile",
50
- label: "Profile & policy",
51
- view: ORCHESTRATOR_VIEWS.PROFILE,
52
- description: "Defaults, scopes, consent, and precedence."
38
+ id: "orchestration",
39
+ label: "Orchestration",
40
+ view: ORCHESTRATOR_VIEWS.RUNS,
41
+ description: "Runs, reviews, and supervised execution."
53
42
  },
54
43
  {
55
- id: "runs",
56
- label: "Runs",
57
- view: ORCHESTRATOR_VIEWS.RUNS,
58
- description: "Optional supervised execution Active, History, and New run."
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
- if (layoutMode === LAYOUT_MODES.WIDE) {
64
- return [COCKPIT_REGIONS.NAV, COCKPIT_REGIONS.CONTENT, COCKPIT_REGIONS.SYSTEM];
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 runsIndex = items.findIndex((item) => item.id === "runs");
75
- return runsIndex >= 0 ? runsIndex : 0;
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 === "runs" && isRunsBranchView(currentView));
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 { text: buildChangesFooterParts(changesPhase).join(` ${glyphs.bullet} `) };
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 { text: buildRecoveryFooterParts(recoveryPhase).join(` ${glyphs.bullet} `) };
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
- const activatesControlCenterCta = view === ORCHESTRATOR_VIEWS.HOME && navIndex === 0;
279
- if (activatesControlCenterCta) {
280
- parts.push("Enter Activate");
281
- } else if (view === ORCHESTRATOR_VIEWS.IDES
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
- || region === COCKPIT_REGIONS.NAV
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(view === ORCHESTRATOR_VIEWS.HOME ? "Esc Exit" : "Esc Back");
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 formatRecoveryLines({ snapshot, recoveryAction, listIndex = 0 }) {
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
- lines.push(`Snapshot · ${preview.snapshot}`);
101
- for (const file of preview.files ?? []) lines.push(` restore · ${file.displayPath}`);
102
- if (preview.fingerprint) lines.push(`Fingerprint · ${preview.fingerprint.slice(0, 12)}…`);
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
- lines.push("", `Receipt · ${receipt.action}`);
107
- if (receipt.safetyBackup) lines.push(`Safety backup · ${receipt.safetyBackup}`);
108
- for (const path of receipt.restored ?? []) lines.push(` restored · ${path}`);
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
- return ["↑↓ Select", "Enter Preview", "R Re-scan", "Esc Back"];
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, 19).replace("T", " ");
26
- return `${receipt.reviewId} ${String(receipt.state).padEnd(10)} ${String(receipt.agentId).padEnd(6)} ${findingTotal}f (h${counts.high}/m${counts.medium}/l${counts.low}) ${created}`;
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
- `Id: ${receipt.reviewId}`,
37
- `Agent: ${receipt.agentId}${receipt.model ? ` · model ${receipt.model}` : ""}`,
38
- `State: ${receipt.state}`,
39
- `Created: ${receipt.createdAt ?? "n/a"}`,
40
- `Findings: ${(receipt.findings ?? []).length} (high ${counts.high}, medium ${counts.medium}, low ${counts.low})`,
41
- `Snapshot: ${snapshot.mode ?? "n/a"} · files ${totals.fileCount ?? 0} · fingerprint ${String(snapshot.fingerprint ?? "").slice(0, 12) || "n/a"}`
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: ${receipt.warnings.length}`);
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 (use kairo reviews show ${receipt.reviewId})`);
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 that are currently executing."
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: "Review completed and failed run outcomes."
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: "Browse secret-free bounded-review receipts (read-only)."
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) => `${item.label} — ${item.description}`);
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
  }