@kal-elsam/kairo-runtime 0.5.1 → 0.7.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 (44) hide show
  1. package/README.md +44 -6
  2. package/global-template/components/catalog.json +4 -2
  3. package/global-template/components/orchestrator/orchestrator.md +1 -1
  4. package/global-template/core/orchestrator.md +1 -1
  5. package/package.json +3 -2
  6. package/scripts/cockpit-smoke.mjs +1 -1
  7. package/scripts/install.sh +1 -1
  8. package/scripts/installer-smoke-test.sh +6 -4
  9. package/src/cli.js +60 -1
  10. package/src/global/adapters/pi.js +53 -0
  11. package/src/global/agent-capabilities/index.js +11 -1
  12. package/src/global/brand/index.js +4 -2
  13. package/src/global/dashboard-guidance.js +1 -1
  14. package/src/global/global-cli.js +1 -1
  15. package/src/global/ink/cockpit-controller.js +10 -0
  16. package/src/global/ink/cockpit-focus.js +18 -3
  17. package/src/global/ink/cockpit-models.js +8 -1
  18. package/src/global/ink/cockpit-reviews.js +62 -0
  19. package/src/global/ink/cockpit-runs.js +11 -2
  20. package/src/global/ink/cockpit-views.js +19 -2
  21. package/src/global/ink/orchestrator-app.js +23 -3
  22. package/src/global/ink/orchestrator-state.js +2 -0
  23. package/src/global/ink/use-orchestrator-data.js +30 -0
  24. package/src/global/integrations/engram-evidence.js +41 -3
  25. package/src/global/integrations/sdd-destinations.js +2 -2
  26. package/src/global/paths.js +1 -0
  27. package/src/global/registry.js +2 -1
  28. package/src/global/runtime/execution-adapters/codex.js +2 -1
  29. package/src/global/runtime/execution-adapters/create-execution-adapter.js +1 -0
  30. package/src/global/runtime/execution-adapters/index.js +3 -1
  31. package/src/global/runtime/execution-adapters/pi.js +184 -0
  32. package/src/global/runtime/review/index.js +37 -0
  33. package/src/global/runtime/review/review-cli.js +150 -0
  34. package/src/global/runtime/review/review-codex.js +132 -0
  35. package/src/global/runtime/review/review-exec.js +136 -0
  36. package/src/global/runtime/review/review-fs.js +52 -0
  37. package/src/global/runtime/review/review-git.js +212 -0
  38. package/src/global/runtime/review/review-patch.js +122 -0
  39. package/src/global/runtime/review/review-pi.js +168 -0
  40. package/src/global/runtime/review/review-receipts.js +128 -0
  41. package/src/global/runtime/review/review-runner.js +119 -0
  42. package/src/global/runtime/review/review-types.js +108 -0
  43. package/src/global/runtime/review/review-validate.js +280 -0
  44. package/src/global/runtime/write-atomic-json.js +24 -20
@@ -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,
@@ -3,12 +3,13 @@ import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { probeCommand } from "../cli-probe.js";
5
5
 
6
- export const KAIRO_ENGRAM_AGENT_IDS = Object.freeze(["cursor", "codex", "opencode", "claude"]);
6
+ export const KAIRO_ENGRAM_AGENT_IDS = Object.freeze(["cursor", "codex", "opencode", "claude", "pi"]);
7
7
  export const ENGRAM_SETUP_SLUG_BY_AGENT = Object.freeze({
8
8
  cursor: "cursor",
9
9
  codex: "codex",
10
10
  opencode: "opencode",
11
- claude: "claude-code"
11
+ claude: "claude-code",
12
+ pi: "pi"
12
13
  });
13
14
  export const ENGRAM_MIN_VERSION = "1.19.0";
14
15
  export const ENGRAM_MAX_MAJOR = 2;
@@ -110,7 +111,7 @@ export function inspectEngramAgentConfig(agentId, { homeDir = homedir() } = {})
110
111
  const conflicts = evidence.filter((item) => item.conflict);
111
112
  let status = ENGRAM_INTEGRATION_STATUS.UNCONFIGURED;
112
113
  if (conflicts.length > 0) status = ENGRAM_INTEGRATION_STATUS.CONFLICT;
113
- else if (evidence.some((item) => item.present && item.kind === "mcp")) {
114
+ else if (isEngramAgentConfigured(agentId, evidence)) {
114
115
  status = ENGRAM_INTEGRATION_STATUS.CONFIGURED;
115
116
  }
116
117
  return { id: agentId, slug, status, evidence };
@@ -165,9 +166,46 @@ function collectAgentEvidence(agentId, homeDir) {
165
166
  fileEvidence(join(homeDir, ".cursor", "rules", "engram.mdc"), "protocol")
166
167
  ];
167
168
  }
