@kal-elsam/kairo-runtime 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/bin/kairo-runtime.js +0 -0
  3. package/bin/kairo.js +0 -0
  4. package/package.json +1 -1
  5. package/src/cli.js +172 -8
  6. package/src/global/check-resolutions.js +31 -0
  7. package/src/global/cli-help.js +19 -2
  8. package/src/global/component-ecosystem-checks.js +2 -0
  9. package/src/global/component-integration-cli.js +29 -10
  10. package/src/global/components-resolve-cli.js +246 -0
  11. package/src/global/connection-actions.js +147 -0
  12. package/src/global/connections.js +269 -0
  13. package/src/global/fleet-configure-plan.js +123 -0
  14. package/src/global/fleet-configure.js +303 -0
  15. package/src/global/fleet-models.js +188 -0
  16. package/src/global/fleet-set.js +219 -0
  17. package/src/global/fleet-shared.js +38 -0
  18. package/src/global/ink/cockpit-controller.js +1 -1
  19. package/src/global/ink/cockpit-models.js +4 -1
  20. package/src/global/ink/orchestrator-app.js +21 -2
  21. package/src/global/ink/ux/live-overview.js +5 -11
  22. package/src/global/ink/ux/overview-needs.js +1 -1
  23. package/src/global/integrations/engram-evidence.js +7 -2
  24. package/src/global/integrations/sdd-apply.js +17 -7
  25. package/src/global/integrations/sdd-evidence.js +22 -3
  26. package/src/global/integrations/sdd-plan.js +21 -3
  27. package/src/global/integrations/sdd-resolutions.js +73 -0
  28. package/src/global/integrations/sdd-state.js +69 -0
  29. package/src/global/integrations/sdd-verify.js +9 -4
  30. package/src/global/mcp/kairo-mcp.js +56 -5
  31. package/src/global/mcp/resolve-mcp-workspace.js +51 -0
  32. package/src/global/mcp/work-snapshot-rule.js +89 -0
  33. package/src/global/mcp/work-snapshot-tool.js +49 -0
  34. package/src/global/mcp-install.js +239 -0
  35. package/src/global/next/next-cli.js +35 -0
  36. package/src/global/next/next-report.js +145 -0
  37. package/src/global/next/project-key.js +36 -0
  38. package/src/global/next/publish-work-snapshot.js +116 -0
  39. package/src/global/next/work-enroll.js +91 -0
  40. package/src/global/next/work-snapshot.js +216 -0
  41. package/src/global/observability/fleet-activity.js +197 -0
  42. package/src/global/observability/fleet-models-catalog.js +137 -0
  43. package/src/global/observability/fleet-platforms.js +166 -0
  44. package/src/global/observability/fleet-probe.js +229 -0
  45. package/src/global/paths.js +2 -1
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Declared fleet topology (orchestrator → minions + models) + optional live activity.
3
+ * Read-only probes — writes go through fleet-set.js with consent.
4
+ */
5
+ import { access, readFile } from "node:fs/promises";
6
+ import { constants as fsConstants } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { resolveHomeDir } from "../paths.js";
9
+ import { isExecutableAvailable } from "../cli-probe.js";
10
+ import { buildOpenCodeActivity } from "./fleet-activity.js";
11
+ import {
12
+ buildClaudeFleet,
13
+ buildCodexFleet,
14
+ buildCursorAgentsFleet
15
+ } from "./fleet-platforms.js";
16
+
17
+ const VARIANT_SUFFIX_RE = /-(?:cheap|zen)$/i;
18
+
19
+ const MINION_ROLES = Object.freeze({
20
+ "sdd-apply": "executor",
21
+ "sdd-explore": "explorer",
22
+ "sdd-verify": "verifier",
23
+ "sdd-design": "designer",
24
+ "sdd-propose": "proposer",
25
+ "sdd-spec": "specifier",
26
+ "sdd-tasks": "planner",
27
+ "sdd-archive": "archiver",
28
+ "sdd-onboard": "onboarder",
29
+ "sdd-init": "initializer"
30
+ });
31
+
32
+ async function pathExists(path) {
33
+ try {
34
+ await access(path, fsConstants.F_OK);
35
+ return true;
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+
41
+ function shortModel(model) {
42
+ if (typeof model !== "string" || !model) return null;
43
+ const slash = model.lastIndexOf("/");
44
+ return slash >= 0 ? model.slice(slash + 1) : model;
45
+ }
46
+
47
+ function agentEntries(agents) {
48
+ if (!agents || typeof agents !== "object" || Array.isArray(agents)) return [];
49
+ return Object.entries(agents).map(([id, raw]) => ({
50
+ id: String(id),
51
+ mode: typeof raw?.mode === "string" ? raw.mode : null,
52
+ model: typeof raw?.model === "string" ? raw.model : null
53
+ }));
54
+ }
55
+
56
+ function pickOrchestrator(entries) {
57
+ const gentle = entries.find((e) => e.id === "gentle-orchestrator" && e.mode === "primary");
58
+ if (gentle) return gentle;
59
+ return entries.find((e) => e.mode === "primary") ?? null;
60
+ }
61
+
62
+ function isSddMinion(entry, { includeVariants = false } = {}) {
63
+ if (!entry?.id?.startsWith("sdd-")) return false;
64
+ if (entry.mode !== "subagent") return false;
65
+ if (!includeVariants && VARIANT_SUFFIX_RE.test(entry.id)) return false;
66
+ return true;
67
+ }
68
+
69
+ function minionRole(id) {
70
+ return MINION_ROLES[id] ?? (id.startsWith("sdd-") ? "specialist" : "minion");
71
+ }
72
+
73
+ export function parseOpenCodeFleet(config, { includeVariants = false } = {}) {
74
+ const defaultModel = typeof config?.model === "string" ? config.model : null;
75
+ const entries = agentEntries(config?.agent ?? config?.agents);
76
+ if (entries.length === 0) {
77
+ return {
78
+ platform: "opencode",
79
+ orchestrator: null,
80
+ minions: [],
81
+ opaque: false,
82
+ writable: true,
83
+ source: "opencode.json"
84
+ };
85
+ }
86
+
87
+ const orch = pickOrchestrator(entries);
88
+ const orchModel = orch?.model ?? defaultModel;
89
+ const minions = entries
90
+ .filter((e) => isSddMinion(e, { includeVariants }))
91
+ .map((e) => {
92
+ const model = e.model ?? defaultModel;
93
+ return {
94
+ id: e.id,
95
+ model,
96
+ modelShort: shortModel(model),
97
+ role: minionRole(e.id),
98
+ mode: e.mode
99
+ };
100
+ })
101
+ .sort((a, b) => a.id.localeCompare(b.id));
102
+
103
+ return {
104
+ platform: "opencode",
105
+ orchestrator: orch
106
+ ? {
107
+ id: orch.id,
108
+ model: orchModel,
109
+ modelShort: shortModel(orchModel),
110
+ mode: orch.mode ?? "primary",
111
+ opaque: false
112
+ }
113
+ : null,
114
+ minions,
115
+ opaque: false,
116
+ writable: true,
117
+ source: "opencode.json"
118
+ };
119
+ }
120
+
121
+ async function readJson(path, read = readFile) {
122
+ try {
123
+ return JSON.parse(await read(path, "utf8"));
124
+ } catch {
125
+ return null;
126
+ }
127
+ }
128
+
129
+ export async function buildFleetReport({
130
+ homeDir = resolveHomeDir(),
131
+ includeVariants = false,
132
+ includeActivity = true,
133
+ read = readFile,
134
+ exists = pathExists,
135
+ gentleAvailable = () => isExecutableAvailable("gentle-ai"),
136
+ buildActivity = buildOpenCodeActivity,
137
+ buildClaude = buildClaudeFleet,
138
+ buildCodex = buildCodexFleet,
139
+ buildCursor = buildCursorAgentsFleet
140
+ } = {}) {
141
+ const fleets = [];
142
+ const openCodePath = join(homeDir, ".config", "opencode", "opencode.json");
143
+ if (await exists(openCodePath)) {
144
+ const config = await readJson(openCodePath, read);
145
+ if (config) {
146
+ const fleet = parseOpenCodeFleet(config, { includeVariants });
147
+ fleet.configPath = openCodePath;
148
+ fleets.push(fleet);
149
+ }
150
+ }
151
+
152
+ if (await exists(join(homeDir, ".cursor"))) {
153
+ fleets.push(await buildCursor({ homeDir, read }));
154
+ }
155
+
156
+ if (await exists(join(homeDir, ".claude"))) {
157
+ const fleet = await buildClaude({ homeDir, read });
158
+ if (fleet) fleets.push(fleet);
159
+ }
160
+
161
+ if (await exists(join(homeDir, ".codex"))) {
162
+ const fleet = await buildCodex({ homeDir, read });
163
+ if (fleet) fleets.push(fleet);
164
+ }
165
+
166
+ const activity = includeActivity
167
+ ? await buildActivity({ homeDir })
168
+ : null;
169
+
170
+ return {
171
+ ok: true,
172
+ kind: "declared+activity",
173
+ note: "Declared config topology + OpenCode live activity when available.",
174
+ orchestratorAuthority: gentleAvailable() ? "gentle-ai" : null,
175
+ fleets,
176
+ activity,
177
+ generatedAt: new Date().toISOString()
178
+ };
179
+ }
180
+
181
+ export function formatFleetText(report, { verbose = false } = {}) {
182
+ const lines = ["Fleet floor", ""];
183
+ if (report.orchestratorAuthority) {
184
+ lines.push(`Authority · ${report.orchestratorAuthority}`);
185
+ lines.push("");
186
+ }
187
+ for (const fleet of report.fleets ?? []) {
188
+ const orch = fleet.orchestrator;
189
+ const modelBit = orch?.opaque
190
+ ? "opaque · IDE-managed"
191
+ : (orch?.modelShort ?? orch?.model ?? "—");
192
+ const minionCount = (fleet.minions ?? []).length;
193
+ lines.push(`${fleet.platform} · ${orch?.id ?? "—"} · ${modelBit}`);
194
+ if (verbose) {
195
+ for (const m of fleet.minions ?? []) {
196
+ const opaque = m.opaque ? " · opaque" : "";
197
+ lines.push(` ${m.id} · ${m.modelShort ?? m.model ?? "—"} · ${m.role}${opaque}`);
198
+ }
199
+ if (fleet.note) lines.push(` note: ${fleet.note}`);
200
+ } else if (minionCount > 0) {
201
+ lines.push(` ${minionCount} minions · kairo fleet --verbose`);
202
+ }
203
+ lines.push("");
204
+ }
205
+
206
+ const act = report.activity;
207
+ if (act?.available) {
208
+ const active = (act.agents ?? []).filter((a) => a.state === "active");
209
+ if (active.length === 0) {
210
+ lines.push("Working floor · quiet (no live OpenCode sessions)");
211
+ lines.push("");
212
+ } else {
213
+ lines.push(`Working floor · ${active.length} live`);
214
+ for (const a of active) {
215
+ lines.push(` ● ${a.id} · ${a.modelShort ?? a.model ?? "—"}`);
216
+ }
217
+ lines.push("");
218
+ }
219
+ } else if (act && !act.available) {
220
+ lines.push(`Working floor · unavailable`);
221
+ lines.push("");
222
+ }
223
+
224
+ if ((report.fleets ?? []).length === 0) {
225
+ lines.push("No agent platforms detected.");
226
+ }
227
+ lines.push(report.note ?? "");
228
+ return lines.join("\n").trimEnd();
229
+ }
@@ -23,7 +23,8 @@ export function harnessHomePaths(homeDir) {
23
23
  monitorDir: join(root, "monitor"),
24
24
  monitorStatePath: join(root, "monitor", "state.json"),
25
25
  coreDir: join(root, "core"),
26
- backupsDir: join(root, "backups")
26
+ backupsDir: join(root, "backups"),
27
+ sessionsDir: join(root, "sessions")
27
28
  };
28
29
  }
29
30