@kal-elsam/kairo-runtime 0.10.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.
- package/global-template/components/agent-skills/LICENSE +21 -0
- package/global-template/components/agent-skills/PROVENANCE.md +26 -0
- package/global-template/components/agent-skills/skills/context-engineering/SKILL.md +289 -0
- package/global-template/components/agent-skills/skills/frontend-ui-engineering/SKILL.md +328 -0
- package/global-template/components/agent-skills/skills/observability-and-instrumentation/SKILL.md +203 -0
- package/global-template/components/agent-skills/skills/performance-optimization/SKILL.md +396 -0
- package/global-template/components/agent-skills/skills/source-driven-development/SKILL.md +194 -0
- package/global-template/components/catalog.json +29 -0
- package/package.json +5 -2
- package/scripts/cockpit-smoke.mjs +1 -1
- package/src/cli.js +136 -8
- package/src/global/component-builders.js +3 -1
- package/src/global/components/agent-skills.js +27 -0
- package/src/global/ink/brand/wordmark.js +50 -0
- package/src/global/ink/cockpit/primitives.js +45 -35
- package/src/global/ink/cockpit-control-center.js +104 -4
- package/src/global/ink/cockpit-scan.js +20 -2
- package/src/global/ink/cockpit-views.js +2 -1
- package/src/global/ink/ecosystem-updates-display.js +37 -0
- package/src/global/ink/launch-input.js +32 -1
- package/src/global/ink/orchestrator-app.js +14 -5
- package/src/global/ink/orchestrator-state.js +17 -2
- package/src/global/ink/system-resources-display.js +109 -0
- package/src/global/ink/theme.js +13 -8
- package/src/global/ink/use-orchestrator-data.js +40 -4
- package/src/global/ink/ux/live-activity.js +4 -7
- package/src/global/ink/ux/live-alerts.js +2 -5
- package/src/global/ink/ux/live-governance.js +2 -6
- package/src/global/ink/ux/live-orchestration.js +2 -5
- package/src/global/ink/ux/live-overview.js +88 -28
- package/src/global/ink/ux/live-settings.js +3 -6
- package/src/global/ink/ux/live-usage.js +5 -7
- package/src/global/ink/ux/semantic.js +23 -6
- package/src/global/mcp/kairo-mcp.js +230 -0
- package/src/global/observability/build-companion-snapshot.js +281 -0
- package/src/global/observability/build-observability-snapshot.js +24 -0
- package/src/global/observability/ecosystem-updates.js +224 -0
- package/src/global/observability/gentle-bundle-export.js +71 -0
- package/src/global/observability/gentle-bundle-import.js +122 -0
- package/src/global/observability/gentle-probe.js +155 -0
- package/src/global/observability/graphify-ops.js +133 -0
- package/src/global/observability/graphify-parse-cache.js +90 -0
- package/src/global/observability/graphify-probe.js +185 -0
- package/src/global/observability/hermes-activity.js +163 -0
- package/src/global/observability/hermes-probe.js +171 -0
- package/src/global/observability/index.js +85 -0
- package/src/global/observability/passive-snapshot-flight.js +93 -0
- package/src/global/observability/probe-contract.js +38 -0
- package/src/global/observability/probe-registry.js +30 -0
- package/src/global/observability/resource-advisor.js +71 -0
- package/src/global/observability/system-resources.js +171 -0
- package/src/global/runtime/alerts/alert-cli.js +31 -0
- package/src/global/runtime/alerts/alert-store.js +29 -6
- package/src/global/runtime/alerts/alert-validate.js +25 -1
- package/src/global/runtime/alerts/controlled-alert-actions.js +56 -0
- package/src/global/runtime/execution-adapters/claude.js +2 -1
- package/src/global/runtime/execution-adapters/codex.js +2 -1
- package/src/global/runtime/execution-adapters/create-execution-adapter.js +3 -14
- package/src/global/runtime/execution-adapters/cursor.js +2 -1
- package/src/global/runtime/execution-adapters/opencode.js +2 -1
- package/src/global/runtime/execution-adapters/pi.js +2 -1
- package/src/global/runtime/review/index.js +1 -1
- package/src/global/runtime/review/review-cli.js +113 -3
- package/src/global/runtime/review/review-git.js +142 -11
- package/src/global/runtime/review/review-patch.js +2 -0
- package/src/global/runtime/review/review-receipts.js +12 -7
- package/src/global/runtime/review/review-runner.js +2 -2
- package/src/global/runtime/review/review-types.js +8 -5
- package/src/global/runtime/review/review-validate.js +5 -1
- package/src/global/runtime/run-cli.js +2 -0
- package/src/global/runtime/run-manager.js +39 -18
- package/src/global/runtime/run-permissions.js +231 -0
- package/src/global/runtime/run-profile.js +2 -0
- package/src/global/runtime/run-supervisor.js +77 -37
- package/src/global/runtime/run-types.js +2 -0
- package/src/global/updates-cli.js +41 -0
|
@@ -12,7 +12,7 @@ export function CockpitEmptyState({ title, message, hint, colorEnabled = true })
|
|
|
12
12
|
return React.createElement(Box, { flexDirection: "column", marginY: 1 },
|
|
13
13
|
title && React.createElement(Text, {
|
|
14
14
|
bold: true,
|
|
15
|
-
color: resolveInkColor(colorEnabled, COCKPIT_COLORS.
|
|
15
|
+
color: resolveInkColor(colorEnabled, COCKPIT_COLORS.brand)
|
|
16
16
|
}, title),
|
|
17
17
|
message && React.createElement(Text, null, message),
|
|
18
18
|
hint && React.createElement(Text, {
|
|
@@ -21,23 +21,20 @@ export function CockpitEmptyState({ title, message, hint, colorEnabled = true })
|
|
|
21
21
|
);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/** Content region — no border; focus is conveyed by interactive children. */
|
|
24
25
|
export function CockpitPanel({ title, focused = false, width, children, colorEnabled = true }) {
|
|
25
26
|
return React.createElement(Box, {
|
|
26
27
|
flexDirection: "column",
|
|
27
28
|
width,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
colorEnabled,
|
|
31
|
-
focused ? COCKPIT_COLORS.primary : COCKPIT_COLORS.muted
|
|
32
|
-
),
|
|
33
|
-
paddingX: 1,
|
|
29
|
+
paddingX: 0,
|
|
30
|
+
marginY: 1,
|
|
34
31
|
flexGrow: 1
|
|
35
32
|
},
|
|
36
33
|
title && React.createElement(Text, {
|
|
37
34
|
bold: true,
|
|
38
35
|
color: resolveInkColor(
|
|
39
36
|
colorEnabled,
|
|
40
|
-
focused ? COCKPIT_COLORS.
|
|
37
|
+
focused ? COCKPIT_COLORS.interactive : COCKPIT_COLORS.brand
|
|
41
38
|
)
|
|
42
39
|
}, title),
|
|
43
40
|
children
|
|
@@ -48,7 +45,7 @@ export function CockpitSection({ title, children, colorEnabled = true }) {
|
|
|
48
45
|
return React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
|
|
49
46
|
title && React.createElement(Text, {
|
|
50
47
|
bold: true,
|
|
51
|
-
color: resolveInkColor(colorEnabled, COCKPIT_COLORS.
|
|
48
|
+
color: resolveInkColor(colorEnabled, COCKPIT_COLORS.brand)
|
|
52
49
|
}, title),
|
|
53
50
|
children
|
|
54
51
|
);
|
|
@@ -60,15 +57,29 @@ export function CockpitKeyHint({ keys, label, colorEnabled = true }) {
|
|
|
60
57
|
}, `${keys} ${label}`);
|
|
61
58
|
}
|
|
62
59
|
|
|
60
|
+
/** Compact header: brand · status · project — no box-drawing. */
|
|
63
61
|
export function CockpitTopBar({ model, colorEnabled = true }) {
|
|
62
|
+
const statusKind = String(model.status ?? "").toUpperCase() === "ONLINE"
|
|
63
|
+
? "online"
|
|
64
|
+
: "muted";
|
|
64
65
|
return React.createElement(Box, { justifyContent: "space-between", width: "100%" },
|
|
65
|
-
React.createElement(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
React.createElement(Box, null,
|
|
67
|
+
React.createElement(Text, {
|
|
68
|
+
bold: true,
|
|
69
|
+
color: resolveInkColor(colorEnabled, COCKPIT_COLORS.brand)
|
|
70
|
+
}, model.brand),
|
|
71
|
+
React.createElement(Text, {
|
|
72
|
+
color: resolveInkColor(colorEnabled, COCKPIT_COLORS.muted)
|
|
73
|
+
}, " · "),
|
|
74
|
+
React.createElement(CockpitBadge, {
|
|
75
|
+
label: model.status,
|
|
76
|
+
kind: statusKind,
|
|
77
|
+
colorEnabled
|
|
78
|
+
})
|
|
79
|
+
),
|
|
69
80
|
React.createElement(Text, {
|
|
70
81
|
color: resolveInkColor(colorEnabled, COCKPIT_COLORS.muted)
|
|
71
|
-
},
|
|
82
|
+
}, model.projectLabel)
|
|
72
83
|
);
|
|
73
84
|
}
|
|
74
85
|
|
|
@@ -78,9 +89,9 @@ export function CockpitNav({ model, colorEnabled = true }) {
|
|
|
78
89
|
model.items.map((item) => {
|
|
79
90
|
const suffix = item.current && !item.selected ? " (open)" : "";
|
|
80
91
|
const color = item.focused
|
|
81
|
-
? resolveInkColor(colorEnabled, COCKPIT_COLORS.
|
|
92
|
+
? resolveInkColor(colorEnabled, COCKPIT_COLORS.interactive)
|
|
82
93
|
: item.selected
|
|
83
|
-
? resolveInkColor(colorEnabled, COCKPIT_COLORS.
|
|
94
|
+
? resolveInkColor(colorEnabled, COCKPIT_COLORS.brand)
|
|
84
95
|
: item.current
|
|
85
96
|
? resolveInkColor(colorEnabled, COCKPIT_COLORS.muted)
|
|
86
97
|
: undefined;
|
|
@@ -101,7 +112,7 @@ function stripLabel(item, { compact }) {
|
|
|
101
112
|
return item.label.split(/[&·]/)[0].trim().split(/\s+/)[0];
|
|
102
113
|
}
|
|
103
114
|
|
|
104
|
-
/**
|
|
115
|
+
/** Segmented horizontal nav — no border; active segment uses interactive. */
|
|
105
116
|
export function CockpitNavStrip({
|
|
106
117
|
model,
|
|
107
118
|
colorEnabled = true,
|
|
@@ -112,16 +123,20 @@ export function CockpitNavStrip({
|
|
|
112
123
|
const parts = model.items.map((item) => {
|
|
113
124
|
const label = stripLabel(item, { compact });
|
|
114
125
|
const suffix = item.current && !item.selected ? "*" : "";
|
|
115
|
-
const
|
|
116
|
-
|
|
126
|
+
const active = item.focused || item.selected;
|
|
127
|
+
const color = item.focused || (focused && item.selected)
|
|
128
|
+
? resolveInkColor(colorEnabled, COCKPIT_COLORS.interactive)
|
|
117
129
|
: item.selected
|
|
118
|
-
? resolveInkColor(colorEnabled, COCKPIT_COLORS.
|
|
130
|
+
? resolveInkColor(colorEnabled, COCKPIT_COLORS.interactive)
|
|
119
131
|
: resolveInkColor(colorEnabled, COCKPIT_COLORS.muted);
|
|
132
|
+
const text = focused && item.selected
|
|
133
|
+
? `[${label}]${suffix}`
|
|
134
|
+
: `${item.marker}${label}${suffix}`;
|
|
120
135
|
return React.createElement(Text, {
|
|
121
136
|
key: item.id,
|
|
122
|
-
bold:
|
|
137
|
+
bold: active,
|
|
123
138
|
color
|
|
124
|
-
},
|
|
139
|
+
}, text);
|
|
125
140
|
});
|
|
126
141
|
|
|
127
142
|
const joined = [];
|
|
@@ -138,12 +153,8 @@ export function CockpitNavStrip({
|
|
|
138
153
|
return React.createElement(Box, {
|
|
139
154
|
flexDirection: "column",
|
|
140
155
|
width: "100%",
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
colorEnabled,
|
|
144
|
-
focused ? COCKPIT_COLORS.primary : COCKPIT_COLORS.muted
|
|
145
|
-
),
|
|
146
|
-
paddingX: 1
|
|
156
|
+
marginTop: 0,
|
|
157
|
+
marginBottom: 0
|
|
147
158
|
},
|
|
148
159
|
React.createElement(Box, { flexDirection: "row", flexWrap: "wrap" }, ...joined),
|
|
149
160
|
model.explanation && React.createElement(Text, {
|
|
@@ -170,20 +181,19 @@ export function CockpitSystemStrip({ model, colorEnabled = true }) {
|
|
|
170
181
|
);
|
|
171
182
|
}
|
|
172
183
|
|
|
184
|
+
/** Single-line shortcut bar — no framed box. */
|
|
173
185
|
export function CockpitFooter({ model, columns = 80, colorEnabled = true }) {
|
|
174
186
|
const width = Math.max(24, Math.min(Number(columns) || 80, 120));
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
React.createElement(Text, { color: muted }, `│ ${model.text}`),
|
|
180
|
-
React.createElement(Text, { color: muted }, `╰${"─".repeat(bar)}╯`)
|
|
187
|
+
return React.createElement(Box, { width: "100%", marginTop: 1 },
|
|
188
|
+
React.createElement(Text, {
|
|
189
|
+
color: resolveInkColor(colorEnabled, COCKPIT_COLORS.muted)
|
|
190
|
+
}, String(model.text ?? "").slice(0, width))
|
|
181
191
|
);
|
|
182
192
|
}
|
|
183
193
|
|
|
184
194
|
/**
|
|
185
195
|
* Responsive single-panel shell: TopBar → NavStrip → Main → Footer.
|
|
186
|
-
*
|
|
196
|
+
* No nested borders — hierarchy via type and spacing.
|
|
187
197
|
*/
|
|
188
198
|
export function CockpitShell({
|
|
189
199
|
topBar,
|
|
@@ -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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
/**
|
|
@@ -111,7 +111,8 @@ export function renderCockpitView({
|
|
|
111
111
|
model: controlCenter,
|
|
112
112
|
detailsOpen: overviewDetailsOpen,
|
|
113
113
|
colorEnabled,
|
|
114
|
-
unicode
|
|
114
|
+
unicode,
|
|
115
|
+
layoutMode
|
|
115
116
|
});
|
|
116
117
|
case ORCHESTRATOR_VIEWS.USAGE:
|
|
117
118
|
return React.createElement(SemanticUsagePanel, {
|
|
@@ -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
|
-
|
|
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;
|
|
@@ -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";
|
|
@@ -466,14 +466,22 @@ export function OrchestratorApp({
|
|
|
466
466
|
|
|
467
467
|
if (data.loading) {
|
|
468
468
|
return React.createElement(Box, { flexDirection: "column" },
|
|
469
|
-
React.createElement(Text, {
|
|
470
|
-
|
|
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…")
|
|
471
476
|
);
|
|
472
477
|
}
|
|
473
478
|
|
|
474
479
|
if (data.error) {
|
|
475
480
|
return React.createElement(Box, { flexDirection: "column" },
|
|
476
|
-
React.createElement(Text, {
|
|
481
|
+
React.createElement(Text, {
|
|
482
|
+
bold: true,
|
|
483
|
+
color: resolveInkColor(caps.color, COCKPIT_COLORS.danger)
|
|
484
|
+
}, "Runtime error"),
|
|
477
485
|
React.createElement(Text, null, data.error),
|
|
478
486
|
React.createElement(Text, { dimColor: true },
|
|
479
487
|
data.retrying ? "Retrying read-only scan…" : "R Retry · Esc to exit")
|
|
@@ -494,7 +502,8 @@ export function OrchestratorApp({
|
|
|
494
502
|
snapshot: data.snapshot,
|
|
495
503
|
dashboard: data.dashboard,
|
|
496
504
|
layoutMode: mode,
|
|
497
|
-
alerts: data.alerts
|
|
505
|
+
alerts: data.alerts,
|
|
506
|
+
companion: data.companion
|
|
498
507
|
});
|
|
499
508
|
const systemOnline = data.snapshot
|
|
500
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
|
+
}
|