169
+ if (agentId === "pi") {
170
+ return [
171
+ piSettingsPackagesEvidence(join(homeDir, ".pi", "agent", "settings.json")),
172
+ jsonKeyEvidence(join(homeDir, ".pi", "agent", "mcp.json"), ["mcpServers", "engram"], "mcp")
173
+ ];
174
+ }
168
175
  throw new Error(`Unsupported Engram agent "${agentId}".`);
169
176
  }
170
177
 
178
+ function isEngramAgentConfigured(agentId, evidence) {
179
+ if (agentId === "pi") {
180
+ return evidence.length > 0 && evidence.every((item) => item.present && !item.conflict);
181
+ }
182
+ return evidence.some((item) => item.present && item.kind === "mcp");
183
+ }
184
+
185
+ function piSettingsPackagesEvidence(path) {
186
+ const required = ["npm:gentle-engram", "npm:pi-mcp-adapter"];
187
+ if (!existsSync(path)) {
188
+ return { path, kind: "settings", present: false, conflict: false, required };
189
+ }
190
+ try {
191
+ const data = JSON.parse(readFileSync(path, "utf8"));
192
+ if (data == null || typeof data !== "object" || Array.isArray(data)) {
193
+ return { path, kind: "settings", present: false, conflict: true, required, detail: "invalid structure" };
194
+ }
195
+ const packages = Array.isArray(data.packages) ? data.packages : null;
196
+ if (packages == null) {
197
+ return { path, kind: "settings", present: false, conflict: true, required, detail: "invalid structure" };
198
+ }
199
+ const normalized = packages.map((entry) => String(entry));
200
+ const present = required.every((req) =>
201
+ normalized.some((entry) => entry === req || entry.startsWith(`${req}@`))
202
+ );
203
+ return { path, kind: "settings", present, conflict: false, required };
204
+ } catch {
205
+ return { path, kind: "settings", present: false, conflict: true, required, detail: "unreadable json" };
206
+ }
207
+ }
208
+
171
209
  function fileEvidence(path, kind) {
172
210
  return { path, kind, present: existsSync(path), conflict: false };
173
211
  }
@@ -12,8 +12,8 @@ export const SDD_SKILL_IDS = Object.freeze([
12
12
  "sdd-archive"
13
13
  ]);
14
14
 
15
- export const SDD_MANAGED_AGENT_IDS = Object.freeze(["cursor", "codex", "opencode", "claude"]);
16
- export const SDD_SHARED_AGENT_IDS = Object.freeze(["cursor", "codex", "opencode"]);
15
+ export const SDD_MANAGED_AGENT_IDS = Object.freeze(["cursor", "codex", "opencode", "claude", "pi"]);
16
+ export const SDD_SHARED_AGENT_IDS = Object.freeze(["cursor", "codex", "opencode", "pi"]);
17
17
  export const SDD_PERSONA_IDS = Object.freeze(["off", "teaching"]);
18
18
 
19
19
  export function resolveSddAgentSelection({
@@ -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
  };
@@ -4,8 +4,9 @@ import claude from "./adapters/claude.js";
4
4
  import codex from "./adapters/codex.js";
5
5
  import cursor from "./adapters/cursor.js";
6
6
  import opencode from "./adapters/opencode.js";
7
+ import pi from "./adapters/pi.js";
7
8
 
8
- const ADAPTERS = [cursor, codex, opencode, claude];
9
+ const ADAPTERS = [cursor, codex, opencode, claude, pi];
9
10
 
10
11
  export const GLOBAL_AGENT_IDS = ADAPTERS.map((adapter) => adapter.id);
11
12
 
@@ -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
 
@@ -2,8 +2,9 @@ import cursor from "./cursor.js";
2
2
  import codex from "./codex.js";
3
3
  import claude from "./claude.js";
4
4
  import opencode from "./opencode.js";
5
+ import pi from "./pi.js";
5
6
 
6
- const EXECUTION_ADAPTERS = [cursor, codex, claude, opencode];
7
+ const EXECUTION_ADAPTERS = [cursor, codex, claude, opencode, pi];
7
8
 
8
9
  export const EXECUTION_ADAPTER_IDS = EXECUTION_ADAPTERS.map((adapter) => adapter.id);
9
10
 
@@ -25,6 +26,7 @@ export function inspectExecutionAdapters(context = {}) {
25
26
  label: adapter.label,
26
27
  executable: adapter.executable,
27
28
  capabilities: adapter.capabilities,
29
+ reviewCompatible: Boolean(adapter.capabilities?.reviewCompatible),
28
30
  ...adapter.availability(context)
29
31
  }));
