@kal-elsam/kairo-runtime 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -3
- package/package.json +3 -1
- package/scripts/cockpit-smoke-test.sh +19 -0
- package/scripts/cockpit-smoke.mjs +51 -0
- package/src/cli.js +6 -5
- package/src/global/ink/cockpit/primitives.js +131 -0
- package/src/global/ink/cockpit-controller.js +131 -0
- package/src/global/ink/cockpit-models.js +153 -0
- package/src/global/ink/cockpit-views.js +141 -0
- package/src/global/ink/fullscreen-session.js +128 -0
- package/src/global/ink/launch-input.js +99 -0
- package/src/global/ink/layout.js +34 -0
- package/src/global/ink/list-window.js +38 -0
- package/src/global/ink/orchestrator-app.js +172 -454
- package/src/global/ink/run-orchestrator-ink.js +36 -16
- package/src/global/ink/run-setup-ink.js +38 -17
- package/src/global/ink/setup-app.js +14 -17
- package/src/global/ink/terminal-capabilities.js +50 -0
- package/src/global/ink/terminal.js +27 -5
- package/src/global/ink/theme.js +72 -0
- package/src/global/ink/use-orchestrator-data.js +158 -0
- package/src/global/ink/use-terminal-size.js +39 -0
- package/src/global/orchestrator.js +56 -35
- package/src/global/setup.js +4 -2
package/README.md
CHANGED
|
@@ -33,10 +33,23 @@ kairo
|
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
**First run** (no `~/.harness/state.json`): interactive onboarding → safe diagnosis →
|
|
36
|
-
setup with confirmation → operations
|
|
36
|
+
setup with confirmation → full-screen operations cockpit.
|
|
37
37
|
|
|
38
|
-
**Later runs** (state present):
|
|
39
|
-
|
|
38
|
+
**Later runs** (state present): full-screen cockpit with mission control (recommended
|
|
39
|
+
action), navigation, and a system strip. Layout adapts to terminal size:
|
|
40
|
+
|
|
41
|
+
| Mode | Size | Layout |
|
|
42
|
+
|------|------|--------|
|
|
43
|
+
| Wide | ≥100 cols × ≥28 rows | Nav + content + system |
|
|
44
|
+
| Compact | ≥72×20 | Nav + content |
|
|
45
|
+
| Minimal | 60–71 cols or short height | Single panel + nav header |
|
|
46
|
+
| Below gate | <60 cols | Explicit TTY fallback (Ink disabled) |
|
|
47
|
+
|
|
48
|
+
Keys: `↑↓` navigate · `Tab` region · `Enter` open · `R` refresh · `C` cancel run ·
|
|
49
|
+
`?` help · `Esc` back (exit only from Home).
|
|
50
|
+
|
|
51
|
+
Respects `NO_COLOR`, `HARNESS_ASCII=1`, and `HARNESS_INK=0`. Status is always labeled
|
|
52
|
+
in text, never color alone.
|
|
40
53
|
|
|
41
54
|
Explicit commands and setup flags keep their current behavior (`kairo setup`,
|
|
42
55
|
`kairo --dry-run`, `kairo shell`, non-TTY scripts, etc.).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kal-elsam/kairo-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Kairo Runtime — local agent operating system for Codex, Cursor, Claude, Gemini, Copilot, Engram, and Graphify.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://github.com/Kal-elSam/harness#readme",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"doctor": "node ./bin/kairo.js doctor",
|
|
34
34
|
"smoke": "bash scripts/smoke-test.sh",
|
|
35
35
|
"ux:smoke": "bash scripts/ux-smoke-test.sh",
|
|
36
|
+
"smoke:cockpit": "bash scripts/cockpit-smoke-test.sh",
|
|
36
37
|
"smoke:registry": "bash scripts/registry-smoke-test.sh",
|
|
37
38
|
"smoke:installer": "bash scripts/installer-smoke-test.sh",
|
|
38
39
|
"smoke:bridge": "bash scripts/bridge-smoke-test.sh",
|
|
@@ -59,6 +60,7 @@
|
|
|
59
60
|
},
|
|
60
61
|
"dependencies": {
|
|
61
62
|
"@clack/prompts": "^1.7.0",
|
|
63
|
+
"ansi-escapes": "^7.3.0",
|
|
62
64
|
"ink": "^5.2.1",
|
|
63
65
|
"react": "^18.3.1"
|
|
64
66
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Cockpit smoke — layout/capabilities + package wiring (no PTY required).
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
6
|
+
cd "$ROOT"
|
|
7
|
+
|
|
8
|
+
echo "Kairo cockpit smoke"
|
|
9
|
+
node "$ROOT/scripts/cockpit-smoke.mjs"
|
|
10
|
+
|
|
11
|
+
node --test \
|
|
12
|
+
test/fullscreen-session.test.js \
|
|
13
|
+
test/layout.test.js \
|
|
14
|
+
test/terminal-capabilities.test.js \
|
|
15
|
+
test/cockpit-models.test.js \
|
|
16
|
+
test/cockpit-controller.test.js \
|
|
17
|
+
test/cockpit-frame.test.js
|
|
18
|
+
|
|
19
|
+
echo "Kairo cockpit smoke passed"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { resolveLayoutMode, LAYOUT_MODES } from "../src/global/ink/layout.js";
|
|
7
|
+
import { resolveTerminalCapabilities } from "../src/global/ink/terminal-capabilities.js";
|
|
8
|
+
import { createFullscreenSession } from "../src/global/ink/fullscreen-session.js";
|
|
9
|
+
import { buildTopBarModel, buildHomeMissionModel } from "../src/global/ink/cockpit-models.js";
|
|
10
|
+
|
|
11
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
const pkg = require(join(root, "package.json"));
|
|
14
|
+
|
|
15
|
+
assert.equal(pkg.version, "0.3.0");
|
|
16
|
+
assert.ok(pkg.dependencies["ansi-escapes"]);
|
|
17
|
+
|
|
18
|
+
assert.equal(resolveLayoutMode({ columns: 120, rows: 40 }), LAYOUT_MODES.WIDE);
|
|
19
|
+
assert.equal(resolveLayoutMode({ columns: 80, rows: 24 }), LAYOUT_MODES.COMPACT);
|
|
20
|
+
assert.equal(resolveLayoutMode({ columns: 65, rows: 24 }), LAYOUT_MODES.MINIMAL);
|
|
21
|
+
assert.equal(resolveLayoutMode({ columns: 50, rows: 24 }), null);
|
|
22
|
+
|
|
23
|
+
const caps = resolveTerminalCapabilities({
|
|
24
|
+
columns: 80,
|
|
25
|
+
rows: 24,
|
|
26
|
+
isTTY: true,
|
|
27
|
+
term: "xterm-256color",
|
|
28
|
+
env: { NO_COLOR: "1" }
|
|
29
|
+
});
|
|
30
|
+
assert.equal(caps.color, false);
|
|
31
|
+
assert.equal(caps.canUseInk, true);
|
|
32
|
+
|
|
33
|
+
const session = createFullscreenSession({
|
|
34
|
+
stdout: { isTTY: true, write: () => true },
|
|
35
|
+
processRef: { on() {}, removeListener() {}, exit() {} },
|
|
36
|
+
onSignal: () => {}
|
|
37
|
+
});
|
|
38
|
+
assert.equal(session.enter(), true);
|
|
39
|
+
assert.equal(session.leave(), true);
|
|
40
|
+
assert.equal(session.leave(), false);
|
|
41
|
+
|
|
42
|
+
const top = buildTopBarModel({ projectName: "smoke" });
|
|
43
|
+
assert.match(top.status, /ONLINE|Offline/);
|
|
44
|
+
const mission = buildHomeMissionModel({
|
|
45
|
+
hasGlobalState: false,
|
|
46
|
+
diagnostics: { diagnostics: { detected: 0 } },
|
|
47
|
+
dashboard: { providers: [], recentRuns: [] }
|
|
48
|
+
});
|
|
49
|
+
assert.match(mission.title, /MISSION CONTROL/);
|
|
50
|
+
|
|
51
|
+
console.log("cockpit smoke OK");
|
package/src/cli.js
CHANGED
|
@@ -696,11 +696,11 @@ sections, components, backups, and drift repair under ~/.harness.
|
|
|
696
696
|
Bootstrap: see README.md (curl install.sh or npx ${PACKAGE_NAME}).
|
|
697
697
|
|
|
698
698
|
Usage:
|
|
699
|
-
${cli} First run: onboarding → setup →
|
|
700
|
-
Later:
|
|
699
|
+
${cli} First run: onboarding → setup → cockpit (TTY).
|
|
700
|
+
Later: full-screen cockpit (wide/compact/minimal).
|
|
701
701
|
${cli} --dry-run Setup dry-run (scriptable)
|
|
702
702
|
${cli} --version
|
|
703
|
-
${cli} shell Operations
|
|
703
|
+
${cli} shell Operations cockpit (TTY)
|
|
704
704
|
${cli} run --agent <id> --task "..." [--model <name>] [--cwd <dir>] [--permissions force] [--capture-transcript] [--follow] [--no-wait] [--json]
|
|
705
705
|
${cli} runs list [--json] [--limit <n>] [--active-only]
|
|
706
706
|
${cli} runs show <runId> [--json] [--limit <n>] [--follow]
|
|
@@ -741,8 +741,9 @@ Scopes:
|
|
|
741
741
|
Explicit --scope=workspace only.
|
|
742
742
|
|
|
743
743
|
Commands:
|
|
744
|
-
shell Operations
|
|
745
|
-
is missing, otherwise the
|
|
744
|
+
shell Operations cockpit (TTY). Bare ${cli} opens onboarding when ~/.harness/state.json
|
|
745
|
+
is missing, otherwise the cockpit. Explicit ${cli} shell always opens the cockpit.
|
|
746
|
+
Keys: ↑↓ · Tab region · Enter · R refresh · C cancel · ? help · Esc back/exit.
|
|
746
747
|
run Launch a managed agent run with local audit trail.
|
|
747
748
|
runs List, inspect, or cancel agent runs under ~/.harness/runs/.
|
|
748
749
|
orchestrator Read-only capability registry diagnostics (--json supported).
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { COCKPIT_COLORS, statusColor } from "../theme.js";
|
|
4
|
+
|
|
5
|
+
export function CockpitBadge({ label, kind = "ready", colorEnabled = true }) {
|
|
6
|
+
return React.createElement(Text, {
|
|
7
|
+
color: statusColor(kind, { colorEnabled })
|
|
8
|
+
}, label);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function CockpitEmptyState({ title, message, hint }) {
|
|
12
|
+
return React.createElement(Box, { flexDirection: "column", marginY: 1 },
|
|
13
|
+
title && React.createElement(Text, { bold: true, color: COCKPIT_COLORS.secondary }, title),
|
|
14
|
+
message && React.createElement(Text, null, message),
|
|
15
|
+
hint && React.createElement(Text, { color: COCKPIT_COLORS.muted }, hint)
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function CockpitPanel({ title, focused = false, width, children }) {
|
|
20
|
+
return React.createElement(Box, {
|
|
21
|
+
flexDirection: "column",
|
|
22
|
+
width,
|
|
23
|
+
borderStyle: "single",
|
|
24
|
+
borderColor: focused ? COCKPIT_COLORS.primary : COCKPIT_COLORS.muted,
|
|
25
|
+
paddingX: 1,
|
|
26
|
+
flexGrow: 1
|
|
27
|
+
},
|
|
28
|
+
title && React.createElement(Text, {
|
|
29
|
+
bold: true,
|
|
30
|
+
color: focused ? COCKPIT_COLORS.primary : COCKPIT_COLORS.secondary
|
|
31
|
+
}, title),
|
|
32
|
+
children
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function CockpitTopBar({ model, colorEnabled = true }) {
|
|
37
|
+
return React.createElement(Box, { justifyContent: "space-between", width: "100%" },
|
|
38
|
+
React.createElement(Text, {
|
|
39
|
+
bold: true,
|
|
40
|
+
color: colorEnabled ? COCKPIT_COLORS.primary : undefined
|
|
41
|
+
}, `╭─ ${model.brand} ─ ${model.status}`),
|
|
42
|
+
React.createElement(Text, {
|
|
43
|
+
color: colorEnabled ? COCKPIT_COLORS.muted : undefined
|
|
44
|
+
}, `${model.projectLabel} ─╮`)
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function CockpitNav({ model, colorEnabled = true }) {
|
|
49
|
+
return React.createElement(Box, { flexDirection: "column" },
|
|
50
|
+
model.items.map((item) =>
|
|
51
|
+
React.createElement(Text, {
|
|
52
|
+
key: item.id,
|
|
53
|
+
bold: item.focused || item.selected,
|
|
54
|
+
color: item.focused
|
|
55
|
+
? (colorEnabled ? COCKPIT_COLORS.primary : undefined)
|
|
56
|
+
: item.selected
|
|
57
|
+
? (colorEnabled ? COCKPIT_COLORS.secondary : undefined)
|
|
58
|
+
: undefined
|
|
59
|
+
}, `${item.marker} ${item.label}`)
|
|
60
|
+
)
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function CockpitSystemStrip({ model, colorEnabled = true }) {
|
|
65
|
+
return React.createElement(Box, { flexDirection: "column" },
|
|
66
|
+
model.rows.map((row) =>
|
|
67
|
+
React.createElement(Text, { key: row.key },
|
|
68
|
+
React.createElement(Text, { color: COCKPIT_COLORS.muted }, `${row.key.padEnd(7)}`),
|
|
69
|
+
React.createElement(CockpitBadge, {
|
|
70
|
+
label: row.value,
|
|
71
|
+
kind: row.kind,
|
|
72
|
+
colorEnabled
|
|
73
|
+
})
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function CockpitFooter({ model }) {
|
|
80
|
+
return React.createElement(Box, { flexDirection: "column" },
|
|
81
|
+
React.createElement(Text, { color: COCKPIT_COLORS.muted },
|
|
82
|
+
`├${"─".repeat(62)}┤`
|
|
83
|
+
),
|
|
84
|
+
React.createElement(Text, { color: COCKPIT_COLORS.muted }, `│ ${model.text}`),
|
|
85
|
+
React.createElement(Text, { color: COCKPIT_COLORS.muted },
|
|
86
|
+
`╰${"─".repeat(62)}╯`
|
|
87
|
+
)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function CockpitShell({
|
|
92
|
+
topBar,
|
|
93
|
+
footer,
|
|
94
|
+
layoutMode,
|
|
95
|
+
nav,
|
|
96
|
+
system,
|
|
97
|
+
navFocused,
|
|
98
|
+
contentFocused,
|
|
99
|
+
systemFocused,
|
|
100
|
+
colorEnabled = true,
|
|
101
|
+
children
|
|
102
|
+
}) {
|
|
103
|
+
const showNav = layoutMode === "wide" || layoutMode === "compact";
|
|
104
|
+
const showSystem = layoutMode === "wide";
|
|
105
|
+
|
|
106
|
+
return React.createElement(Box, { flexDirection: "column", width: "100%" },
|
|
107
|
+
React.createElement(CockpitTopBar, { model: topBar, colorEnabled }),
|
|
108
|
+
layoutMode === "minimal" && nav && React.createElement(Box, { marginY: 0 },
|
|
109
|
+
React.createElement(Text, { color: COCKPIT_COLORS.muted }, "Nav: "),
|
|
110
|
+
React.createElement(CockpitNav, { model: nav, colorEnabled })
|
|
111
|
+
),
|
|
112
|
+
React.createElement(Box, { flexDirection: "row", width: "100%" },
|
|
113
|
+
showNav && React.createElement(CockpitPanel, {
|
|
114
|
+
title: nav?.title ?? "NAVIGATION",
|
|
115
|
+
focused: navFocused,
|
|
116
|
+
width: 22
|
|
117
|
+
}, React.createElement(CockpitNav, { model: nav, colorEnabled })),
|
|
118
|
+
React.createElement(CockpitPanel, {
|
|
119
|
+
title: undefined,
|
|
120
|
+
focused: contentFocused,
|
|
121
|
+
width: showSystem ? 48 : showNav ? 56 : "100%"
|
|
122
|
+
}, children),
|
|
123
|
+
showSystem && React.createElement(CockpitPanel, {
|
|
124
|
+
title: system?.title ?? "SYSTEM",
|
|
125
|
+
focused: systemFocused,
|
|
126
|
+
width: 20
|
|
127
|
+
}, React.createElement(CockpitSystemStrip, { model: system, colorEnabled }))
|
|
128
|
+
),
|
|
129
|
+
React.createElement(CockpitFooter, { model: footer })
|
|
130
|
+
);
|
|
131
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { COCKPIT_NAV, COCKPIT_REGIONS, regionsForLayout } from "./cockpit-models.js";
|
|
2
|
+
import { ORCHESTRATOR_VIEWS } from "./orchestrator-state.js";
|
|
3
|
+
import { LAYOUT_MODES } from "./layout.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Pure cockpit focus / navigation reducer — no I/O, no React.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export function createCockpitUiState({
|
|
10
|
+
layoutMode = LAYOUT_MODES.COMPACT,
|
|
11
|
+
view = ORCHESTRATOR_VIEWS.HOME,
|
|
12
|
+
region = COCKPIT_REGIONS.NAV,
|
|
13
|
+
navIndex = 0,
|
|
14
|
+
listIndex = 0,
|
|
15
|
+
helpOpen = false
|
|
16
|
+
} = {}) {
|
|
17
|
+
const regions = regionsForLayout(layoutMode);
|
|
18
|
+
const safeRegion = regions.includes(region) ? region : regions[0];
|
|
19
|
+
return {
|
|
20
|
+
layoutMode,
|
|
21
|
+
view,
|
|
22
|
+
region: safeRegion,
|
|
23
|
+
navIndex: clamp(navIndex, 0, COCKPIT_NAV.length - 1),
|
|
24
|
+
listIndex: Math.max(0, listIndex),
|
|
25
|
+
helpOpen,
|
|
26
|
+
shouldExit: false
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function reduceCockpitUi(state, action) {
|
|
31
|
+
switch (action.type) {
|
|
32
|
+
case "resize": {
|
|
33
|
+
const regions = regionsForLayout(action.layoutMode);
|
|
34
|
+
const region = regions.includes(state.region) ? state.region : regions[0];
|
|
35
|
+
return { ...state, layoutMode: action.layoutMode, region };
|
|
36
|
+
}
|
|
37
|
+
case "tab": {
|
|
38
|
+
const regions = regionsForLayout(state.layoutMode);
|
|
39
|
+
if (regions.length < 2) return state;
|
|
40
|
+
const index = regions.indexOf(state.region);
|
|
41
|
+
const next = regions[(index + 1) % regions.length];
|
|
42
|
+
return { ...state, region: next };
|
|
43
|
+
}
|
|
44
|
+
case "arrow": {
|
|
45
|
+
if (state.helpOpen) return state;
|
|
46
|
+
if (state.region === COCKPIT_REGIONS.SYSTEM) return state;
|
|
47
|
+
|
|
48
|
+
const navigatesNav = state.region === COCKPIT_REGIONS.NAV
|
|
49
|
+
|| (state.view === ORCHESTRATOR_VIEWS.HOME
|
|
50
|
+
&& state.layoutMode === LAYOUT_MODES.MINIMAL);
|
|
51
|
+
|
|
52
|
+
if (navigatesNav) {
|
|
53
|
+
const delta = action.direction === "up" ? -1 : 1;
|
|
54
|
+
return {
|
|
55
|
+
...state,
|
|
56
|
+
navIndex: clamp(state.navIndex + delta, 0, COCKPIT_NAV.length - 1)
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const delta = action.direction === "up" ? -1 : 1;
|
|
61
|
+
const max = Math.max(0, (action.listLength ?? 1) - 1);
|
|
62
|
+
return { ...state, listIndex: clamp(state.listIndex + delta, 0, max) };
|
|
63
|
+
}
|
|
64
|
+
case "enter-nav": {
|
|
65
|
+
const item = COCKPIT_NAV[state.navIndex];
|
|
66
|
+
if (!item) return state;
|
|
67
|
+
if (item.view === ORCHESTRATOR_VIEWS.HOME) {
|
|
68
|
+
return {
|
|
69
|
+
...state,
|
|
70
|
+
view: ORCHESTRATOR_VIEWS.HOME,
|
|
71
|
+
listIndex: 0,
|
|
72
|
+
region: COCKPIT_REGIONS.CONTENT
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
...state,
|
|
77
|
+
view: item.view,
|
|
78
|
+
listIndex: 0,
|
|
79
|
+
region: COCKPIT_REGIONS.CONTENT,
|
|
80
|
+
helpOpen: false
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
case "set-view":
|
|
84
|
+
return {
|
|
85
|
+
...state,
|
|
86
|
+
view: action.view,
|
|
87
|
+
listIndex: 0,
|
|
88
|
+
region: action.region ?? COCKPIT_REGIONS.CONTENT
|
|
89
|
+
};
|
|
90
|
+
case "toggle-help":
|
|
91
|
+
if (state.helpOpen) {
|
|
92
|
+
return {
|
|
93
|
+
...state,
|
|
94
|
+
helpOpen: false,
|
|
95
|
+
view: state.view === ORCHESTRATOR_VIEWS.HELP
|
|
96
|
+
? ORCHESTRATOR_VIEWS.HOME
|
|
97
|
+
: state.view
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return { ...state, helpOpen: true, view: ORCHESTRATOR_VIEWS.HELP };
|
|
101
|
+
case "escape": {
|
|
102
|
+
if (state.helpOpen) {
|
|
103
|
+
return { ...state, helpOpen: false, view: ORCHESTRATOR_VIEWS.HOME };
|
|
104
|
+
}
|
|
105
|
+
if (state.view !== ORCHESTRATOR_VIEWS.HOME) {
|
|
106
|
+
const regions = regionsForLayout(state.layoutMode);
|
|
107
|
+
return {
|
|
108
|
+
...state,
|
|
109
|
+
view: ORCHESTRATOR_VIEWS.HOME,
|
|
110
|
+
listIndex: 0,
|
|
111
|
+
region: regions.includes(COCKPIT_REGIONS.NAV)
|
|
112
|
+
? COCKPIT_REGIONS.NAV
|
|
113
|
+
: COCKPIT_REGIONS.CONTENT
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return { ...state, shouldExit: true };
|
|
117
|
+
}
|
|
118
|
+
case "clear-exit":
|
|
119
|
+
return { ...state, shouldExit: false };
|
|
120
|
+
default:
|
|
121
|
+
return state;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function resolveNavAction(navIndex) {
|
|
126
|
+
return COCKPIT_NAV[navIndex] ?? null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function clamp(value, min, max) {
|
|
130
|
+
return Math.min(max, Math.max(min, value));
|
|
131
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { LAYOUT_MODES } from "./layout.js";
|
|
2
|
+
import { ORCHESTRATOR_VIEWS } from "./orchestrator-state.js";
|
|
3
|
+
import { resolveDashboardRecommendation } from "../dashboard-guidance.js";
|
|
4
|
+
import { STATUS_LABELS, resolveGlyphs } from "./theme.js";
|
|
5
|
+
import { windowList } from "./list-window.js";
|
|
6
|
+
import { resolveListLimit } from "./layout.js";
|
|
7
|
+
|
|
8
|
+
export const COCKPIT_REGIONS = {
|
|
9
|
+
NAV: "nav",
|
|
10
|
+
CONTENT: "content",
|
|
11
|
+
SYSTEM: "system"
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const COCKPIT_NAV = [
|
|
15
|
+
{ id: "overview", label: "Overview", view: ORCHESTRATOR_VIEWS.HOME },
|
|
16
|
+
{ id: "active", label: "Active runs", view: ORCHESTRATOR_VIEWS.ACTIVE_RUNS },
|
|
17
|
+
{ id: "recent", label: "Recent runs", view: ORCHESTRATOR_VIEWS.RECENT_RUNS },
|
|
18
|
+
{ id: "providers", label: "Providers", view: ORCHESTRATOR_VIEWS.PROVIDERS },
|
|
19
|
+
{ id: "launch", label: "Launch run", view: ORCHESTRATOR_VIEWS.LAUNCH, action: "launch" },
|
|
20
|
+
{ id: "diagnostics", label: "Diagnostics", view: ORCHESTRATOR_VIEWS.DIAGNOSTICS }
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
export function regionsForLayout(layoutMode) {
|
|
24
|
+
if (layoutMode === LAYOUT_MODES.WIDE) {
|
|
25
|
+
return [COCKPIT_REGIONS.NAV, COCKPIT_REGIONS.CONTENT, COCKPIT_REGIONS.SYSTEM];
|
|
26
|
+
}
|
|
27
|
+
if (layoutMode === LAYOUT_MODES.COMPACT) {
|
|
28
|
+
return [COCKPIT_REGIONS.NAV, COCKPIT_REGIONS.CONTENT];
|
|
29
|
+
}
|
|
30
|
+
return [COCKPIT_REGIONS.CONTENT];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function buildTopBarModel({
|
|
34
|
+
projectName = "project",
|
|
35
|
+
systemOnline = true,
|
|
36
|
+
unicode = true
|
|
37
|
+
} = {}) {
|
|
38
|
+
const glyphs = resolveGlyphs(unicode);
|
|
39
|
+
const status = systemOnline ? STATUS_LABELS.online : STATUS_LABELS.offline;
|
|
40
|
+
return {
|
|
41
|
+
brand: "KAIRO",
|
|
42
|
+
status,
|
|
43
|
+
statusKind: systemOnline ? "online" : "offline",
|
|
44
|
+
projectLabel: `Project: ${projectName}`,
|
|
45
|
+
separator: glyphs.bullet
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function buildNavModel({
|
|
50
|
+
navIndex = 0,
|
|
51
|
+
focused = false,
|
|
52
|
+
unicode = true,
|
|
53
|
+
items = COCKPIT_NAV
|
|
54
|
+
} = {}) {
|
|
55
|
+
const glyphs = resolveGlyphs(unicode);
|
|
56
|
+
return {
|
|
57
|
+
title: "NAVIGATION",
|
|
58
|
+
items: items.map((item, index) => ({
|
|
59
|
+
...item,
|
|
60
|
+
marker: index === navIndex ? glyphs.focus : " ",
|
|
61
|
+
selected: index === navIndex,
|
|
62
|
+
focused: focused && index === navIndex
|
|
63
|
+
}))
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function buildSystemStripModel({
|
|
68
|
+
dashboard = null,
|
|
69
|
+
diagnostics = null,
|
|
70
|
+
healthKind = "ready"
|
|
71
|
+
} = {}) {
|
|
72
|
+
const agentsDetected = diagnostics?.diagnostics?.detected
|
|
73
|
+
?? (dashboard?.providers ?? []).filter((p) => p.available).length;
|
|
74
|
+
const agentsTotal = diagnostics?.capabilities?.length
|
|
75
|
+
?? dashboard?.providers?.length
|
|
76
|
+
?? 0;
|
|
77
|
+
const activeRuns = dashboard?.activeRuns?.length ?? 0;
|
|
78
|
+
const intelligence = diagnostics?.intelligence?.summary;
|
|
79
|
+
const intelLabel = intelligence?.localAvailable
|
|
80
|
+
? STATUS_LABELS.local
|
|
81
|
+
: intelligence?.cloudAuthenticated
|
|
82
|
+
? "Cloud"
|
|
83
|
+
: "None";
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
title: "SYSTEM",
|
|
87
|
+
rows: [
|
|
88
|
+
{ key: "Agents", value: `${agentsDetected}/${agentsTotal}`, kind: agentsDetected > 0 ? "ready" : "warn" },
|
|
89
|
+
{ key: "Runs", value: String(activeRuns), kind: activeRuns > 0 ? "ready" : "muted" },
|
|
90
|
+
{ key: "Intel", value: intelLabel, kind: intelLabel === "None" ? "warn" : "ready" },
|
|
91
|
+
{ key: "Health", value: STATUS_LABELS[healthKind] ?? healthKind, kind: healthKind }
|
|
92
|
+
]
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function buildHomeMissionModel({
|
|
97
|
+
hasGlobalState = false,
|
|
98
|
+
diagnostics = null,
|
|
99
|
+
dashboard = null,
|
|
100
|
+
layoutMode = LAYOUT_MODES.COMPACT,
|
|
101
|
+
activityLines = []
|
|
102
|
+
} = {}) {
|
|
103
|
+
const recommendation = resolveDashboardRecommendation({
|
|
104
|
+
hasGlobalState,
|
|
105
|
+
diagnostics,
|
|
106
|
+
dashboard
|
|
107
|
+
});
|
|
108
|
+
const limit = resolveListLimit(layoutMode, { contentRows: 10 });
|
|
109
|
+
const windowed = windowList(activityLines, limit);
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
title: "MISSION CONTROL",
|
|
113
|
+
recommendedTitle: "Recommended action",
|
|
114
|
+
recommendedAction: recommendation.message,
|
|
115
|
+
recommendedKind: recommendation.kind,
|
|
116
|
+
activityTitle: activityLines.length ? "Activity" : "Activity / empty state",
|
|
117
|
+
activityLines: windowed.items,
|
|
118
|
+
moreLine: windowed.moreLine,
|
|
119
|
+
emptyHint: activityLines.length === 0
|
|
120
|
+
? "No recent activity. Launch a run when agents are ready."
|
|
121
|
+
: null
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function buildFooterModel({
|
|
126
|
+
view = ORCHESTRATOR_VIEWS.HOME,
|
|
127
|
+
region = COCKPIT_REGIONS.NAV,
|
|
128
|
+
helpOpen = false,
|
|
129
|
+
canCancel = false,
|
|
130
|
+
unicode = true
|
|
131
|
+
} = {}) {
|
|
132
|
+
const glyphs = resolveGlyphs(unicode);
|
|
133
|
+
if (helpOpen) {
|
|
134
|
+
return {
|
|
135
|
+
text: `Esc close help ${glyphs.bullet} R refresh ${glyphs.bullet} C cancel run`
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (view === ORCHESTRATOR_VIEWS.HOME) {
|
|
139
|
+
return {
|
|
140
|
+
text: `↑↓ Navigate ${glyphs.bullet} Tab Region ${glyphs.bullet} Enter Open ${glyphs.bullet} ? Help ${glyphs.bullet} Esc Exit`
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const cancel = canCancel ? ` ${glyphs.bullet} C cancel` : "";
|
|
144
|
+
return {
|
|
145
|
+
text: `↑↓ Navigate ${glyphs.bullet} Enter Open ${glyphs.bullet} R refresh${cancel} ${glyphs.bullet} Esc Back · focus:${region}`
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function resolveProjectName(workspaceRoot = "") {
|
|
150
|
+
if (!workspaceRoot) return "project";
|
|
151
|
+
const parts = String(workspaceRoot).split(/[/\\]/).filter(Boolean);
|
|
152
|
+
return parts[parts.length - 1] || "project";
|
|
153
|
+
}
|