@kal-elsam/kairo-runtime 0.15.0 → 0.17.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 (92) hide show
  1. package/CHANGELOG.md +76 -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 +106 -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 +12 -1
  13. package/src/global/cockpit/app.js +475 -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 +683 -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 +1263 -0
  21. package/src/global/control-plane/attention.js +141 -0
  22. package/src/global/control-plane/build-report.js +146 -0
  23. package/src/global/control-plane/cli.js +36 -0
  24. package/src/global/control-plane/constants.js +38 -0
  25. package/src/global/control-plane/gentle-adapters.js +183 -0
  26. package/src/global/control-plane/provider.js +69 -0
  27. package/src/global/control-plane/review-status.js +115 -0
  28. package/src/global/control-plane/sdd-status.js +49 -0
  29. package/src/global/control-plane/team.js +63 -0
  30. package/src/global/conversation/bootstrap-analyzer-adapters.js +251 -0
  31. package/src/global/conversation/cli.js +53 -0
  32. package/src/global/conversation/codex-sandbox.js +230 -0
  33. package/src/global/conversation/cursor-sandbox.js +215 -0
  34. package/src/global/conversation/project-analysis.js +204 -0
  35. package/src/global/conversation/project-profile.js +178 -0
  36. package/src/global/conversation/project-router.js +149 -0
  37. package/src/global/conversation/project-strategy-store.js +64 -0
  38. package/src/global/conversation/project-strategy.js +514 -0
  39. package/src/global/conversation/sanitized-snapshot.js +169 -0
  40. package/src/global/conversation/secret-scanner.js +71 -0
  41. package/src/global/conversation/service.js +1063 -0
  42. package/src/global/conversation/session-store.js +75 -0
  43. package/src/global/conversation/transcript-store.js +79 -0
  44. package/src/global/conversation/ui.js +195 -0
  45. package/src/global/intelligence/capability-scoring.js +480 -0
  46. package/src/global/intelligence/execution-router.js +444 -0
  47. package/src/global/intelligence/kairo-telemetry-source.js +59 -0
  48. package/src/global/intelligence/kairobench-runner.js +85 -0
  49. package/src/global/intelligence/kairobench-source.js +34 -0
  50. package/src/global/intelligence/kairobench-tasks.js +47 -0
  51. package/src/global/intelligence/model-candidate-catalog.js +456 -0
  52. package/src/global/intelligence/model-capability-registry-sources.js +145 -0
  53. package/src/global/intelligence/model-capability-registry.js +125 -0
  54. package/src/global/intelligence/model-intelligence.js +1646 -0
  55. package/src/global/intelligence/official-benchmark-snapshots.js +162 -0
  56. package/src/global/intelligence/quick-ask.js +149 -0
  57. package/src/global/intelligence/role-profiles.js +251 -0
  58. package/src/global/intelligence/skill-catalog.js +67 -0
  59. package/src/global/intelligence/subscription-pressure-source.js +41 -0
  60. package/src/global/mcp/kairo-mcp.js +51 -18
  61. package/src/global/mcp/work-snapshot-rule.js +4 -2
  62. package/src/global/mcp/workspace-binding.js +88 -0
  63. package/src/global/mcp/workspace-mcp-entry.js +74 -0
  64. package/src/global/mcp-install.js +8 -1
  65. package/src/global/observability/artificial-analysis-models.js +118 -0
  66. package/src/global/observability/claude-models.js +31 -0
  67. package/src/global/observability/claude-usage.js +112 -0
  68. package/src/global/observability/codex-models.js +96 -0
  69. package/src/global/observability/codex-usage.js +160 -0
  70. package/src/global/observability/cursor-auth.js +88 -0
  71. package/src/global/observability/cursor-models.js +101 -0
  72. package/src/global/observability/gentle-probe.js +30 -2
  73. package/src/global/observability/huggingface-leaderboard.js +97 -0
  74. package/src/global/observability/index.js +2 -1
  75. package/src/global/observability/opencode-models.js +101 -0
  76. package/src/global/observability/opencode-usage.js +162 -0
  77. package/src/global/paths.js +49 -2
  78. package/src/global/profile.js +23 -1
  79. package/src/global/runtime/execution-adapters/claude.js +63 -30
  80. package/src/global/runtime/execution-adapters/codex.js +9 -2
  81. package/src/global/runtime/execution-adapters/create-execution-adapter.js +6 -1
  82. package/src/global/runtime/execution-adapters/opencode.js +83 -18
  83. package/src/global/runtime/execution-worktree-manager.js +924 -0
  84. package/src/global/runtime/execution-worktree-orchestrator.js +194 -0
  85. package/src/global/runtime/execution-worktree-store.js +83 -0
  86. package/src/global/runtime/execution-worktree-types.js +45 -0
  87. package/src/global/runtime/run-events.js +38 -0
  88. package/src/global/runtime/run-manager.js +22 -6
  89. package/src/global/runtime/run-supervisor.js +41 -12
  90. package/src/global/runtime/usage-manager.js +96 -0
  91. package/src/global/runtime/usage-store.js +69 -0
  92. package/src/global/runtime/usage-types.js +62 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,82 @@ Historical entries below may reference the legacy `@kal-elsam/harness` package n
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ## 0.17.0 — 2026-09-17 (Kairo Runtime)
9
+
10
+ Minor release. Execution-worktree isolation (real git worktrees, per-role
11
+ commit transactions, preview + `git merge --ff-only` apply, cancellation
12
+ and crash recovery), a per-provider budget/usage state machine, automatic
13
+ Builder→Debugger→Tester orchestration, and several real `/project`
14
+ cockpit UX fixes.
15
+
16
+ ### Added
17
+
18
+ - Execution-worktree isolation (`execution-worktree-manager.js`): real
19
+ `git worktree add`-backed isolation per task, a per-role commit
20
+ transaction (`beginRoleRun`/`completeRoleRun`) that only ever commits
21
+ a role's own validated diff (Kairo owns every real commit, never the
22
+ agent), `previewWorktreeMerge`/`applyWorktreeMerge` (confirmation
23
+ bound to an exact `{baseSha, finalHeadSha, fingerprint}`, applied only
24
+ via `git merge --ff-only`), and `cancelWorktree`/`reconcileWorktrees`/
25
+ `discardWorktree` for cancellation and crash recovery.
26
+ - `controlledHeadSha`: every state-changing boundary (not just ACTIVE)
27
+ verifies the worktree's real HEAD against the last commit Kairo itself
28
+ produced, closing a rogue-commit bypass reachable while a worktree sat
29
+ idle in PENDING.
30
+ - Per-provider budget/usage state machine (`usage-manager.js`):
31
+ cumulative token/cost tracking per provider, a
32
+ HEALTHY/MODERATE/CONSERVE/CRITICAL/EXHAUSTED classification against a
33
+ configurable `profile.providerTokenBudgets`, and one real enforcement
34
+ point — a new run is refused once its provider is EXHAUSTED.
35
+ - `runOrchestratedChain` (`execution-worktree-orchestrator.js`):
36
+ automatic Builder→Debugger→Tester chaining inside one execution
37
+ worktree, stopping immediately on any real failure. Injects matching
38
+ real project skills (`docs/skills`, `.claude/skills`, etc. — the same
39
+ catalog ASK-mode routing already reads) into each role's task by
40
+ name/path, never their body content.
41
+ - `/project` cockpit overlay: the PROJECT TEAM panel now shows the real
42
+ decision reason behind each role's model, a real bordered panel, the
43
+ cockpit's own live spinner during every in-flight action, and real
44
+ mouse support (clicks now reach the analyst/role picker lists).
45
+
46
+ ### Fixed
47
+
48
+ - Dashboard and `/project status` showed `qualityTeam` (comparative
49
+ reference) under a "PROJECT TEAM" title while the interactive overlay
50
+ showed the real operational `projectTeam` — both now read the same
51
+ real operational team.
52
+ - MCP snapshot writes require `--workspace-bound` plus an absolute `--cwd`
53
+ that matches `process.cwd()` or the unique `WORKSPACE_FOLDER_PATHS` folder.
54
+ Bound servers expose only `kairo_publish_work_snapshot`; the Cursor
55
+ extension ships that runtime as `dist/kairo-workspace.cjs` and spawns
56
+ absolute Node ≥20 — never PATH `kairo`. See [MCP](docs/mcp.md).
57
+
58
+ ## 0.16.0 — 2026-08-13 (Kairo Runtime)
59
+
60
+ Minor release. Public `kairo control-plane` command and
61
+ `kairo.control-plane/v1` Gentle companion report. Publish tag:
62
+ `kairo-runtime-v0.16.0`. Extension VSIX stays out of this unit.
63
+
64
+ ### Added
65
+
66
+ - `kairo control-plane [--json] [--client cursor]`: atomic panel report
67
+ (work + Gentle workflow + team + attention) as `kairo.control-plane/v1`.
68
+ - Negotiate `gentle-ai.review-integration/v2` (protocol 2.0 and 2.1) before
69
+ any workflow fetch. Provider states: `connected`, `upgrade_required`,
70
+ `unavailable`, `incompatible`.
71
+ - Official `review status` from Gentle's announced bootstrap argv; pass
72
+ `next_transition` through unaltered. Receipt/gate only when Gentle publishes
73
+ them.
74
+ - `sdd-status --json` projection copies `changeName` / `nextRecommended` only.
75
+
76
+ ### Docs
77
+
78
+ - Gentle companion boundary: Kairo observes `gentle-ai.review-integration/v2`
79
+ and projects official `next_transition` / `sdd-status --json`. Freeze
80
+ `kairo review`, Cockpit receipts, orchestrator, and intelligence routing so
81
+ they do not feed the panel Workflow. Propose upstream `gentle-ai observe --json`
82
+ in Kairo docs only (`docs/gentle-companion.md`).
83
+
8
84
  ## 0.15.0 — 2026-08-12 (Kairo Runtime)
