@kal-elsam/kairo-runtime 0.9.0 → 0.11.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 (33) hide show
  1. package/README.md +14 -8
  2. package/package.json +1 -1
  3. package/scripts/cockpit-smoke.mjs +3 -3
  4. package/scripts/ux-prototype-tty.mjs +9 -0
  5. package/src/global/control-plane-proposals.js +2 -1
  6. package/src/global/control-plane-snapshot.js +1 -1
  7. package/src/global/ink/brand/wordmark.js +50 -0
  8. package/src/global/ink/cockpit/primitives.js +72 -50
  9. package/src/global/ink/cockpit-changes.js +2 -2
  10. package/src/global/ink/cockpit-control-center.js +12 -64
  11. package/src/global/ink/cockpit-controller.js +41 -5
  12. package/src/global/ink/cockpit-enter.js +1 -0
  13. package/src/global/ink/cockpit-models.js +14 -7
  14. package/src/global/ink/cockpit-palette.js +18 -7
  15. package/src/global/ink/cockpit-recovery.js +9 -6
  16. package/src/global/ink/cockpit-usage.js +111 -0
  17. package/src/global/ink/cockpit-views.js +70 -97
  18. package/src/global/ink/orchestrator-app.js +44 -4
  19. package/src/global/ink/setup-app.js +55 -72
  20. package/src/global/ink/setup-state.js +16 -0
  21. package/src/global/ink/theme.js +40 -8
  22. package/src/global/ink/ux/live-activity.js +191 -0
  23. package/src/global/ink/ux/live-alerts.js +156 -0
  24. package/src/global/ink/ux/live-governance.js +191 -0
  25. package/src/global/ink/ux/live-orchestration.js +185 -0
  26. package/src/global/ink/ux/live-overview.js +175 -0
  27. package/src/global/ink/ux/live-settings.js +186 -0
  28. package/src/global/ink/ux/live-setup.js +160 -0
  29. package/src/global/ink/ux/live-usage.js +49 -0
  30. package/src/global/ink/ux/semantic.js +101 -0
  31. package/src/global/ink/ux/task-flow-app.js +85 -0
  32. package/src/global/ink/ux/task-flow.js +173 -0
  33. package/src/global/orchestrator.js +37 -20
