@kal-elsam/kairo-runtime 0.6.0 → 0.8.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 (43) hide show
  1. package/README.md +36 -0
  2. package/global-template/components/catalog.json +4 -1
  3. package/global-template/components/orchestrator/extensions/pi/kairo-minion.js +604 -0
  4. package/package.json +1 -1
  5. package/scripts/cockpit-smoke.mjs +1 -1
  6. package/src/cli.js +69 -1
  7. package/src/global/adapters/pi.js +1 -1
  8. package/src/global/ink/cockpit-controller.js +10 -0
  9. package/src/global/ink/cockpit-focus.js +18 -3
  10. package/src/global/ink/cockpit-models.js +8 -1
  11. package/src/global/ink/cockpit-reviews.js +62 -0
  12. package/src/global/ink/cockpit-runs.js +11 -2
  13. package/src/global/ink/cockpit-views.js +19 -2
  14. package/src/global/ink/orchestrator-app.js +23 -3
  15. package/src/global/ink/orchestrator-state.js +2 -0
  16. package/src/global/ink/use-orchestrator-data.js +30 -0
  17. package/src/global/paths.js +1 -0
  18. package/src/global/runtime/execution-adapters/codex.js +2 -1
  19. package/src/global/runtime/execution-adapters/create-execution-adapter.js +1 -0
  20. package/src/global/runtime/execution-adapters/index.js +1 -0
  21. package/src/global/runtime/execution-adapters/pi.js +14 -3
  22. package/src/global/runtime/orchestration/index.js +23 -0
  23. package/src/global/runtime/orchestration/orch-receipts.js +234 -0
  24. package/src/global/runtime/orchestration/orch-types.js +173 -0
  25. package/src/global/runtime/orchestration/orch-validate.js +63 -0
  26. package/src/global/runtime/review/index.js +37 -0
  27. package/src/global/runtime/review/review-cli.js +150 -0
  28. package/src/global/runtime/review/review-codex.js +132 -0
  29. package/src/global/runtime/review/review-exec.js +136 -0
  30. package/src/global/runtime/review/review-fs.js +52 -0
  31. package/src/global/runtime/review/review-git.js +212 -0
  32. package/src/global/runtime/review/review-patch.js +122 -0
  33. package/src/global/runtime/review/review-pi.js +168 -0
  34. package/src/global/runtime/review/review-receipts.js +128 -0
  35. package/src/global/runtime/review/review-runner.js +119 -0
  36. package/src/global/runtime/review/review-types.js +108 -0
  37. package/src/global/runtime/review/review-validate.js +280 -0
  38. package/src/global/runtime/run-cli.js +1 -0
  39. package/src/global/runtime/run-manager.js +59 -4
  40. package/src/global/runtime/run-strategy.js +71 -0
  41. package/src/global/runtime/run-supervisor.js +22 -2
  42. package/src/global/runtime/run-types.js +5 -1
  43. package/src/global/runtime/write-atomic-json.js +24 -20
