@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,149 @@
1
+ // Deterministic role -> real model resolver for Kairo's approved
2
+ // ProjectStrategy.projectTeam. This is pure routing logic over already-
3
+ // decided data (the human-approved team + current provider eligibility) —
4
+ // it never spends another model call, never ranks anything itself, and
5
+ // never reclassifies a task into a role from keywords: the caller (the
6
+ // future Builder/Debugger/Tester orchestrator) always hands in an explicit
7
+ // role. Execution itself — actually launching a run against the resolved
8
+ // model, worktrees, checkpoints — is a separate, later increment; this
9
+ // module only ever decides WHAT would run, never runs it.
10
+ //
11
+ // Alternative selection is DOMAIN policy, not UI policy: when a role's
12
+ // approved assignment becomes unroutable, this module is also the one
13
+ // place that decides what real alternative (if any) to suggest — never
14
+ // the cockpit, the CLI, or an execution layer independently re-deriving
15
+ // one, which would risk three different answers for the same real state.
16
+ // The alternative itself is never invented here either — it's whichever
17
+ // real fallback candidate buildEfficientTeam already found for this role
18
+ // at analysis time (see project-strategy.js's projectTeam.fallback,
19
+ // persisted, not recomputed) — this module only checks whether that
20
+ // persisted candidate is STILL real-eligible right now.
21
+
22
+ /**
23
+ * Whether Kairo can launch an automatic run against this real candidate
24
+ * right now — reads model-candidate-catalog.js's own `accessMode`
25
+ * ("automatic"|"manual"), the canonical source, never a hardcoded
26
+ * adapterId list. Cursor/OpenCode Go are "manual" today because
27
+ * resolveAccessMode says so, not because this module knows their names —
28
+ * if OpenCode Go ever gets real, proven automatic execution, that change
29
+ * lands once in resolveAccessMode and this router picks it up for free,
30
+ * with no adapter-list edit needed here.
31
+ */
32
+ function isAutomatic(model) {
33
+ return model?.accessMode === "automatic";
34
+ }
35
+
36
+ export const PROJECT_ROUTE_DECISION = {
37
+ ROUTED: "ROUTED",
38
+ MANUAL_HANDOFF: "MANUAL_HANDOFF",
39
+ WAIT_FOR_PROJECT_TEAM: "WAIT_FOR_PROJECT_TEAM"
40
+ };
41
+
42
+ function assignmentRef(model, assignmentSource) {
43
+ if (!model) return null;
44
+ return { provider: model.adapterId, model, assignmentSource };
45
+ }
46
+
47
+ /** A real candidate is only ever offered as `suggestedAlternative` when it's currently automatically-executable — the same bar ROUTED itself requires. Never suggests another manual-only or currently-ineligible provider; honestly null instead. */
48
+ function routableAlternative(model, eligibility) {
49
+ if (!model) return null;
50
+ if (!isAutomatic(model)) return null;
51
+ if (eligibility[model.adapterId]?.ok !== true) return null;
52
+ return { provider: model.adapterId, model };
53
+ }
54
+
55
+ function blocked(role, strategyFingerprint, why, { blockedAssignment = null, suggestedAlternative = null } = {}) {
56
+ return {
57
+ decision: PROJECT_ROUTE_DECISION.WAIT_FOR_PROJECT_TEAM, role, strategyFingerprint, why,
58
+ provider: null, model: null, assignmentSource: null,
59
+ blockedAssignment, suggestedAlternative
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Resolves one role to the real model Kairo would delegate to right now,
65
+ * strictly from the approved ProjectStrategy — never the global QUALITY/
66
+ * EFFICIENT team, and never a fabricated fallback.
67
+ *
68
+ * Blocking rules, checked in order:
69
+ * 1. No strategy, or not ACTIVE (suggested/stale), or missing `projectTeam`
70
+ * entirely (an old strategy built before projectTeam existed) ->
71
+ * WAIT_FOR_PROJECT_TEAM, no blockedAssignment/suggestedAlternative
72
+ * (there's no real known assignment to block or suggest around). No
73
+ * silent migration — a pre-projectTeam strategy must be re-analyzed
74
+ * and re-approved.
75
+ * 2. The role has no real projectTeam entry, or that entry's model is
76
+ * null (no real eligible candidate was ever found for it) ->
77
+ * WAIT_FOR_PROJECT_TEAM, same as above.
78
+ * 3. The assigned model's own real `accessMode` isn't "automatic" (Cursor/
79
+ * OpenCode Go today, per resolveAccessMode — see isAutomatic) ->
80
+ * MANUAL_HANDOFF, still naming the real model/provider so the caller
81
+ * can show a concrete handoff ("Continue in Cursor with <model>"),
82
+ * never a bare "not supported".
83
+ * 4. The assigned provider isn't currently eligible (quota/availability
84
+ * changed since the strategy was approved) -> WAIT_FOR_PROJECT_TEAM,
85
+ * with `blockedAssignment` naming the real unavailable model/reason and
86
+ * `suggestedAlternative` set to the strategy's own PERSISTED fallback
87
+ * for this role (see project-strategy.js) when that fallback is
88
+ * itself currently real-eligible and automatically executable —
89
+ * otherwise honestly null. Never a silent automatic substitution: the
90
+ * caller must still explicitly confirm before anything runs on the
91
+ * suggested alternative.
92
+ * 5. Otherwise -> ROUTED, with the real provider/model/assignmentSource
93
+ * this role delegates to.
94
+ * @param {object} args
95
+ * @param {string} args.role - given explicitly by the caller, never
96
+ * inferred from task text.
97
+ * @param {object|null} args.strategy - the persisted ProjectStrategy, or
98
+ * null when none exists yet.
99
+ * @param {Record<string, {ok: boolean, reason?: string}>} [args.eligibility] -
100
+ * CURRENT provider eligibility — may have changed since the strategy was
101
+ * built/approved.
102
+ * @returns {{decision: "ROUTED"|"MANUAL_HANDOFF"|"WAIT_FOR_PROJECT_TEAM", role: string, provider: string|null, model: object|null, assignmentSource: string|null, strategyFingerprint: string|null, blockedAssignment: {provider: string, model: object, assignmentSource: string}|null, suggestedAlternative: {provider: string, model: object}|null, why: string}}
103
+ */
104
+ export function resolveProjectRoute({ role, strategy, eligibility = {} }) {
105
+ const strategyFingerprint = strategy?.profileFingerprint ?? null;
106
+
107
+ if (!strategy) {
108
+ return blocked(role, strategyFingerprint, "No project strategy exists yet — run /project to analyze and approve one.");
109
+ }
110
+ if (strategy.status !== "active") {
111
+ return blocked(role, strategyFingerprint, `Project strategy is ${strategy.status?.toUpperCase() ?? "UNKNOWN"}, not ACTIVE — approve it before Kairo can delegate automatically.`);
112
+ }
113
+ if (!Array.isArray(strategy.projectTeam)) {
114
+ return blocked(role, strategyFingerprint, "This project strategy was approved before projectTeam existed — re-analyze and approve to enable automatic delegation.");
115
+ }
116
+
117
+ const entry = strategy.projectTeam.find((e) => e.role === role);
118
+ if (!entry || !entry.model) {
119
+ return blocked(role, strategyFingerprint, `No real eligible model was assigned to ${role} in this project's team.`);
120
+ }
121
+
122
+ const { model, assignmentSource, fallback = null } = entry;
123
+
124
+ if (!isAutomatic(model)) {
125
+ return {
126
+ decision: PROJECT_ROUTE_DECISION.MANUAL_HANDOFF, role, provider: model.adapterId, model, assignmentSource, strategyFingerprint,
127
+ blockedAssignment: null, suggestedAlternative: null,
128
+ why: `${model.adapterId} isn't executable by Kairo automatically — continue manually with ${model.displayName ?? model.modelId}.`
129
+ };
130
+ }
131
+
132
+ if (eligibility[model.adapterId]?.ok !== true) {
133
+ const reason = eligibility[model.adapterId]?.reason ?? "unknown reason";
134
+ const suggestedAlternative = routableAlternative(fallback, eligibility);
135
+ const why = suggestedAlternative
136
+ ? `${model.adapterId} is not currently eligible (${reason}) — no automatic substitution; confirm the suggested alternative for ${role} before proceeding.`
137
+ : `${model.adapterId} is not currently eligible (${reason}) — no automatic alternative is available for ${role} right now.`;
138
+ return blocked(role, strategyFingerprint, why, {
139
+ blockedAssignment: assignmentRef(model, assignmentSource),
140
+ suggestedAlternative
141
+ });
142
+ }
143
+
144
+ return {
145
+ decision: PROJECT_ROUTE_DECISION.ROUTED, role, provider: model.adapterId, model, assignmentSource, strategyFingerprint,
146
+ blockedAssignment: null, suggestedAlternative: null,
147
+ why: `${role} delegates to ${model.displayName ?? model.modelId} per the approved project team.`
148
+ };
149
+ }
@@ -0,0 +1,64 @@
1
+ // Persists ProjectStrategy — the approved (or suggested) real per-project
2
+ // role assignment — under the same global `~/.harness/sessions/<projectKey>/`
3
+ // tree session-store.js and transcript-store.js already use, but as its
4
+ // own separate file (project-strategy.json): a strategy's lifecycle
5
+ // (suggested -> approved -> stale) is independent of both the WorkMode and
6
+ // the chat transcript, and none of the three should need to read the
7
+ // others just to change.
8
+
9
+ import { mkdir, readFile } from "node:fs/promises";
10
+ import { dirname, join } from "node:path";
11
+ import { harnessHomePaths } from "../paths.js";
12
+ import { projectKeyForPath } from "../next/project-key.js";
13
+ import { writeAtomicJson } from "../runtime/write-atomic-json.js";
14
+
15
+ export const PROJECT_STRATEGY_SCHEMA = "kairo.project-strategy/v1";
16
+ export const PROJECT_STRATEGY_STATUSES = ["suggested", "active", "stale"];
17
+
18
+ function strategyPath(homeDir, projectRoot) {
19
+ const { sessionsDir } = harnessHomePaths(homeDir);
20
+ return join(sessionsDir, projectKeyForPath(projectRoot), "project-strategy.json");
21
+ }
22
+
23
+ /**
24
+ * Reads the persisted ProjectStrategy for a project. Returns `null` on a
25
+ * missing or malformed file — unlike session-store's readSession, there is
26
+ * no safe default strategy to fall back to (a project genuinely has NOT
27
+ * been analyzed yet until one is computed), so `null` here is the honest
28
+ * "NOT_ANALYZED" signal the cockpit checks for.
29
+ * @param {string} homeDir
30
+ * @param {string} projectRoot
31
+ * @returns {Promise<object|null>}
32
+ */
33
+ export async function readProjectStrategy(homeDir, projectRoot, deps = {}) {
34
+ const read = deps.readFile ?? readFile;
35
+ try {
36
+ const raw = await read(strategyPath(homeDir, projectRoot), "utf8");
37
+ const doc = JSON.parse(raw);
38
+ if (doc?.schema !== PROJECT_STRATEGY_SCHEMA || !PROJECT_STRATEGY_STATUSES.includes(doc.status)) return null;
39
+ return doc;
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Persists a ProjectStrategy (create or replace — a strategy is always
47
+ * recomputed as a whole document, never patched field by field, so a
48
+ * stale nested field can never survive a refresh by accident).
49
+ * @param {string} homeDir
50
+ * @param {string} projectRoot
51
+ * @param {object} strategy - a ProjectStrategy document (see project-strategy.js)
52
+ */
53
+ export async function writeProjectStrategy(homeDir, projectRoot, strategy, deps = {}) {
54
+ if (!PROJECT_STRATEGY_STATUSES.includes(strategy?.status)) {
55
+ throw new Error(`ProjectStrategy must have a real status (one of ${PROJECT_STRATEGY_STATUSES.join(", ")}).`);
56
+ }
57
+ const mkdirImpl = deps.mkdir ?? mkdir;
58
+ const writeJson = deps.writeAtomicJson ?? writeAtomicJson;
59
+ const path = strategyPath(homeDir, projectRoot);
60
+ const doc = { ...strategy, schema: PROJECT_STRATEGY_SCHEMA };
61
+ await mkdirImpl(dirname(path), { recursive: true });
62
+ await writeJson(path, doc);
63
+ return doc;
64
+ }