@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.
- package/CHANGELOG.md +76 -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 +106 -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 +12 -1
- package/src/global/cockpit/app.js +475 -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 +683 -0
- package/src/global/cockpit/rows.js +148 -0
- package/src/global/cockpit/theme.js +118 -0
- package/src/global/cockpit/view.js +1263 -0
- package/src/global/control-plane/attention.js +141 -0
- package/src/global/control-plane/build-report.js +146 -0
- package/src/global/control-plane/cli.js +36 -0
- package/src/global/control-plane/constants.js +38 -0
- package/src/global/control-plane/gentle-adapters.js +183 -0
- package/src/global/control-plane/provider.js +69 -0
- package/src/global/control-plane/review-status.js +115 -0
- package/src/global/control-plane/sdd-status.js +49 -0
- package/src/global/control-plane/team.js +63 -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 +1063 -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 +444 -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/gentle-probe.js +30 -2
- package/src/global/observability/huggingface-leaderboard.js +97 -0
- package/src/global/observability/index.js +2 -1
- 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
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.
|
|
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.
|
|
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);
|
package/scripts/ux-smoke-test.sh
CHANGED
|
@@ -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" "
|
|
80
|
-
assert_contains "$CAPTURE_DIR/help.txt" "
|
|
81
|
-
assert_contains "$CAPTURE_DIR/help.txt" "
|
|
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 ?
|
|
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
|
+
}
|