@kal-elsam/kairo-runtime 0.11.0 → 0.12.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 (64) hide show
  1. package/global-template/components/agent-skills/LICENSE +21 -0
  2. package/global-template/components/agent-skills/PROVENANCE.md +26 -0
  3. package/global-template/components/agent-skills/skills/context-engineering/SKILL.md +289 -0
  4. package/global-template/components/agent-skills/skills/frontend-ui-engineering/SKILL.md +328 -0
  5. package/global-template/components/agent-skills/skills/observability-and-instrumentation/SKILL.md +203 -0
  6. package/global-template/components/agent-skills/skills/performance-optimization/SKILL.md +396 -0
  7. package/global-template/components/agent-skills/skills/source-driven-development/SKILL.md +194 -0
  8. package/global-template/components/catalog.json +29 -0
  9. package/package.json +5 -2
  10. package/src/cli.js +136 -8
  11. package/src/global/component-builders.js +3 -1
  12. package/src/global/components/agent-skills.js +27 -0
  13. package/src/global/ink/cockpit-control-center.js +104 -4
  14. package/src/global/ink/cockpit-scan.js +20 -2
  15. package/src/global/ink/ecosystem-updates-display.js +37 -0
  16. package/src/global/ink/launch-input.js +32 -1
  17. package/src/global/ink/orchestrator-app.js +2 -1
  18. package/src/global/ink/orchestrator-state.js +17 -2
  19. package/src/global/ink/system-resources-display.js +109 -0
  20. package/src/global/ink/use-orchestrator-data.js +40 -4
  21. package/src/global/ink/ux/live-overview.js +11 -1
  22. package/src/global/mcp/kairo-mcp.js +230 -0
  23. package/src/global/observability/build-companion-snapshot.js +281 -0
  24. package/src/global/observability/build-observability-snapshot.js +24 -0
  25. package/src/global/observability/ecosystem-updates.js +224 -0
  26. package/src/global/observability/gentle-bundle-export.js +71 -0
  27. package/src/global/observability/gentle-bundle-import.js +122 -0
  28. package/src/global/observability/gentle-probe.js +155 -0
  29. package/src/global/observability/graphify-ops.js +133 -0
  30. package/src/global/observability/graphify-parse-cache.js +90 -0
  31. package/src/global/observability/graphify-probe.js +185 -0
  32. package/src/global/observability/hermes-activity.js +163 -0
  33. package/src/global/observability/hermes-probe.js +171 -0
  34. package/src/global/observability/index.js +85 -0
  35. package/src/global/observability/passive-snapshot-flight.js +93 -0
  36. package/src/global/observability/probe-contract.js +38 -0
  37. package/src/global/observability/probe-registry.js +30 -0
  38. package/src/global/observability/resource-advisor.js +71 -0
  39. package/src/global/observability/system-resources.js +171 -0
  40. package/src/global/runtime/alerts/alert-cli.js +31 -0
  41. package/src/global/runtime/alerts/alert-store.js +29 -6
  42. package/src/global/runtime/alerts/alert-validate.js +25 -1
  43. package/src/global/runtime/alerts/controlled-alert-actions.js +56 -0
  44. package/src/global/runtime/execution-adapters/claude.js +2 -1
  45. package/src/global/runtime/execution-adapters/codex.js +2 -1
  46. package/src/global/runtime/execution-adapters/create-execution-adapter.js +3 -14
  47. package/src/global/runtime/execution-adapters/cursor.js +2 -1
  48. package/src/global/runtime/execution-adapters/opencode.js +2 -1
  49. package/src/global/runtime/execution-adapters/pi.js +2 -1
  50. package/src/global/runtime/review/index.js +1 -1
  51. package/src/global/runtime/review/review-cli.js +113 -3
  52. package/src/global/runtime/review/review-git.js +142 -11
  53. package/src/global/runtime/review/review-patch.js +2 -0
  54. package/src/global/runtime/review/review-receipts.js +12 -7
  55. package/src/global/runtime/review/review-runner.js +2 -2
  56. package/src/global/runtime/review/review-types.js +8 -5
  57. package/src/global/runtime/review/review-validate.js +5 -1
  58. package/src/global/runtime/run-cli.js +2 -0
  59. package/src/global/runtime/run-manager.js +39 -18
  60. package/src/global/runtime/run-permissions.js +231 -0
  61. package/src/global/runtime/run-profile.js +2 -0
  62. package/src/global/runtime/run-supervisor.js +77 -37
  63. package/src/global/runtime/run-types.js +2 -0
  64. package/src/global/updates-cli.js +41 -0
