@kal-elsam/kairo-runtime 0.16.0 → 0.18.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 (81) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/package.json +2 -1
  3. package/scripts/cockpit-smoke.mjs +1 -1
  4. package/scripts/ux-smoke-test.sh +3 -3
  5. package/src/cli.js +96 -11
  6. package/src/global/agent-capabilities/create-capability-adapter.js +2 -2
  7. package/src/global/architect/architect-cli.js +76 -0
  8. package/src/global/architect/architect-codex.js +146 -0
  9. package/src/global/architect/architect-manager.js +125 -0
  10. package/src/global/architect/architect-store.js +377 -0
  11. package/src/global/architect/architect-types.js +47 -0
  12. package/src/global/cli-help.js +10 -1
  13. package/src/global/cockpit/app.js +493 -0
  14. package/src/global/cockpit/card.js +111 -0
  15. package/src/global/cockpit/cli.js +33 -0
  16. package/src/global/cockpit/gauge.js +31 -0
  17. package/src/global/cockpit/project-overlay.js +693 -0
  18. package/src/global/cockpit/rows.js +148 -0
  19. package/src/global/cockpit/theme.js +118 -0
  20. package/src/global/cockpit/view.js +1298 -0
  21. package/src/global/conversation/bootstrap-analyzer-adapters.js +251 -0
  22. package/src/global/conversation/cli.js +53 -0
  23. package/src/global/conversation/codex-sandbox.js +230 -0
  24. package/src/global/conversation/cursor-sandbox.js +215 -0
  25. package/src/global/conversation/project-analysis.js +204 -0
  26. package/src/global/conversation/project-profile.js +178 -0
  27. package/src/global/conversation/project-router.js +149 -0
  28. package/src/global/conversation/project-strategy-store.js +64 -0
  29. package/src/global/conversation/project-strategy.js +514 -0
  30. package/src/global/conversation/sanitized-snapshot.js +169 -0
  31. package/src/global/conversation/secret-scanner.js +71 -0
  32. package/src/global/conversation/service.js +1090 -0
  33. package/src/global/conversation/session-store.js +75 -0
  34. package/src/global/conversation/transcript-store.js +79 -0
  35. package/src/global/conversation/ui.js +195 -0
  36. package/src/global/intelligence/capability-scoring.js +480 -0
  37. package/src/global/intelligence/execution-router.js +466 -0
  38. package/src/global/intelligence/kairo-telemetry-source.js +59 -0
  39. package/src/global/intelligence/kairobench-runner.js +85 -0
  40. package/src/global/intelligence/kairobench-source.js +34 -0
  41. package/src/global/intelligence/kairobench-tasks.js +47 -0
  42. package/src/global/intelligence/model-candidate-catalog.js +456 -0
  43. package/src/global/intelligence/model-capability-registry-sources.js +145 -0
  44. package/src/global/intelligence/model-capability-registry.js +125 -0
  45. package/src/global/intelligence/model-intelligence.js +1646 -0
  46. package/src/global/intelligence/official-benchmark-snapshots.js +162 -0
  47. package/src/global/intelligence/quick-ask.js +149 -0
  48. package/src/global/intelligence/role-profiles.js +251 -0
  49. package/src/global/intelligence/skill-catalog.js +67 -0
  50. package/src/global/intelligence/subscription-pressure-source.js +41 -0
  51. package/src/global/mcp/kairo-mcp.js +51 -18
  52. package/src/global/mcp/work-snapshot-rule.js +4 -2
  53. package/src/global/mcp/workspace-binding.js +88 -0
  54. package/src/global/mcp/workspace-mcp-entry.js +74 -0
  55. package/src/global/mcp-install.js +8 -1
  56. package/src/global/observability/artificial-analysis-models.js +118 -0
  57. package/src/global/observability/claude-models.js +31 -0
  58. package/src/global/observability/claude-usage.js +112 -0
  59. package/src/global/observability/codex-models.js +96 -0
  60. package/src/global/observability/codex-usage.js +160 -0
  61. package/src/global/observability/cursor-auth.js +88 -0
  62. package/src/global/observability/cursor-models.js +101 -0
  63. package/src/global/observability/huggingface-leaderboard.js +97 -0
  64. package/src/global/observability/opencode-models.js +101 -0
  65. package/src/global/observability/opencode-usage.js +162 -0
  66. package/src/global/paths.js +49 -2
  67. package/src/global/profile.js +23 -1
  68. package/src/global/runtime/execution-adapters/claude.js +63 -30
  69. package/src/global/runtime/execution-adapters/codex.js +9 -2
  70. package/src/global/runtime/execution-adapters/create-execution-adapter.js +6 -1
  71. package/src/global/runtime/execution-adapters/opencode.js +83 -18
  72. package/src/global/runtime/execution-worktree-manager.js +924 -0
  73. package/src/global/runtime/execution-worktree-orchestrator.js +194 -0
  74. package/src/global/runtime/execution-worktree-store.js +83 -0
  75. package/src/global/runtime/execution-worktree-types.js +45 -0
  76. package/src/global/runtime/run-events.js +38 -0
  77. package/src/global/runtime/run-manager.js +22 -6
  78. package/src/global/runtime/run-supervisor.js +41 -12
  79. package/src/global/runtime/usage-manager.js +96 -0
  80. package/src/global/runtime/usage-store.js +69 -0
  81. package/src/global/runtime/usage-types.js +62 -0
