@kal-elsam/kairo-runtime 0.3.0 → 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.
@@ -0,0 +1,92 @@
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
+ const NAV_FOCUSED_VIEWS = new Set([
6
+ ORCHESTRATOR_VIEWS.HOME,
7
+ ORCHESTRATOR_VIEWS.PROVIDERS,
8
+ ORCHESTRATOR_VIEWS.DIAGNOSTICS,
9
+ ORCHESTRATOR_VIEWS.HELP
10
+ ]);
11
+
12
+ const CONTENT_INTERACTIVE_VIEWS = new Set([
13
+ ORCHESTRATOR_VIEWS.ACTIVE_RUNS,
14
+ ORCHESTRATOR_VIEWS.RECENT_RUNS,
15
+ ORCHESTRATOR_VIEWS.RUN_DETAIL,
16
+ ORCHESTRATOR_VIEWS.LAUNCH
17
+ ]);
18
+
19
+ export function isNavFocusedView(view) {
20
+ return NAV_FOCUSED_VIEWS.has(view);
21
+ }
22
+
23
+ export function isContentInteractiveView(view) {
24
+ return CONTENT_INTERACTIVE_VIEWS.has(view);
25
+ }
26
+
27
+ export function defaultRegionForView(view, layoutMode = LAYOUT_MODES.COMPACT) {
28
+ const regions = regionsForLayout(layoutMode);
29
+ if (isContentInteractiveView(view) && regions.includes(COCKPIT_REGIONS.CONTENT)) {
30
+ return COCKPIT_REGIONS.CONTENT;
31
+ }
32
+ if (regions.includes(COCKPIT_REGIONS.NAV)) return COCKPIT_REGIONS.NAV;
33
+ return regions[0] ?? COCKPIT_REGIONS.CONTENT;
34
+ }
35
+
36
+ export function interactiveRegionsFor(state) {
37
+ const regions = regionsForLayout(state.layoutMode);
38
+ if (!isContentInteractiveView(state.view) || state.view === ORCHESTRATOR_VIEWS.RUN_DETAIL) {
39
+ return regions.filter((region) => region === COCKPIT_REGIONS.NAV);
40
+ }
41
+ return regions.filter(
42
+ (region) => region === COCKPIT_REGIONS.NAV || region === COCKPIT_REGIONS.CONTENT
43
+ );
44
+ }
45
+
46
+ export function canTabBetweenRegions(state) {
47
+ if (!isContentInteractiveView(state.view)) return false;
48
+ if (state.view === ORCHESTRATOR_VIEWS.RUN_DETAIL) return false;
49
+ return interactiveRegionsFor(state).length >= 2;
50
+ }
51
+
52
+ /**
53
+ * Map raw key intent to a reducer action (or null when inactive / view-owned).
54
+ */
55
+ export function routeCockpitKey(state, keyAction) {
56
+ switch (keyAction.type) {
57
+ case "escape":
58
+ return { type: "escape" };
59
+ case "tab":
60
+ return canTabBetweenRegions(state) ? { type: "tab" } : null;
61
+ case "arrow": {
62
+ if (state.region === COCKPIT_REGIONS.SYSTEM) return null;
63
+ if (state.region === COCKPIT_REGIONS.NAV || isNavFocusedView(state.view)) {
64
+ return { type: "arrow", direction: keyAction.direction };
65
+ }
66
+ if (state.region === COCKPIT_REGIONS.CONTENT && isContentInteractiveView(state.view)) {
67
+ if (state.view === ORCHESTRATOR_VIEWS.RUN_DETAIL || state.view === ORCHESTRATOR_VIEWS.LAUNCH) {
68
+ return null;
69
+ }
70
+ return {
71
+ type: "arrow",
72
+ direction: keyAction.direction,
73
+ listLength: keyAction.listLength
74
+ };
75
+ }
76
+ return null;
77
+ }
78
+ case "enter":
79
+ if (state.region === COCKPIT_REGIONS.NAV || isNavFocusedView(state.view)) {
80
+ return { type: "enter-nav" };
81
+ }
82
+ return null;
83
+ default:
84
+ return null;
85
+ }
86
+ }
87
+
88
+ export function clamp(value, min, max) {
89
+ return Math.min(max, Math.max(min, value));
90
+ }
91
+
92
+ export { COCKPIT_NAV, COCKPIT_REGIONS, LAYOUT_MODES, ORCHESTRATOR_VIEWS, regionsForLayout };
@@ -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 { resolveDashboardRecommendation } from "../dashboard-guidance.js";
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 { resolveListLimit } from "./layout.js";
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
- { 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 }
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
- items: items.map((item, index) => ({
59
- ...item,
60
- marker: index === navIndex ? glyphs.focus : " ",
61
- selected: index === navIndex,
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
- healthKind = "ready"
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,62 +174,76 @@ 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
- { key: "Health", value: STATUS_LABELS[healthKind] ?? healthKind, kind: healthKind }
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
- if (helpOpen) {
134
- return {
135
- text: `Esc close help ${glyphs.bullet} R refresh ${glyphs.bullet} C cancel run`
136
- };
195
+ const parts = [];
196
+
197
+ if (hasError) {
198
+ parts.push("R Retry");
199
+ parts.push("Esc Exit");
200
+ return { text: parts.join(` ${glyphs.bullet} `) };
137
201
  }
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
- };
202
+
203
+ if (helpOpen || view === ORCHESTRATOR_VIEWS.HELP) {
204
+ parts.push("Esc close help");
205
+ parts.push("? Help");
206
+ return { text: parts.join(` ${glyphs.bullet} `) };
142
207
  }
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
- };
208
+
209
+ if (view === ORCHESTRATOR_VIEWS.RUN_DETAIL) {
210
+ parts.push("R refresh");
211
+ if (canCancel) parts.push("C cancel");
212
+ parts.push("Esc Back");
213
+ return { text: parts.join(` ${glyphs.bullet} `) };
214
+ }
215
+
216
+ parts.push("↑↓ Navigate");
217
+
218
+ const showTab = view === ORCHESTRATOR_VIEWS.ACTIVE_RUNS
219
+ || view === ORCHESTRATOR_VIEWS.RECENT_RUNS
220
+ || view === ORCHESTRATOR_VIEWS.LAUNCH;
221
+ if (showTab) {
222
+ parts.push("Tab Region");
223
+ }
224
+
225
+ if (view === ORCHESTRATOR_VIEWS.HOME
226
+ || view === ORCHESTRATOR_VIEWS.PROVIDERS
227
+ || view === ORCHESTRATOR_VIEWS.DIAGNOSTICS
228
+ || region === COCKPIT_REGIONS.NAV
229
+ || view === ORCHESTRATOR_VIEWS.ACTIVE_RUNS
230
+ || view === ORCHESTRATOR_VIEWS.RECENT_RUNS) {
231
+ parts.push("Enter Open");
232
+ }
233
+
234
+ if (view !== ORCHESTRATOR_VIEWS.LAUNCH) {
235
+ parts.push("R refresh");
236
+ }
237
+
238
+ parts.push("? Help");
239
+ parts.push(view === ORCHESTRATOR_VIEWS.HOME ? "Esc Exit" : "Esc Back");
240
+
241
+ return { text: parts.join(` ${glyphs.bullet} `) };
242
+ }
243
+
244
+ export function windowLinesForLayout(lines = [], layoutMode = LAYOUT_MODES.COMPACT, contentRows = 12) {
245
+ const limit = resolveListLimit(layoutMode, { contentRows });
246
+ return windowList(lines, limit);
147
247
  }
148
248
 
149
249
  export function resolveProjectName(workspaceRoot = "") {
@@ -151,3 +251,5 @@ export function resolveProjectName(workspaceRoot = "") {
151
251
  const parts = String(workspaceRoot).split(/[/\\]/).filter(Boolean);
152
252
  return parts[parts.length - 1] || "project";
153
253
  }
254
+
255
+ export { buildHomeMissionModel, formatHomeRecentRun };