9
85
 
10
86
  Minor release. Declared Fleet board + model configure, and the Cursor
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kal-elsam/kairo-runtime",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "description": "Kairo Runtime — local agent operating system for Codex, Cursor, Claude, Pi, Engram, and Graphify.",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/Kal-elSam/harness#readme",
@@ -68,6 +68,7 @@
68
68
  },
69
69
  "dependencies": {
70
70
  "@clack/prompts": "^1.7.0",
71
+ "@earendil-works/pi-tui": "0.85.1",
71
72
  "@modelcontextprotocol/server": "2.0.0",
72
73
  "ansi-escapes": "^7.3.0",
73
74
  "ink": "^5.2.1",
@@ -121,7 +121,7 @@ function smokeNavigation(layoutMode) {
121
121
  region: COCKPIT_REGIONS.CONTENT,
122
122
  unicode: false
123
123
  });
124
- assert.match(footer.text, /Tab/);
124
+ assert.equal(footer.text, "1·2 Select - Enter Run - ? Help - Esc Exit");
125
125
 
126
126
  state = applyKey(state, { type: "escape" });
127
127
  assert.equal(state.view, ORCHESTRATOR_VIEWS.HOME);
@@ -76,9 +76,9 @@ echo "Kairo Runtime UX smoke — capturing terminal output to $CAPTURE_DIR"
76
76
  echo