@@ -243,6 +243,7 @@ export function buildFooterModel({
243
243
  hasError = false,
244
244
  changesPhase = null,
245
245
  recoveryPhase = null,
246
+ recoveryHasPreview = false,
246
247
  settingsPhase = null,
247
248
  columns = 80
248
249
  } = {}) {
@@ -290,7 +291,8 @@ export function buildFooterModel({
290
291
 
291
292
  if (view === ORCHESTRATOR_VIEWS.ACTIVITY) {
292
293
  return {
293
- text: buildRecoveryFooterParts(recoveryPhase).join(` ${glyphs.bullet} `),
294
+ text: buildRecoveryFooterParts(recoveryPhase, { hasPreview: recoveryHasPreview })
295
+ .join(` ${glyphs.bullet} `),
294
296
  columns: footerColumns
295
297
  };
296
298
  }
@@ -315,16 +317,21 @@ export function buildFooterModel({
315
317
  parts.push("Tab Region");
316
318
  }
317
319
 
318
- const activatesOverviewCta = view === ORCHESTRATOR_VIEWS.HOME && navIndex === 0;
319
- if (activatesOverviewCta) {
320
- parts.push("Enter Activate");
321
- } 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
322
330
  || view === ORCHESTRATOR_VIEWS.MODULES
323
331
  || view === ORCHESTRATOR_VIEWS.PROFILE
324
332
  || view === ORCHESTRATOR_VIEWS.PROVIDERS
325
333
  || view === ORCHESTRATOR_VIEWS.DIAGNOSTICS
326
334
  || view === ORCHESTRATOR_VIEWS.USAGE
327
- || region === COCKPIT_REGIONS.NAV
328
335
  || view === ORCHESTRATOR_VIEWS.RUNS
329
336
  || view === ORCHESTRATOR_VIEWS.ACTIVE_RUNS
330
337
  || view === ORCHESTRATOR_VIEWS.RECENT_RUNS
@@ -337,7 +344,7 @@ export function buildFooterModel({
337
344
 
338
345
  parts.push("? Help");
339
346
  parts.push("/ Actions");
340
- parts.push(view === ORCHESTRATOR_VIEWS.HOME ? "Esc Exit" : "Esc Back");
347
+ parts.push("Esc Back");
341
348
 
342
349
  return { text: parts.join(` ${glyphs.bullet} `), columns: footerColumns };
343
350
  }
@@ -4,7 +4,8 @@ import { ORCHESTRATOR_VIEWS } from "./orchestrator-state.js";
4
4
  export const PALETTE_KINDS = Object.freeze({
5
5
  NAVIGATE: "navigate",
6
6
  REFRESH: "refresh",
7
- HELP: "help"
7
+ HELP: "help",
8
+ SETUP: "setup"
8
9
  });
9
10
 
10
11
  const DESTINATION_VIEWS = Object.freeze({
@@ -37,15 +38,25 @@ export function buildPaletteActions({
37
38
  navItems = COCKPIT_NAV
38
39
  } = {}) {
39
40
  const actions = [];
40
- const recommendedView = resolvePaletteDestination(ctaDestination);
41
- if (recommendedView) {
41
+ if (ctaDestination === "setup") {
42
42
  actions.push({
43
43
  id: "recommended",
44
- kind: PALETTE_KINDS.NAVIGATE,
45
- label: ctaTitle?.trim() || "Recommended next action",
46
- view: recommendedView,
47
- description: ctaDetail?.trim() || "Overview recommended destination."
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
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
+ }
49
60
  }
50
61
  for (const item of navItems) {
51
62
  actions.push({
@@ -75,7 +75,7 @@ export function listRecoverySnapshots(snapshot) {
75
75
  return snapshot?.backups?.snapshots ?? [];
76
76
  }
77
77
 
78
- function formatWhen(timestamp) {
78
+ export function formatWhen(timestamp) {
79
79
  if (!timestamp) return "unknown time";
80
80
  const raw = String(timestamp);
81
81
  if (/^\d{4}-\d{2}-\d{2}/.test(raw)) return raw.slice(0, 16).replace("T", " ");
@@ -84,7 +84,7 @@ function formatWhen(timestamp) {
84
84
  return date.toISOString().slice(0, 16).replace("T", " ");
85
85
  }
86
86
 
87
- function formatResult(action) {
87
+ export function formatResult(action) {
88
88
  const value = String(action ?? "").toLowerCase();
89
89
  if (!value) return "done";
90
90
  if (value === "applied" || value === "ok" || value === "success") return "ok";
@@ -93,7 +93,7 @@ function formatResult(action) {
93
93
  return value;
94
94
  }
95
95
 
96
- function shortName(name) {
96
+ export function shortName(name) {
97
97
  const raw = String(name ?? "").trim();
98
98
  if (!raw) return "snapshot";
99
99
  const parts = raw.split(/[/\\]/).filter(Boolean);
@@ -176,8 +176,11 @@ export function formatRecoveryLines({
176
176
  return lines;
177
177
  }
178
178
 
179
- export function buildRecoveryFooterParts(phase) {
179
+ export function buildRecoveryFooterParts(phase, { hasPreview = false } = {}) {
180
180
  if (phase === RECOVERY_PHASE.PREVIEWING || phase === RECOVERY_PHASE.APPLYING) return ["Working…", "Esc Back"];
181
- if (phase === RECOVERY_PHASE.CONFIRMING) return ["Y Restore", "N/Esc Cancel"];
182
- 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;
183
186
  }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Pure Usage / Tokens model — no React/Ink.
3
+ * Shared by formatUsageLines and SemanticUsagePanel.
4
+ */
5
+ import { LAYOUT_MODES } from "./layout.js";
6
+
7
+ export function usageRunLimit(layoutMode = LAYOUT_MODES.COMPACT) {
8
+ return layoutMode === LAYOUT_MODES.WIDE ? 8 : 3;
9
+ }
10
+
11
+ export function formatMeasuredBudgets(budgets) {
12
+ if (!budgets || typeof budgets !== "object") return null;
13
+ const parts = [];
14
+ if (Number.isFinite(budgets.stableUsedTokens) && Number.isFinite(budgets.stableBudgetTokens)) {
15
+ parts.push(`stable ${budgets.stableUsedTokens}/${budgets.stableBudgetTokens}`);
16
+ }
17
+ if (Number.isFinite(budgets.requestUsedTokens) && Number.isFinite(budgets.requestBudgetTokens)) {
18
+ parts.push(`request ${budgets.requestUsedTokens}/${budgets.requestBudgetTokens}`);
19
+ }
20
+ return parts.length > 0 ? parts.join(" · ") : null;
21
+ }
22
+
23
+ export function resolveConfiguredLimits(dashboard) {
24
+ const resolved = dashboard?.profile?.profile ?? {};
25
+ const configured = [];
26
+ if (Number.isFinite(resolved.tokenBudget)) configured.push(`token ${resolved.tokenBudget}`);
27
+ if (Number.isFinite(resolved.stableContextBudget)) configured.push(`stable ${resolved.stableContextBudget}`);
28
+ if (Number.isFinite(resolved.requestContextBudget)) configured.push(`request ${resolved.requestContextBudget}`);
29
+ return configured;
30
+ }
31
+
32
+ export function hasFiniteUsage(usage) {
33
+ if (!usage || typeof usage !== "object") return false;
34
+ return Number.isFinite(usage.total)
35
+ || Number.isFinite(usage.input)
36
+ || Number.isFinite(usage.output);
37
+ }
38
+
39
+ export function formatRunUsageLabel(run) {
40
+ const usage = run?.tokenUsage;
41
+ if (!hasFiniteUsage(usage)) return null;
42
+ const parts = [];
43
+ if (Number.isFinite(usage.input)) parts.push(`in ${usage.input}`);
44
+ if (Number.isFinite(usage.output)) parts.push(`out ${usage.output}`);
45
+ if (Number.isFinite(usage.total)) parts.push(`total ${usage.total}`);
46
+ return `${run.agentId ?? "agent"} · ${parts.join(" · ")}`;
47
+ }
48
+
49
+ function collectAuditableRuns(dashboard) {
50
+ return [...(dashboard?.activeRuns ?? []), ...(dashboard?.recentRuns ?? [])]
51
+ .filter((run) => hasFiniteUsage(run?.tokenUsage));
52
+ }
53
+
54
+ /** Pure adapter: Callout=status · Measured section owns the value. */
55
+ export function adaptUsageModel({
56
+ snapshot = null,
57
+ dashboard = null,
58
+ layoutMode = LAYOUT_MODES.COMPACT
59
+ } = {}) {
60
+ const measured = formatMeasuredBudgets(snapshot?.budgets);
61
+ const configured = resolveConfiguredLimits(dashboard);
62
+ const allRuns = collectAuditableRuns(dashboard);
63
+ const limit = usageRunLimit(layoutMode);
64
+ const visible = allRuns.slice(0, limit);
65
+ const hidden = Math.max(0, allRuns.length - visible.length);
66
+ const hasEvidence = Boolean(measured) || configured.length > 0 || allRuns.length > 0;
67
+
68
+ return {
69
+ title: "Usage",
70
+ callout: hasEvidence
71
+ ? {
72
+ tone: "info",
73
+ title: "Usage evidence available",
74
+ body: "Budgets and run tokenUsage only — no invented totals."
75
+ }
76
+ : {
77
+ tone: "info",
78
+ title: "Data unavailable",
79
+ body: "No measured budgets, profile limits, or auditable run tokenUsage."
80
+ },
81
+ measured: measured ?? "Data unavailable",
82
+ configured: configured.length > 0
83
+ ? configured.join(" · ")
84
+ : "No profile token budgets configured.",
85
+ runs: visible.map((run, i) => ({
86
+ id: `usage-run-${i}`,
87
+ label: formatRunUsageLabel(run)
88
+ })),
89
+ runTotal: allRuns.length,
90
+ runLimit: limit,
91
+ moreLine: hidden > 0 ? `… ${hidden} more` : null,
92
+ hasEvidence,
93
+ footnote: "Auditable budgets only — no invented token savings."
94
+ };
95
+ }
96
+
97
+ export function formatUsageLinesFromModel(model) {
98
+ const lines = [
99
+ "MEASURED", model.measured, "",
100
+ "CONFIGURED LIMITS", model.configured, "",
101
+ "RUN USAGE"
102
+ ];
103
+ if (model.runs.length === 0) {
104
+ lines.push("No auditable run tokenUsage yet.");
105
+ } else {
106
+ for (const item of model.runs) lines.push(item.label);
107
+ if (model.moreLine) lines.push(model.moreLine);
108
+ }
109
+ lines.push("", model.footnote);
110
+ return lines;
111
+ }
@@ -5,7 +5,6 @@ import { CockpitEmptyState } from "./cockpit/primitives.js";
5
5
  import {
6
6
  formatProviderLines,
7
7
  formatRunDetailLines,
8
- formatRunLines,
9
8
  formatSystemHealthLines,
10
9
  formatLaunchWizardLines,
11
10
  ORCHESTRATOR_VIEWS,
@@ -13,14 +12,15 @@ import {
13
12
  } from "./orchestrator-state.js";
14
13
  import { windowLinesForLayout } from "./cockpit-models.js";
15
14
  import { LAYOUT_MODES } from "./layout.js";
16
- import { formatRunsHubLines, RUNS_HUB_ITEMS } from "./cockpit-runs.js";
17
- import { formatReviewDetailLines, formatReviewListLines } from "./cockpit-reviews.js";
18
- import { formatChangesActionLines } from "./cockpit-changes.js";
19
- import { formatRecoveryLines } from "./cockpit-recovery.js";
20
- import { formatUsageLines } from "./cockpit-control-center.js";
21
- import { formatOrchestrationStatus } from "./cockpit-runs.js";
22
- import { formatAlertListLines } from "./cockpit-alerts.js";
23
- import { formatSettingsLines } from "./cockpit-settings.js";
15
+ import { SemanticOverviewPanel } from "./ux/live-overview.js";
16
+ import { SemanticGovernancePanel } from "./ux/live-governance.js";
17
+ import { SemanticActivityPanel } from "./ux/live-activity.js";
18
+ import { SemanticOrchestrationPanel } from "./ux/live-orchestration.js";
19
+ import { SemanticAlertsPanel } from "./ux/live-alerts.js";
20
+ import { SemanticSettingsPanel } from "./ux/live-settings.js";
21
+ import { SemanticUsagePanel } from "./ux/live-usage.js";
22
+ import { formatReviewDetailLines } from "./cockpit-reviews.js";
23
+ import { listCuratedIntegrations } from "./cockpit-settings.js";
24
24
 
25
25
  export function PalettePanel({ model, colorEnabled = true }) {
26
26
  return React.createElement(Box, { flexDirection: "column" },
@@ -95,6 +95,11 @@ export function renderCockpitView({
95
95
  recoveryAction = null,
96
96
  settingsAction = null,
97
97
  colorEnabled = true,
98
+ unicode = true,
99
+ overviewDetailsOpen = false,
100
+ governanceDetailsOpen = false,
101
+ activityDetailsOpen = false,
102
+ contentFocused = false,
98
103
  homeDir = null
99
104
  }) {
100
105
  if (palette) {
@@ -102,14 +107,21 @@ export function renderCockpitView({
102
107
  }
103
108
  switch (view) {
104
109
  case ORCHESTRATOR_VIEWS.HOME:
105
- return React.createElement(ControlCenterPanel, { model: controlCenter, colorEnabled });
110
+ return React.createElement(SemanticOverviewPanel, {
111
+ model: controlCenter,
112
+ detailsOpen: overviewDetailsOpen,
113
+ colorEnabled,
114
+ unicode,
115
+ layoutMode
116
+ });
106
117
  case ORCHESTRATOR_VIEWS.USAGE:
107
- return governanceList(
108
- "Usage",
109
- formatUsageLines({ snapshot, dashboard }),
118
+ return React.createElement(SemanticUsagePanel, {
119
+ snapshot,
120
+ dashboard,
110
121
  layoutMode,
111
- colorEnabled
112
- );
122
+ colorEnabled,
123
+ unicode
124
+ });
113
125
  case ORCHESTRATOR_VIEWS.IDES:
114
126
  case ORCHESTRATOR_VIEWS.PROVIDERS:
115
127
  return governanceList("IDEs & models", [
@@ -120,73 +132,54 @@ export function renderCockpitView({
120
132
  case ORCHESTRATOR_VIEWS.MODULES:
121
133
  return governanceList("Harness modules", formatModuleLines(snapshot), layoutMode, colorEnabled);
122
134
  case ORCHESTRATOR_VIEWS.CHANGES:
123
- return governanceList(
124
- "Governance",
125
- formatChangeLines(snapshot, changesAction, layoutMode, homeDir),
135
+ return React.createElement(SemanticGovernancePanel, {
136
+ snapshot,
137
+ changesAction,
138
+ homeDir,
139
+ detailsOpen: governanceDetailsOpen,
126
140
  layoutMode,
127
- colorEnabled
128
- );
141
+ colorEnabled,
142
+ unicode
143
+ });
129
144
  case ORCHESTRATOR_VIEWS.ACTIVITY:
130
- return governanceList(
131
- "Activity",
132
- formatRecoveryLines({ snapshot, recoveryAction, listIndex, dashboard, homeDir }),
145
+ return React.createElement(SemanticActivityPanel, {
146
+ snapshot,
147
+ recoveryAction,
148
+ dashboard,
149
+ listIndex,
150
+ homeDir,
151
+ detailsOpen: activityDetailsOpen,
133
152
  layoutMode,
134
- colorEnabled
135
- );
153
+ contentFocused,
154
+ colorEnabled,
155
+ unicode
156
+ });
136
157
  case ORCHESTRATOR_VIEWS.PROFILE:
137
- return governanceList(
138
- "Settings",
139
- formatSettingsLines({
140
- listIndex,
141
- settingsAction,
142
- snapshot,
143
- diagnostics
144
- }),
145
- layoutMode,
146
- colorEnabled
147
- );
148
- case ORCHESTRATOR_VIEWS.RUNS:
149
- return listBlock(
150
- `Orchestration · ${formatOrchestrationStatus({
151
- active: (dashboard?.activeRuns ?? []).length,
152
- recent: (dashboard?.recentRuns ?? []).length,
153
- reviews: (reviews ?? []).length
154
- })}`,
155
- formatRunsHubLines(RUNS_HUB_ITEMS),
158
+ return React.createElement(SemanticSettingsPanel, {
159
+ integrations: listCuratedIntegrations(),
156
160
  listIndex,
161
+ settingsAction,
162
+ snapshot,
163
+ diagnostics,
164
+ layoutMode,
165
+ contentFocused,
157
166
  colorEnabled,
158
- "Choose Active runs, History, Reviews, or New run."
159
- );
167
+ unicode
168
+ });
169
+ case ORCHESTRATOR_VIEWS.RUNS:
160
170
  case ORCHESTRATOR_VIEWS.ACTIVE_RUNS:
161
- return listBlock(
162
- "Active runs",
163
- formatRunLines(dashboard?.activeRuns ?? [], {
164
- emptyMessage: "No runs executing. Governance first — launch only after setup/repairs.",
165
- readable: true
166
- }),
167
- listIndex,
168
- colorEnabled,
169
- "Enter opens detail · Esc back to Orchestration"
170
- );
171
171
  case ORCHESTRATOR_VIEWS.RECENT_RUNS:
172
- return listBlock(
173
- "Run history",
174
- formatRunLines(dashboard?.recentRuns ?? [], {
175
- emptyMessage: "No completed runs yet.",
176
- readable: true
177
- }),
178
- listIndex,
179
- colorEnabled,
180
- "Enter opens detail · Esc back to Orchestration"
181
- );
182
172
  case ORCHESTRATOR_VIEWS.REVIEWS:
183
- return listBlock(
184
- "Reviews",
185
- formatReviewListLines(reviews),
173
+ return React.createElement(SemanticOrchestrationPanel, {
174
+ view,
175
+ dashboard,
176
+ reviews,
186
177
  listIndex,
178
+ layoutMode,
179
+ contentFocused,
187
180
  colorEnabled,
188
- "Receipts are read-only. Launch reviews via kairo review --agent codex|pi."
189
- );
181
+ unicode
182
+ });
190
183
  case ORCHESTRATOR_VIEWS.LAUNCH:
191
184
  if (launchableAgents.length === 0) {
192
185
  return React.createElement(CockpitEmptyState, {
@@ -223,13 +216,14 @@ export function renderCockpitView({
223
216
  .map((line) => React.createElement(Text, { key: line }, line))
224
217
  );
225
218
  case ORCHESTRATOR_VIEWS.ALERTS:
226
- return listBlock(
227
- "Alerts",
228
- formatAlertListLines(alerts),
219
+ return React.createElement(SemanticAlertsPanel, {
220
+ alerts,
229
221
  listIndex,
222
+ layoutMode,
223
+ contentFocused,
230
224
  colorEnabled,
231
- "Enter resolves · D dismisses · Esc back · / Alerts"
232
- );
225
+ unicode
226
+ });
233
227
  case ORCHESTRATOR_VIEWS.DIAGNOSTICS:
234
228
  return governanceList(
235
229
  "System health",
@@ -287,25 +281,4 @@ function formatModuleLines(snapshot) {
287
281
  ];
288
282
  }
289
283
 
290
- function formatChangeLines(snapshot, changesAction, layoutMode = LAYOUT_MODES.COMPACT, homeDir = null) {
291
- return formatChangesActionLines({ snapshot, changesAction, layoutMode, homeDir });
292
- }
293
-
294
- function listBlock(title, lines, listIndex, colorEnabled, emptyHint) {
295
- const isEmpty = lines.length === 1 && /no |nothing |empty/i.test(lines[0]);
296
- return React.createElement(Box, { flexDirection: "column" },
297
- React.createElement(Text, { bold: true }, title),
298
- isEmpty
299
- ? React.createElement(CockpitEmptyState, {
300
- message: lines[0],
301
- hint: emptyHint
302
- })
303
- : lines.map((line, index) => React.createElement(Text, {
304
- key: `${index}-${line}`,
305
- bold: index === listIndex,
306
- color: index === listIndex && colorEnabled ? COCKPIT_COLORS.primary : undefined
307
- }, `${index === listIndex ? "› " : " "}${line}`))
308
- );
309
- }
310
-
311
284
  export { LAUNCH_WIZARD_STEPS };
@@ -41,7 +41,7 @@ import { handleLaunchInput } from "./launch-input.js";
41
41
  import { useTerminalSize } from "./use-terminal-size.js";
42
42
  import { useOrchestratorData } from "./use-orchestrator-data.js";
43
43
  import { resolveTerminalCapabilities } from "./terminal-capabilities.js";
44
- import { COCKPIT_COLORS } from "./theme.js";
44
+ import { COCKPIT_COLORS, resolveInkColor } from "./theme.js";
45
45
  import { LAYOUT_MODES } from "./layout.js";
46
46
  import { CHANGES_PHASE } from "./cockpit-changes.js";
47
47
  import { RECOVERY_PHASE, listRecoverySnapshots } from "./cockpit-recovery.js";
@@ -146,6 +146,10 @@ export function OrchestratorApp({
146
146
  data.reload().catch(() => {});
147
147
  return;
148
148
  }
149
+ if (selected.kind === PALETTE_KINDS.SETUP) {
150
+ finish({ cancelled: false, action: "setup" });
151
+ return;
152
+ }
149
153
  dispatch({
150
154
  type: "run-palette",
151
155
  kind: selected.kind,
@@ -161,6 +165,24 @@ export function OrchestratorApp({
161
165
  return;
162
166
  }
163
167
 
168
+ if (inputKey === " " && ui.view === ORCHESTRATOR_VIEWS.HOME && !ui.paletteOpen) {
169
+ dispatch({ type: "toggle-overview-details" });
170
+ return;
171
+ }
172
+
173
+ if (inputKey === " " && ui.view === ORCHESTRATOR_VIEWS.CHANGES && !ui.paletteOpen) {
174
+ dispatch({ type: "toggle-governance-details" });
175
+ return;
176
+ }
177
+
178
+ if (inputKey === " "
179
+ && ui.view === ORCHESTRATOR_VIEWS.ACTIVITY
180
+ && !ui.paletteOpen
181
+ && data.recoveryAction?.preview) {
182
+ dispatch({ type: "toggle-activity-details" });
183
+ return;
184
+ }
185
+
164
186
  if (key.escape) {
165
187
  if (ui.view === ORCHESTRATOR_VIEWS.CHANGES
166
188
  && data.changesAction?.phase === CHANGES_PHASE.CONFIRMING) {
@@ -262,6 +284,10 @@ export function OrchestratorApp({
262
284
  navItem: item,
263
285
  ctaDestination: data.snapshot?.cta?.destination ?? null
264
286
  });
287
+ if (intent.kind === "activate-setup") {
288
+ finish({ cancelled: false, action: "setup" });
289
+ return;
290
+ }
265
291
  if (intent.kind === "activate-cta") {
266
292
  if (openDestination(intent.destination)) return;
267
293
  }
@@ -440,14 +466,22 @@ export function OrchestratorApp({
440
466
 
441
467
  if (data.loading) {
442
468
  return React.createElement(Box, { flexDirection: "column" },
443
- React.createElement(Text, { bold: true, color: COCKPIT_COLORS.primary }, "KAIRO"),
444
- React.createElement(Text, { color: COCKPIT_COLORS.muted }, "Loading cockpit…")
469
+ React.createElement(Text, {
470
+ bold: true,
471
+ color: resolveInkColor(caps.color, COCKPIT_COLORS.brand)
472
+ }, "KAIRO"),
473
+ React.createElement(Text, {
474
+ color: resolveInkColor(caps.color, COCKPIT_COLORS.muted)
475
+ }, "Loading cockpit…")
445
476
  );
446
477
  }
447
478
 
448
479
  if (data.error) {
449
480
  return React.createElement(Box, { flexDirection: "column" },
450
- React.createElement(Text, { bold: true, color: COCKPIT_COLORS.danger }, "Runtime error"),
481
+ React.createElement(Text, {
482
+ bold: true,
483
+ color: resolveInkColor(caps.color, COCKPIT_COLORS.danger)
484
+ }, "Runtime error"),
451
485
  React.createElement(Text, null, data.error),
452
486
  React.createElement(Text, { dimColor: true },
453
487
  data.retrying ? "Retrying read-only scan…" : "R Retry · Esc to exit")
@@ -495,6 +529,7 @@ export function OrchestratorApp({
495
529
  unicode,
496
530
  changesPhase: data.changesAction?.phase ?? null,
497
531
  recoveryPhase: data.recoveryAction?.phase ?? null,
532
+ recoveryHasPreview: Boolean(data.recoveryAction?.preview),
498
533
  settingsPhase: data.settingsAction?.phase ?? null,
499
534
  columns
500
535
  }),
@@ -542,6 +577,11 @@ export function OrchestratorApp({
542
577
  : null,
543
578
  layoutMode: mode,
544
579
  colorEnabled,
580
+ unicode,
581
+ overviewDetailsOpen: ui.overviewDetailsOpen,
582
+ governanceDetailsOpen: ui.governanceDetailsOpen,
583
+ activityDetailsOpen: ui.activityDetailsOpen,
584
+ contentFocused: ui.region === COCKPIT_REGIONS.CONTENT,
545
585
  homeDir
546
586
  })
547
587
  )