@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.
- package/README.md +44 -6
- package/global-template/components/catalog.json +4 -2
- package/global-template/components/orchestrator/orchestrator.md +1 -1
- package/global-template/core/orchestrator.md +1 -1
- package/package.json +3 -2
- package/scripts/cockpit-smoke.mjs +1 -1
- package/scripts/install.sh +1 -1
- package/scripts/installer-smoke-test.sh +6 -4
- package/src/cli.js +60 -1
- package/src/global/adapters/pi.js +53 -0
- package/src/global/agent-capabilities/index.js +11 -1
- package/src/global/brand/index.js +4 -2
- package/src/global/dashboard-guidance.js +1 -1
- package/src/global/global-cli.js +1 -1
- package/src/global/ink/cockpit-controller.js +10 -0
- package/src/global/ink/cockpit-focus.js +18 -3
- package/src/global/ink/cockpit-models.js +8 -1
- package/src/global/ink/cockpit-reviews.js +62 -0
- package/src/global/ink/cockpit-runs.js +11 -2
- package/src/global/ink/cockpit-views.js +19 -2
- package/src/global/ink/orchestrator-app.js +23 -3
- package/src/global/ink/orchestrator-state.js +2 -0
- package/src/global/ink/use-orchestrator-data.js +30 -0
- package/src/global/integrations/engram-evidence.js +41 -3
- package/src/global/integrations/sdd-destinations.js +2 -2
- package/src/global/paths.js +1 -0
- package/src/global/registry.js +2 -1
- package/src/global/runtime/execution-adapters/codex.js +2 -1
- package/src/global/runtime/execution-adapters/create-execution-adapter.js +1 -0
- package/src/global/runtime/execution-adapters/index.js +3 -1
- package/src/global/runtime/execution-adapters/pi.js +184 -0
- package/src/global/runtime/review/index.js +37 -0
- package/src/global/runtime/review/review-cli.js +150 -0
- package/src/global/runtime/review/review-codex.js +132 -0
- package/src/global/runtime/review/review-exec.js +136 -0
- package/src/global/runtime/review/review-fs.js +52 -0
- package/src/global/runtime/review/review-git.js +212 -0
- package/src/global/runtime/review/review-patch.js +122 -0
- package/src/global/runtime/review/review-pi.js +168 -0
- package/src/global/runtime/review/review-receipts.js +128 -0
- package/src/global/runtime/review/review-runner.js +119 -0
- package/src/global/runtime/review/review-types.js +108 -0
- package/src/global/runtime/review/review-validate.js +280 -0
- 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.
|
|
162
|
-
?
|
|
163
|
-
:
|
|
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 (
|
|
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({
|
package/src/global/paths.js
CHANGED
|
@@ -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
|
};
|
package/src/global/registry.js
CHANGED
|
@@ -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
|
|
|
@@ -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";
|