@kal-elsam/kairo-runtime 0.2.0 → 0.2.2

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.
@@ -1,5 +1,6 @@
1
1
  import { stdin as input, stdout as output } from "node:process";
2
2
  import { canUseSetupInk } from "./terminal.js";
3
+ import { isActiveRunState, formatTaskLabel } from "../runtime/run-types.js";
3
4
 
4
5
  export function canUseOrchestratorShell({
5
6
  interactive = Boolean(input.isTTY && output.isTTY),
@@ -12,96 +13,268 @@ export function canUseOrchestratorShell({
12
13
 
13
14
  export const ORCHESTRATOR_VIEWS = {
14
15
  HOME: "home",
15
- AGENTS: "agents",
16
- PROFILE: "profile",
17
- PLAN: "plan",
18
- CONFIRM: "confirm",
19
- HELP: "help",
20
- INTELLIGENCE: "intelligence"
16
+ ACTIVE_RUNS: "active-runs",
17
+ RECENT_RUNS: "recent-runs",
18
+ RUN_DETAIL: "run-detail",
19
+ PROVIDERS: "providers",
20
+ LAUNCH: "launch",
21
+ DIAGNOSTICS: "diagnostics",
22
+ HELP: "help"
21
23
  };
22
24
 
23
25
  export const ORCHESTRATOR_MENU = [
24
- { id: "status", label: "Diagnostics", view: ORCHESTRATOR_VIEWS.HOME },
25
- { id: "agents", label: "Agents", view: ORCHESTRATOR_VIEWS.AGENTS },
26
- { id: "intelligence", label: "Intelligence", view: ORCHESTRATOR_VIEWS.INTELLIGENCE },
27
- { id: "profile", label: "Profile", view: ORCHESTRATOR_VIEWS.PROFILE },
28
- { id: "plan-setup", label: "Plan setup", view: ORCHESTRATOR_VIEWS.PLAN, action: "setup" },
26
+ { id: "active", label: "Active runs", view: ORCHESTRATOR_VIEWS.ACTIVE_RUNS },
27
+ { id: "recent", label: "Recent runs", view: ORCHESTRATOR_VIEWS.RECENT_RUNS },
28
+ { id: "providers", label: "Providers", view: ORCHESTRATOR_VIEWS.PROVIDERS },
29
+ { id: "launch", label: "Launch run", view: ORCHESTRATOR_VIEWS.LAUNCH, action: "launch" },
30
+ { id: "diagnostics", label: "Diagnostics", view: ORCHESTRATOR_VIEWS.DIAGNOSTICS },
29
31
  { id: "help", label: "Help", view: ORCHESTRATOR_VIEWS.HELP }
30
32
  ];
31
33
 
32
- export function formatAgentStatusLines(capabilities) {
33
- return capabilities.map((entry) => {
34
- const auth = entry.authenticated == null ? "n/a" : (entry.authenticated ? "yes" : "no");
35
- const version = entry.version ?? "unknown";
36
- return `${entry.label.padEnd(14)} ${entry.state.padEnd(14)} v${version} auth=${auth}`;
37
- });
34
+ export const LAUNCH_WIZARD_STEPS = {
35
+ AGENT: "agent",
36
+ TASK: "task",
37
+ MODEL: "model",
38
+ PERMISSIONS: "permissions",
39
+ CONFIRM: "confirm"
40
+ };
41
+
42
+ export const LAUNCH_PERMISSION_OPTIONS = [
43
+ { id: "default", label: "Default (agent prompts)", permissions: [] },
44
+ { id: "force", label: "Force / auto-approve", permissions: ["force"] },
45
+ { id: "yolo", label: "YOLO / skip permissions", permissions: ["yolo"] }
46
+ ];
47
+
48
+ export function resolveLaunchableAgents(providers = []) {
49
+ return providers
50
+ .filter((provider) => provider.launchable)
51
+ .map((provider) => provider.id);
38
52
  }
39
53
 
40
- export function formatProfileLines(profileJson) {
41
- const lines = [
42
- `Coordinator: ${profileJson.coordinator ?? "none"}`,
43
- `Default agents: ${formatAgentsLabel(profileJson.defaultAgents)}`,
44
- `Apply mode: ${profileJson.applyMode}`,
45
- `Preferred backend: ${profileJson.preferredBackend ?? "auto"}`,
46
- `Preferred model: ${profileJson.preferredModel ?? "auto"}`,
47
- `Cloud consent preference: ${profileJson.cloudConsent ? "recorded (session --cloud-consent still required)" : "no"}`,
48
- `Token budget: ${profileJson.tokenBudget ?? "none"}`
49
- ];
54
+ export function createLaunchDraft() {
55
+ return {
56
+ agentId: null,
57
+ task: "",
58
+ model: "",
59
+ permissionIndex: 0
60
+ };
61
+ }
62
+
63
+ export function resolveLaunchPermissions(draft) {
64
+ return LAUNCH_PERMISSION_OPTIONS[draft.permissionIndex]?.permissions ?? [];
65
+ }
66
+
67
+ export function advanceLaunchWizardStep(currentStep) {
68
+ switch (currentStep) {
69
+ case LAUNCH_WIZARD_STEPS.AGENT:
70
+ return LAUNCH_WIZARD_STEPS.TASK;
71
+ case LAUNCH_WIZARD_STEPS.TASK:
72
+ return LAUNCH_WIZARD_STEPS.MODEL;
73
+ case LAUNCH_WIZARD_STEPS.MODEL:
74
+ return LAUNCH_WIZARD_STEPS.PERMISSIONS;
75
+ case LAUNCH_WIZARD_STEPS.PERMISSIONS:
76
+ return LAUNCH_WIZARD_STEPS.CONFIRM;
77
+ default:
78
+ return LAUNCH_WIZARD_STEPS.CONFIRM;
79
+ }
80
+ }
50
81
 
51
- if (profileJson.sources.global) {
52
- lines.push(`Global: ${profileJson.sources.global}`);
82
+ export function retreatLaunchWizardStep(currentStep) {
83
+ switch (currentStep) {
84
+ case LAUNCH_WIZARD_STEPS.CONFIRM:
85
+ return LAUNCH_WIZARD_STEPS.PERMISSIONS;
86
+ case LAUNCH_WIZARD_STEPS.PERMISSIONS:
87
+ return LAUNCH_WIZARD_STEPS.MODEL;
88
+ case LAUNCH_WIZARD_STEPS.MODEL:
89
+ return LAUNCH_WIZARD_STEPS.TASK;
90
+ case LAUNCH_WIZARD_STEPS.TASK:
91
+ return LAUNCH_WIZARD_STEPS.AGENT;
92
+ default:
93
+ return LAUNCH_WIZARD_STEPS.AGENT;
53
94
  }
95
+ }
96
+
97
+ export function formatLaunchWizardLines({
98
+ step,
99
+ draft,
100
+ launchableAgents,
101
+ agentIndex,
102
+ permissionIndex
103
+ }) {
104
+ const lines = [`Step: ${step}`];
54
105
 
55
- if (profileJson.sources.project) {
56
- lines.push(`Project: ${profileJson.sources.project}`);
106
+ if (step === LAUNCH_WIZARD_STEPS.AGENT) {
107
+ lines.push("Select agent:");
108
+ for (const [index, agentId] of launchableAgents.entries()) {
109
+ const marker = index === agentIndex ? "›" : " ";
110
+ lines.push(`${marker} ${agentId}`);
111
+ }
112
+ return lines;
57
113
  }
58
114
 
59
- lines.push(`Precedence: ${profileJson.sources.precedence}`);
115
+ if (step === LAUNCH_WIZARD_STEPS.TASK) {
116
+ lines.push(`Agent: ${draft.agentId ?? "—"}`);
117
+ lines.push(`Task: ${draft.task || "(type your task)"}`);
118
+ lines.push("Enter to continue · Backspace to edit");
119
+ return lines;
120
+ }
121
+
122
+ if (step === LAUNCH_WIZARD_STEPS.MODEL) {
123
+ lines.push(`Agent: ${draft.agentId ?? "—"}`);
124
+ lines.push(`Task length: ${draft.task.length} chars`);
125
+ lines.push(`Model: ${draft.model || "(default — press Enter)"}`);
126
+ lines.push("Type model alias or Enter for default");
127
+ return lines;
128
+ }
129
+
130
+ if (step === LAUNCH_WIZARD_STEPS.PERMISSIONS) {
131
+ lines.push("Permissions:");
132
+ for (const [index, option] of LAUNCH_PERMISSION_OPTIONS.entries()) {
133
+ const marker = index === permissionIndex ? "›" : " ";
134
+ lines.push(`${marker} ${option.label}`);
135
+ }
136
+ return lines;
137
+ }
138
+
139
+ lines.push(`Agent: ${draft.agentId ?? "—"}`);
140
+ lines.push(`Task length: ${draft.task.length} chars (content not stored)`);
141
+ lines.push(`Model: ${draft.model || "default"}`);
142
+ lines.push(`Permissions: ${LAUNCH_PERMISSION_OPTIONS[permissionIndex]?.label ?? "default"}`);
143
+ lines.push("Enter to launch · Esc to go back");
60
144
  return lines;
61
145
  }
62
146
 
63
- export function formatIntelligenceLines(diagnostics) {
64
- const intelligence = diagnostics?.intelligence;
65
- if (!intelligence) {
66
- return ["Intelligence layer unavailable."];
67
- }
147
+ export function resolveMenuItem(menuIndex) {
148
+ return ORCHESTRATOR_MENU[menuIndex] ?? null;
149
+ }
150
+
151
+ export function resolveMenuItemView(menuIndex) {
152
+ return resolveMenuItem(menuIndex)?.view ?? ORCHESTRATOR_VIEWS.HOME;
153
+ }
154
+
155
+ export function shiftMenuIndex(currentIndex, direction, menuLength = ORCHESTRATOR_MENU.length) {
156
+ const delta = direction === "up" ? -1 : direction === "down" ? 1 : 0;
157
+ return Math.min(menuLength - 1, Math.max(0, currentIndex + delta));
158
+ }
68
159
 
160
+ export function formatRunLines(runs, { emptyMessage = "No runs." } = {}) {
161
+ if (!runs || runs.length === 0) return [emptyMessage];
162
+
163
+ return runs.map((run) => {
164
+ const state = run.state.padEnd(12);
165
+ const agent = run.agentId.padEnd(10);
166
+ return `${run.runId} ${state} ${agent} ${formatTaskLabel(run)}`;
167
+ });
168
+ }
169
+
170
+ export function formatProviderLines(providers) {
171
+ return providers.map((provider) => {
172
+ const status = provider.launchable
173
+ ? "launchable"
174
+ : provider.compatible
175
+ ? "auditable"
176
+ : provider.available
177
+ ? "limited"
178
+ : "missing";
179
+ return `${provider.label.padEnd(14)} ${status.padEnd(12)} ${provider.reason ?? ""}`.trimEnd();
180
+ });
181
+ }
182
+
183
+ export function formatDiagnosticsLines(diagnostics) {
184
+ const summary = diagnostics?.diagnostics;
69
185
  const lines = [
70
- `Local available: ${intelligence.summary.localAvailable ? "yes" : "no"}`,
71
- `Cloud authenticated: ${intelligence.summary.cloudAuthenticated ? "yes" : "no"}`,
72
- `Routing: ${intelligence.routingPreview?.reason ?? "n/a"}`,
73
- `Can invoke: ${intelligence.routingPreview?.canInvoke ? "yes" : "no"}`,
74
- ""
186
+ "Summary",
187
+ `CLI version: ${diagnostics?.cliVersion ?? "unknown"}`,
188
+ `Agents detected: ${summary?.detected ?? 0}/${diagnostics?.capabilities?.length ?? 0}`,
189
+ `Available: ${summary?.available ?? 0}`,
190
+ `Unknown: ${summary?.unknown ?? 0}`,
191
+ `Errors: ${summary?.errors ?? 0}`,
192
+ "",
193
+ "Agent capabilities",
194
+ ...formatAgentStatusLines(diagnostics?.capabilities ?? [])
75
195
  ];
76
196
 
77
- for (const backend of intelligence.backends ?? []) {
78
- lines.push(
79
- `${backend.label.padEnd(14)} ${backend.state.padEnd(14)} models=${backend.models?.length ?? 0}`
80
- );
197
+ const recommendations = diagnostics?.recommendations ?? [];
198
+ if (recommendations.length > 0) {
199
+ lines.push("", "Recommendations");
200
+ for (const recommendation of recommendations) {
201
+ lines.push(` • ${recommendation}`);
202
+ }
81
203
  }
82
204
 
83
205
  return lines;
84
206
  }
85
207
 
86
- export function formatPlanLines(plan) {
87
- const lines = [`Action: ${plan.action}`, ""];
88
- for (const step of plan.steps) {
89
- lines.push(` • ${step}`);
90
- }
208
+ export function formatAgentStatusLines(capabilities) {
209
+ return capabilities.map((entry) => {
210
+ const auth = entry.authenticated == null ? "n/a" : (entry.authenticated ? "yes" : "no");
211
+ const version = entry.version ?? "unknown";
212
+ return `${entry.label.padEnd(14)} ${entry.state.padEnd(14)} v${version} auth=${auth}`;
213
+ });
214
+ }
215
+
216
+ export function formatRunDetailLines(run, events = []) {
217
+ if (!run) return ["Run not found."];
91
218
 
92
- if (plan.warnings.length > 0) {
93
- lines.push("", "Warnings:");
94
- for (const warning of plan.warnings) {
95
- lines.push(` ! ${warning}`);
219
+ const lines = [
220
+ `Run: ${run.runId}`,
221
+ `Agent: ${run.agentId} (${run.provider})`,
222
+ `State: ${run.state}`,
223
+ `Model: ${run.model ?? "default"}`,
224
+ `Cwd: ${run.cwd}`,
225
+ `Started: ${run.startedAt}`,
226
+ `Updated: ${run.updatedAt}`
227
+ ];
228
+
229
+ if (run.completedAt) lines.push(`Completed: ${run.completedAt}`);
230
+ if (run.tokenUsage) lines.push(`Tokens: ${JSON.stringify(run.tokenUsage)}`);
231
+ if (run.error) lines.push(`Error: ${run.error}`);
232
+ if (run.tools?.length) lines.push(`Tools: ${run.tools.join(", ")}`);
233
+
234
+ if (events.length > 0) {
235
+ lines.push("", "Recent events");
236
+ for (const event of events.slice(-8)) {
237
+ if (event.parseError) continue;
238
+ lines.push(` ${event.type} ${summarizeEvent(event)}`);
96
239
  }
97
240
  }
98
241
 
99
242
  return lines;
100
243
  }
101
244
 
102
- function formatAgentsLabel(agents) {
103
- if (agents === "detected") return "detected";
104
- if (agents === "all") return "all";
105
- if (Array.isArray(agents)) return agents.join(", ");
106
- return String(agents);
245
+ export function formatDashboardSnapshot(dashboard) {
246
+ const active = dashboard?.activeRuns?.length ?? 0;
247
+ const recent = dashboard?.recentRuns?.length ?? 0;
248
+ const auditable = (dashboard?.providers ?? []).filter((entry) => entry.compatible).length;
249
+ return [
250
+ `Active runs: ${active}`,
251
+ `Recent runs: ${recent}`,
252
+ `Auditable providers: ${auditable}/${dashboard?.providers?.length ?? 0}`
253
+ ];
254
+ }
255
+
256
+ export function selectRunFromList(runs, index) {
257
+ return runs[index] ?? null;
258
+ }
259
+
260
+ export function filterInspectableRuns(runs) {
261
+ return runs ?? [];
262
+ }
263
+
264
+ export function isRunCancellable(run) {
265
+ return run && isActiveRunState(run.state);
266
+ }
267
+
268
+ function summarizeEvent(event) {
269
+ if (event.type === "agent.tool_call") {
270
+ return event.data?.tool_name ?? event.data?.name ?? "tool";
271
+ }
272
+ if (event.type === "process.stdout" || event.type === "process.stderr") {
273
+ const line = event.data?.line ?? "";
274
+ return line.length > 60 ? `${line.slice(0, 59)}…` : line;
275
+ }
276
+ if (event.type === "run.completed" || event.type === "run.failed") {
277
+ return `exit=${event.data?.exitCode ?? "n/a"}`;
278
+ }
279
+ return "";
107
280
  }
@@ -1,11 +1,10 @@
1
1
  import { stdin as input, stdout as output } from "node:process";
2
2
  import { resolveHomeDir } from "./paths.js";
3
- import { runGlobalSetup } from "./global-cli.js";
4
- import { PLAN_ACTIONS, buildReadOnlyDiagnostics, shouldExecutePlan } from "./action-planner.js";
5
3
  import { canUseOrchestratorShell } from "./ink/orchestrator-state.js";
6
4
  import { runOrchestratorInk as defaultRunOrchestratorInk } from "./ink/run-orchestrator-ink.js";
7
5
  import { formatCliCommand } from "./brand/cli.js";
8
6
  import { BRAND } from "./brand/index.js";
7
+ import { buildReadOnlyDiagnostics, shouldExecutePlan } from "./action-planner.js";
9
8
 
10
9
  export { canUseOrchestratorShell };
11
10
 
@@ -28,13 +27,13 @@ export async function runOrchestratorShell({
28
27
  }) {
29
28
  if (!interactive) {
30
29
  throw new Error(
31
- `Non-interactive shell requires an explicit command. Try ${formatCliCommand("help")} or ${formatCliCommand("setup --yes")}.`
30
+ `Non-interactive shell requires an explicit command. Try ${formatCliCommand("help")} or ${formatCliCommand("runs list")}.`
32
31
  );
33
32
  }
34
33
 
35
34
  if (!canUseOrchestratorShell({ interactive })) {
36
35
  throw new Error(
37
- `Interactive shell requires a capable TTY. Use ${formatCliCommand("setup --simple")} or explicit commands.`
36
+ `Interactive shell requires a capable TTY. Use ${formatCliCommand("runs list")} or explicit commands.`
38
37
  );
39
38
  }
40
39
 
@@ -51,41 +50,11 @@ export async function runOrchestratorShell({
51
50
  throw outcome.error;
52
51
  }
53
52
 
54
- if (outcome.cancelled) {
55
- return { cancelled: true, wrote: false };
56
- }
57
-
58
- if (outcome.confirmed && outcome.action === PLAN_ACTIONS.SETUP) {
59
- const setupOutcome = await runGlobalSetup(
60
- {
61
- cwd: workspaceRoot,
62
- adapters: null,
63
- components: null,
64
- noDefaultComponents: false,
65
- dryRun: false,
66
- yes: false,
67
- confirm: false,
68
- preflight: true,
69
- preflightExplicit: false,
70
- yesExplicit: false,
71
- confirmExplicit: false,
72
- json: false,
73
- interactive: true,
74
- simple: false
75
- },
76
- packageManifest,
77
- packageRoot
78
- );
79
-
80
- return {
81
- cancelled: Boolean(setupOutcome.cancelled),
82
- wrote: !setupOutcome.cancelled && !setupOutcome.result?.dryRun,
83
- action: PLAN_ACTIONS.SETUP,
84
- setupOutcome
85
- };
86
- }
87
-
88
- return { cancelled: false, wrote: false, action: outcome.action ?? null };
53
+ return {
54
+ cancelled: Boolean(outcome.cancelled),
55
+ wrote: false,
56
+ action: outcome.action ?? null
57
+ };
89
58
  }
90
59
 
91
60
  export async function runOrchestratorDiagnostics({
@@ -17,7 +17,20 @@ export function harnessHomePaths(homeDir) {
17
17
  policyPath: join(root, "policy.json"),
18
18
  profilePath: join(root, "profile.json"),
19
19
  historyPath: join(root, "history.jsonl"),
20
+ runsDir: join(root, "runs"),
20
21
  coreDir: join(root, "core"),
21
22
  backupsDir: join(root, "backups")
22
23
  };
23
24
  }
25
+
26
+ export function runPaths(homeDir, runId) {
27
+ const { runsDir } = harnessHomePaths(homeDir);
28
+ const runDir = join(runsDir, runId);
29
+
30
+ return {
31
+ runDir,
32
+ statePath: join(runDir, "state.json"),
33
+ eventsPath: join(runDir, "events.jsonl"),
34
+ transcriptPath: join(runDir, "transcript.jsonl")
35
+ };
36
+ }
@@ -4,6 +4,7 @@ import { join } from "node:path";
4
4
  import { harnessHomePaths } from "./paths.js";
5
5
  import { AGENT_CAPABILITY_IDS } from "./agent-capabilities/index.js";
6
6
  import { classifyCustomBaseUrl, isValidEnvironmentName } from "./intelligence/custom-url.js";
7
+ import { validateRuntimeProfile, RUNTIME_PROFILE_KEYS } from "./runtime/run-profile.js";
7
8
 
8
9
  export const PROFILE_KEYS = new Set([
9
10
  "coordinator",
@@ -16,7 +17,8 @@ export const PROFILE_KEYS = new Set([
16
17
  "tokenBudget",
17
18
  "stableContextBudget",
18
19
  "requestContextBudget",
19
- "customProviders"
20
+ "customProviders",
21
+ ...RUNTIME_PROFILE_KEYS
20
22
  ]);
21
23
 
22
24
  export const DEFAULT_PROFILE = {
@@ -30,7 +32,13 @@ export const DEFAULT_PROFILE = {
30
32
  tokenBudget: null,
31
33
  stableContextBudget: null,
32
34
  requestContextBudget: null,
33
- customProviders: []
35
+ customProviders: [],
36
+ agentAliases: {},
37
+ modelAliases: {},
38
+ defaultPermissions: [],
39
+ defaultRuntimeAgent: null,
40
+ defaultRuntimeModel: null,
41
+ captureTranscript: false
34
42
  };
35
43
 
36
44
  const APPLY_MODES = new Set(["prompt", "confirm"]);
@@ -105,6 +113,12 @@ export function buildProfileJson(resolved) {
105
113
  stableContextBudget: profile.stableContextBudget,
106
114
  requestContextBudget: profile.requestContextBudget,
107
115
  customProviders: sanitizeCustomProviders(profile.customProviders),
116
+ agentAliases: profile.agentAliases ?? {},
117
+ modelAliases: profile.modelAliases ?? {},
118
+ defaultPermissions: profile.defaultPermissions ?? [],
119
+ defaultRuntimeAgent: profile.defaultRuntimeAgent ?? null,
120
+ defaultRuntimeModel: profile.defaultRuntimeModel ?? null,
121
+ captureTranscript: profile.captureTranscript ?? false,
108
122
  sources: {
109
123
  global: sources.global,
110
124
  project: sources.project,
@@ -187,6 +201,7 @@ function validateProfile(profile) {
187
201
 
188
202
  validateNoSecrets(profile);
189
203
  validateCustomProviders(profile.customProviders);
204
+ validateRuntimeProfile(profile);
190
205
  }
191
206
 
192
207
  /**
@@ -0,0 +1,65 @@
1
+ import { createExecutionAdapter, parseNdjsonLine, buildPermissionsArgs } from "./create-execution-adapter.js";
2
+
3
+ const EXECUTABLE = "claude";
4
+
5
+ function buildClaudeLaunch({ task, cwd, model, permissions = [] }) {
6
+ const args = [
7
+ "-p",
8
+ "--output-format",
9
+ "stream-json",
10
+ ...buildPermissionsArgs(permissions),
11
+ task
12
+ ];
13
+
14
+ if (model) {
15
+ args.unshift("--model", model);
16
+ }
17
+
18
+ return {
19
+ command: EXECUTABLE,
20
+ args,
21
+ cwd,
22
+ env: process.env
23
+ };
24
+ }
25
+
26
+ function parseClaudeEventLine(line) {
27
+ const parsed = parseNdjsonLine(line);
28
+ if (!parsed || typeof parsed !== "object") return null;
29
+
30
+ if (parsed.type === "tool_use" || parsed.type === "tool_call") {
31
+ return {
32
+ type: "tool_call",
33
+ tool_name: parsed.name ?? parsed.tool ?? "unknown",
34
+ status: parsed.status ?? "started"
35
+ };
36
+ }
37
+
38
+ if (parsed.type === "usage" || parsed.usage) {
39
+ const usage = parsed.usage ?? parsed;
40
+ return {
41
+ type: "usage",
42
+ inputTokens: usage.input_tokens ?? usage.input ?? null,
43
+ outputTokens: usage.output_tokens ?? usage.output ?? null,
44
+ totalTokens: usage.total_tokens ?? usage.total ?? null,
45
+ cost: usage.cost ?? null
46
+ };
47
+ }
48
+
49
+ return parsed;
50
+ }
51
+
52
+ export default createExecutionAdapter({
53
+ id: "claude",
54
+ label: "Claude Code",
55
+ executable: EXECUTABLE,
56
+ capabilities: {
57
+ structuredEvents: true,
58
+ tokens: true,
59
+ diff: false,
60
+ cancel: true,
61
+ transcript: true
62
+ },
63
+ buildLaunch: buildClaudeLaunch,
64
+ parseEventLine: parseClaudeEventLine
65
+ });
@@ -0,0 +1,78 @@
1
+ import { createExecutionAdapter, parseNdjsonLine } from "./create-execution-adapter.js";
2
+
3
+ const EXECUTABLE = "codex";
4
+
5
+ function buildCodexPermissionsArgs(permissions = []) {
6
+ const normalized = new Set(permissions.map((entry) => String(entry).toLowerCase()));
7
+
8
+ if (
9
+ normalized.has("yolo")
10
+ || normalized.has("dangerously-skip-permissions")
11
+ || normalized.has("dangerously-bypass-approvals-and-sandbox")
12
+ ) {
13
+ return ["--dangerously-bypass-approvals-and-sandbox"];
14
+ }
15
+
16
+ return [];
17
+ }
18
+
19
+ function buildCodexLaunch({ task, cwd, model, permissions = [] }) {
20
+ const args = [
21
+ "exec",
22
+ "--json",
23
+ ...buildCodexPermissionsArgs(permissions),
24
+ task
25
+ ];
26
+
27
+ if (model) {
28
+ args.unshift("--model", model);
29
+ }
30
+
31
+ return {
32
+ command: EXECUTABLE,
33
+ args,
34
+ cwd,
35
+ env: process.env
36
+ };
37
+ }
38
+
39
+ function parseCodexEventLine(line) {
40
+ const parsed = parseNdjsonLine(line);
41
+ if (!parsed || typeof parsed !== "object") return null;
42
+
43
+ if (parsed.type === "tool" || parsed.type === "tool_call") {
44
+ return {
45
+ type: "tool_call",
46
+ tool_name: parsed.tool ?? parsed.name ?? "unknown",
47
+ status: parsed.status ?? "started"
48
+ };
49
+ }
50
+
51
+ if (parsed.usage || parsed.token_usage) {
52
+ const usage = parsed.usage ?? parsed.token_usage;
53
+ return {
54
+ type: "usage",
55
+ inputTokens: usage.input_tokens ?? usage.input ?? null,
56
+ outputTokens: usage.output_tokens ?? usage.output ?? null,
57
+ totalTokens: usage.total_tokens ?? usage.total ?? null,
58
+ cost: usage.cost ?? null
59
+ };
60
+ }
61
+
62
+ return parsed;
63
+ }
64
+
65
+ export default createExecutionAdapter({
66
+ id: "codex",
67
+ label: "Codex",
68
+ executable: EXECUTABLE,
69
+ capabilities: {
70
+ structuredEvents: true,
71
+ tokens: true,
72
+ diff: false,
73
+ cancel: true,
74
+ transcript: true
75
+ },
76
+ buildLaunch: buildCodexLaunch,
77
+ parseEventLine: parseCodexEventLine
78
+ });