@kal-elsam/kairo-runtime 0.3.1 → 0.4.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 +11 -8
- package/package.json +1 -1
- package/scripts/cockpit-smoke.mjs +32 -6
- package/src/global/dashboard-guidance.js +146 -17
- package/src/global/ink/cockpit/primitives.js +16 -9
- package/src/global/ink/cockpit-controller.js +3 -0
- package/src/global/ink/cockpit-home.js +115 -0
- package/src/global/ink/cockpit-models.js +123 -48
- package/src/global/ink/cockpit-views.js +92 -40
- package/src/global/ink/orchestrator-app.js +72 -18
- package/src/global/ink/orchestrator-state.js +71 -14
- package/src/global/ink/theme.js +7 -1
package/README.md
CHANGED
|
@@ -35,19 +35,22 @@ kairo
|
|
|
35
35
|
**First run** (no `~/.harness/state.json`): interactive onboarding → safe diagnosis →
|
|
36
36
|
setup with confirmation → full-screen operations cockpit.
|
|
37
37
|
|
|
38
|
-
**Later runs** (state present): full-screen cockpit
|
|
39
|
-
|
|
38
|
+
**Later runs** (state present): full-screen cockpit Home that explains what Kairo
|
|
39
|
+
does, shows real readiness, and recommends a useful next action. Layout adapts to
|
|
40
|
+
terminal size:
|
|
40
41
|
|
|
41
42
|
| Mode | Size | Layout |
|
|
42
43
|
|------|------|--------|
|
|
43
|
-
| Wide | ≥100 cols × ≥28 rows | Nav + content + system |
|
|
44
|
-
| Compact | ≥72×20 | Nav + content |
|
|
45
|
-
| Minimal | 60–71 cols or short height |
|
|
44
|
+
| Wide | ≥100 cols × ≥28 rows | Nav + Home/content + system |
|
|
45
|
+
| Compact | ≥72×20 | Nav + Home/content (readiness embedded) |
|
|
46
|
+
| Minimal | 60–71 cols or short height | Selected section + essential readiness/next/recent |
|
|
46
47
|
| Below gate | <60 cols | Explicit TTY fallback (Ink disabled) |
|
|
47
48
|
|
|
48
|
-
Keys: `↑↓` navigate
|
|
49
|
-
`R` refresh · `C` cancel run · `?` help.
|
|
50
|
-
is interactive (run lists /
|
|
49
|
+
Keys: `↑↓` navigate (selection explanation updates) · `Enter` open ·
|
|
50
|
+
`Esc` back (exit only from Home) · `R` refresh/retry · `C` cancel run · `?` help.
|
|
51
|
+
`Tab` switches region only when content is interactive (run lists / New run).
|
|
52
|
+
|
|
53
|
+
Navigation labels: Home · Running now · History · Agents · New run · System health.
|
|
51
54
|
|
|
52
55
|
Respects `NO_COLOR`, `HARNESS_ASCII=1`, and `HARNESS_INK=0`. Status is always labeled
|
|
53
56
|
in text, never color alone.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kal-elsam/kairo-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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",
|
|
@@ -9,6 +9,7 @@ import { createFullscreenSession } from "../src/global/ink/fullscreen-session.js
|
|
|
9
9
|
import {
|
|
10
10
|
buildFooterModel,
|
|
11
11
|
buildHomeMissionModel,
|
|
12
|
+
buildNavModel,
|
|
12
13
|
buildTopBarModel,
|
|
13
14
|
COCKPIT_NAV,
|
|
14
15
|
COCKPIT_REGIONS
|
|
@@ -24,7 +25,7 @@ const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
|
24
25
|
const require = createRequire(import.meta.url);
|
|
25
26
|
const pkg = require(join(root, "package.json"));
|
|
26
27
|
|
|
27
|
-
assert.equal(pkg.version, "0.
|
|
28
|
+
assert.equal(pkg.version, "0.4.0");
|
|
28
29
|
assert.ok(pkg.dependencies["ansi-escapes"]);
|
|
29
30
|
|
|
30
31
|
assert.equal(resolveLayoutMode({ columns: 120, rows: 40 }), LAYOUT_MODES.WIDE);
|
|
@@ -51,14 +52,39 @@ assert.equal(session.enter(), true);
|
|
|
51
52
|
assert.equal(session.leave(), true);
|
|
52
53
|
assert.equal(session.leave(), false);
|
|
53
54
|
|
|
54
|
-
const top = buildTopBarModel({ projectName: "smoke" });
|
|
55
|
+
const top = buildTopBarModel({ projectName: "smoke", unicode: false });
|
|
55
56
|
assert.match(top.status, /ONLINE|Offline/);
|
|
56
57
|
const mission = buildHomeMissionModel({
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
projectName: "smoke",
|
|
59
|
+
hasGlobalState: true,
|
|
60
|
+
diagnostics: {
|
|
61
|
+
diagnostics: { detected: 2, errors: 0 },
|
|
62
|
+
intelligence: { summary: { localAvailable: false, cloudAuthenticated: false } },
|
|
63
|
+
recommendations: []
|
|
64
|
+
},
|
|
65
|
+
dashboard: {
|
|
66
|
+
providers: [{ launchable: true }],
|
|
67
|
+
recentRuns: [{ runId: "r1", agentId: "codex", state: "failed" }]
|
|
68
|
+
},
|
|
69
|
+
layoutMode: LAYOUT_MODES.MINIMAL
|
|
60
70
|
});
|
|
61
|
-
assert.match(mission.title, /
|
|
71
|
+
assert.match(mission.title, /HOME — smoke/);
|
|
72
|
+
assert.match(mission.readiness.headline, /LIMITED|READY|NEEDS/i);
|
|
73
|
+
assert.equal(mission.next.targetAction, "launch");
|
|
74
|
+
assert.match(mission.recent.headline, /Codex · Failed/);
|
|
75
|
+
|
|
76
|
+
const asciiNav = buildNavModel({
|
|
77
|
+
navIndex: 1,
|
|
78
|
+
currentView: ORCHESTRATOR_VIEWS.HOME,
|
|
79
|
+
focused: true,
|
|
80
|
+
unicode: false,
|
|
81
|
+
dashboard: { activeRuns: [], recentRuns: [], providers: [{ launchable: true }] },
|
|
82
|
+
diagnostics: { diagnostics: { detected: 1, errors: 0 } }
|
|
83
|
+
});
|
|
84
|
+
assert.equal(asciiNav.items[1].marker, ">");
|
|
85
|
+
assert.equal(asciiNav.items[0].current, true);
|
|
86
|
+
assert.equal(asciiNav.items[1].selected, true);
|
|
87
|
+
assert.match(asciiNav.explanation, /Supervised runs|Running/i);
|
|
62
88
|
|
|
63
89
|
function applyKey(state, keyAction) {
|
|
64
90
|
const routed = routeCockpitKey(state, keyAction);
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { formatCliCommand } from "./brand/cli.js";
|
|
2
2
|
|
|
3
|
+
/** Public cockpit destinations for recommended actions (match ORCHESTRATOR_VIEWS). */
|
|
4
|
+
export const RECOMMENDATION_TARGETS = {
|
|
5
|
+
DIAGNOSTICS: "diagnostics",
|
|
6
|
+
LAUNCH: "launch",
|
|
7
|
+
RECENT_RUNS: "recent-runs",
|
|
8
|
+
PROVIDERS: "providers"
|
|
9
|
+
};
|
|
10
|
+
|
|
3
11
|
export const DASHBOARD_PURPOSE =
|
|
4
|
-
"
|
|
12
|
+
"Kairo coordinates installed AI agents for this project, with controlled execution and auditable results.";
|
|
5
13
|
|
|
6
14
|
export const NEXT_STEP_KINDS = {
|
|
7
15
|
CONFIGURE: "configure",
|
|
@@ -10,13 +18,117 @@ export const NEXT_STEP_KINDS = {
|
|
|
10
18
|
REVIEW: "review"
|
|
11
19
|
};
|
|
12
20
|
|
|
21
|
+
export const READINESS_KINDS = {
|
|
22
|
+
NEEDS_SETUP: "needs_setup",
|
|
23
|
+
NEEDS_ATTENTION: "needs_attention",
|
|
24
|
+
LIMITED: "limited",
|
|
25
|
+
READY: "ready"
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const READINESS_LABELS = {
|
|
29
|
+
[READINESS_KINDS.NEEDS_SETUP]: "Needs setup",
|
|
30
|
+
[READINESS_KINDS.NEEDS_ATTENTION]: "Needs attention",
|
|
31
|
+
[READINESS_KINDS.LIMITED]: "Limited",
|
|
32
|
+
[READINESS_KINDS.READY]: "Ready to work"
|
|
33
|
+
};
|
|
34
|
+
|
|
13
35
|
export function formatDashboardPurpose() {
|
|
14
36
|
return DASHBOARD_PURPOSE;
|
|
15
37
|
}
|
|
16
38
|
|
|
39
|
+
export function countLaunchableAgents(dashboard = null) {
|
|
40
|
+
return (dashboard?.providers ?? []).filter((entry) => entry.launchable).length;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function hasIntelligenceConfigured(diagnostics = null) {
|
|
44
|
+
const summary = diagnostics?.intelligence?.summary;
|
|
45
|
+
return Boolean(summary?.localAvailable || summary?.cloudAuthenticated);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function hasBlockingDiagnostics(diagnostics = null) {
|
|
49
|
+
const summary = diagnostics?.diagnostics ?? { errors: 0 };
|
|
50
|
+
const hasErrors = (summary.errors ?? 0) > 0;
|
|
51
|
+
const hasProblemRecommendation = (diagnostics?.recommendations ?? []).some((line) =>
|
|
52
|
+
/error|fix|drift|not detected|failed|problem/i.test(line)
|
|
53
|
+
);
|
|
54
|
+
return hasErrors || hasProblemRecommendation;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Derive project readiness from diagnostics + dashboard.
|
|
59
|
+
* Intelligence absence is Limited (optional), never Needs attention by itself.
|
|
60
|
+
*/
|
|
61
|
+
export function resolveProjectReadiness({
|
|
62
|
+
hasGlobalState = false,
|
|
63
|
+
diagnostics = null,
|
|
64
|
+
dashboard = null
|
|
65
|
+
} = {}) {
|
|
66
|
+
const summary = diagnostics?.diagnostics ?? { detected: 0, errors: 0 };
|
|
67
|
+
const launchableCount = countLaunchableAgents(dashboard);
|
|
68
|
+
const detected = summary.detected ?? 0;
|
|
69
|
+
const agentsReady = launchableCount;
|
|
70
|
+
const activeRuns = dashboard?.activeRuns?.length ?? 0;
|
|
71
|
+
const intelConfigured = hasIntelligenceConfigured(diagnostics);
|
|
72
|
+
const summaryLine = `${agentsReady} agents ready · ${detected} detected · ${activeRuns} active runs`;
|
|
73
|
+
|
|
74
|
+
if (!hasGlobalState || detected === 0) {
|
|
75
|
+
return {
|
|
76
|
+
kind: READINESS_KINDS.NEEDS_SETUP,
|
|
77
|
+
label: READINESS_LABELS[READINESS_KINDS.NEEDS_SETUP],
|
|
78
|
+
headline: "NEEDS SETUP",
|
|
79
|
+
summaryLine,
|
|
80
|
+
capabilityLines: [
|
|
81
|
+
"Intelligence: Optional capability not configured"
|
|
82
|
+
],
|
|
83
|
+
healthKind: "warn"
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (hasBlockingDiagnostics(diagnostics) || launchableCount === 0) {
|
|
88
|
+
return {
|
|
89
|
+
kind: READINESS_KINDS.NEEDS_ATTENTION,
|
|
90
|
+
label: READINESS_LABELS[READINESS_KINDS.NEEDS_ATTENTION],
|
|
91
|
+
headline: "NEEDS ATTENTION",
|
|
92
|
+
summaryLine,
|
|
93
|
+
capabilityLines: [
|
|
94
|
+
intelConfigured
|
|
95
|
+
? "Intelligence: Configured"
|
|
96
|
+
: "Intelligence: Optional capability not configured"
|
|
97
|
+
],
|
|
98
|
+
healthKind: "error"
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!intelConfigured) {
|
|
103
|
+
return {
|
|
104
|
+
kind: READINESS_KINDS.LIMITED,
|
|
105
|
+
label: READINESS_LABELS[READINESS_KINDS.LIMITED],
|
|
106
|
+
headline: "LIMITED",
|
|
107
|
+
summaryLine,
|
|
108
|
+
capabilityLines: [
|
|
109
|
+
"Intelligence: Optional capability not configured"
|
|
110
|
+
],
|
|
111
|
+
healthKind: "warn"
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
kind: READINESS_KINDS.READY,
|
|
117
|
+
label: READINESS_LABELS[READINESS_KINDS.READY],
|
|
118
|
+
headline: "READY TO WORK",
|
|
119
|
+
summaryLine,
|
|
120
|
+
capabilityLines: [
|
|
121
|
+
"Intelligence: Configured"
|
|
122
|
+
],
|
|
123
|
+
healthKind: "ready"
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
17
127
|
/**
|
|
18
128
|
* Contextual next step from existing diagnostics + dashboard snapshot.
|
|
19
|
-
* Priority: configure → review
|
|
129
|
+
* Priority: configure → review when blocked → launch when launchable →
|
|
130
|
+
* enable intelligence → review.
|
|
131
|
+
* Launchable agents win over missing optional intelligence.
|
|
20
132
|
*/
|
|
21
133
|
export function resolveDashboardRecommendation({
|
|
22
134
|
hasGlobalState = false,
|
|
@@ -25,42 +137,59 @@ export function resolveDashboardRecommendation({
|
|
|
25
137
|
} = {}) {
|
|
26
138
|
const summary = diagnostics?.diagnostics ?? { detected: 0, errors: 0 };
|
|
27
139
|
const intelligence = diagnostics?.intelligence?.summary;
|
|
28
|
-
const launchableCount = (dashboard
|
|
29
|
-
const
|
|
30
|
-
const hasProblemRecommendation = (diagnostics?.recommendations ?? []).some((line) =>
|
|
31
|
-
/error|fix|drift|not detected|failed|problem/i.test(line)
|
|
32
|
-
);
|
|
140
|
+
const launchableCount = countLaunchableAgents(dashboard);
|
|
141
|
+
const blocked = hasBlockingDiagnostics(diagnostics);
|
|
33
142
|
|
|
34
143
|
if (!hasGlobalState || (summary.detected ?? 0) === 0) {
|
|
35
144
|
return {
|
|
36
145
|
kind: NEXT_STEP_KINDS.CONFIGURE,
|
|
37
|
-
|
|
146
|
+
title: "Finish local setup",
|
|
147
|
+
message: `Configure the local environment with ${formatCliCommand("setup")}.`,
|
|
148
|
+
detail: "Open System health to review what Kairo detected.",
|
|
149
|
+
targetView: RECOMMENDATION_TARGETS.DIAGNOSTICS,
|
|
150
|
+
targetAction: null
|
|
38
151
|
};
|
|
39
152
|
}
|
|
40
153
|
|
|
41
|
-
if (
|
|
154
|
+
if (blocked && launchableCount === 0) {
|
|
42
155
|
return {
|
|
43
156
|
kind: NEXT_STEP_KINDS.REVIEW,
|
|
44
|
-
|
|
157
|
+
title: "Review system health",
|
|
158
|
+
message: "Review System health for problems before launching a run.",
|
|
159
|
+
detail: "Fix agent or configuration issues, then try again.",
|
|
160
|
+
targetView: RECOMMENDATION_TARGETS.DIAGNOSTICS,
|
|
161
|
+
targetAction: null
|
|
45
162
|
};
|
|
46
163
|
}
|
|
47
164
|
|
|
48
|
-
if (
|
|
165
|
+
if (launchableCount > 0) {
|
|
49
166
|
return {
|
|
50
|
-
kind: NEXT_STEP_KINDS.
|
|
51
|
-
|
|
167
|
+
kind: NEXT_STEP_KINDS.LAUNCH,
|
|
168
|
+
title: "Create a new run",
|
|
169
|
+
message: "Create a new run",
|
|
170
|
+
detail: "Delegate a task to Cursor, Codex or Claude.",
|
|
171
|
+
targetView: RECOMMENDATION_TARGETS.LAUNCH,
|
|
172
|
+
targetAction: "launch"
|
|
52
173
|
};
|
|
53
174
|
}
|
|
54
175
|
|
|
55
|
-
if (
|
|
176
|
+
if (!intelligence?.localAvailable && !intelligence?.cloudAuthenticated) {
|
|
56
177
|
return {
|
|
57
|
-
kind: NEXT_STEP_KINDS.
|
|
58
|
-
|
|
178
|
+
kind: NEXT_STEP_KINDS.ENABLE_INTELLIGENCE,
|
|
179
|
+
title: "Enable optional intelligence",
|
|
180
|
+
message: "Enable intelligence: start Ollama or set OPENROUTER_API_KEY, then retry.",
|
|
181
|
+
detail: "Agents can still run without this optional capability.",
|
|
182
|
+
targetView: RECOMMENDATION_TARGETS.DIAGNOSTICS,
|
|
183
|
+
targetAction: null
|
|
59
184
|
};
|
|
60
185
|
}
|
|
61
186
|
|
|
62
187
|
return {
|
|
63
188
|
kind: NEXT_STEP_KINDS.REVIEW,
|
|
64
|
-
|
|
189
|
+
title: "Review system health",
|
|
190
|
+
message: "Review System health for problems before launching a run.",
|
|
191
|
+
detail: "Open System health to inspect agents and configuration.",
|
|
192
|
+
targetView: RECOMMENDATION_TARGETS.DIAGNOSTICS,
|
|
193
|
+
targetAction: null
|
|
65
194
|
};
|
|
66
195
|
}
|
|
@@ -47,17 +47,24 @@ export function CockpitTopBar({ model, colorEnabled = true }) {
|
|
|
47
47
|
|
|
48
48
|
export function CockpitNav({ model, colorEnabled = true }) {
|
|
49
49
|
return React.createElement(Box, { flexDirection: "column" },
|
|
50
|
-
model.items.map((item) =>
|
|
51
|
-
|
|
50
|
+
model.items.map((item) => {
|
|
51
|
+
const suffix = item.current && !item.selected ? " (open)" : "";
|
|
52
|
+
const color = item.focused
|
|
53
|
+
? (colorEnabled ? COCKPIT_COLORS.primary : undefined)
|
|
54
|
+
: item.selected
|
|
55
|
+
? (colorEnabled ? COCKPIT_COLORS.secondary : undefined)
|
|
56
|
+
: item.current
|
|
57
|
+
? (colorEnabled ? COCKPIT_COLORS.muted : undefined)
|
|
58
|
+
: undefined;
|
|
59
|
+
return React.createElement(Text, {
|
|
52
60
|
key: item.id,
|
|
53
61
|
bold: item.focused || item.selected,
|
|
54
|
-
color
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
)
|
|
62
|
+
color
|
|
63
|
+
}, `${item.marker} ${item.label}${suffix}`);
|
|
64
|
+
}),
|
|
65
|
+
model.explanation && React.createElement(Text, {
|
|
66
|
+
color: COCKPIT_COLORS.muted
|
|
67
|
+
}, model.explanation)
|
|
61
68
|
);
|
|
62
69
|
}
|
|
63
70
|
|
|
@@ -108,6 +108,9 @@ export function reduceCockpitUi(state, action) {
|
|
|
108
108
|
...state,
|
|
109
109
|
view: nextView,
|
|
110
110
|
listIndex: 0,
|
|
111
|
+
navIndex: action.navIndex == null
|
|
112
|
+
? state.navIndex
|
|
113
|
+
: clamp(action.navIndex, 0, COCKPIT_NAV.length - 1),
|
|
111
114
|
region: action.region ?? defaultRegionForView(nextView, state.layoutMode),
|
|
112
115
|
returnView: action.returnView ?? state.returnView,
|
|
113
116
|
helpOpen: nextView === ORCHESTRATOR_VIEWS.HELP
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { ORCHESTRATOR_VIEWS } from "./orchestrator-state.js";
|
|
2
|
+
import {
|
|
3
|
+
DASHBOARD_PURPOSE,
|
|
4
|
+
resolveDashboardRecommendation,
|
|
5
|
+
resolveProjectReadiness
|
|
6
|
+
} from "../dashboard-guidance.js";
|
|
7
|
+
import { LAYOUT_MODES } from "./layout.js";
|
|
8
|
+
|
|
9
|
+
export function formatHomeRecentRun(run) {
|
|
10
|
+
if (!run) return null;
|
|
11
|
+
const agent = humanizeId(run.agentId);
|
|
12
|
+
const result = humanizeRunState(run.state);
|
|
13
|
+
return {
|
|
14
|
+
headline: `Last run · ${agent} · ${result}`,
|
|
15
|
+
hint: "Open History to inspect the result."
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function buildHomeMissionModel({
|
|
20
|
+
projectName = "project",
|
|
21
|
+
hasGlobalState = false,
|
|
22
|
+
diagnostics = null,
|
|
23
|
+
dashboard = null,
|
|
24
|
+
layoutMode = LAYOUT_MODES.COMPACT,
|
|
25
|
+
recentRuns = null
|
|
26
|
+
} = {}) {
|
|
27
|
+
const readiness = resolveProjectReadiness({ hasGlobalState, diagnostics, dashboard });
|
|
28
|
+
const recommendation = resolveDashboardRecommendation({
|
|
29
|
+
hasGlobalState,
|
|
30
|
+
diagnostics,
|
|
31
|
+
dashboard
|
|
32
|
+
});
|
|
33
|
+
const runs = recentRuns ?? dashboard?.recentRuns ?? [];
|
|
34
|
+
const lastRun = formatHomeRecentRun(runs[0] ?? null);
|
|
35
|
+
const includeEmbeddedStatus = layoutMode !== LAYOUT_MODES.WIDE;
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
title: `HOME — ${projectName}`,
|
|
39
|
+
purpose: DASHBOARD_PURPOSE,
|
|
40
|
+
readiness,
|
|
41
|
+
includeEmbeddedStatus,
|
|
42
|
+
next: {
|
|
43
|
+
title: "NEXT",
|
|
44
|
+
actionTitle: recommendation.title,
|
|
45
|
+
actionDetail: recommendation.detail ?? recommendation.message,
|
|
46
|
+
enterHint: "Enter →",
|
|
47
|
+
kind: recommendation.kind,
|
|
48
|
+
targetView: recommendation.targetView,
|
|
49
|
+
targetAction: recommendation.targetAction,
|
|
50
|
+
message: recommendation.message
|
|
51
|
+
},
|
|
52
|
+
recent: lastRun
|
|
53
|
+
? {
|
|
54
|
+
title: "RECENT",
|
|
55
|
+
headline: lastRun.headline,
|
|
56
|
+
hint: lastRun.hint,
|
|
57
|
+
emptyHint: null
|
|
58
|
+
}
|
|
59
|
+
: {
|
|
60
|
+
title: "RECENT",
|
|
61
|
+
headline: null,
|
|
62
|
+
hint: null,
|
|
63
|
+
emptyHint: "No runs yet. Create a new run when an agent is ready."
|
|
64
|
+
},
|
|
65
|
+
explore: {
|
|
66
|
+
title: "EXPLORE",
|
|
67
|
+
lines: [
|
|
68
|
+
"Agents — See which installed tools Kairo can launch and audit.",
|
|
69
|
+
"System health — Inspect intelligence, authentication, and configuration."
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
recommendedTitle: recommendation.title,
|
|
73
|
+
recommendedAction: recommendation.message,
|
|
74
|
+
recommendedKind: recommendation.kind,
|
|
75
|
+
recommendedTargetView: recommendation.targetView,
|
|
76
|
+
recommendedTargetAction: recommendation.targetAction,
|
|
77
|
+
emptyHint: lastRun ? null : "No runs yet. Create a new run when an agent is ready."
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function openRecommendedDestination(recommendation, { navItems } = {}) {
|
|
82
|
+
if (!recommendation?.targetView) return null;
|
|
83
|
+
const index = (navItems ?? []).findIndex((item) => item.view === recommendation.targetView
|
|
84
|
+
|| (recommendation.targetAction && item.action === recommendation.targetAction));
|
|
85
|
+
return {
|
|
86
|
+
view: recommendation.targetView,
|
|
87
|
+
action: recommendation.targetAction ?? null,
|
|
88
|
+
navIndex: index >= 0 ? index : null
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function humanizeId(value) {
|
|
93
|
+
const raw = String(value ?? "agent");
|
|
94
|
+
return raw.charAt(0).toUpperCase() + raw.slice(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function humanizeRunState(state) {
|
|
98
|
+
switch (state) {
|
|
99
|
+
case "succeeded":
|
|
100
|
+
case "completed":
|
|
101
|
+
return "Succeeded";
|
|
102
|
+
case "failed":
|
|
103
|
+
return "Failed";
|
|
104
|
+
case "cancelled":
|
|
105
|
+
case "canceled":
|
|
106
|
+
return "Cancelled";
|
|
107
|
+
case "running":
|
|
108
|
+
case "starting":
|
|
109
|
+
return "Running";
|
|
110
|
+
default:
|
|
111
|
+
return humanizeId(state);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export { ORCHESTRATOR_VIEWS };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { LAYOUT_MODES } from "./layout.js";
|
|
1
|
+
import { LAYOUT_MODES, resolveListLimit } from "./layout.js";
|
|
2
2
|
import { ORCHESTRATOR_VIEWS } from "./orchestrator-state.js";
|
|
3
|
-
import {
|
|
3
|
+
import { resolveProjectReadiness } from "../dashboard-guidance.js";
|
|
4
4
|
import { STATUS_LABELS, resolveGlyphs } from "./theme.js";
|
|
5
5
|
import { windowList } from "./list-window.js";
|
|
6
|
-
import {
|
|
6
|
+
import { buildHomeMissionModel, formatHomeRecentRun } from "./cockpit-home.js";
|
|
7
7
|
|
|
8
8
|
export const COCKPIT_REGIONS = {
|
|
9
9
|
NAV: "nav",
|
|
@@ -12,12 +12,43 @@ export const COCKPIT_REGIONS = {
|
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
export const COCKPIT_NAV = [
|
|
15
|
-
{
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
{
|
|
16
|
+
id: "overview",
|
|
17
|
+
label: "Home",
|
|
18
|
+
view: ORCHESTRATOR_VIEWS.HOME,
|
|
19
|
+
description: "What Kairo does here, readiness, and the next useful action."
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "active",
|
|
23
|
+
label: "Running now",
|
|
24
|
+
view: ORCHESTRATOR_VIEWS.ACTIVE_RUNS,
|
|
25
|
+
description: "Supervised runs executing right now."
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "recent",
|
|
29
|
+
label: "History",
|
|
30
|
+
view: ORCHESTRATOR_VIEWS.RECENT_RUNS,
|
|
31
|
+
description: "Past runs with agent, state, and readable result."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "providers",
|
|
35
|
+
label: "Agents",
|
|
36
|
+
view: ORCHESTRATOR_VIEWS.PROVIDERS,
|
|
37
|
+
description: "Installed tools Kairo can launch and audit."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "launch",
|
|
41
|
+
label: "New run",
|
|
42
|
+
view: ORCHESTRATOR_VIEWS.LAUNCH,
|
|
43
|
+
action: "launch",
|
|
44
|
+
description: "Delegate a task to an executable agent."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "diagnostics",
|
|
48
|
+
label: "System health",
|
|
49
|
+
view: ORCHESTRATOR_VIEWS.DIAGNOSTICS,
|
|
50
|
+
description: "Agents, intelligence, authentication, and configuration."
|
|
51
|
+
}
|
|
21
52
|
];
|
|
22
53
|
|
|
23
54
|
export function regionsForLayout(layoutMode) {
|
|
@@ -30,6 +61,11 @@ export function regionsForLayout(layoutMode) {
|
|
|
30
61
|
return [COCKPIT_REGIONS.CONTENT];
|
|
31
62
|
}
|
|
32
63
|
|
|
64
|
+
export function navIndexForView(view, items = COCKPIT_NAV) {
|
|
65
|
+
const index = items.findIndex((item) => item.view === view);
|
|
66
|
+
return index >= 0 ? index : 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
33
69
|
export function buildTopBarModel({
|
|
34
70
|
projectName = "project",
|
|
35
71
|
systemOnline = true,
|
|
@@ -46,28 +82,73 @@ export function buildTopBarModel({
|
|
|
46
82
|
};
|
|
47
83
|
}
|
|
48
84
|
|
|
85
|
+
export function resolveNavStatusSummary(item, { dashboard = null, diagnostics = null } = {}) {
|
|
86
|
+
const active = dashboard?.activeRuns?.length ?? 0;
|
|
87
|
+
const recent = dashboard?.recentRuns?.length ?? 0;
|
|
88
|
+
const providers = dashboard?.providers ?? [];
|
|
89
|
+
const launchable = providers.filter((entry) => entry.launchable).length;
|
|
90
|
+
const detected = diagnostics?.diagnostics?.detected ?? providers.filter((p) => p.available).length;
|
|
91
|
+
const errors = diagnostics?.diagnostics?.errors ?? 0;
|
|
92
|
+
|
|
93
|
+
switch (item.id) {
|
|
94
|
+
case "overview":
|
|
95
|
+
return resolveProjectReadiness({
|
|
96
|
+
hasGlobalState: true,
|
|
97
|
+
diagnostics,
|
|
98
|
+
dashboard
|
|
99
|
+
}).label;
|
|
100
|
+
case "active":
|
|
101
|
+
return active === 0 ? "Idle" : `${active} active`;
|
|
102
|
+
case "recent":
|
|
103
|
+
return recent === 0 ? "No history" : `${recent} recent`;
|
|
104
|
+
case "providers":
|
|
105
|
+
return `${launchable}/${providers.length || detected} ready`;
|
|
106
|
+
case "launch":
|
|
107
|
+
return launchable > 0 ? "Ready" : "Unavailable";
|
|
108
|
+
case "diagnostics":
|
|
109
|
+
return errors > 0 ? `${errors} issues` : "Checked";
|
|
110
|
+
default:
|
|
111
|
+
return "";
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
49
115
|
export function buildNavModel({
|
|
50
116
|
navIndex = 0,
|
|
117
|
+
currentView = ORCHESTRATOR_VIEWS.HOME,
|
|
51
118
|
focused = false,
|
|
52
119
|
unicode = true,
|
|
53
|
-
items = COCKPIT_NAV
|
|
120
|
+
items = COCKPIT_NAV,
|
|
121
|
+
dashboard = null,
|
|
122
|
+
diagnostics = null
|
|
54
123
|
} = {}) {
|
|
55
124
|
const glyphs = resolveGlyphs(unicode);
|
|
125
|
+
const selected = items[navIndex] ?? items[0];
|
|
126
|
+
const mapped = items.map((item, index) => {
|
|
127
|
+
const isSelected = index === navIndex;
|
|
128
|
+
const isCurrent = item.view === currentView;
|
|
129
|
+
return {
|
|
130
|
+
...item,
|
|
131
|
+
marker: isSelected ? glyphs.focus : (isCurrent ? glyphs.bullet : " "),
|
|
132
|
+
selected: isSelected,
|
|
133
|
+
current: isCurrent,
|
|
134
|
+
focused: focused && isSelected,
|
|
135
|
+
statusSummary: resolveNavStatusSummary(item, { dashboard, diagnostics })
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
|
|
56
139
|
return {
|
|
57
140
|
title: "NAVIGATION",
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
focused: focused && index === navIndex
|
|
63
|
-
}))
|
|
141
|
+
explanation: selected
|
|
142
|
+
? `${selected.description} (${resolveNavStatusSummary(selected, { dashboard, diagnostics })})`
|
|
143
|
+
: "",
|
|
144
|
+
items: mapped
|
|
64
145
|
};
|
|
65
146
|
}
|
|
66
147
|
|
|
67
148
|
export function buildSystemStripModel({
|
|
68
149
|
dashboard = null,
|
|
69
150
|
diagnostics = null,
|
|
70
|
-
|
|
151
|
+
readiness = null
|
|
71
152
|
} = {}) {
|
|
72
153
|
const agentsDetected = diagnostics?.diagnostics?.detected
|
|
73
154
|
?? (dashboard?.providers ?? []).filter((p) => p.available).length;
|
|
@@ -81,6 +162,11 @@ export function buildSystemStripModel({
|
|
|
81
162
|
: intelligence?.cloudAuthenticated
|
|
82
163
|
? "Cloud"
|
|
83
164
|
: "None";
|
|
165
|
+
const resolved = readiness ?? resolveProjectReadiness({
|
|
166
|
+
hasGlobalState: agentsDetected > 0,
|
|
167
|
+
diagnostics,
|
|
168
|
+
dashboard
|
|
169
|
+
});
|
|
84
170
|
|
|
85
171
|
return {
|
|
86
172
|
title: "SYSTEM",
|
|
@@ -88,50 +174,32 @@ export function buildSystemStripModel({
|
|
|
88
174
|
{ key: "Agents", value: `${agentsDetected}/${agentsTotal}`, kind: agentsDetected > 0 ? "ready" : "warn" },
|
|
89
175
|
{ key: "Runs", value: String(activeRuns), kind: activeRuns > 0 ? "ready" : "muted" },
|
|
90
176
|
{ key: "Intel", value: intelLabel, kind: intelLabel === "None" ? "warn" : "ready" },
|
|
91
|
-
{
|
|
177
|
+
{
|
|
178
|
+
key: "Health",
|
|
179
|
+
value: STATUS_LABELS[resolved.kind] ?? resolved.label ?? resolved.kind,
|
|
180
|
+
kind: resolved.healthKind ?? "warn"
|
|
181
|
+
}
|
|
92
182
|
]
|
|
93
183
|
};
|
|
94
184
|
}
|
|
95
185
|
|
|
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
186
|
export function buildFooterModel({
|
|
126
187
|
view = ORCHESTRATOR_VIEWS.HOME,
|
|
127
188
|
region = COCKPIT_REGIONS.NAV,
|
|
128
189
|
helpOpen = false,
|
|
129
190
|
canCancel = false,
|
|
130
|
-
unicode = true
|
|
191
|
+
unicode = true,
|
|
192
|
+
hasError = false
|
|
131
193
|
} = {}) {
|
|
132
194
|
const glyphs = resolveGlyphs(unicode);
|
|
133
195
|
const parts = [];
|
|
134
196
|
|
|
197
|
+
if (hasError) {
|
|
198
|
+
parts.push("R Retry");
|
|
199
|
+
parts.push("Esc Exit");
|
|
200
|
+
return { text: parts.join(` ${glyphs.bullet} `) };
|
|
201
|
+
}
|
|
202
|
+
|
|
135
203
|
if (helpOpen || view === ORCHESTRATOR_VIEWS.HELP) {
|
|
136
204
|
parts.push("Esc close help");
|
|
137
205
|
parts.push("? Help");
|
|
@@ -173,8 +241,15 @@ export function buildFooterModel({
|
|
|
173
241
|
return { text: parts.join(` ${glyphs.bullet} `) };
|
|
174
242
|
}
|
|
175
243
|
|
|
244
|
+
export function windowLinesForLayout(lines = [], layoutMode = LAYOUT_MODES.COMPACT, contentRows = 12) {
|
|
245
|
+
const limit = resolveListLimit(layoutMode, { contentRows });
|
|
246
|
+
return windowList(lines, limit);
|
|
247
|
+
}
|
|
248
|
+
|
|
176
249
|
export function resolveProjectName(workspaceRoot = "") {
|
|
177
250
|
if (!workspaceRoot) return "project";
|
|
178
251
|
const parts = String(workspaceRoot).split(/[/\\]/).filter(Boolean);
|
|
179
252
|
return parts[parts.length - 1] || "project";
|
|
180
253
|
}
|
|
254
|
+
|
|
255
|
+
export { buildHomeMissionModel, formatHomeRecentRun };
|
|
@@ -3,38 +3,60 @@ import { Box, Text } from "ink";
|
|
|
3
3
|
import { COCKPIT_COLORS } from "./theme.js";
|
|
4
4
|
import { CockpitEmptyState } from "./cockpit/primitives.js";
|
|
5
5
|
import {
|
|
6
|
-
formatDiagnosticsLines,
|
|
7
|
-
formatLaunchWizardLines,
|
|
8
6
|
formatProviderLines,
|
|
9
7
|
formatRunDetailLines,
|
|
10
8
|
formatRunLines,
|
|
9
|
+
formatSystemHealthLines,
|
|
10
|
+
formatLaunchWizardLines,
|
|
11
11
|
ORCHESTRATOR_VIEWS,
|
|
12
12
|
LAUNCH_WIZARD_STEPS
|
|
13
13
|
} from "./orchestrator-state.js";
|
|
14
|
+
import { windowLinesForLayout } from "./cockpit-models.js";
|
|
15
|
+
import { LAYOUT_MODES } from "./layout.js";
|
|
14
16
|
|
|
15
17
|
export function HomeMissionPanel({ model, colorEnabled = true }) {
|
|
18
|
+
const readiness = model.readiness;
|
|
16
19
|
return React.createElement(Box, { flexDirection: "column" },
|
|
17
20
|
React.createElement(Text, {
|
|
18
21
|
bold: true,
|
|
19
22
|
color: colorEnabled ? COCKPIT_COLORS.secondary : undefined
|
|
20
23
|
}, model.title),
|
|
21
|
-
React.createElement(Text,
|
|
24
|
+
React.createElement(Text, null, model.purpose),
|
|
25
|
+
React.createElement(Text, null, ""),
|
|
26
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
27
|
+
React.createElement(Text, {
|
|
28
|
+
bold: true,
|
|
29
|
+
color: colorEnabled ? COCKPIT_COLORS.primary : undefined
|
|
30
|
+
}, readiness.headline),
|
|
31
|
+
React.createElement(Text, null, readiness.summaryLine),
|
|
32
|
+
...(readiness.capabilityLines ?? []).map((line) =>
|
|
33
|
+
React.createElement(Text, { key: line, color: COCKPIT_COLORS.muted }, line)
|
|
34
|
+
),
|
|
35
|
+
React.createElement(Text, null, "")
|
|
36
|
+
),
|
|
37
|
+
React.createElement(Text, { bold: true }, model.next.title),
|
|
22
38
|
React.createElement(Text, {
|
|
39
|
+
bold: true,
|
|
23
40
|
color: colorEnabled ? COCKPIT_COLORS.primary : undefined
|
|
24
|
-
}, model.
|
|
41
|
+
}, model.next.actionTitle),
|
|
42
|
+
React.createElement(Text, null, model.next.actionDetail),
|
|
43
|
+
React.createElement(Text, { color: COCKPIT_COLORS.muted }, model.next.enterHint),
|
|
25
44
|
React.createElement(Text, null, ""),
|
|
26
|
-
React.createElement(Text, { bold: true }, model.
|
|
27
|
-
model.emptyHint
|
|
45
|
+
React.createElement(Text, { bold: true }, model.recent.title),
|
|
46
|
+
model.recent.emptyHint
|
|
28
47
|
? React.createElement(CockpitEmptyState, {
|
|
29
|
-
message: model.emptyHint,
|
|
30
|
-
hint: "Enter
|
|
48
|
+
message: model.recent.emptyHint,
|
|
49
|
+
hint: "Enter opens the recommended next step."
|
|
31
50
|
})
|
|
32
|
-
:
|
|
33
|
-
React.createElement(Text,
|
|
51
|
+
: React.createElement(Box, { flexDirection: "column" },
|
|
52
|
+
React.createElement(Text, null, model.recent.headline),
|
|
53
|
+
React.createElement(Text, { color: COCKPIT_COLORS.muted }, model.recent.hint)
|
|
34
54
|
),
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
55
|
+
React.createElement(Text, null, ""),
|
|
56
|
+
React.createElement(Text, { bold: true }, model.explore.title),
|
|
57
|
+
model.explore.lines.map((line) =>
|
|
58
|
+
React.createElement(Text, { key: line, color: COCKPIT_COLORS.muted }, line)
|
|
59
|
+
)
|
|
38
60
|
);
|
|
39
61
|
}
|
|
40
62
|
|
|
@@ -51,6 +73,7 @@ export function renderCockpitView({
|
|
|
51
73
|
selectedRun,
|
|
52
74
|
selectedEvents,
|
|
53
75
|
homeMission,
|
|
76
|
+
layoutMode = LAYOUT_MODES.COMPACT,
|
|
54
77
|
colorEnabled = true
|
|
55
78
|
}) {
|
|
56
79
|
switch (view) {
|
|
@@ -58,34 +81,52 @@ export function renderCockpitView({
|
|
|
58
81
|
return React.createElement(HomeMissionPanel, { model: homeMission, colorEnabled });
|
|
59
82
|
case ORCHESTRATOR_VIEWS.ACTIVE_RUNS:
|
|
60
83
|
return listBlock(
|
|
61
|
-
"
|
|
62
|
-
formatRunLines(dashboard?.activeRuns ?? [], {
|
|
84
|
+
"Running now",
|
|
85
|
+
formatRunLines(dashboard?.activeRuns ?? [], {
|
|
86
|
+
emptyMessage: "No runs are executing. Open New run to start one.",
|
|
87
|
+
readable: true
|
|
88
|
+
}),
|
|
63
89
|
listIndex,
|
|
64
|
-
colorEnabled
|
|
90
|
+
colorEnabled,
|
|
91
|
+
"Nothing is running yet — that means Kairo is idle, not broken."
|
|
65
92
|
);
|
|
66
93
|
case ORCHESTRATOR_VIEWS.RECENT_RUNS:
|
|
67
94
|
return listBlock(
|
|
68
|
-
"
|
|
69
|
-
formatRunLines(dashboard?.recentRuns ?? [], {
|
|
95
|
+
"History",
|
|
96
|
+
formatRunLines(dashboard?.recentRuns ?? [], {
|
|
97
|
+
emptyMessage: "No completed runs yet. Create a new run to build history.",
|
|
98
|
+
readable: true
|
|
99
|
+
}),
|
|
70
100
|
listIndex,
|
|
71
|
-
colorEnabled
|
|
101
|
+
colorEnabled,
|
|
102
|
+
"History fills after supervised runs finish."
|
|
72
103
|
);
|
|
73
|
-
case ORCHESTRATOR_VIEWS.PROVIDERS:
|
|
104
|
+
case ORCHESTRATOR_VIEWS.PROVIDERS: {
|
|
105
|
+
const lines = formatProviderLines(dashboard?.providers ?? []);
|
|
106
|
+
const windowed = windowLinesForLayout(lines, layoutMode);
|
|
74
107
|
return React.createElement(Box, { flexDirection: "column" },
|
|
75
|
-
React.createElement(Text, { bold: true }, "
|
|
76
|
-
|
|
77
|
-
|
|
108
|
+
React.createElement(Text, { bold: true }, "Agents"),
|
|
109
|
+
lines.length === 0
|
|
110
|
+
? React.createElement(CockpitEmptyState, {
|
|
111
|
+
message: "No agents detected yet.",
|
|
112
|
+
hint: "Open System health or finish setup, then return here."
|
|
113
|
+
})
|
|
114
|
+
: windowed.items.map((line) => React.createElement(Text, { key: line }, line)),
|
|
115
|
+
windowed.moreLine && React.createElement(Text, {
|
|
116
|
+
color: COCKPIT_COLORS.muted
|
|
117
|
+
}, windowed.moreLine)
|
|
78
118
|
);
|
|
119
|
+
}
|
|
79
120
|
case ORCHESTRATOR_VIEWS.LAUNCH:
|
|
80
121
|
if (launchableAgents.length === 0) {
|
|
81
122
|
return React.createElement(CockpitEmptyState, {
|
|
82
|
-
title: "
|
|
83
|
-
message: "No
|
|
84
|
-
hint: "Esc to return ·
|
|
123
|
+
title: "New run",
|
|
124
|
+
message: "No executable agents are ready. Kairo cannot start a run yet.",
|
|
125
|
+
hint: "Esc to return · open System health or Agents"
|
|
85
126
|
});
|
|
86
127
|
}
|
|
87
128
|
return React.createElement(Box, { flexDirection: "column" },
|
|
88
|
-
React.createElement(Text, { bold: true }, "
|
|
129
|
+
React.createElement(Text, { bold: true }, "New run"),
|
|
89
130
|
formatLaunchWizardLines({
|
|
90
131
|
step: launchStep,
|
|
91
132
|
draft: launchDraft,
|
|
@@ -105,18 +146,23 @@ export function renderCockpitView({
|
|
|
105
146
|
formatRunDetailLines(selectedRun, selectedEvents)
|
|
106
147
|
.map((line) => React.createElement(Text, { key: line }, line))
|
|
107
148
|
);
|
|
108
|
-
case ORCHESTRATOR_VIEWS.DIAGNOSTICS:
|
|
149
|
+
case ORCHESTRATOR_VIEWS.DIAGNOSTICS: {
|
|
150
|
+
const lines = formatSystemHealthLines(diagnostics);
|
|
151
|
+
const windowed = windowLinesForLayout(lines, layoutMode);
|
|
109
152
|
return React.createElement(Box, { flexDirection: "column" },
|
|
110
|
-
React.createElement(Text, { bold: true }, "
|
|
111
|
-
|
|
112
|
-
|
|
153
|
+
React.createElement(Text, { bold: true }, "System health"),
|
|
154
|
+
windowed.items.map((line) => React.createElement(Text, { key: line }, line)),
|
|
155
|
+
windowed.moreLine && React.createElement(Text, {
|
|
156
|
+
color: COCKPIT_COLORS.muted
|
|
157
|
+
}, windowed.moreLine)
|
|
113
158
|
);
|
|
159
|
+
}
|
|
114
160
|
case ORCHESTRATOR_VIEWS.HELP:
|
|
115
161
|
return React.createElement(Box, { flexDirection: "column" },
|
|
116
162
|
React.createElement(Text, { bold: true }, "Help"),
|
|
117
|
-
React.createElement(Text, null, "Kairo
|
|
118
|
-
React.createElement(Text, null, "↑↓ navigate ·
|
|
119
|
-
React.createElement(Text, null, "R refresh · C cancel
|
|
163
|
+
React.createElement(Text, null, "Kairo coordinates installed AI agents with controlled, auditable runs."),
|
|
164
|
+
React.createElement(Text, null, "↑↓ navigate · Enter open recommended or selected section · Esc back/exit"),
|
|
165
|
+
React.createElement(Text, null, "Tab region (lists / New run) · R refresh/retry · C cancel · ? help"),
|
|
120
166
|
React.createElement(Text, null, "CLI: kairo run --agent <id> --task \"...\""),
|
|
121
167
|
React.createElement(Text, null, "Audit trail: ~/.harness/runs/<runId>/")
|
|
122
168
|
);
|
|
@@ -127,14 +173,20 @@ export function renderCockpitView({
|
|
|
127
173
|
}
|
|
128
174
|
}
|
|
129
175
|
|
|
130
|
-
function listBlock(title, lines, listIndex, colorEnabled) {
|
|
176
|
+
function listBlock(title, lines, listIndex, colorEnabled, emptyHint) {
|
|
177
|
+
const isEmpty = lines.length === 1 && /no |nothing |empty/i.test(lines[0]);
|
|
131
178
|
return React.createElement(Box, { flexDirection: "column" },
|
|
132
179
|
React.createElement(Text, { bold: true }, title),
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
180
|
+
isEmpty
|
|
181
|
+
? React.createElement(CockpitEmptyState, {
|
|
182
|
+
message: lines[0],
|
|
183
|
+
hint: emptyHint
|
|
184
|
+
})
|
|
185
|
+
: lines.map((line, index) => React.createElement(Text, {
|
|
186
|
+
key: `${index}-${line}`,
|
|
187
|
+
bold: index === listIndex,
|
|
188
|
+
color: index === listIndex && colorEnabled ? COCKPIT_COLORS.primary : undefined
|
|
189
|
+
}, `${index === listIndex ? "› " : " "}${line}`))
|
|
138
190
|
);
|
|
139
191
|
}
|
|
140
192
|
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
isContentInteractiveView
|
|
14
14
|
} from "./cockpit-controller.js";
|
|
15
15
|
import {
|
|
16
|
+
COCKPIT_NAV,
|
|
16
17
|
COCKPIT_REGIONS,
|
|
17
18
|
buildFooterModel,
|
|
18
19
|
buildHomeMissionModel,
|
|
@@ -21,6 +22,8 @@ import {
|
|
|
21
22
|
buildTopBarModel,
|
|
22
23
|
resolveProjectName
|
|
23
24
|
} from "./cockpit-models.js";
|
|
25
|
+
import { openRecommendedDestination } from "./cockpit-home.js";
|
|
26
|
+
import { resolveProjectReadiness } from "../dashboard-guidance.js";
|
|
24
27
|
import { CockpitShell } from "./cockpit/primitives.js";
|
|
25
28
|
import { renderCockpitView } from "./cockpit-views.js";
|
|
26
29
|
import { handleLaunchInput } from "./launch-input.js";
|
|
@@ -66,11 +69,40 @@ export function OrchestratorApp({
|
|
|
66
69
|
exit();
|
|
67
70
|
};
|
|
68
71
|
|
|
72
|
+
const openDestination = (recommendation) => {
|
|
73
|
+
const destination = openRecommendedDestination(recommendation, { navItems: COCKPIT_NAV });
|
|
74
|
+
if (!destination) return false;
|
|
75
|
+
if (destination.action === "launch") {
|
|
76
|
+
data.resetLaunchWizard();
|
|
77
|
+
}
|
|
78
|
+
dispatch({
|
|
79
|
+
type: "set-view",
|
|
80
|
+
view: destination.view,
|
|
81
|
+
navIndex: destination.navIndex ?? undefined
|
|
82
|
+
});
|
|
83
|
+
return true;
|
|
84
|
+
};
|
|
85
|
+
|
|
69
86
|
useInput((inputKey, key) => {
|
|
70
|
-
if (data.loading
|
|
87
|
+
if (data.loading) return;
|
|
88
|
+
|
|
89
|
+
if (data.error) {
|
|
90
|
+
if (key.escape) {
|
|
91
|
+
finish({ cancelled: true });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (inputKey.toLowerCase() === "r") {
|
|
95
|
+
data.setError(null);
|
|
96
|
+
data.reload().catch((reloadError) => {
|
|
97
|
+
data.setError(reloadError instanceof Error ? reloadError.message : String(reloadError));
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (data.busy) return;
|
|
71
104
|
|
|
72
105
|
if (key.escape) {
|
|
73
|
-
// Launch wizard may retreat a step before leaving the view.
|
|
74
106
|
if (ui.view === ORCHESTRATOR_VIEWS.LAUNCH && data.launchableAgents.length > 0) {
|
|
75
107
|
const retreated = handleLaunchInput({
|
|
76
108
|
key,
|
|
@@ -130,9 +162,23 @@ export function OrchestratorApp({
|
|
|
130
162
|
if (routed) {
|
|
131
163
|
if (routed.type === "enter-nav") {
|
|
132
164
|
const item = resolveNavAction(ui.navIndex);
|
|
165
|
+
if (item?.id === "overview" || item?.view === ORCHESTRATOR_VIEWS.HOME) {
|
|
166
|
+
const mission = buildHomeMissionModel({
|
|
167
|
+
projectName: resolveProjectName(workspaceRoot),
|
|
168
|
+
hasGlobalState,
|
|
169
|
+
diagnostics: data.diagnostics,
|
|
170
|
+
dashboard: data.dashboard,
|
|
171
|
+
layoutMode: ui.layoutMode ?? LAYOUT_MODES.COMPACT
|
|
172
|
+
});
|
|
173
|
+
if (openDestination(mission.next)) return;
|
|
174
|
+
}
|
|
133
175
|
if (item?.action === "launch") {
|
|
134
176
|
data.resetLaunchWizard();
|
|
135
|
-
dispatch({
|
|
177
|
+
dispatch({
|
|
178
|
+
type: "set-view",
|
|
179
|
+
view: ORCHESTRATOR_VIEWS.LAUNCH,
|
|
180
|
+
navIndex: ui.navIndex
|
|
181
|
+
});
|
|
136
182
|
return;
|
|
137
183
|
}
|
|
138
184
|
}
|
|
@@ -140,7 +186,6 @@ export function OrchestratorApp({
|
|
|
140
186
|
return;
|
|
141
187
|
}
|
|
142
188
|
|
|
143
|
-
// View-specific handlers after centralized region routing.
|
|
144
189
|
if (ui.view === ORCHESTRATOR_VIEWS.LAUNCH && data.launchableAgents.length > 0) {
|
|
145
190
|
if (handleLaunchInput({
|
|
146
191
|
key,
|
|
@@ -201,13 +246,26 @@ export function OrchestratorApp({
|
|
|
201
246
|
return React.createElement(Box, { flexDirection: "column" },
|
|
202
247
|
React.createElement(Text, { bold: true, color: COCKPIT_COLORS.danger }, "Runtime error"),
|
|
203
248
|
React.createElement(Text, null, data.error),
|
|
204
|
-
React.createElement(Text, { dimColor: true }, "Esc to exit")
|
|
249
|
+
React.createElement(Text, { dimColor: true }, "R Retry · Esc to exit")
|
|
205
250
|
);
|
|
206
251
|
}
|
|
207
252
|
|
|
208
253
|
const mode = ui.layoutMode ?? LAYOUT_MODES.COMPACT;
|
|
209
254
|
const colorEnabled = caps.color;
|
|
210
255
|
const unicode = caps.unicode;
|
|
256
|
+
const projectName = resolveProjectName(workspaceRoot);
|
|
257
|
+
const readiness = resolveProjectReadiness({
|
|
258
|
+
hasGlobalState,
|
|
259
|
+
diagnostics: data.diagnostics,
|
|
260
|
+
dashboard: data.dashboard
|
|
261
|
+
});
|
|
262
|
+
const homeMission = buildHomeMissionModel({
|
|
263
|
+
projectName,
|
|
264
|
+
hasGlobalState,
|
|
265
|
+
diagnostics: data.diagnostics,
|
|
266
|
+
dashboard: data.dashboard,
|
|
267
|
+
layoutMode: mode
|
|
268
|
+
});
|
|
211
269
|
|
|
212
270
|
return React.createElement(Box, { flexDirection: "column" },
|
|
213
271
|
data.statusMessage && React.createElement(Text, {
|
|
@@ -215,8 +273,8 @@ export function OrchestratorApp({
|
|
|
215
273
|
}, data.statusMessage),
|
|
216
274
|
React.createElement(CockpitShell, {
|
|
217
275
|
topBar: buildTopBarModel({
|
|
218
|
-
projectName
|
|
219
|
-
systemOnline:
|
|
276
|
+
projectName,
|
|
277
|
+
systemOnline: readiness.kind !== "needs_setup",
|
|
220
278
|
unicode
|
|
221
279
|
}),
|
|
222
280
|
footer: buildFooterModel({
|
|
@@ -229,13 +287,16 @@ export function OrchestratorApp({
|
|
|
229
287
|
layoutMode: mode,
|
|
230
288
|
nav: buildNavModel({
|
|
231
289
|
navIndex: ui.navIndex,
|
|
290
|
+
currentView: ui.view,
|
|
232
291
|
focused: ui.region === COCKPIT_REGIONS.NAV || !isContentInteractiveView(ui.view),
|
|
233
|
-
unicode
|
|
292
|
+
unicode,
|
|
293
|
+
dashboard: data.dashboard,
|
|
294
|
+
diagnostics: data.diagnostics
|
|
234
295
|
}),
|
|
235
296
|
system: buildSystemStripModel({
|
|
236
297
|
dashboard: data.dashboard,
|
|
237
298
|
diagnostics: data.diagnostics,
|
|
238
|
-
|
|
299
|
+
readiness
|
|
239
300
|
}),
|
|
240
301
|
navFocused: ui.region === COCKPIT_REGIONS.NAV,
|
|
241
302
|
contentFocused: ui.region === COCKPIT_REGIONS.CONTENT,
|
|
@@ -254,15 +315,8 @@ export function OrchestratorApp({
|
|
|
254
315
|
launchableAgents: data.launchableAgents,
|
|
255
316
|
selectedRun: data.selectedRun,
|
|
256
317
|
selectedEvents: data.selectedEvents,
|
|
257
|
-
homeMission
|
|
258
|
-
|
|
259
|
-
diagnostics: data.diagnostics,
|
|
260
|
-
dashboard: data.dashboard,
|
|
261
|
-
layoutMode: mode,
|
|
262
|
-
activityLines: (data.dashboard?.recentRuns ?? []).map((run) =>
|
|
263
|
-
`${run.runId} ${run.state} ${run.agentId}`
|
|
264
|
-
)
|
|
265
|
-
}),
|
|
318
|
+
homeMission,
|
|
319
|
+
layoutMode: mode,
|
|
266
320
|
colorEnabled
|
|
267
321
|
})
|
|
268
322
|
)
|
|
@@ -23,11 +23,11 @@ export const ORCHESTRATOR_VIEWS = {
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
export const ORCHESTRATOR_MENU = [
|
|
26
|
-
{ id: "active", label: "
|
|
27
|
-
{ id: "recent", label: "
|
|
28
|
-
{ id: "providers", label: "
|
|
29
|
-
{ id: "launch", label: "
|
|
30
|
-
{ id: "diagnostics", label: "
|
|
26
|
+
{ id: "active", label: "Running now", view: ORCHESTRATOR_VIEWS.ACTIVE_RUNS },
|
|
27
|
+
{ id: "recent", label: "History", view: ORCHESTRATOR_VIEWS.RECENT_RUNS },
|
|
28
|
+
{ id: "providers", label: "Agents", view: ORCHESTRATOR_VIEWS.PROVIDERS },
|
|
29
|
+
{ id: "launch", label: "New run", view: ORCHESTRATOR_VIEWS.LAUNCH, action: "launch" },
|
|
30
|
+
{ id: "diagnostics", label: "System health", view: ORCHESTRATOR_VIEWS.DIAGNOSTICS },
|
|
31
31
|
{ id: "help", label: "Help", view: ORCHESTRATOR_VIEWS.HELP }
|
|
32
32
|
];
|
|
33
33
|
|
|
@@ -157,13 +157,22 @@ export function shiftMenuIndex(currentIndex, direction, menuLength = ORCHESTRATO
|
|
|
157
157
|
return Math.min(menuLength - 1, Math.max(0, currentIndex + delta));
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
export function formatRunLines(runs, {
|
|
160
|
+
export function formatRunLines(runs, {
|
|
161
|
+
emptyMessage = "No runs.",
|
|
162
|
+
readable = false
|
|
163
|
+
} = {}) {
|
|
161
164
|
if (!runs || runs.length === 0) return [emptyMessage];
|
|
162
165
|
|
|
163
166
|
return runs.map((run) => {
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
+
const agent = humanizeToken(run.agentId);
|
|
168
|
+
const state = humanizeRunState(run.state);
|
|
169
|
+
const task = formatTaskLabel(run);
|
|
170
|
+
if (readable) {
|
|
171
|
+
return `${agent} · ${state} · ${task}`;
|
|
172
|
+
}
|
|
173
|
+
const statePad = run.state.padEnd(12);
|
|
174
|
+
const agentPad = run.agentId.padEnd(10);
|
|
175
|
+
return `${run.runId} ${statePad} ${agentPad} ${task}`;
|
|
167
176
|
});
|
|
168
177
|
}
|
|
169
178
|
|
|
@@ -181,17 +190,42 @@ export function formatProviderLines(providers) {
|
|
|
181
190
|
}
|
|
182
191
|
|
|
183
192
|
export function formatDiagnosticsLines(diagnostics) {
|
|
193
|
+
return formatSystemHealthLines(diagnostics);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function formatSystemHealthLines(diagnostics) {
|
|
184
197
|
const summary = diagnostics?.diagnostics;
|
|
198
|
+
const intelligence = diagnostics?.intelligence?.summary;
|
|
199
|
+
const profile = diagnostics?.profile;
|
|
200
|
+
const capabilities = diagnostics?.capabilities ?? [];
|
|
201
|
+
const authKnown = capabilities.filter((entry) => entry.authenticated != null);
|
|
202
|
+
const authReady = authKnown.filter((entry) => entry.authenticated).length;
|
|
203
|
+
|
|
185
204
|
const lines = [
|
|
186
|
-
"
|
|
187
|
-
`
|
|
188
|
-
`Agents detected: ${summary?.detected ?? 0}/${diagnostics?.capabilities?.length ?? 0}`,
|
|
205
|
+
"Agents",
|
|
206
|
+
`Detected: ${summary?.detected ?? 0}/${capabilities.length}`,
|
|
189
207
|
`Available: ${summary?.available ?? 0}`,
|
|
190
208
|
`Unknown: ${summary?.unknown ?? 0}`,
|
|
191
209
|
`Errors: ${summary?.errors ?? 0}`,
|
|
210
|
+
...formatAgentStatusLines(capabilities),
|
|
211
|
+
"",
|
|
212
|
+
"Intelligence",
|
|
213
|
+
intelligence?.localAvailable
|
|
214
|
+
? "Local backend available"
|
|
215
|
+
: "Local backend unavailable",
|
|
216
|
+
intelligence?.cloudAuthenticated
|
|
217
|
+
? "Cloud backend authenticated"
|
|
218
|
+
: "Cloud backend not configured",
|
|
219
|
+
`Routing: ${diagnostics?.intelligence?.routingPreview?.reason ?? "n/a"}`,
|
|
220
|
+
"",
|
|
221
|
+
"Authentication",
|
|
222
|
+
authKnown.length === 0
|
|
223
|
+
? "No agent authentication signals yet"
|
|
224
|
+
: `${authReady}/${authKnown.length} agents report ready auth`,
|
|
192
225
|
"",
|
|
193
|
-
"
|
|
194
|
-
|
|
226
|
+
"Configuration",
|
|
227
|
+
`CLI version: ${diagnostics?.cliVersion ?? "unknown"}`,
|
|
228
|
+
`Profile sources: ${(profile?.sources ?? []).join(", ") || "none"}`
|
|
195
229
|
];
|
|
196
230
|
|
|
197
231
|
const recommendations = diagnostics?.recommendations ?? [];
|
|
@@ -265,6 +299,29 @@ export function isRunCancellable(run) {
|
|
|
265
299
|
return run && isActiveRunState(run.state);
|
|
266
300
|
}
|
|
267
301
|
|
|
302
|
+
function humanizeToken(value) {
|
|
303
|
+
const raw = String(value ?? "agent");
|
|
304
|
+
return raw.charAt(0).toUpperCase() + raw.slice(1);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function humanizeRunState(state) {
|
|
308
|
+
switch (state) {
|
|
309
|
+
case "succeeded":
|
|
310
|
+
case "completed":
|
|
311
|
+
return "Succeeded";
|
|
312
|
+
case "failed":
|
|
313
|
+
return "Failed";
|
|
314
|
+
case "cancelled":
|
|
315
|
+
case "canceled":
|
|
316
|
+
return "Cancelled";
|
|
317
|
+
case "running":
|
|
318
|
+
case "starting":
|
|
319
|
+
return "Running";
|
|
320
|
+
default:
|
|
321
|
+
return humanizeToken(state);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
268
325
|
function summarizeEvent(event) {
|
|
269
326
|
if (event.type === "agent.tool_call") {
|
|
270
327
|
return event.data?.tool_name ?? event.data?.name ?? "tool";
|
package/src/global/ink/theme.js
CHANGED
|
@@ -19,7 +19,10 @@ export const STATUS_LABELS = {
|
|
|
19
19
|
offline: "Offline",
|
|
20
20
|
local: "Local",
|
|
21
21
|
online: "ONLINE",
|
|
22
|
-
loading: "Loading"
|
|
22
|
+
loading: "Loading",
|
|
23
|
+
needs_setup: "Needs setup",
|
|
24
|
+
needs_attention: "Needs attention",
|
|
25
|
+
limited: "Limited"
|
|
23
26
|
};
|
|
24
27
|
|
|
25
28
|
export const COCKPIT_GLYPHS = {
|
|
@@ -55,9 +58,12 @@ export function statusColor(kind, { colorEnabled = true } = {}) {
|
|
|
55
58
|
return COCKPIT_COLORS.success;
|
|
56
59
|
case "warn":
|
|
57
60
|
case "warning":
|
|
61
|
+
case "needs_setup":
|
|
62
|
+
case "limited":
|
|
58
63
|
return COCKPIT_COLORS.warning;
|
|
59
64
|
case "error":
|
|
60
65
|
case "danger":
|
|
66
|
+
case "needs_attention":
|
|
61
67
|
return COCKPIT_COLORS.danger;
|
|
62
68
|
case "offline":
|
|
63
69
|
case "muted":
|