@@ -5,13 +5,30 @@ import {
5
5
  formatMeasuredBudgets,
6
6
  formatUsageLinesFromModel
7
7
  } from "./cockpit-usage.js";
8
+ import { LAYOUT_MODES } from "./layout.js";
9
+ import {
10
+ formatSystemResourcesLines,
11
+ formatResourceAdviceLines
12
+ } from "./system-resources-display.js";
13
+ import { formatEcosystemUpdateLines } from "./ecosystem-updates-display.js";
14
+
15
+ const HERMES_WIDE_SESSION_LIMIT = 3;
16
+ const HERMES_TITLE_MAX = 48;
17
+
18
+ export {
19
+ diskFreeTone,
20
+ formatSystemResourcesLines,
21
+ formatResourceAdviceLines
22
+ } from "./system-resources-display.js";
23
+ export { formatEcosystemUpdateLines } from "./ecosystem-updates-display.js";
8
24
 
9
25
  export function buildControlCenterModel({
10
26
  projectName = "project",
11
27
  snapshot = null,
12
28
  dashboard = null,
13
- layoutMode = "compact",
14
- alerts = null
29
+ layoutMode = LAYOUT_MODES.COMPACT,
30
+ alerts = null,
31
+ companion = null
15
32
  } = {}) {
16
33
  if (!snapshot) {
17
34
  return {
@@ -49,7 +66,9 @@ export function buildControlCenterModel({
49
66
  activity: { headline: "No activity yet" },
50
67
  alerts: formatAlertsHeadline(alerts),
51
68
  tokens: { headline: "Data unavailable" },
52
- includeEmbeddedStatus: layoutMode !== "wide",
69
+ companion: null,
70
+ companionNextAction: null,
71
+ includeEmbeddedStatus: layoutMode !== LAYOUT_MODES.WIDE,
53
72
  runsSecondaryHint: "Detail via Enter · / actions"
54
73
  };
55
74
  }
@@ -94,11 +113,92 @@ export function buildControlCenterModel({
94
113
  },
95
114
  alerts: formatAlertsHeadline(alerts),
96
115
  tokens: { headline: formatTokenHeadline(snapshot.budgets) },
97
- includeEmbeddedStatus: layoutMode !== "wide",
116
+ companion: formatCompanionOverlay(companion, layoutMode),
117
+ companionNextAction: companion?.nextSafeAction ?? null,
118
+ includeEmbeddedStatus: layoutMode !== LAYOUT_MODES.WIDE,
98
119
  runsSecondaryHint: "Detail via Enter · / actions"
99
120
  };
100
121
  }
101
122
 
123
+ /**
124
+ * Observe-only Hermes activity lines for Cockpit overlay.
125
+ * Never includes session ids, baseUrl, diagnostics dumps, or control affordances.
126
+ */
127
+ export function formatHermesActivityLines(activity, layoutMode = LAYOUT_MODES.COMPACT) {
128
+ if (activity == null || typeof activity !== "object") {
129
+ return ["Hermes · unavailable"];
130
+ }
131
+ const state = typeof activity.state === "string" && activity.state.length > 0
132
+ ? activity.state
133
+ : "error";
134
+ if (state !== "available" && state !== "partial") {
135
+ return [`Hermes · ${state}`];
136
+ }
137
+
138
+ const agg = activity.aggregates && typeof activity.aggregates === "object"
139
+ ? activity.aggregates
140
+ : {};
141
+ const active = Number.isInteger(agg.activeCount) ? agg.activeCount : null;
142
+ const ended = Number.isInteger(agg.endedCount) ? agg.endedCount : null;
143
+ const bits = [`Hermes · ${state}`];
144
+ if (active != null) bits.push(`${active} active`);
145
+ if (layoutMode !== LAYOUT_MODES.MINIMAL && ended != null) {
146
+ bits.push(`${ended} ended`);
147
+ }
148
+ const lines = [bits.join(" · ")];
149
+
150
+ if (layoutMode !== LAYOUT_MODES.WIDE) return lines;
151
+
152
+ const sessions = Array.isArray(activity.sessions) ? activity.sessions : [];
153
+ for (const session of sessions.slice(0, HERMES_WIDE_SESSION_LIMIT)) {
154
+ const label = hermesSessionLabel(session);
155
+ if (label) lines.push(` · ${label}`);
156
+ }
157
+ if (agg.hasMore === true || sessions.length > HERMES_WIDE_SESSION_LIMIT) {
158
+ lines.push(" · … more sessions");
159
+ }
160
+ return lines;
161
+ }
162
+
163
+ function hermesSessionLabel(session) {
164
+ if (session == null || typeof session !== "object" || Array.isArray(session)) return null;
165
+ const title = typeof session.title === "string" && session.title.trim()
166
+ ? session.title.trim()
167
+ : null;
168
+ const source = typeof session.source === "string" && session.source.trim()
169
+ ? session.source.trim()
170
+ : null;
171
+ const head = (title ?? source ?? "untitled")
172
+ .replace(/[\r\n\t]+/g, " ")
173
+ .slice(0, HERMES_TITLE_MAX);
174
+ const flag = session.active === true ? "active" : "ended";
175
+ return `${head} · ${flag}`;
176
+ }
177
+
178
+ function formatCompanionOverlay(companion, layoutMode = LAYOUT_MODES.COMPACT) {
179
+ if (!companion) return null;
180
+ const g = companion.signals?.gentle?.state ?? "unknown";
181
+ const gy = companion.signals?.graphify;
182
+ const graphBit = gy?.graphStatus ? `/${gy.graphStatus}` : "";
183
+ const en = companion.engram?.status ?? "unknown";
184
+ const links = companion.links?.length ?? 0;
185
+ return {
186
+ ok: companion.ok !== false,
187
+ lines: [
188
+ `Gentle · ${g}`,
189
+ `Graphify · ${gy?.state ?? "unknown"}${graphBit}`,
190
+ `Engram · ${en}`,
191
+ `Soft links · ${links}`,
192
+ ...formatHermesActivityLines(companion.signals?.hermes?.activity, layoutMode),
193
+ ...formatSystemResourcesLines(companion.signals?.system?.resources, layoutMode),
194
+ ...formatResourceAdviceLines(companion.signals?.system?.advice, layoutMode),
195
+ ...formatEcosystemUpdateLines(companion.signals?.ecosystem?.updates, layoutMode)
196
+ ],
197
+ links: companion.links ?? [],
198
+ error: companion.error ?? null
199
+ };
200
+ }
201
+
102
202
  function formatHealthLabel(kind) {
103
203
  switch (kind) {
104
204
  case CONTROL_PLANE_HEALTH.NOT_CONFIGURED:
@@ -12,7 +12,8 @@ export async function loadCockpitScanBundle({
12
12
  cliVersion,
13
13
  buildDashboard,
14
14
  buildDiagnostics,
15
- buildSnapshot
15
+ buildSnapshot,
16
+ buildCompanion = null
16
17
  }) {
17
18
  const [dashboard, diagnostics, snapshot] = await Promise.all([
18
19
  buildDashboard({ homeDir, workspaceRoot, cliVersion }),
@@ -26,7 +27,24 @@ export async function loadCockpitScanBundle({
26
27
  ...CONTROL_PLANE_AUTO_SCAN
27
28
  })
28
29
  ]);
29
- return { dashboard, diagnostics, snapshot };
30
+ let companion = null;
31
+ if (typeof buildCompanion === "function") {
32
+ try {
33
+ companion = await buildCompanion({
34
+ homeDir, workspaceRoot, packageName, packageRoot, cliVersion, dashboard, snapshot
35
+ });
36
+ } catch (error) {
37
+ const detail = error instanceof Error ? error.message : String(error);
38
+ companion = {
39
+ ok: false, error: detail, signals: null, engram: null, links: [], alertsCount: null,
40
+ nextSafeAction: {
41
+ kind: "investigate", title: "Investigate companion diagnostics",
42
+ detail, secondary: true, displayOnly: true
43
+ }
44
+ };
45
+ }
46
+ }
47
+ return { dashboard, diagnostics, snapshot, companion };
30
48
  }
31
49
 
32
50
  /**
@@ -0,0 +1,37 @@
1
+ import { LAYOUT_MODES } from "./layout.js";
2
+
3
+ /** Display-only ecosystem update lines for Cockpit companion overlay. */
4
+ export function formatEcosystemUpdateLines(updates, layoutMode = LAYOUT_MODES.COMPACT) {
5
+ if (updates == null || typeof updates !== "object") return ["Updates · unavailable"];
6
+ const state = typeof updates.state === "string" && updates.state ? updates.state : "error";
7
+ const tools = updates.tools && typeof updates.tools === "object" ? updates.tools : {};
8
+ const pending = ["kairo", "hermes", "gentle", "skills"]
9
+ .map((id) => tools[id])
10
+ .filter((t) => t && t.updateAvailable === true);
11
+ const cache = updates.cacheHit === true ? " · cache" : "";
12
+
13
+ if (state !== "available" && state !== "partial" && pending.length === 0) {
14
+ return [`Updates · ${state}${cache}`];
15
+ }
16
+
17
+ const lines = [
18
+ pending.length > 0
19
+ ? `Updates · ${pending.length} available${cache}`
20
+ : `Updates · current${cache}`
21
+ ];
22
+ if (layoutMode === LAYOUT_MODES.MINIMAL) return lines;
23
+
24
+ for (const tool of pending.slice(0, layoutMode === LAYOUT_MODES.WIDE ? 4 : 2)) {
25
+ const inst = tool.installed ?? "—";
26
+ const latest = tool.latest ?? "—";
27
+ lines.push(` · ${tool.id} ${inst} → ${latest}`);
28
+ }
29
+ if (pending.length === 0 && layoutMode === LAYOUT_MODES.WIDE) {
30
+ for (const id of ["kairo", "hermes", "gentle", "skills"]) {
31
+ const tool = tools[id];
32
+ if (!tool) continue;
33
+ lines.push(` · ${id} · ${tool.state ?? "unknown"}`);
34
+ }
35
+ }
36
+ return lines;
37
+ }
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  LAUNCH_PERMISSION_OPTIONS,
3
3
  LAUNCH_WIZARD_STEPS,
4
+ advanceLaunchWizardStep,
4
5
  retreatLaunchWizardStep
5
6
  } from "./orchestrator-state.js";
6
7
 
@@ -92,7 +93,19 @@ export function handleLaunchInput(ctx) {
92
93
 
93
94
  if (launchStep === LAUNCH_WIZARD_STEPS.CONFIRM) {
94
95
  if (key.return) {
95
- handleLaunch({ ...launchDraft, permissionIndex: launchPermissionIndex });
96
+ const next = advanceLaunchWizardStep(LAUNCH_WIZARD_STEPS.CONFIRM, {
97
+ permissionIndex: launchPermissionIndex
98
+ });
99
+ if (next === LAUNCH_WIZARD_STEPS.UNSAFE_CONFIRM) {
100
+ setLaunchStep(LAUNCH_WIZARD_STEPS.UNSAFE_CONFIRM);
101
+ return true;
102
+ }
103
+ handleLaunch({
104
+ ...launchDraft,
105
+ permissionIndex: launchPermissionIndex,
106
+ allowUnsafePermissions: false,
107
+ permissionSource: "cockpit"
108
+ });
96
109
  return true;
97
110
  }
98
111
  if (key.escape) {
@@ -101,6 +114,24 @@ export function handleLaunchInput(ctx) {
101
114
  }
102
115
  }
103
116
 
117
+ if (launchStep === LAUNCH_WIZARD_STEPS.UNSAFE_CONFIRM) {
118
+ const answer = String(inputKey ?? "").toLowerCase();
119
+ if (answer === "y") {
120
+ handleLaunch({
121
+ ...launchDraft,
122
+ permissionIndex: launchPermissionIndex,
123
+ allowUnsafePermissions: true,
124
+ permissionSource: "cockpit"
125
+ });
126
+ return true;
127
+ }
128
+ if (answer === "n" || key.escape) {
129
+ setLaunchStep(LAUNCH_WIZARD_STEPS.CONFIRM);
130
+ return allowEscapeRetreat && key.escape ? "retreated" : true;
131
+ }
132
+ return true;
133
+ }
134
+
104
135
  if (inputKey.toLowerCase() === "r") {
105
136
  reload().catch(() => {});
106
137
  return true;
@@ -502,7 +502,8 @@ export function OrchestratorApp({
502
502
  snapshot: data.snapshot,
503
503
  dashboard: data.dashboard,
504
504
  layoutMode: mode,
505
- alerts: data.alerts
505
+ alerts: data.alerts,
506
+ companion: data.companion
506
507
  });
507
508
  const systemOnline = data.snapshot
508
509
  ? data.snapshot.health !== CONTROL_PLANE_HEALTH.NOT_CONFIGURED
@@ -47,7 +47,8 @@ export const LAUNCH_WIZARD_STEPS = {
47
47
  TASK: "task",
48
48
  MODEL: "model",
49
49
  PERMISSIONS: "permissions",
50
- CONFIRM: "confirm"
50
+ CONFIRM: "confirm",
51
+ UNSAFE_CONFIRM: "unsafe-confirm"
51
52
  };
52
53
 
53
54
  export const LAUNCH_PERMISSION_OPTIONS = [
@@ -75,7 +76,7 @@ export function resolveLaunchPermissions(draft) {
75
76
  return LAUNCH_PERMISSION_OPTIONS[draft.permissionIndex]?.permissions ?? [];
76
77
  }
77
78
 
78
- export function advanceLaunchWizardStep(currentStep) {
79
+ export function advanceLaunchWizardStep(currentStep, { permissionIndex = 0 } = {}) {
79
80
  switch (currentStep) {
80
81
  case LAUNCH_WIZARD_STEPS.AGENT:
81
82
  return LAUNCH_WIZARD_STEPS.TASK;
@@ -85,6 +86,10 @@ export function advanceLaunchWizardStep(currentStep) {
85
86
  return LAUNCH_WIZARD_STEPS.PERMISSIONS;
86
87
  case LAUNCH_WIZARD_STEPS.PERMISSIONS:
87
88
  return LAUNCH_WIZARD_STEPS.CONFIRM;
89
+ case LAUNCH_WIZARD_STEPS.CONFIRM: {
90
+ const perms = resolveLaunchPermissions({ permissionIndex });
91
+ return perms.length > 0 ? LAUNCH_WIZARD_STEPS.UNSAFE_CONFIRM : LAUNCH_WIZARD_STEPS.CONFIRM;
92
+ }
88
93
  default:
89
94
  return LAUNCH_WIZARD_STEPS.CONFIRM;
90
95
  }
@@ -92,6 +97,8 @@ export function advanceLaunchWizardStep(currentStep) {
92
97
 
93
98
  export function retreatLaunchWizardStep(currentStep) {
94
99
  switch (currentStep) {
100
+ case LAUNCH_WIZARD_STEPS.UNSAFE_CONFIRM:
101
+ return LAUNCH_WIZARD_STEPS.CONFIRM;
95
102
  case LAUNCH_WIZARD_STEPS.CONFIRM:
96
103
  return LAUNCH_WIZARD_STEPS.PERMISSIONS;
97
104
  case LAUNCH_WIZARD_STEPS.PERMISSIONS:
@@ -147,6 +154,14 @@ export function formatLaunchWizardLines({
147
154
  return lines;
148
155
  }
149
156
 
157
+ if (step === LAUNCH_WIZARD_STEPS.UNSAFE_CONFIRM) {
158
+ const label = LAUNCH_PERMISSION_OPTIONS[permissionIndex]?.label ?? "unsafe";
159
+ lines.push(`Unsafe mode selected: ${label}`);
160
+ lines.push("This skips agent permission prompts for this run only.");
161
+ lines.push("Press Y to confirm unsafe launch · N/Esc to go back");
162
+ return lines;
163
+ }
164
+
150
165
  lines.push(`Agent: ${draft.agentId ?? "—"}`);
151
166
  lines.push(`Task length: ${draft.task.length} chars (content not stored)`);
152
167
  lines.push(`Model: ${draft.model || "default"}`);
@@ -0,0 +1,109 @@
1
+ import { LAYOUT_MODES } from "./layout.js";
2
+
3
+ /** Disk free% tone — never invents a percent. */
4
+ export function diskFreeTone(freePercent) {
5
+ if (typeof freePercent !== "number" || !Number.isFinite(freePercent)) return null;
6
+ if (freePercent < 10) return "critical";
7
+ if (freePercent < 20) return "warning";
8
+ return "healthy";
9
+ }
10
+
11
+ function formatBytesShort(bytes) {
12
+ if (typeof bytes !== "number" || !Number.isFinite(bytes) || bytes < 0) return null;
13
+ const gib = bytes / (1024 ** 3);
14
+ if (gib >= 1) return `${Math.round(gib * 10) / 10}G`;
15
+ const mib = bytes / (1024 ** 2);
16
+ return `${Math.round(mib)}M`;
17
+ }
18
+
19
+ function pctLabel(value, suffix = "% free") {
20
+ if (typeof value !== "number" || !Number.isFinite(value)) return null;
21
+ return `${value}${suffix}`;
22
+ }
23
+
24
+ /**
25
+ * Display-only System resource lines for Cockpit companion overlay.
26
+ * Never invents metrics; never includes paths, args, or control affordances.
27
+ */
28
+ export function formatSystemResourcesLines(resources, layoutMode = LAYOUT_MODES.COMPACT) {
29
+ if (resources == null || typeof resources !== "object") {
30
+ return ["System · unavailable"];
31
+ }
32
+ const state = typeof resources.state === "string" && resources.state.length > 0
33
+ ? resources.state
34
+ : "error";
35
+ if (state !== "available" && state !== "partial") {
36
+ return [`System · ${state}`];
37
+ }
38
+
39
+ const ram = pctLabel(resources.memory?.freePercent);
40
+ const swapUsed = formatBytesShort(resources.swap?.usedBytes);
41
+ const diskPct = resources.disk?.freePercent;
42
+ const disk = pctLabel(diskPct);
43
+ const tone = diskFreeTone(diskPct);
44
+
45
+ const bits = ["System"];
46
+ bits.push(ram ? `RAM ${ram}` : "RAM n/a");
47
+ bits.push(swapUsed ? `Swap ${swapUsed} used` : "Swap n/a");
48
+ bits.push(disk ? `Disk ${disk}` : "Disk n/a");
49
+ if (tone && tone !== "healthy") bits.push(tone);
50
+
51
+ const lines = [bits.join(" · ")];
52
+ if (layoutMode === LAYOUT_MODES.MINIMAL) return lines;
53
+
54
+ const ramFree = resources.memory?.freePercent;
55
+ const memoryPressure = typeof ramFree === "number" && Number.isFinite(ramFree) && ramFree < 15;
56
+ if (memoryPressure) {
57
+ lines.push(" · Memory pressure · swap elevated relevance");
58
+ } else {
59
+ lines.push(" · Swap informational");
60
+ }
61
+
62
+ const proc = resources.processes && typeof resources.processes === "object"
63
+ ? resources.processes
64
+ : null;
65
+ if (proc) {
66
+ const total = Number.isInteger(proc.totalCount) ? proc.totalCount : null;
67
+ const zombies = Number.isInteger(proc.zombieCount) ? proc.zombieCount : null;
68
+ if (total != null && zombies != null) {
69
+ lines.push(` · Processes ${total} · zombies ${zombies}`);
70
+ }
71
+ }
72
+
73
+ if (layoutMode !== LAYOUT_MODES.WIDE) return lines;
74
+
75
+ const tracked = Array.isArray(proc?.tracked) ? proc.tracked : [];
76
+ for (const entry of tracked.slice(0, 4)) {
77
+ if (entry == null || typeof entry !== "object") continue;
78
+ if (typeof entry.name !== "string" || !Number.isInteger(entry.count)) continue;
79
+ lines.push(` · ${entry.name} ×${entry.count}`);
80
+ }
81
+ const thermal = resources.thermal?.state ?? "unavailable";
82
+ const ssd = resources.ssdWear?.state ?? "unavailable";
83
+ lines.push(` · Thermal · ${thermal}`);
84
+ lines.push(` · SSD wear · ${ssd}`);
85
+ return lines;
86
+ }
87
+
88
+ /** Display-only advisor lines from deterministic recommendations. */
89
+ export function formatResourceAdviceLines(advice, layoutMode = LAYOUT_MODES.COMPACT) {
90
+ const list = Array.isArray(advice?.recommendations) ? advice.recommendations : [];
91
+ if (list.length === 0) return ["Advisor · quiet"];
92
+ const top = list[0];
93
+ const severity = typeof top.severity === "string" ? top.severity : "info";
94
+ const title = typeof top.title === "string" ? top.title : "recommendation";
95
+ const lines = [`Advisor · ${severity} · ${title}`];
96
+ if (layoutMode === LAYOUT_MODES.MINIMAL) return lines;
97
+ if (typeof top.detail === "string" && top.detail.length > 0) {
98
+ lines.push(` · ${top.detail.slice(0, 96)}`);
99
+ }
100
+ if (layoutMode === LAYOUT_MODES.WIDE) {
101
+ for (const item of list.slice(1, 3)) {
102
+ if (item == null || typeof item !== "object") continue;
103
+ const sev = typeof item.severity === "string" ? item.severity : "info";
104
+ const t = typeof item.title === "string" ? item.title : "recommendation";
105
+ lines.push(` · ${sev} · ${t}`);
106
+ }
107
+ }
108
+ return lines;
109
+ }
@@ -40,7 +40,14 @@ import {
40
40
  } from "./cockpit-settings.js";
41
41
  import { listReviewReceipts } from "../runtime/review/review-receipts.js";
42
42
  import { assertReceiptSecretFree } from "../runtime/review/review-validate.js";
43
- import { listAlerts, resolveAlert, dismissAlert } from "../runtime/alerts/alert-store.js";
43
+ import { listAlerts } from "../runtime/alerts/alert-store.js";
44
+ import {
45
+ controlledDismissAlert, controlledResolveAlert
46
+ } from "../runtime/alerts/controlled-alert-actions.js";
47
+ import { buildCompanionSnapshot } from "../observability/build-companion-snapshot.js";
48
+ import { resolveGitHeadSha } from "../observability/graphify-probe.js";
49
+ import { runPassiveObservabilitySnapshot } from "../observability/passive-snapshot-flight.js";
50
+ import { inspectEngramIntegration } from "../integrations/engram-evidence.js";
44
51
 
45
52
  export function useOrchestratorData({
46
53
  homeDir,
@@ -56,6 +63,7 @@ export function useOrchestratorData({
56
63
  const [dashboard, setDashboard] = useState(null);
57
64
  const [diagnostics, setDiagnostics] = useState(null);
58
65
  const [snapshot, setSnapshot] = useState(null);
66
+ const [companion, setCompanion] = useState(null);
59
67
  const [selectedRun, setSelectedRun] = useState(null);
60
68
  const [selectedEvents, setSelectedEvents] = useState([]);
61
69
  const [reviews, setReviews] = useState([]);
@@ -78,7 +86,24 @@ export function useOrchestratorData({
78
86
  cliVersion,
79
87
  buildDashboard: buildRuntimeDashboardData,
80
88
  buildDiagnostics: buildReadOnlyDiagnostics,
81
- buildSnapshot: buildControlPlaneSnapshot
89
+ buildSnapshot: buildControlPlaneSnapshot,
90
+ buildCompanion: async ({ dashboard, snapshot: snap }) => buildCompanionSnapshot({
91
+ controlPlaneHealth: snap?.health ?? null,
92
+ runs: dashboard?.recentRuns ?? [],
93
+ inspectEngram: (ctx) => inspectEngramIntegration({
94
+ env: ctx?.env ?? process.env,
95
+ homeDir: ctx?.homeDir ?? homeDir
96
+ }),
97
+ buildObservability: (ctx) => runPassiveObservabilitySnapshot(ctx, {
98
+ force: Boolean(ctx?.force)
99
+ }),
100
+ observabilityContext: {
101
+ cwd: workspaceRoot, workspaceRoot, env: process.env,
102
+ headSha: resolveGitHeadSha(workspaceRoot)
103
+ },
104
+ loadReviews: async () => listReviewReceipts({ homeDir, limit: 20 }),
105
+ loadAlerts: async () => listAlerts({ homeDir, limit: 50 })
106
+ })
82
107
  })), [homeDir, workspaceRoot, packageName, packageRoot, cliVersion]);
83
108
 
84
109
  const reload = async ({ showLoading = false, asRetry = false } = {}) => {
@@ -98,6 +123,7 @@ export function useOrchestratorData({
98
123
  setDashboard(outcome.result.dashboard);
99
124
  setDiagnostics(outcome.result.diagnostics);
100
125
  setSnapshot(outcome.result.snapshot);
126
+ setCompanion(outcome.result.companion ?? null);
101
127
  try {
102
128
  setAlerts(await listAlerts({ homeDir, limit: 50 }));
103
129
  } catch {
@@ -166,8 +192,14 @@ export function useOrchestratorData({
166
192
  if (!alert) return;
167
193
  setBusy(true);
168
194
  try {
169
- if (action === "dismiss") await dismissAlert(alert.alertId, { homeDir });
170
- else await resolveAlert(alert.alertId, { homeDir });
195
+ const result = action === "dismiss"
196
+ ? await controlledDismissAlert({
197
+ alertId: alert.alertId, confirmed: true, source: "cockpit", homeDir
198
+ })
199
+ : await controlledResolveAlert({
200
+ alertId: alert.alertId, confirmed: true, source: "cockpit", homeDir
201
+ });
202
+ if (!result.ok) throw new Error(result.message ?? result.code);
171
203
  setAlerts(await listAlerts({ homeDir, limit: 50 }));
172
204
  setStatusMessage(action === "dismiss" ? "Alert dismissed" : "Alert resolved");
173
205
  } catch (error) {
@@ -197,6 +229,9 @@ export function useOrchestratorData({
197
229
  cwd: workspaceRoot,
198
230
  model: draft.model.trim() || null,
199
231
  permissions,
232
+ allowUnsafePermissions: Boolean(draft.allowUnsafePermissions),
233
+ permissionSource: draft.permissionSource ?? "cockpit",
234
+ permissionConsentType: draft.allowUnsafePermissions ? "cockpit-unsafe-confirm" : null,
200
235
  cliVersion,
201
236
  profile: profile ?? null,
202
237
  follow: false,
@@ -424,6 +459,7 @@ export function useOrchestratorData({
424
459
  dashboard,
425
460
  diagnostics,
426
461
  snapshot,
462
+ companion,
427
463
  selectedRun,
428
464
  setSelectedRun,
429
465
  selectedEvents,
@@ -55,6 +55,10 @@ export function buildOverviewDetails(model = {}) {
55
55
  if (typeof model.alerts?.count === "number") {
56
56
  lines.push(`Open alerts · ${model.alerts.count}`);
57
57
  }
58
+ const secondary = model.companionNextAction;
59
+ if (secondary?.title && secondary.kind !== "idle") {
60
+ lines.push(`Companion · ${secondary.title}`);
61
+ }
58
62
  if (lines.length === 0) return ["No extra evidence beyond the metrics above."];
59
63
  return lines;
60
64
  }
@@ -62,10 +66,15 @@ export function buildOverviewDetails(model = {}) {
62
66
  /**
63
67
  * Pure adapter: buildControlCenterModel → semantic overview props.
64
68
  * Callout / CTA / metrics never include paths or IDs.
69
+ * Primary action is always governance CTA — companion is secondary metrics/details only.
65
70
  */
66
71
  export function adaptControlCenterToOverview(model = {}) {
67
72
  const status = model.status ?? model.health ?? {};
68
73
  const next = model.nextAction ?? model.cta ?? {};
74
+ const companionMetrics = (model.companion?.lines ?? []).map((label, i) => ({
75
+ id: `companion-${i}`,
76
+ label
77
+ }));
69
78
  return {
70
79
  title: model.title ?? "Overview",
71
80
  callout: {
@@ -81,7 +90,8 @@ export function adaptControlCenterToOverview(model = {}) {
81
90
  metrics: [
82
91
  { id: "activity", label: `Activity · ${model.activity?.headline ?? "Idle"}` },
83
92
  { id: "alerts", label: `Alerts · ${model.alerts?.headline ?? "Alert data unavailable"}` },
84
- { id: "tokens", label: `Tokens · ${model.tokens?.headline ?? "Data unavailable"}` }
93
+ { id: "tokens", label: `Tokens · ${model.tokens?.headline ?? "Data unavailable"}` },
94
+ ...companionMetrics
85
95
  ],
86
96
  details: buildOverviewDetails(model)
87
97
  };