@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.
- package/CHANGELOG.md +79 -0
- package/package.json +2 -1
- package/scripts/cockpit-smoke.mjs +1 -1
- package/scripts/ux-smoke-test.sh +3 -3
- package/src/cli.js +96 -11
- package/src/global/agent-capabilities/create-capability-adapter.js +2 -2
- package/src/global/architect/architect-cli.js +76 -0
- package/src/global/architect/architect-codex.js +146 -0
- package/src/global/architect/architect-manager.js +125 -0
- package/src/global/architect/architect-store.js +377 -0
- package/src/global/architect/architect-types.js +47 -0
- package/src/global/cli-help.js +10 -1
- package/src/global/cockpit/app.js +493 -0
- package/src/global/cockpit/card.js +111 -0
- package/src/global/cockpit/cli.js +33 -0
- package/src/global/cockpit/gauge.js +31 -0
- package/src/global/cockpit/project-overlay.js +693 -0
- package/src/global/cockpit/rows.js +148 -0
- package/src/global/cockpit/theme.js +118 -0
- package/src/global/cockpit/view.js +1298 -0
- package/src/global/conversation/bootstrap-analyzer-adapters.js +251 -0
- package/src/global/conversation/cli.js +53 -0
- package/src/global/conversation/codex-sandbox.js +230 -0
- package/src/global/conversation/cursor-sandbox.js +215 -0
- package/src/global/conversation/project-analysis.js +204 -0
- package/src/global/conversation/project-profile.js +178 -0
- package/src/global/conversation/project-router.js +149 -0
- package/src/global/conversation/project-strategy-store.js +64 -0
- package/src/global/conversation/project-strategy.js +514 -0
- package/src/global/conversation/sanitized-snapshot.js +169 -0
- package/src/global/conversation/secret-scanner.js +71 -0
- package/src/global/conversation/service.js +1090 -0
- package/src/global/conversation/session-store.js +75 -0
- package/src/global/conversation/transcript-store.js +79 -0
- package/src/global/conversation/ui.js +195 -0
- package/src/global/intelligence/capability-scoring.js +480 -0
- package/src/global/intelligence/execution-router.js +466 -0
- package/src/global/intelligence/kairo-telemetry-source.js +59 -0
- package/src/global/intelligence/kairobench-runner.js +85 -0
- package/src/global/intelligence/kairobench-source.js +34 -0
- package/src/global/intelligence/kairobench-tasks.js +47 -0
- package/src/global/intelligence/model-candidate-catalog.js +456 -0
- package/src/global/intelligence/model-capability-registry-sources.js +145 -0
- package/src/global/intelligence/model-capability-registry.js +125 -0
- package/src/global/intelligence/model-intelligence.js +1646 -0
- package/src/global/intelligence/official-benchmark-snapshots.js +162 -0
- package/src/global/intelligence/quick-ask.js +149 -0
- package/src/global/intelligence/role-profiles.js +251 -0
- package/src/global/intelligence/skill-catalog.js +67 -0
- package/src/global/intelligence/subscription-pressure-source.js +41 -0
- package/src/global/mcp/kairo-mcp.js +51 -18
- package/src/global/mcp/work-snapshot-rule.js +4 -2
- package/src/global/mcp/workspace-binding.js +88 -0
- package/src/global/mcp/workspace-mcp-entry.js +74 -0
- package/src/global/mcp-install.js +8 -1
- package/src/global/observability/artificial-analysis-models.js +118 -0
- package/src/global/observability/claude-models.js +31 -0
- package/src/global/observability/claude-usage.js +112 -0
- package/src/global/observability/codex-models.js +96 -0
- package/src/global/observability/codex-usage.js +160 -0
- package/src/global/observability/cursor-auth.js +88 -0
- package/src/global/observability/cursor-models.js +101 -0
- package/src/global/observability/huggingface-leaderboard.js +97 -0
- package/src/global/observability/opencode-models.js +101 -0
- package/src/global/observability/opencode-usage.js +162 -0
- package/src/global/paths.js +49 -2
- package/src/global/profile.js +23 -1
- package/src/global/runtime/execution-adapters/claude.js +63 -30
- package/src/global/runtime/execution-adapters/codex.js +9 -2
- package/src/global/runtime/execution-adapters/create-execution-adapter.js +6 -1
- package/src/global/runtime/execution-adapters/opencode.js +83 -18
- package/src/global/runtime/execution-worktree-manager.js +924 -0
- package/src/global/runtime/execution-worktree-orchestrator.js +194 -0
- package/src/global/runtime/execution-worktree-store.js +83 -0
- package/src/global/runtime/execution-worktree-types.js +45 -0
- package/src/global/runtime/run-events.js +38 -0
- package/src/global/runtime/run-manager.js +22 -6
- package/src/global/runtime/run-supervisor.js +41 -12
- package/src/global/runtime/usage-manager.js +96 -0
- package/src/global/runtime/usage-store.js +69 -0
- 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
|
+
};
|