30
32
  }
@@ -0,0 +1,184 @@
1
+ import { createExecutionAdapter, parseNdjsonLine } from "./create-execution-adapter.js";
2
+ import { isExecutableAvailable, probeCommand } from "../../cli-probe.js";
3
+
4
+ const EXECUTABLE = "pi";
5
+ const READ_ONLY_TOOLS = "read,grep,find,ls";
6
+
7
+ const LIFECYCLE_KINDS = new Set([
8
+ "session",
9
+ "agent_start",
10
+ "agent_end",
11
+ "turn_start",
12
+ "message_start",
13
+ "message_update",
14
+ "compaction_start",
15
+ "compaction_end",
16
+ "auto_retry_start",
17
+ "auto_retry_end",
18
+ "queue_update",
19
+ "summarization_retry_scheduled",
20
+ "summarization_retry_attempt_start",
21
+ "summarization_retry_finished"
22
+ ]);
23
+
24
+ export function buildPiPermissionsArgs(permissions = []) {
25
+ if (!Array.isArray(permissions) || permissions.length === 0) return [];
26
+
27
+ const normalized = [...new Set(permissions.map((entry) => String(entry).toLowerCase()))];
28
+ if (normalized.length === 1 && normalized[0] === "read-only") {
29
+ return ["--tools", READ_ONLY_TOOLS];
30
+ }
31
+
32
+ throw new Error(
33
+ `Pi permissions only support "read-only" in Kairo 0.6.0 (got: ${permissions.join(", ")}). `
34
+ + "Other aliases are rejected and are never translated to --approve."
35
+ );
36
+ }
37
+
38
+ export function buildPiLaunch({ task, cwd, model, permissions = [], env = process.env } = {}) {
39
+ const args = [
40
+ "--mode",
41
+ "json",
42
+ "--no-session",
43
+ ...buildPiPermissionsArgs(permissions)
44
+ ];
45
+
46
+ if (model) {
47
+ args.push("--model", model);
48
+ }
49
+
50
+ args.push(task);
51
+
52
+ return {
53
+ command: EXECUTABLE,
54
+ args,
55
+ cwd,
56
+ env
57
+ };
58
+ }
59
+
60
+ export function checkPiAvailability(context = {}) {
61
+ const env = context.env ?? process.env;
62
+ const probe = context.probeImpl ?? probeCommand;
63
+ const availableCheck = context.isAvailableImpl ?? isExecutableAvailable;
64
+
65
+ if (!availableCheck(EXECUTABLE, { env })) {
66
+ return {
67
+ available: false,
68
+ compatible: false,
69
+ launchable: false,
70
+ reason: 'Pi CLI "pi" is not on PATH.'
71
+ };
72
+ }
73
+
74
+ const help = probe(EXECUTABLE, ["--help"], { env, timeoutMs: 5000 });
75
+ const text = `${help.stdout ?? ""}\n${help.stderr ?? ""}`;
76
+ const hasMode = /--mode\b/.test(text);
77
+ const hasNoSession = /--no-session\b/.test(text);
78
+
79
+ if (!help.ok || !hasMode || !hasNoSession) {
80
+ return {
81
+ available: true,
82
+ compatible: false,
83
+ launchable: false,
84
+ reason: "Pi CLI is missing required --mode / --no-session flags for auditable JSON runs."
85
+ };
86
+ }
87
+
88
+ return {
89
+ available: true,
90
+ compatible: true,
91
+ launchable: true,
92
+ reason: null
93
+ };
94
+ }
95
+
96
+ export function parsePiEventLine(line) {
97
+ const parsed = parseNdjsonLine(line);
98
+ if (!parsed || typeof parsed !== "object") return null;
99
+
100
+ if (parsed.type === "tool_execution_start") {
101
+ return {
102
+ type: "tool_call",
103
+ tool_name: parsed.toolName ?? "unknown",
104
+ status: "started",
105
+ id: parsed.toolCallId ?? null
106
+ };
107
+ }
108
+
109
+ if (parsed.type === "tool_execution_end") {
110
+ return {
111
+ type: "tool_result",
112
+ tool_name: parsed.toolName ?? "unknown",
113
+ status: parsed.isError ? "error" : "completed",
114
+ id: parsed.toolCallId ?? null
115
+ };
116
+ }
117
+
118
+ if (parsed.type === "tool_execution_update") {
119
+ return null;
120
+ }
121
+
122
+ if (parsed.type === "message_end") {
123
+ if (parsed.message?.role !== "assistant") return null;
124
+ const content = Array.isArray(parsed.message.content)
125
+ ? parsed.message.content
126
+ .filter((block) => block?.type === "text")
127
+ .map((block) => block.text ?? "")
128
+ .join("")
129
+ : "";
130
+ return {
131
+ type: "assistant",
132
+ role: "assistant",
133
+ content
134
+ };
135
+ }
136
+
137
+ if (parsed.type === "turn_end") {
138
+ const usage = parsed.message?.usage;
139
+ if (usage && typeof usage === "object") {
140
+ const cost = typeof usage.cost === "number"
141
+ ? usage.cost
142
+ : typeof usage.cost?.total === "number" ? usage.cost.total : null;
143
+ return {
144
+ type: "usage",
145
+ inputTokens: usage.input ?? usage.inputTokens ?? usage.input_tokens ?? null,
146
+ outputTokens: usage.output ?? usage.outputTokens ?? usage.output_tokens ?? null,
147
+ totalTokens: usage.totalTokens ?? usage.total ?? usage.total_tokens ?? null,
148
+ cost
149
+ };
150
+ }
151
+ return { type: "system", kind: "turn_end" };
152
+ }
153
+
154
+ if (LIFECYCLE_KINDS.has(parsed.type)) {
155
+ return {
156
+ type: "system",
157
+ kind: parsed.type,
158
+ ...(parsed.reason ? { reason: parsed.reason } : {}),
159
+ ...(parsed.attempt != null ? { attempt: parsed.attempt } : {})
160
+ };
161
+ }
162
+
163
+ return {
164
+ type: "system",
165
+ kind: parsed.type ?? "unknown"
166
+ };
167
+ }
168
+
169
+ export default createExecutionAdapter({
170
+ id: "pi",
171
+ label: "Pi",
172
+ executable: EXECUTABLE,
173
+ capabilities: {
174
+ structuredEvents: true,
175
+ tokens: true,
176
+ diff: false,
177
+ cancel: true,
178
+ transcript: true,
179
+ reviewCompatible: true
180
+ },
181
+ checkAvailability: checkPiAvailability,
182
+ buildLaunch: buildPiLaunch,
183
+ parseEventLine: parsePiEventLine
184
+ });
@@ -0,0 +1,37 @@
1
+ export {
2
+ REVIEW_SCOPE_MODES, REVIEW_SEVERITIES, REVIEW_STATES, REVIEW_EXIT_CODES, REVIEW_AGENTS,
3
+ REVIEW_LIMITS, REVIEW_SNAPSHOT_ERROR_CODES, ReviewSnapshotError, resolveReviewScopeMode,
4
+ createFindingId, canonicalFingerprint, isReviewPrivatePath, isBinaryContent,
5
+ assertReviewPathSafe, assertWithinReviewLimits, requirePrivateConsent
6
+ } from "./review-types.js";
7
+ export {
8
+ resolveReviewSnapshot, fingerprintReviewSnapshot, detectReviewSnapshotDrift,
9
+ readReviewRegularFile
10
+ } from "./review-git.js";
11
+ export {
12
+ REVIEW_PATCH_ERROR_CODES, filterDiffToAdmittedPaths, buildScopedReviewPatch
13
+ } from "./review-patch.js";
14
+ export {
15
+ REVIEW_VALIDATION_ERROR_CODES, ReviewValidationError,
16
+ validateReviewOutput, assertReceiptSecretFree
17
+ } from "./review-validate.js";
18
+ export {
19
+ assertSafeReviewId, createReviewId, reviewPaths,
20
+ buildReviewReceipt, saveReviewReceipt, loadReviewReceipt, listReviewReceipts
21
+ } from "./review-receipts.js";
22
+ export {
23
+ REVIEW_EXEC_ERROR_CODES, REVIEW_EXEC_LIMITS, ReviewExecError,
24
+ runBoundedProcess, assertBoundedProcessOk
25
+ } from "./review-exec.js";
26
+ export {
27
+ REVIEW_CODEX_ERROR_CODES, buildCodexReviewArgs, buildCodexCliEnv,
28
+ buildCodexReviewPrompt, parseCodexReviewJsonl, runCodexReview
29
+ } from "./review-codex.js";
30
+ export {
31
+ REVIEW_PI_ERROR_CODES, buildPiReviewArgs, buildPiCliEnv,
32
+ buildPiReviewPrompt, buildPiReviewStdin, parsePiReviewJsonl, runPiReview
33
+ } from "./review-pi.js";
34
+ export {
35
+ REVIEW_RUNNER_ERROR_CODES, ReviewRunnerError,
36
+ resolveReviewAgent, resolveReviewExitCode, runReview
37
+ } from "./review-runner.js";