77
77
 
78
78
  run_capture help help
79
- assert_contains "$CAPTURE_DIR/help.txt" "JSON output (--json on supported commands):"
80
- assert_contains "$CAPTURE_DIR/help.txt" "report"
81
- assert_contains "$CAPTURE_DIR/help.txt" "More examples: README.md"
79
+ assert_contains "$CAPTURE_DIR/help.txt" "Usage:"
80
+ assert_contains "$CAPTURE_DIR/help.txt" "kairo help --all"
81
+ assert_contains "$CAPTURE_DIR/help.txt" "Docs: README.md · docs/cli-reference.md"
82
82
  assert_not_contains "$CAPTURE_DIR/help.txt" "Stable fields: ok, overall"
83
83
  assert_exit help 0
84
84
 
package/src/cli.js CHANGED
@@ -41,6 +41,10 @@ import { runWorkspaceDetect, runWorkspaceDoctor, runWorkspaceInit, runWorkspaceU
41
41
  import { runOrchestratorDiagnostics, runOrchestratorShell } from "./global/orchestrator.js";
42
42
  import { runIntelligenceCli } from "./global/intelligence-cli.js";
43
43
  import { runGlobalRun, runGlobalRuns } from "./global/runtime/run-cli.js";
44
+ import { runArchitectCli, runPlansCli } from "./global/architect/architect-cli.js";
45
+ import { runConversationCli } from "./global/conversation/cli.js";
46
+ import { runUiCli } from "./global/conversation/ui.js";
47
+ import { runCockpitCli } from "./global/cockpit/cli.js";
44
48
  import { runGlobalReview, runGlobalReviews } from "./global/runtime/review/review-cli.js";
45
49
  import { runGlobalMonitor } from "./global/runtime/monitor/monitor-cli.js";
46
50
  import { runGlobalAlerts } from "./global/runtime/alerts/alert-cli.js";
@@ -116,6 +120,21 @@ export async function runCli(argv) {
116
120
  case "runs":
117
121
  await runGlobalRuns(optionsWithPolicy, packageManifest);
118
122
  return;
123
+ case "architect":
124
+ await runArchitectCli(optionsWithPolicy);
125
+ return;
126
+ case "plans":
127
+ await runPlansCli(optionsWithPolicy);
128
+ return;
129
+ case "conversation":
130
+ await runConversationCli(optionsWithPolicy);
131
+ return;
132
+ case "ui":
133
+ await runUiCli(optionsWithPolicy);
134
+ return;
135
+ case "start":
136
+ await runCockpitCli(optionsWithPolicy);
137
+ return;
119
138
  case "review":
120
139
  await runGlobalReview(optionsWithPolicy, packageManifest);
121
140
  return;
@@ -133,17 +152,11 @@ export async function runCli(argv) {
133
152
  return;
134
153
  case "mcp": {
135
154
  const { runMcpCli } = await import("./global/mcp-install.js");
136
- await runMcpCli({
137
- mcpAction: optionsWithPolicy.mcpAction ?? "serve",
138
- mcpClient: optionsWithPolicy.mcpClient ?? "cursor",
139
- yes: optionsWithPolicy.yes === true,
140
- json: optionsWithPolicy.json === true,
141
- cwd: optionsWithPolicy.cwd,
142
- cwdExplicit: optionsWithPolicy.cwdExplicit,
155
+ await runMcpCli(buildMcpCliOptions(optionsWithPolicy, {
143
156
  packageRoot,
144
157
  packageName: packageManifest.name,
145
158
  version: packageManifest.version
146
- });
159
+ }));
147
160
  return;
148
161
  }
149
162
  case "connections": {
@@ -178,6 +191,15 @@ export async function runCli(argv) {
178
191
  });
179
192
  return;
180
193
  }
194
+ case "control-plane": {
195
+ const { runControlPlaneCli } = await import("./global/control-plane/cli.js");
196
+ await runControlPlaneCli({
197
+ cwd: optionsWithPolicy.cwd,
198
+ json: optionsWithPolicy.json === true,
199
+ mcpClient: optionsWithPolicy.mcpClient ?? "cursor"
200
+ });
201
+ return;
202
+ }
181
203
  case "fleet": {
182
204
  const fleetAction = optionsWithPolicy.fleetAction ?? "show";
183
205
  if (fleetAction === "set") {
@@ -448,6 +470,19 @@ function argsWantsWorkspaceScope(args) {
448
470
  return false;
449
471
  }
450
472
 
473
+ export function buildMcpCliOptions(options = {}, extras = {}) {
474
+ return {
475
+ mcpAction: options.mcpAction ?? "serve",
476
+ mcpClient: options.mcpClient ?? "cursor",
477
+ yes: options.yes === true,
478
+ json: options.json === true,
479
+ cwd: options.cwd,
480
+ cwdExplicit: options.cwdExplicit,
481
+ workspaceBound: options.workspaceBound === true,
482
+ ...extras
483
+ };
484
+ }
485
+
451
486
  export function parseArgs(argv) {
452
487
  const args = [...argv];
453
488
  const firstArg = args[0];
@@ -457,6 +492,7 @@ export function parseArgs(argv) {
457
492
  const options = {
458
493
  cwd: process.cwd(),
459
494
  cwdExplicit: false,
495
+ workspaceBound: false,
460
496
  scope: null,
461
497
  mode: "standard",
462
498
  modeExplicit: false,
@@ -504,6 +540,9 @@ export function parseArgs(argv) {
504
540
  intelligencePaths: [],
505
541
  runsAction: null,
506
542
  runId: null,
543
+ plansAction: null,
544
+ conversationAction: null,
545
+ taskId: null,
507
546
  reviewId: null,
508
547
  lineage: null,
509
548
  reviewsAction: null,
@@ -543,7 +582,8 @@ export function parseArgs(argv) {
543
582
  activeOnly: false,
544
583
  timeoutMs: null,
545
584
  includePrivate: false,
546
- cloudConsent: false
585
+ cloudConsent: false,
586
+ port: null
547
587
  };
548
588
 
549
589
  if (command === "components") {
@@ -562,6 +602,12 @@ export function parseArgs(argv) {
562
602
  parseRunsAction(args, options);
563
603
  }
564
604
 
605
+ if (command === "plans") {
606
+ parsePlansAction(args, options);
607
+ }
608
+
609
+ if (command === "conversation") parseConversationAction(args, options);
610
+
565
611
  if (command === "reviews") {
566
612
  parseReviewsAction(args, options);
567
613
  }
@@ -597,6 +643,7 @@ export function parseArgs(argv) {
597
643
  options.cwd = resolve(args[++index]);
598
644
  options.cwdExplicit = true;
599
645
  }
646
+ else if (arg === "--workspace-bound") options.workspaceBound = true;
600
647
  else if (arg === "--scope") options.scope = parseScope(args[++index]);
601
648
  else if (arg.startsWith("--scope=")) options.scope = parseScope(arg.slice("--scope=".length));
602
649
  else if (arg === "--mode") {
@@ -678,7 +725,7 @@ export function parseArgs(argv) {
678
725
  else if (arg === "--simple") options.simple = true;
679
726
  else if (arg === "--task" || arg.startsWith("--task=")) {
680
727
  const taskValue = arg.startsWith("--task=") ? arg.slice("--task=".length) : args[++index];
681
- if (command === "run") options.task = taskValue;
728
+ if (command === "run" || command === "architect" || command === "conversation") options.task = taskValue;
682
729
  else options.intelligenceTask = taskValue;
683
730
  }
684
731
  else if (arg === "--prompt") options.intelligencePrompt = args[++index];
@@ -689,6 +736,8 @@ export function parseArgs(argv) {
689
736
  else if (arg.startsWith("--agent=")) options.agent = arg.slice("--agent=".length);
690
737
  else if (arg === "--model") options.model = args[++index];
691
738
  else if (arg.startsWith("--model=")) options.model = arg.slice("--model=".length);
739
+ else if (arg === "--role") options.role = args[++index];
740
+ else if (arg.startsWith("--role=")) options.role = arg.slice("--role=".length);
692
741
  else if (arg === "--strategy") {
693
742
  options.strategy = normalizeRunStrategy(requireFlagValue("--strategy", args[++index]));
694
743
  } else if (arg.startsWith("--strategy=")) {
@@ -718,6 +767,8 @@ export function parseArgs(argv) {
718
767
  else if (arg.startsWith("--timeout=")) options.timeoutMs = parsePositiveInt(arg.slice("--timeout=".length), "timeout") * 1000;
719
768
  else if (arg === "--include-private") options.includePrivate = true;
720
769
  else if (arg === "--cloud-consent") options.cloudConsent = true;
770
+ else if (arg === "--port") options.port = parsePositiveInt(args[++index], "port");
771
+ else if (arg.startsWith("--port=")) options.port = parsePositiveInt(arg.slice("--port=".length), "port");
721
772
  else if (arg === "--base") options.base = requireFlagValue("--base", args[++index]);
722
773
  else if (arg.startsWith("--base=")) options.base = requireFlagValue("--base", arg.slice("--base=".length));
723
774
  else if (arg === "--commit") options.commit = requireFlagValue("--commit", args[++index]);
@@ -734,7 +785,7 @@ export function parseArgs(argv) {
734
785
  else throw new Error(`Unknown option "${arg}".`);
735
786
  }
736
787
 
737
- if (command === "run" && !options.task && args.length > 0) {
788
+ if ((command === "run" || command === "architect") && !options.task && args.length > 0) {
738
789
  options.task = args.join(" ").trim();
739
790
  }
740
791
 
@@ -751,6 +802,24 @@ export function parseArgs(argv) {
751
802
  return { command, options, isImplicitCommand: implicitCommand };
752
803
  }
753
804
 
805
+ function parseConversationAction(args, options) {
806
+ const action = args[0];
807
+ if (!action || action.startsWith("-")) {
808
+ options.conversationAction = "snapshot";
809
+ return;
810
+ }
811
+ if (!new Set(["snapshot", "architect", "show", "approve", "reject", "execute", "cancel"]).has(action)) {
812
+ throw new Error(`Unknown conversation action "${action}".`);
813
+ }
814
+ args.shift();
815
+ options.conversationAction = action;
816
+ if (["show", "approve", "reject", "execute", "cancel"].includes(action)) {
817
+ const taskId = args.shift();
818
+ if (!taskId || taskId.startsWith("-")) throw new Error(`Missing task id for conversation ${action}.`);
819
+ options.taskId = taskId;
820
+ }
821
+ }
822
+
754
823
  function parseComponentsAction(args, options) {
755
824
  const action = args[0];
756
825
  if (!action || action.startsWith("-")) return;
@@ -869,6 +938,26 @@ function parseRunsAction(args, options) {
869
938
  }
870
939
  }
871
940
 
941
+ function parsePlansAction(args, options) {
942
+ const action = args[0];
943
+ if (!action || action.startsWith("-")) {
944
+ options.plansAction = "list";
945
+ return;
946
+ }
947
+ if (!new Set(["list", "show", "approve", "reject"]).has(action)) {
948
+ throw new Error(`Unknown plans action "${action}". Use list, show, approve, or reject.`);
949
+ }
950
+ args.shift();
951
+ options.plansAction = action;
952
+ if (action !== "list") {
953
+ const taskId = args[0];
954
+ if (!taskId || taskId.startsWith("-")) {
955
+ throw new Error(`Missing task id. Use: ${formatCliCommand(`plans ${action} <taskId>`)}`);
956
+ }
957
+ options.taskId = args.shift();
958
+ }
959
+ }
960
+
872
961
  function parseReviewsAction(args, options) {
873
962
  const action = args[0];
874
963
  if (!action || action.startsWith("-")) {
@@ -1044,6 +1133,11 @@ function normalizeCommand(command) {
1044
1133
  if (command === "orchestrator") return "orchestrator";
1045
1134
  if (command === "run") return "run";
1046
1135
  if (command === "runs") return "runs";
1136
+ if (command === "architect") return "architect";
1137
+ if (command === "plans") return "plans";
1138
+ if (command === "conversation") return "conversation";
1139
+ if (command === "ui") return "ui";
1140
+ if (command === "start") return "start";
1047
1141
  if (command === "review") return "review";
1048
1142
  if (command === "reviews") return "reviews";
1049
1143
  if (command === "monitor") return "monitor";
@@ -1052,6 +1146,7 @@ function normalizeCommand(command) {
1052
1146
  if (command === "mcp") return "mcp";
1053
1147
  if (command === "connections") return "connections";
1054
1148
  if (command === "next") return "next";
1149
+ if (command === "control-plane") return "control-plane";
1055
1150
  if (command === "fleet") return "fleet";
1056
1151
  if (command === "intelligence" || command === "intel") return "intelligence";
1057
1152
  if (command === "setup") return "setup";
@@ -28,9 +28,9 @@ export function createAgentCapabilityAdapter({
28
28
  return managedAdapter.detect(context);
29
29
  },
30
30
 
31
- inspect(context, { probeImpl = defaultProbe } = {}) {
31
+ inspect(context, { probeImpl = defaultProbe, isExecutableAvailableImpl = isExecutableAvailable } = {}) {
32
32
  const detected = managedAdapter.detect(context);
33
- const cliAvailable = executable ? isExecutableAvailable(executable) : false;
33
+ const cliAvailable = executable ? isExecutableAvailableImpl(executable) : false;
34
34
 
35
35
  if (!detected && !cliAvailable) {
36
36
  return buildInspection({
@@ -0,0 +1,76 @@
1
+ import { resolve } from "node:path";
2
+ import { createArchitecturePlan } from "./architect-manager.js";
3
+ import { PLAN_STATES } from "./architect-types.js";
4
+ import {
5
+ listTaskRecords, readTaskRecord, resolveProjectRoot, transitionTask
6
+ } from "./architect-store.js";
7
+ import { printJson } from "../json-output.js";
8
+ import { commandHeader } from "../brand/index.js";
9
+
10
+ function publicRecord(projectRoot, record) {
11
+ const status = record.status ?? record;
12
+ const planExists = record.planMarkdown != null
13
+ || [PLAN_STATES.AWAITING_APPROVAL, PLAN_STATES.APPROVED, PLAN_STATES.REJECTED].includes(status.state);
14
+ return {
15
+ ...status,
16
+ ...(record.taskMarkdown ? { taskMarkdown: record.taskMarkdown } : {}),
17
+ ...(record.planMarkdown ? { planMarkdown: record.planMarkdown } : {}),
18
+ planPath: status.artifacts?.plan && planExists
19
+ ? (record.paths?.planPath ?? resolve(projectRoot, status.artifacts.plan))
20
+ : null
21
+ };
22
+ }
23
+
24
+ export async function runArchitectCli(options, deps = {}) {
25
+ const createPlan = deps.createPlan ?? createArchitecturePlan;
26
+ const result = await createPlan({ task: options.task, cwd: options.cwd, model: options.model });
27
+ const data = {
28
+ ...publicRecord(result.status.projectRoot, { status: result.status, paths: result.paths }),
29
+ reused: result.reused === true
30
+ };
31
+ if (options.json) printJson(data);
32
+ else {
33
+ console.log(commandHeader(`architecture plan · ${data.taskId}`));
34
+ console.log(`State: ${data.state}`);
35
+ if (data.planPath) {
36
+ console.log(`Plan: ${data.artifacts.plan}`);
37
+ console.log(`Approve: kairo plans approve ${data.taskId}`);
38
+ } else {
39
+ console.log("Planning is still active; no plan artifact is ready yet.");
40
+ }
41
+ }
42
+ return data;
43
+ }
44
+
45
+ export async function runPlansCli(options, deps = {}) {
46
+ const projectRoot = await (deps.resolveRoot ?? resolveProjectRoot)(options.cwd);
47
+ const action = options.plansAction ?? "list";
48
+ if (action === "list") {
49
+ const plans = await (deps.listRecords ?? listTaskRecords)(projectRoot);
50
+ const data = { projectRoot, plans };
51
+ if (options.json) printJson(data);
52
+ else {
53
+ console.log(commandHeader("architecture plans"));
54
+ for (const plan of plans) console.log(` ${plan.taskId} ${plan.state} ${plan.artifacts?.plan ?? ""}`);
55
+ if (!plans.length) console.log(" (no plans)");
56
+ }
57
+ return data;
58
+ }
59
+ if (action === "show") {
60
+ const record = await (deps.readRecord ?? readTaskRecord)(projectRoot, options.taskId);
61
+ if (!record) throw new Error(`Plan "${options.taskId}" not found.`);
62
+ const data = publicRecord(projectRoot, record);
63
+ if (options.json) printJson(data);
64
+ else {
65
+ console.log(commandHeader(`architecture plan · ${data.taskId}`));
66
+ console.log(record.planMarkdown ?? `No plan artifact (${data.state}): ${data.error?.message ?? "planning did not complete"}`);
67
+ }
68
+ return data;
69
+ }
70
+ const state = action === "approve" ? PLAN_STATES.APPROVED : PLAN_STATES.REJECTED;
71
+ const record = await (deps.transition ?? transitionTask)(projectRoot, options.taskId, state);
72
+ const data = publicRecord(projectRoot, record);
73
+ if (options.json) printJson(data);
74
+ else console.log(`${data.taskId}: ${data.state}`);
75
+ return data;
76
+ }
@@ -0,0 +1,146 @@
1
+ import {
2
+ ReviewExecError,
3
+ assertBoundedProcessOk,
4
+ runBoundedProcess
5
+ } from "../runtime/review/review-exec.js";
6
+ import { buildCodexCliEnv } from "../runtime/review/review-codex.js";
7
+
8
+ export const ARCHITECT_CODEX_ERRORS = Object.freeze({
9
+ INVALID_JSONL: "invalid_jsonl",
10
+ MISSING_PLAN: "missing_plan",
11
+ STREAM_ERROR: "stream_error",
12
+ SUBSCRIPTION_AUTH_REQUIRED: "subscription_auth_required"
13
+ });
14
+
15
+ const AUTH_TIMEOUT_MS = 10_000;
16
+ const AUTH_OUTPUT_LIMIT = 8_192;
17
+
18
+ export function buildArchitectCodexArgs({ cwd, model = null } = {}) {
19
+ if (typeof cwd !== "string" || !cwd) throw new Error("Architect requires cwd.");
20
+ const args = [
21
+ "--ask-for-approval", "never",
22
+ "exec", "--json", "--ephemeral", "--ignore-user-config",
23
+ "--sandbox", "read-only", "-C", cwd,
24
+ "-c", "shell_environment_policy.inherit=none"
25
+ ];
26
+ if (model) args.push("-m", String(model));
27
+ args.push("-");
28
+ return args;
29
+ }
30
+
31
+ export function buildArchitectPrompt(task, { contextPack = null } = {}) {
32
+ return [
33
+ "You are the architecture planner for this repository.",
34
+ "Use the bounded Kairo context pack below as primary evidence.",
35
+ "Do not broadly scan the repository. Inspect additional files only when the context pack identifies a concrete gap required to plan safely.",
36
+ "Honor all AGENTS.md and governance instructions included in the context pack.",
37
+ "Do not modify files, run destructive commands, or implement code.",
38
+ "Return a concise implementation plan in Markdown with: Goal, Verified context, Design, Files, Tests, Risks, and Acceptance criteria.",
39
+ "Challenge unsupported assumptions and identify any blocking unknowns.",
40
+ "",
41
+ "TASK",
42
+ String(task).trim(),
43
+ "",
44
+ "BOUNDED KAIRO CONTEXT PACK",
45
+ contextPack?.systemPrompt ?? "No context pack was available; state this limitation in Verified context."
46
+ ].join("\n");
47
+ }
48
+
49
+ export function buildArchitectCodexEnv(sourceEnv = process.env) {
50
+ const env = buildCodexCliEnv(sourceEnv);
51
+ // M1 is subscription-only. Never let an ambient API key or alternate paid
52
+ // endpoint turn subscription exhaustion into PAYG.
53
+ for (const key of ["OPENAI_API_KEY", "OPENAI_BASE_URL", "OPENAI_API_BASE"]) delete env[key];
54
+ return env;
55
+ }
56
+
57
+ export async function verifyCodexSubscriptionAuth({
58
+ env = process.env, runProcess = runBoundedProcess
59
+ } = {}) {
60
+ const result = await runProcess({
61
+ command: "codex",
62
+ args: ["login", "status"],
63
+ env: buildArchitectCodexEnv(env),
64
+ stdin: null,
65
+ timeoutMs: AUTH_TIMEOUT_MS,
66
+ stdoutLimit: AUTH_OUTPUT_LIMIT,
67
+ stderrLimit: AUTH_OUTPUT_LIMIT
68
+ });
69
+ assertBoundedProcessOk(result);
70
+ const evidence = `${result.stdout ?? ""}\n${result.stderr ?? ""}`;
71
+ if (!/(?:^|\n)Logged in using ChatGPT\r?(?:\n|$)/.test(evidence)) {
72
+ throw new ReviewExecError(
73
+ "Codex must be logged in using ChatGPT subscription auth; API-key and unknown auth modes are refused.",
74
+ { code: ARCHITECT_CODEX_ERRORS.SUBSCRIPTION_AUTH_REQUIRED }
75
+ );
76
+ }
77
+ return { mode: "chatgpt" };
78
+ }
79
+
80
+ export function parseArchitectCodexJsonl(stdout) {
81
+ let plan = null;
82
+ let usage = null;
83
+ let streamError = null;
84
+ const raw = String(stdout ?? "");
85
+ const lines = raw.split(/\r?\n/);
86
+ const complete = raw.endsWith("\n") || raw.endsWith("\r\n") ? lines : lines.slice(0, -1);
87
+ for (const line of complete) {
88
+ const trimmed = line.trim();
89
+ if (!trimmed) continue;
90
+ let event;
91
+ try { event = JSON.parse(trimmed); }
92
+ catch (error) {
93
+ throw new ReviewExecError(`Malformed Codex JSONL: ${error.message}`, {
94
+ code: ARCHITECT_CODEX_ERRORS.INVALID_JSONL
95
+ });
96
+ }
97
+ if (!event || typeof event !== "object") {
98
+ throw new ReviewExecError("Malformed Codex JSONL event.", {
99
+ code: ARCHITECT_CODEX_ERRORS.INVALID_JSONL
100
+ });
101
+ }
102
+ if (event.type === "error") streamError = String(event.message ?? "Codex stream error.");
103
+ else if (event.type === "turn.failed") streamError = String(event.error?.message ?? "Codex turn failed.");
104
+ else if (event.type === "turn.completed" && event.usage && typeof event.usage === "object") {
105
+ const input = Number.isFinite(event.usage.input_tokens) ? event.usage.input_tokens : null;
106
+ const output = Number.isFinite(event.usage.output_tokens) ? event.usage.output_tokens : null;
107
+ usage = {
108
+ inputTokens: input,
109
+ outputTokens: output,
110
+ totalTokens: input != null && output != null ? input + output : null,
111
+ cost: null
112
+ };
113
+ } else if (event.type === "item.completed" && event.item?.type === "agent_message"
114
+ && typeof event.item.text === "string") {
115
+ plan = event.item.text;
116
+ }
117
+ }
118
+ if (streamError) {
119
+ throw new ReviewExecError(streamError, { code: ARCHITECT_CODEX_ERRORS.STREAM_ERROR });
120
+ }
121
+ if (typeof plan !== "string" || !plan.trim()) {
122
+ throw new ReviewExecError("Codex JSONL missing final architecture plan.", {
123
+ code: ARCHITECT_CODEX_ERRORS.MISSING_PLAN
124
+ });
125
+ }
126
+ return { plan: plan.trim(), usage };
127
+ }
128
+
129
+ export async function runArchitectCodex({
130
+ task, cwd, model = null, contextPack = null, env = process.env, spawnImpl, timeoutMs,
131
+ runProcess = runBoundedProcess, verifyAuth = verifyCodexSubscriptionAuth
132
+ } = {}) {
133
+ const codexEnv = buildArchitectCodexEnv(env);
134
+ await verifyAuth({ env: codexEnv });
135
+ const result = await runProcess({
136
+ command: "codex",
137
+ args: buildArchitectCodexArgs({ cwd, model }),
138
+ cwd,
139
+ env: codexEnv,
140
+ stdin: buildArchitectPrompt(task, { contextPack }),
141
+ spawnImpl,
142
+ timeoutMs
143
+ });
144
+ assertBoundedProcessOk(result);
145
+ return parseArchitectCodexJsonl(result.stdout);
146
+ }