@@ -0,0 +1,148 @@
1
+ // Pure, dependency-free logic for the `kairo start` interactive cockpit.
2
+ // Kept separate from the pi-tui rendering so it can be unit tested without a terminal.
3
+
4
+ /**
5
+ * @typedef {object} CockpitRow
6
+ * @property {string} taskId
7
+ * @property {string|null} taskText - the real task description, never the noisy taskId slug
8
+ * @property {string} planState - one of the PLAN_STATES values (draft/awaiting_approval/approved/rejected/failed)
9
+ * @property {string} approval - "approved" | "rejected" | "not_decided"
10
+ * @property {string} execState - "not_started" | "reserved" | "running" | "done" | "failed" | ...
11
+ * @property {boolean} execActive
12
+ * @property {string|null} execMessage
13
+ * @property {string|null} planProvider - who planned it (e.g. "codex"); real, never inferred
14
+ * @property {string|null} planModel - explicit model if one was requested; null means provider default
15
+ * @property {string|null} execProvider - who is/was executing it (e.g. "claude")
16
+ */
17
+
18
+ /**
19
+ * Maps a conversation-service snapshot's timeline into display rows.
20
+ * @param {Array<object>} timeline - conversation snapshot().timeline entries
21
+ * @returns {CockpitRow[]}
22
+ */
23
+ export function buildTaskRows(timeline) {
24
+ if (!Array.isArray(timeline)) return [];
25
+ return timeline.map((plan) => ({
26
+ taskId: plan.taskId,
27
+ taskText: plan.taskText ?? null,
28
+ planState: plan.state,
29
+ approval: plan.approval ?? "not_decided",
30
+ execState: plan.execution?.state ?? "not_started",
31
+ execActive: plan.execution?.active === true,
32
+ execMessage: plan.execution?.message ?? null,
33
+ planProvider: plan.provider ?? null,
34
+ planModel: plan.model ?? null,
35
+ execProvider: plan.execution?.provider ?? null
36
+ }));
37
+ }
38
+
39
+ /**
40
+ * Derives an honest lifecycle phase from real plan/execution state — never
41
+ * a phase Kairo can't actually distinguish today (e.g. no "REVIEWING" step
42
+ * exists in the data model, so it's not offered).
43
+ * @param {CockpitRow|null} row
44
+ * @returns {"IDLE"|"WAITING APPROVAL"|"REJECTED"|"READY"|"IMPLEMENTING"|"COMPLETE"|"ERROR"|"PLANNING"}
45
+ */
46
+ export function derivePhase(row) {
47
+ if (!row) return "IDLE";
48
+ if (row.execActive) return "IMPLEMENTING";
49
+ if (row.execState === "done") return "COMPLETE";
50
+ if (row.execState === "failed" || row.planState === "failed") return "ERROR";
51
+ if (row.planState === "rejected") return "REJECTED";
52
+ if (row.planState === "approved") return "READY";
53
+ if (row.planState === "awaiting_approval") return "WAITING APPROVAL";
54
+ return "PLANNING";
55
+ }
56
+
57
+ /**
58
+ * Clamps a selection index into the valid range for the given rows array.
59
+ * @param {number} index
60
+ * @param {number} length
61
+ * @returns {number}
62
+ */
63
+ export function clampSelection(index, length) {
64
+ if (length <= 0) return 0;
65
+ if (index < 0) return 0;
66
+ if (index > length - 1) return length - 1;
67
+ return index;
68
+ }
69
+
70
+ /**
71
+ * Formats a single row as one display line.
72
+ * @param {CockpitRow} row
73
+ * @param {{selected?: boolean}} [options]
74
+ * @returns {string}
75
+ */
76
+ export function formatRowText(row, { selected = false } = {}) {
77
+ const marker = selected ? ">" : " ";
78
+ const planState = String(row.planState ?? "").padEnd(18);
79
+ const execState = String(row.execState ?? "").padEnd(12);
80
+ return `${marker} ${planState} ${execState} ${row.taskId}`;
81
+ }
82
+
83
+ /**
84
+ * Returns the header line matching formatRowText's column layout.
85
+ * @returns {string}
86
+ */
87
+ export function headerLine() {
88
+ return ` ${"PLAN".padEnd(18)} ${"EXECUTION".padEnd(12)} TASK`;
89
+ }
90
+
91
+ /**
92
+ * Maps a row's plan/execution state to a card tone (see ./card.js CARD_TONE)
93
+ * so its rendered line's left rail reflects its state at a glance.
94
+ * @param {CockpitRow} row
95
+ * @returns {"info"|"success"|"warning"|"error"}
96
+ */
97
+ export function rowTone(row) {
98
+ if (row.execState === "failed" || row.planState === "rejected") return "error";
99
+ if (row.execActive || row.execState === "running" || row.planState === "awaiting_approval") return "warning";
100
+ if (row.planState === "approved" || row.execState === "done") return "success";
101
+ return "info";
102
+ }
103
+
104
+ /**
105
+ * Returns whether a given action key is currently available for a row (or
106
+ * with no row selected). `mode` (the cockpit's WorkMode, when given) gates
107
+ * which actions the current mode allows advancing through — ASK is
108
+ * strictly read-only (blocks approve/reject/execute outright, matching
109
+ * "ASK: estrictamente read-only"); PLAN can review a plan (approve/reject)
110
+ * but never execute it ("PLAN: genera y persiste un plan, sin ejecutar");
111
+ * AGENT allows every gated action ("AGENT: permite avanzar bajo permisos y
112
+ * gates"). `cancel` is deliberately NEVER mode-gated — stopping a run that
113
+ * is already active is a safety action, not a way to advance the
114
+ * pipeline, and must stay available no matter which mode the cockpit is
115
+ * in when the need to cancel comes up. Omitting `mode` keeps the original,
116
+ * mode-unaware behavior (every real caller passes it; a few existing
117
+ * direct unit tests don't and must see no change).
118
+ * @param {"approve"|"reject"|"execute"|"cancel"} action
119
+ * @param {CockpitRow|null} row
120
+ * @param {"ask"|"plan"|"agent"|null} [mode]
121
+ * @returns {boolean}
122
+ */
123
+ export function isActionAvailable(action, row, mode = null) {
124
+ if (!row) return false;
125
+ if (action === "cancel") return row.execActive === true;
126
+ if (mode === "ask") return false;
127
+ if (action === "approve" || action === "reject") return row.planState === "awaiting_approval";
128
+ if (action === "execute") {
129
+ if (mode === "plan") return false;
130
+ return row.planState === "approved" && row.execState === "not_started";
131
+ }
132
+ return false;
133
+ }
134
+
135
+ /**
136
+ * Builds the key-hint bar text for the current selection.
137
+ * @param {CockpitRow|null} row
138
+ * @param {"ask"|"plan"|"agent"|null} [mode]
139
+ * @returns {string}
140
+ */
141
+ export function keyHintsLine(row, mode = null) {
142
+ const hints = ["up/down select", "enter view plan", "r refresh", "q quit"];
143
+ if (isActionAvailable("approve", row, mode)) hints.push("a approve");
144
+ if (isActionAvailable("reject", row, mode)) hints.push("j reject");
145
+ if (isActionAvailable("execute", row, mode)) hints.push("x execute");
146
+ if (isActionAvailable("cancel", row, mode)) hints.push("c cancel");
147
+ return hints.join(" | ");
148
+ }
@@ -0,0 +1,118 @@
1
+ // Minimal ANSI true-color theme for the `kairo start` cockpit, ported from
2
+ // gentle-pi's Gentle.json palette (docs/gentle-pi reference, MIT) so the
3
+ // cockpit reads like a real TUI instead of plain padded text.
4
+
5
+ // Exported so real contrast (WCAG relative luminance) can be verified
6
+ // directly against the actual values in use, not a guess — see
7
+ // theme.test.js's own regression for the real bug this caught (muted
8
+ // measured ~3.40:1 on black, below the 4.5:1 floor for normal text).
9
+ export const PALETTE = {
10
+ border: [49, 51, 66],
11
+ accent: [224, 193, 90],
12
+ text: [243, 246, 249],
13
+ // Bumped from [92,97,112] (real measured WCAG contrast on black: ~3.40:1
14
+ // — below the 4.5:1 minimum for normal text, which is why provider
15
+ // names, tags, and hint lines read as illegible in a real screenshot).
16
+ // [118,124,142] measures ~5.04:1, comfortably above the floor while
17
+ // staying visibly muted relative to `text` — still a secondary-info
18
+ // color, just a real, readable one.
19
+ muted: [118, 124, 142],
20
+ warning: [222, 186, 135],
21
+ error: [203, 124, 148],
22
+ success: [183, 204, 133],
23
+ info: [127, 180, 202],
24
+ // Bumped from [35,42,64] (real measured contrast against a plain black
25
+ // terminal background: ~1.48:1 — nearly indistinguishable, which is why
26
+ // a real screenshot of the analyst/model picker showed the selected row
27
+ // and the unselected background as "both black"). [50,80,140] measures
28
+ // ~2.66:1 against black — a real, clearly visible steel-blue bar — while
29
+ // still keeping `text` on top of it at ~7.27:1, comfortably above the
30
+ // 4.5:1 floor for normal text.
31
+ selection: [50, 80, 140]
32
+ };
33
+
34
+ /**
35
+ * @param {string} role - a PALETTE key
36
+ * @param {string} text
37
+ * @returns {string}
38
+ */
39
+ function fg(role, text) {
40
+ const rgb = PALETTE[role] ?? PALETTE.text;
41
+ return `\x1b[38;2;${rgb[0]};${rgb[1]};${rgb[2]}m${text}\x1b[0m`;
42
+ }
43
+
44
+ /** @param {string} text */
45
+ function bold(text) {
46
+ return `\x1b[1m${text}\x1b[0m`;
47
+ }
48
+
49
+ /**
50
+ * Background-color highlight (e.g. the selected row in a list), like the
51
+ * blue selection bar in a real file tree.
52
+ * @param {string} role
53
+ * @param {string} text
54
+ */
55
+ function bg(role, text) {
56
+ const rgb = PALETTE[role] ?? PALETTE.text;
57
+ return `\x1b[48;2;${rgb[0]};${rgb[1]};${rgb[2]}m${text}\x1b[0m`;
58
+ }
59
+
60
+ /**
61
+ * Bold + foreground + background combined in ONE ANSI escape with a
62
+ * SINGLE trailing reset — never nest `bold(bg(...))`/`fg(bg(...))`:
63
+ * each of those helpers ends with its own `\x1b[0m`, and an inner reset
64
+ * inside an outer one silently cuts the outer styling short partway
65
+ * through the text. Used for a real, clearly-highlighted selected row —
66
+ * "fondo/acentuación clara únicamente a la fila seleccionada" — without
67
+ * that hazard.
68
+ * @param {string} bgRole
69
+ * @param {string} fgRole
70
+ * @param {string} text
71
+ */
72
+ function bgFg(bgRole, fgRole, text) {
73
+ const bgRgb = PALETTE[bgRole] ?? PALETTE.text;
74
+ const fgRgb = PALETTE[fgRole] ?? PALETTE.text;
75
+ // Strip any ANSI already embedded in `text` first — a real SelectList
76
+ // row can arrive here already carrying its own fg()-wrapped label
77
+ // (project-overlay.js explicitly colors model+provider text) and/or a
78
+ // theme.fg("muted", ...)-wrapped description; a caller can't be trusted
79
+ // to hand back plain text just because it usually does. Each of those
80
+ // inner wrappers ends with its own `\x1b[0m`, which — left in place —
81
+ // cuts the outer background short partway through the row (verified:
82
+ // the real bug this fixes, caught only by rendering a real SelectList
83
+ // row, never by testing bgFg against plain text alone). One combined
84
+ // escape with a single trailing reset is the only composition that's
85
+ // actually safe here.
86
+ return `\x1b[1m\x1b[38;2;${fgRgb[0]};${fgRgb[1]};${fgRgb[2]}m\x1b[48;2;${bgRgb[0]};${bgRgb[1]};${bgRgb[2]}m${stripAnsi(text)}\x1b[0m`;
87
+ }
88
+
89
+ /** Removes any ANSI SGR escape sequences already embedded in `text` — see bgFg's own doc for why this must happen before re-wrapping. */
90
+ function stripAnsi(text) {
91
+ // eslint-disable-next-line no-control-regex
92
+ return text.replace(/\x1b\[[0-9;]*m/g, "");
93
+ }
94
+
95
+ export const theme = { fg, bold, bg, bgFg };
96
+
97
+ /**
98
+ * pi-tui's Editor component requires this shape (EditorTheme). We don't use
99
+ * autocomplete yet, so the selectList sub-theme is only there to satisfy the
100
+ * type contract.
101
+ */
102
+ export const editorTheme = {
103
+ // Bright, not the muted "border" gray used for card rules elsewhere — the
104
+ // composer is the one place the user is actively typing, so it needs to
105
+ // read as a distinct, easy-to-find zone rather than blend into the frame.
106
+ borderColor: (text) => fg("success", text),
107
+ selectList: {
108
+ selectedPrefix: (text) => fg("accent", text),
109
+ // A real background highlight on the selected row only (never the
110
+ // whole panel — see project-overlay.js's own Box, which applies no
111
+ // background of its own), bold + bright text for clear legibility
112
+ // against it.
113
+ selectedText: (text) => bgFg("selection", "text", text),
114
+ description: (text) => fg("muted", text),
115
+ scrollInfo: (text) => fg("muted", text),
116
+ noMatch: (text) => fg("muted", text)
117
+ }
118
+ };