package/src/cli.js CHANGED
@@ -38,6 +38,8 @@ import { runWorkspaceDetect, runWorkspaceDoctor, runWorkspaceInit, runWorkspaceU
38
38
  import { runOrchestratorDiagnostics, runOrchestratorShell } from "./global/orchestrator.js";
39
39
  import { runIntelligenceCli } from "./global/intelligence-cli.js";
40
40
  import { runGlobalRun, runGlobalRuns } from "./global/runtime/run-cli.js";
41
+ import { runGlobalReview, runGlobalReviews } from "./global/runtime/review/review-cli.js";
42
+ import { normalizeRunStrategy } from "./global/runtime/run-strategy.js";
41
43
  import {
42
44
  LEGACY_PACKAGE_NAME,
43
45
  PACKAGE_NAME,
@@ -111,6 +113,12 @@ export async function runCli(argv) {
111
113
  case "runs":
112
114
  await runGlobalRuns(optionsWithPolicy, packageManifest);
113
115
  return;
116
+ case "review":
117
+ await runGlobalReview(optionsWithPolicy, packageManifest);
118
+ return;
119
+ case "reviews":
120
+ await runGlobalReviews(optionsWithPolicy, packageManifest);
121
+ return;
114
122
  case "intelligence":
115
123
  await runIntelligenceCli(optionsWithPolicy, packageManifest);
116
124
  return;
@@ -371,9 +379,15 @@ export function parseArgs(argv) {
371
379
  intelligencePaths: [],
372
380
  runsAction: null,
373
381
  runId: null,
382
+ reviewId: null,
383
+ reviewsAction: null,
384
+ base: null,
385
+ commit: null,
386
+ failOn: null,
374
387
  agent: null,
375
388
  task: null,
376
389
  model: null,
390
+ strategy: "direct",
377
391
  intelligenceBackend: null,
378
392
  permissions: null,
379
393
  captureTranscript: false,
@@ -401,6 +415,10 @@ export function parseArgs(argv) {
401
415
  parseRunsAction(args, options);
402
416
  }
403
417
 
418
+ if (command === "reviews") {
419
+ parseReviewsAction(args, options);
420
+ }
421
+
404
422
  if (command === "intelligence") {
405
423
  parseIntelligenceAction(args, options);
406
424
  }
@@ -483,6 +501,13 @@ export function parseArgs(argv) {
483
501
  else if (arg.startsWith("--agent=")) options.agent = arg.slice("--agent=".length);
484
502
  else if (arg === "--model") options.model = args[++index];
485
503
  else if (arg.startsWith("--model=")) options.model = arg.slice("--model=".length);
504
+ else if (arg === "--strategy") {
505
+ options.strategy = normalizeRunStrategy(requireFlagValue("--strategy", args[++index]));
506
+ } else if (arg.startsWith("--strategy=")) {
507
+ options.strategy = normalizeRunStrategy(
508
+ requireFlagValue("--strategy", arg.slice("--strategy=".length))
509
+ );
510
+ }
486
511
  else if (arg === "--backend") options.intelligenceBackend = args[++index] ?? "";
487
512
  else if (arg.startsWith("--backend=")) options.intelligenceBackend = arg.slice("--backend=".length);
488
513
  else if (arg === "--permissions") options.permissions = parsePathList(args[++index]);
@@ -495,6 +520,15 @@ export function parseArgs(argv) {
495
520
  else if (arg.startsWith("--timeout=")) options.timeoutMs = parsePositiveInt(arg.slice("--timeout=".length), "timeout") * 1000;
496
521
  else if (arg === "--include-private") options.includePrivate = true;
497
522
  else if (arg === "--cloud-consent") options.cloudConsent = true;
523
+ else if (arg === "--base") options.base = requireFlagValue("--base", args[++index]);
524
+ else if (arg.startsWith("--base=")) options.base = requireFlagValue("--base", arg.slice("--base=".length));
525
+ else if (arg === "--commit") options.commit = requireFlagValue("--commit", args[++index]);
526
+ else if (arg.startsWith("--commit=")) {
527
+ options.commit = requireFlagValue("--commit", arg.slice("--commit=".length));
528
+ } else if (arg === "--fail-on") options.failOn = requireFlagValue("--fail-on", args[++index]);
529
+ else if (arg.startsWith("--fail-on=")) {
530
+ options.failOn = requireFlagValue("--fail-on", arg.slice("--fail-on=".length));
531
+ }
498
532
  else if (arg === "--help" || arg === "-h") options.help = true;
499
533
  else if (arg === "--version" || arg === "-v") options.version = true;
500
534
  else throw new Error(`Unknown option "${arg}".`);
@@ -624,6 +658,26 @@ function parseRunsAction(args, options) {
624
658
  }
625
659
  }
626
660
 
661
+ function parseReviewsAction(args, options) {
662
+ const action = args[0];
663
+ if (!action || action.startsWith("-")) {
664
+ options.reviewsAction = "list";
665
+ return;
666
+ }
667
+ if (!new Set(["list", "show"]).has(action)) {
668
+ throw new Error(`Unknown reviews action "${action}". Use list or show.`);
669
+ }
670
+ args.shift();
671
+ options.reviewsAction = action;
672
+ if (action === "show") {
673
+ const reviewId = args[0];
674
+ if (!reviewId || reviewId.startsWith("-")) {
675
+ throw new Error(`Missing review id. Use: ${formatCliCommand("reviews show <reviewId>")}`);
676
+ }
677
+ options.reviewId = args.shift();
678
+ }
679
+ }
680
+
627
681
  function parseIntelligenceAction(args, options) {
628
682
  const action = args[0];
629
683
  if (!action || action.startsWith("-")) {
@@ -657,6 +711,13 @@ function parsePositiveInt(value, label) {
657
711
  return parsed;
658
712
  }
659
713
 
714
+ function requireFlagValue(flag, value) {
715
+ if (value == null || value === "" || String(value).startsWith("-")) {
716
+ throw new Error(`Missing value for ${flag}.`);
717
+ }
718
+ return value;
719
+ }
720
+
660
721
  function parseScope(value) {
661
722
  if (!SCOPES.has(value)) {
662
723
  throw new Error(`Invalid scope "${value}". Use agent-global or workspace.`);
@@ -673,6 +734,8 @@ function normalizeCommand(command) {
673
734
  if (command === "orchestrator") return "orchestrator";
674
735
  if (command === "run") return "run";
675
736
  if (command === "runs") return "runs";
737
+ if (command === "review") return "review";
738
+ if (command === "reviews") return "reviews";
676
739
  if (command === "intelligence" || command === "intel") return "intelligence";
677
740
  if (command === "setup") return "setup";
678
741
  if (command === "status") return "status";
@@ -743,10 +806,13 @@ Usage:
743
806
  ${cli} --dry-run Setup dry-run (scriptable)
744
807
  ${cli} --version
745
808
  ${cli} shell Operations cockpit (TTY)
746
- ${cli} run --agent <id> --task "..." [--model <name>] [--cwd <dir>] [--permissions force] [--capture-transcript] [--follow] [--no-wait] [--json]
809
+ ${cli} run --agent <id> --task "..." [--strategy direct|orchestrated] [--model <name>] [--cwd <dir>] [--permissions force] [--capture-transcript] [--follow] [--no-wait] [--json]
747
810
  ${cli} runs list [--json] [--limit <n>] [--active-only]
748
811
  ${cli} runs show <runId> [--json] [--limit <n>] [--follow]
749
812
  ${cli} runs stop <runId> [--json]
813
+ ${cli} review --agent codex|pi [--base <ref>|--commit <sha>] [--model <name>] [--include-private] [--yes|--confirm] [--fail-on high|medium|low] [--json]
814
+ ${cli} reviews list [--limit <n>] [--json]
815
+ ${cli} reviews show <reviewId> [--json]
750
816
  ${cli} orchestrator [--json] Read-only agent capability diagnostics
751
817
  ${cli} intelligence [status|models|context|route|ask] [--json]
752
818
  ${cli} intelligence models --backend opencode-go|opencode-zen|opencode
@@ -795,6 +861,8 @@ Commands:
795
861
  Tab switches region only when content is interactive (runs/launch).
796
862
  run Launch a managed agent run with local audit trail.
797
863
  runs List, inspect, or cancel agent runs under ~/.harness/runs/.
864
+ review Bounded read-only review via Codex or Pi; receipts under ~/.harness/reviews/.
865
+ reviews List or show prior review receipts (secret-free).
798
866
  orchestrator Read-only capability registry diagnostics (--json supported).
799
867
  intelligence Harness Engineering layer: backends, context packs, routing, budgets.
800
868
  Local-first (Ollama). Cloud only with --cloud-consent.
@@ -7,7 +7,7 @@ export const PI_CODING_AGENT_DIR_ENV = "PI_CODING_AGENT_DIR";
7
7
 
8
8
  const CUSTOM_DIR_MESSAGE =
9
9
  `Pi config writes are unsupported when ${PI_CODING_AGENT_DIR_ENV} points away from `
10
- + `~/${PI_DEFAULT_ROOT_DIR} (out of scope for Kairo 0.6.0). `
10
+ + `~/${PI_DEFAULT_ROOT_DIR} (out of scope for Kairo managed config). `
11
11
  + `Unset ${PI_CODING_AGENT_DIR_ENV} to manage the default directory; runtime launches remain available.`;
12
12
 
13
13
  /** True when PI_CODING_AGENT_DIR is set to a non-default absolute/relative path. */
@@ -144,6 +144,16 @@ export function reduceCockpitUi(state, action) {
144
144
  returnView: null
145
145
  };
146
146
  }
147
+ if (state.view === ORCHESTRATOR_VIEWS.REVIEW_DETAIL) {
148
+ const back = ORCHESTRATOR_VIEWS.REVIEWS;
149
+ return {
150
+ ...state,
151
+ view: back,
152
+ listIndex: 0,
153
+ region: defaultRegionForView(back, state.layoutMode),
154
+ returnView: null
155
+ };
156
+ }
147
157
  if (isRunsChildView(state.view)) {
148
158
  return goRunsHub(state);
149
159
  }
@@ -18,6 +18,8 @@ const CONTENT_INTERACTIVE_VIEWS = new Set([
18
18
  ORCHESTRATOR_VIEWS.ACTIVE_RUNS,
19
19
  ORCHESTRATOR_VIEWS.RECENT_RUNS,
20
20
  ORCHESTRATOR_VIEWS.RUN_DETAIL,
21
+ ORCHESTRATOR_VIEWS.REVIEWS,
22
+ ORCHESTRATOR_VIEWS.REVIEW_DETAIL,
21
23
  ORCHESTRATOR_VIEWS.LAUNCH,
22
24
  ORCHESTRATOR_VIEWS.ACTIVITY
23
25
  ]);
@@ -41,7 +43,11 @@ export function defaultRegionForView(view, layoutMode = LAYOUT_MODES.COMPACT) {
41
43
 
42
44
  export function interactiveRegionsFor(state) {
43
45
  const regions = regionsForLayout(state.layoutMode);
44
- if (!isContentInteractiveView(state.view) || state.view === ORCHESTRATOR_VIEWS.RUN_DETAIL) {
46
+ if (
47
+ !isContentInteractiveView(state.view)
48
+ || state.view === ORCHESTRATOR_VIEWS.RUN_DETAIL
49
+ || state.view === ORCHESTRATOR_VIEWS.REVIEW_DETAIL
50
+ ) {
45
51
  return regions.filter((region) => region === COCKPIT_REGIONS.NAV);
46
52
  }
47
53
  return regions.filter(
@@ -51,7 +57,12 @@ export function interactiveRegionsFor(state) {
51
57
 
52
58
  export function canTabBetweenRegions(state) {
53
59
  if (!isContentInteractiveView(state.view)) return false;
54
- if (state.view === ORCHESTRATOR_VIEWS.RUN_DETAIL) return false;
60
+ if (
61
+ state.view === ORCHESTRATOR_VIEWS.RUN_DETAIL
62
+ || state.view === ORCHESTRATOR_VIEWS.REVIEW_DETAIL
63
+ ) {
64
+ return false;
65
+ }
55
66
  return interactiveRegionsFor(state).length >= 2;
56
67
  }
57
68
 
@@ -70,7 +81,11 @@ export function routeCockpitKey(state, keyAction) {
70
81
  return { type: "arrow", direction: keyAction.direction };
71
82
  }
72
83
  if (state.region === COCKPIT_REGIONS.CONTENT && isContentInteractiveView(state.view)) {
73
- if (state.view === ORCHESTRATOR_VIEWS.RUN_DETAIL || state.view === ORCHESTRATOR_VIEWS.LAUNCH) {
84
+ if (
85
+ state.view === ORCHESTRATOR_VIEWS.RUN_DETAIL
86
+ || state.view === ORCHESTRATOR_VIEWS.REVIEW_DETAIL
87
+ || state.view === ORCHESTRATOR_VIEWS.LAUNCH
88
+ ) {
74
89
  return null;
75
90
  }
76
91
  return {
@@ -250,6 +250,11 @@ export function buildFooterModel({
250
250
  return { text: parts.join(` ${glyphs.bullet} `) };
251
251
  }
252
252
 
253
+ if (view === ORCHESTRATOR_VIEWS.REVIEW_DETAIL) {
254
+ parts.push("Esc Back");
255
+ return { text: parts.join(` ${glyphs.bullet} `) };
256
+ }
257
+
253
258
  if (view === ORCHESTRATOR_VIEWS.CHANGES) {
254
259
  return { text: buildChangesFooterParts(changesPhase).join(` ${glyphs.bullet} `) };
255
260
  }
@@ -263,6 +268,7 @@ export function buildFooterModel({
263
268
  const showTab = view === ORCHESTRATOR_VIEWS.RUNS
264
269
  || view === ORCHESTRATOR_VIEWS.ACTIVE_RUNS
265
270
  || view === ORCHESTRATOR_VIEWS.RECENT_RUNS
271
+ || view === ORCHESTRATOR_VIEWS.REVIEWS
266
272
  || view === ORCHESTRATOR_VIEWS.LAUNCH
267
273
  || view === ORCHESTRATOR_VIEWS.ACTIVITY;
268
274
  if (showTab) {
@@ -280,7 +286,8 @@ export function buildFooterModel({
280
286
  || region === COCKPIT_REGIONS.NAV
281
287
  || view === ORCHESTRATOR_VIEWS.RUNS
282
288
  || view === ORCHESTRATOR_VIEWS.ACTIVE_RUNS
283
- || view === ORCHESTRATOR_VIEWS.RECENT_RUNS) {
289
+ || view === ORCHESTRATOR_VIEWS.RECENT_RUNS
290
+ || view === ORCHESTRATOR_VIEWS.REVIEWS) {
284
291
  parts.push("Enter Open");
285
292
  }
286
293
  if (view !== ORCHESTRATOR_VIEWS.LAUNCH) {
@@ -0,0 +1,62 @@
1
+ /** Read-only Cockpit helpers for review receipts under ~/.harness/reviews/. */
2
+
3
+ export function countFindingsBySeverity(findings = []) {
4
+ const counts = { high: 0, medium: 0, low: 0 };
5
+ for (const finding of findings) {
6
+ const severity = String(finding?.severity ?? "").toLowerCase();
7
+ if (counts[severity] != null) counts[severity] += 1;
8
+ }
9
+ return counts;
10
+ }
11
+
12
+ export function selectReviewFromList(receipts = [], listIndex = 0) {
13
+ if (!Array.isArray(receipts) || receipts.length === 0) return null;
14
+ const index = Math.min(Math.max(0, listIndex), receipts.length - 1);
15
+ return receipts[index] ?? null;
16
+ }
17
+
18
+ export function formatReviewListLines(receipts = []) {
19
+ if (!Array.isArray(receipts) || receipts.length === 0) {
20
+ return ["No review receipts yet. Run kairo review --agent codex|pi."];
21
+ }
22
+ return receipts.map((receipt) => {
23
+ const counts = countFindingsBySeverity(receipt.findings);
24
+ const findingTotal = (receipt.findings ?? []).length;
25
+ const created = String(receipt.createdAt ?? "").slice(0, 19).replace("T", " ");
26
+ return `${receipt.reviewId} ${String(receipt.state).padEnd(10)} ${String(receipt.agentId).padEnd(6)} ${findingTotal}f (h${counts.high}/m${counts.medium}/l${counts.low}) ${created}`;
27
+ });
28
+ }
29
+
30
+ export function formatReviewDetailLines(receipt) {
31
+ if (!receipt) return ["Review receipt not found."];
32
+ const counts = countFindingsBySeverity(receipt.findings);
33
+ const snapshot = receipt.snapshot ?? {};
34
+ const totals = snapshot.totals ?? {};
35
+ const lines = [
36
+ `Id: ${receipt.reviewId}`,
37
+ `Agent: ${receipt.agentId}${receipt.model ? ` · model ${receipt.model}` : ""}`,
38
+ `State: ${receipt.state}`,
39
+ `Created: ${receipt.createdAt ?? "n/a"}`,
40
+ `Findings: ${(receipt.findings ?? []).length} (high ${counts.high}, medium ${counts.medium}, low ${counts.low})`,
41
+ `Snapshot: ${snapshot.mode ?? "n/a"} · files ${totals.fileCount ?? 0} · fingerprint ${String(snapshot.fingerprint ?? "").slice(0, 12) || "n/a"}`
42
+ ];
43
+ if ((receipt.warnings ?? []).length > 0) {
44
+ lines.push(`Warnings: ${receipt.warnings.length}`);
45
+ }
46
+ const findings = receipt.findings ?? [];
47
+ if (findings.length === 0) {
48
+ lines.push("", "No findings recorded.");
49
+ return lines;
50
+ }
51
+ lines.push("", "Findings (read-only):");
52
+ for (const finding of findings.slice(0, 20)) {
53
+ const loc = finding.line != null ? `:${finding.line}` : "";
54
+ lines.push(
55
+ ` [${String(finding.severity).toUpperCase()}] ${finding.path ?? "?"}${loc} — ${finding.title ?? "untitled"}`
56
+ );
57
+ }
58
+ if (findings.length > 20) {
59
+ lines.push(` … ${findings.length - 20} more (use kairo reviews show ${receipt.reviewId})`);
60
+ }
61
+ return lines;
62
+ }
@@ -13,6 +13,12 @@ export const RUNS_HUB_ITEMS = [
13
13
  view: ORCHESTRATOR_VIEWS.RECENT_RUNS,
14
14
  description: "Review completed and failed run outcomes."
15
15
  },
16
+ {
17
+ id: "reviews",
18
+ label: "Reviews",
19
+ view: ORCHESTRATOR_VIEWS.REVIEWS,
20
+ description: "Browse secret-free bounded-review receipts (read-only)."
21
+ },
16
22
  {
17
23
  id: "launch",
18
24
  label: "New run",
@@ -27,13 +33,16 @@ export function isRunsBranchView(view) {
27
33
  || view === ORCHESTRATOR_VIEWS.ACTIVE_RUNS
28
34
  || view === ORCHESTRATOR_VIEWS.RECENT_RUNS
29
35
  || view === ORCHESTRATOR_VIEWS.LAUNCH
30
- || view === ORCHESTRATOR_VIEWS.RUN_DETAIL;
36
+ || view === ORCHESTRATOR_VIEWS.RUN_DETAIL
37
+ || view === ORCHESTRATOR_VIEWS.REVIEWS
38
+ || view === ORCHESTRATOR_VIEWS.REVIEW_DETAIL;
31
39
  }
32
40
 
33
41
  export function isRunsChildView(view) {
34
42
  return view === ORCHESTRATOR_VIEWS.ACTIVE_RUNS
35
43
  || view === ORCHESTRATOR_VIEWS.RECENT_RUNS
36
- || view === ORCHESTRATOR_VIEWS.LAUNCH;
44
+ || view === ORCHESTRATOR_VIEWS.LAUNCH
45
+ || view === ORCHESTRATOR_VIEWS.REVIEWS;
37
46
  }
38
47
 
39
48
  export function resolveRunsHubItem(listIndex = 0, items = RUNS_HUB_ITEMS) {
@@ -14,6 +14,7 @@ import {
14
14
  import { windowLinesForLayout } from "./cockpit-models.js";
15
15
  import { LAYOUT_MODES } from "./layout.js";
16
16
  import { formatRunsHubLines, RUNS_HUB_ITEMS } from "./cockpit-runs.js";
17
+ import { formatReviewDetailLines, formatReviewListLines } from "./cockpit-reviews.js";
17
18
  import { formatChangesActionLines } from "./cockpit-changes.js";
18
19
  import { formatRecoveryLines } from "./cockpit-recovery.js";
19
20
 
@@ -74,6 +75,8 @@ export function renderCockpitView({
74
75
  layoutMode = LAYOUT_MODES.COMPACT,
75
76
  selectedRun,
76
77
  selectedEvents,
78
+ reviews = [],
79
+ selectedReview = null,
77
80
  changesAction = null,
78
81
  recoveryAction = null,
79
82
  colorEnabled = true
@@ -112,7 +115,7 @@ export function renderCockpitView({
112
115
  formatRunsHubLines(RUNS_HUB_ITEMS),
113
116
  listIndex,
114
117
  colorEnabled,
115
- "Choose Active runs, History, or New run."
118
+ "Choose Active runs, History, Reviews, or New run."
116
119
  );
117
120
  case ORCHESTRATOR_VIEWS.ACTIVE_RUNS:
118
121
  return listBlock(
@@ -136,6 +139,14 @@ export function renderCockpitView({
136
139
  colorEnabled,
137
140
  "Open Runs after governance is healthy."
138
141
  );
142
+ case ORCHESTRATOR_VIEWS.REVIEWS:
143
+ return listBlock(
144
+ "Reviews",
145
+ formatReviewListLines(reviews),
146
+ listIndex,
147
+ colorEnabled,
148
+ "Receipts are read-only. Launch reviews via kairo review --agent codex|pi."
149
+ );
139
150
  case ORCHESTRATOR_VIEWS.LAUNCH:
140
151
  if (launchableAgents.length === 0) {
141
152
  return React.createElement(CockpitEmptyState, {
@@ -165,6 +176,12 @@ export function renderCockpitView({
165
176
  formatRunDetailLines(selectedRun, selectedEvents)
166
177
  .map((line) => React.createElement(Text, { key: line }, line))
167
178
  );
179
+ case ORCHESTRATOR_VIEWS.REVIEW_DETAIL:
180
+ return React.createElement(Box, { flexDirection: "column" },
181
+ React.createElement(Text, { bold: true }, "Review detail"),
182
+ formatReviewDetailLines(selectedReview)
183
+ .map((line) => React.createElement(Text, { key: line }, line))
184
+ );
168
185
  case ORCHESTRATOR_VIEWS.DIAGNOSTICS:
169
186
  return governanceList(
170
187
  "System health",
@@ -178,7 +195,7 @@ export function renderCockpitView({
178
195
  React.createElement(Text, null, "Kairo keeps IDEs and agents aligned with project architecture and workflows."),
179
196
  React.createElement(Text, null, "Primary flow: scan → findings → preview → confirm → apply → re-scan."),
180
197
  React.createElement(Text, null, "↑↓ navigate · Enter open · Esc back/exit · R refresh/retry · ? help"),
181
- React.createElement(Text, null, "Runs are secondary after setup and repairs.")
198
+ React.createElement(Text, null, "Runs are secondary after setup and repairs. Reviews are read-only receipts.")
182
199
  );
183
200
  default: {
184
201
  const _exhaustive = view;
@@ -25,6 +25,7 @@ import {
25
25
  import { buildControlCenterModel } from "./cockpit-control-center.js";
26
26
  import { resolveEnterNavIntent } from "./cockpit-enter.js";
27
27
  import { resolveRunsHubItem, RUNS_HUB_ITEMS } from "./cockpit-runs.js";
28
+ import { selectReviewFromList } from "./cockpit-reviews.js";
28
29
  import { resolveProjectReadiness } from "../dashboard-guidance.js";
29
30
  import { CONTROL_PLANE_HEALTH } from "../control-plane-snapshot.js";
30
31
  import { CockpitShell } from "./cockpit/primitives.js";
@@ -142,6 +143,7 @@ export function OrchestratorApp({
142
143
  return;
143
144
  }
144
145
  data.setSelectedRun(null);
146
+ data.setSelectedReview(null);
145
147
  data.resetLaunchWizard();
146
148
  dispatch({ type: "escape" });
147
149
  return;
@@ -158,9 +160,11 @@ export function OrchestratorApp({
158
160
  ? (data.dashboard?.activeRuns ?? []).length
159
161
  : ui.view === ORCHESTRATOR_VIEWS.RECENT_RUNS
160
162
  ? (data.dashboard?.recentRuns ?? []).length
161
- : ui.view === ORCHESTRATOR_VIEWS.ACTIVITY
162
- ? listRecoverySnapshots(data.snapshot).length
163
- : 0;
163
+ : ui.view === ORCHESTRATOR_VIEWS.REVIEWS
164
+ ? (data.reviews ?? []).length
165
+ : ui.view === ORCHESTRATOR_VIEWS.ACTIVITY
166
+ ? listRecoverySnapshots(data.snapshot).length
167
+ : 0;
164
168
 
165
169
  let routed = null;
166
170
  if (key.tab) {
@@ -229,6 +233,9 @@ export function OrchestratorApp({
229
233
  if (hubItem.action === "launch") {
230
234
  data.resetLaunchWizard();
231
235
  }
236
+ if (hubItem.view === ORCHESTRATOR_VIEWS.REVIEWS) {
237
+ data.loadReviews().catch(() => {});
238
+ }
232
239
  dispatch({
233
240
  type: "set-view",
234
241
  view: hubItem.view,
@@ -247,6 +254,13 @@ export function OrchestratorApp({
247
254
  return;
248
255
  }
249
256
 
257
+ if (ui.region === COCKPIT_REGIONS.CONTENT
258
+ && ui.view === ORCHESTRATOR_VIEWS.REVIEWS
259
+ && key.return) {
260
+ data.openReviewDetail(selectReviewFromList(data.reviews ?? [], ui.listIndex), dispatch);
261
+ return;
262
+ }
263
+
250
264
  if (ui.view === ORCHESTRATOR_VIEWS.RUN_DETAIL) {
251
265
  if (inputKey.toLowerCase() === "c" && isRunCancellable(data.selectedRun)) {
252
266
  data.handleCancelRun();
@@ -304,6 +318,10 @@ export function OrchestratorApp({
304
318
  }
305
319
 
306
320
  if (inputKey.toLowerCase() === "r" && ui.view !== ORCHESTRATOR_VIEWS.LAUNCH) {
321
+ if (ui.view === ORCHESTRATOR_VIEWS.REVIEWS || ui.view === ORCHESTRATOR_VIEWS.REVIEW_DETAIL) {
322
+ data.loadReviews().catch(() => {});
323
+ return;
324
+ }
307
325
  data.reload().catch(() => {});
308
326
  }
309
327
  });
@@ -406,6 +424,8 @@ export function OrchestratorApp({
406
424
  launchableAgents: data.launchableAgents,
407
425
  selectedRun: data.selectedRun,
408
426
  selectedEvents: data.selectedEvents,
427
+ reviews: data.reviews,
428
+ selectedReview: data.selectedReview,
409
429
  changesAction: data.changesAction,
410
430
  recoveryAction: data.recoveryAction,
411
431
  controlCenter,
@@ -22,6 +22,8 @@ export const ORCHESTRATOR_VIEWS = {
22
22
  ACTIVE_RUNS: "active-runs",
23
23
  RECENT_RUNS: "recent-runs",
24
24
  RUN_DETAIL: "run-detail",
25
+ REVIEWS: "reviews",
26
+ REVIEW_DETAIL: "review-detail",
25
27
  PROVIDERS: "providers",
26
28
  LAUNCH: "launch",
27
29
  DIAGNOSTICS: "diagnostics",
@@ -33,6 +33,8 @@ import {
33
33
  listRecoverySnapshots,
34
34
  reduceRecoveryAction
35
35
  } from "./cockpit-recovery.js";
36
+ import { listReviewReceipts } from "../runtime/review/review-receipts.js";
37
+ import { assertReceiptSecretFree } from "../runtime/review/review-validate.js";
36
38
 
37
39
  export function useOrchestratorData({
38
40
  homeDir,
@@ -50,6 +52,8 @@ export function useOrchestratorData({
50
52
  const [snapshot, setSnapshot] = useState(null);
51
53
  const [selectedRun, setSelectedRun] = useState(null);
52
54
  const [selectedEvents, setSelectedEvents] = useState([]);
55
+ const [reviews, setReviews] = useState([]);
56
+ const [selectedReview, setSelectedReview] = useState(null);
53
57
  const [statusMessage, setStatusMessage] = useState(null);
54
58
  const [launchAgentIndex, setLaunchAgentIndex] = useState(0);
55
59
  const [launchStep, setLaunchStep] = useState(LAUNCH_WIZARD_STEPS.AGENT);
@@ -124,6 +128,27 @@ export function useOrchestratorData({
124
128
  });
125
129
  };
126
130
 
131
+ const loadReviews = async () => {
132
+ const receipts = (await listReviewReceipts({ homeDir, limit: 50 }))
133
+ .map((receipt) => assertReceiptSecretFree(receipt));
134
+ setReviews(receipts);
135
+ if (selectedReview) {
136
+ const refreshed = receipts.find((entry) => entry.reviewId === selectedReview.reviewId) ?? null;
137
+ setSelectedReview(refreshed);
138
+ }
139
+ return receipts;
140
+ };
141
+
142
+ const openReviewDetail = (receipt, dispatch) => {
143
+ if (!receipt) return;
144
+ setSelectedReview(assertReceiptSecretFree(receipt));
145
+ dispatch({
146
+ type: "set-view",
147
+ view: ORCHESTRATOR_VIEWS.REVIEW_DETAIL,
148
+ returnView: ORCHESTRATOR_VIEWS.REVIEWS
149
+ });
150
+ };
151
+
127
152
  const handleLaunch = async (draft, profile, dispatch) => {
128
153
  if (!draft.agentId || !draft.task.trim()) {
129
154
  setError("Agent and task are required.");
@@ -353,6 +378,9 @@ export function useOrchestratorData({
353
378
  selectedRun,
354
379
  setSelectedRun,
355
380
  selectedEvents,
381
+ reviews,
382
+ selectedReview,
383
+ setSelectedReview,
356
384
  statusMessage,
357
385
  launchAgentIndex,
358
386
  setLaunchAgentIndex,
@@ -369,6 +397,8 @@ export function useOrchestratorData({
369
397
  reload,
370
398
  resetLaunchWizard,
371
399
  openRunDetail,
400
+ loadReviews,
401
+ openReviewDetail,
372
402
  handleLaunch,
373
403
  handleCancelRun,
374
404
  previewChanges,
@@ -18,6 +18,7 @@ export function harnessHomePaths(homeDir) {
18
18
  profilePath: join(root, "profile.json"),
19
19
  historyPath: join(root, "history.jsonl"),
20
20
  runsDir: join(root, "runs"),
21
+ reviewsDir: join(root, "reviews"),
21
22
  coreDir: join(root, "core"),
22
23
  backupsDir: join(root, "backups")
23
24
  };
@@ -71,7 +71,8 @@ export default createExecutionAdapter({
71
71
  tokens: true,
72
72
  diff: false,
73
73
  cancel: true,
74
- transcript: true
74
+ transcript: true,
75
+ reviewCompatible: true
75
76
  },
76
77
  buildLaunch: buildCodexLaunch,
77
78
  parseEventLine: parseCodexEventLine
@@ -20,6 +20,7 @@ export function createExecutionAdapter({
20
20
  diff: false,
21
21
  cancel: true,
22
22
  transcript: false,
23
+ reviewCompatible: false,
23
24
  ...capabilities
24
25
  },
25
26
 
@@ -26,6 +26,7 @@ export function inspectExecutionAdapters(context = {}) {
26
26
  label: adapter.label,
27
27
  executable: adapter.executable,
28
28
  capabilities: adapter.capabilities,
29
+ reviewCompatible: Boolean(adapter.capabilities?.reviewCompatible),
29
30
  ...adapter.availability(context)
30
31
  }));
31
32
  }
@@ -30,12 +30,15 @@ export function buildPiPermissionsArgs(permissions = []) {
30
30
  }
31
31
 
32
32
  throw new Error(
33
- `Pi permissions only support "read-only" in Kairo 0.6.0 (got: ${permissions.join(", ")}). `
33
+ `Pi permissions only support "read-only" (got: ${permissions.join(", ")}). `
34
34
  + "Other aliases are rejected and are never translated to --approve."
35
35
  );
36
36
  }
37
37
 
38
- export function buildPiLaunch({ task, cwd, model, permissions = [], env = process.env } = {}) {
38
+ export function buildPiLaunch({
39
+ task, cwd, model, permissions = [], env = process.env,
40
+ strategy = "direct", extensionPath = null
41
+ } = {}) {
39
42
  const args = [
40
43
  "--mode",
41
44
  "json",
@@ -47,6 +50,13 @@ export function buildPiLaunch({ task, cwd, model, permissions = [], env = proces
47
50
  args.push("--model", model);
48
51
  }
49
52
 
53
+ if (strategy === "orchestrated") {
54
+ if (typeof extensionPath !== "string" || !extensionPath) {
55
+ throw new Error("Orchestrated Pi requires a managed extension path.");
56
+ }
57
+ args.push("--no-extensions", "--extension", extensionPath);
58
+ }
59
+
50
60
  args.push(task);
51
61
 
52
62
  return {
@@ -175,7 +185,8 @@ export default createExecutionAdapter({
175
185
  tokens: true,
176
186
  diff: false,
177
187
  cancel: true,
178
- transcript: true
188
+ transcript: true,
189
+ reviewCompatible: true
179
190
  },
180
191
  checkAvailability: checkPiAvailability,
181
192
  buildLaunch: buildPiLaunch,
@@ -0,0 +1,23 @@
1
+ import { join } from "node:path";
2
+ import { harnessHomePaths } from "../../paths.js";
3
+
4
+ export {
5
+ RUN_STRATEGIES, DAG_NODE_STATES, DAG_TERMINAL_STATES, ORCH_LIMITS, ORCH_ERROR_CODES,
6
+ OrchContractError, createTaskId, isTerminalDagState, normalizeRunStrategy,
7
+ createOrchLineage, createBudgetUsage, createDagNode, createMinionBrief,
8
+ createMinionResult, digestAllowlisted
9
+ } from "./orch-types.js";
10
+ export { assertOrchReceiptSecretFree, FORBIDDEN_KEYS, walkForbiddenKeys } from "./orch-validate.js";
11
+ export {
12
+ orchPaths, buildOrchReceipt, saveOrchReceipt, loadOrchReceipt,
13
+ createOrchState, saveOrchState, loadOrchState, terminalizeOrchNodes,
14
+ updateOrchState, applyMinionDagUpdate, finalizeOrchState, reconcileOrchState
15
+ } from "./orch-receipts.js";
16
+
17
+ export const KAIRO_MINION_RELATIVE_ASSET =
18
+ "components/orchestrator/extensions/pi/kairo-minion.js";
19
+
20
+ /** Materialized extension path under ~/.harness (never Pi global auto-discover). */
21
+ export function resolveKairoMinionExtensionPath(homeDir) {
22
+ return join(harnessHomePaths(homeDir).root, KAIRO_MINION_RELATIVE_ASSET);
23
+ }