@sma1lboy/kobe 0.5.15 → 0.5.17

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/dist/bin/kobed.js CHANGED
@@ -48,17 +48,38 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
48
48
  var __require = import.meta.require;
49
49
 
50
50
  // src/daemon/paths.ts
51
+ import { createHash } from "crypto";
51
52
  import { homedir, tmpdir } from "os";
52
53
  import { join } from "path";
53
- function defaultDaemonSocketPath(homeDir = process.env.KOBE_HOME_DIR ?? homedir()) {
54
+ function shortHomeTag(homeDir) {
55
+ return createHash("sha1").update(homeDir).digest("hex").slice(0, 8);
56
+ }
57
+ function fitSocketPath(naturalPath, homeDir, role, pidTag) {
58
+ if (Buffer.byteLength(naturalPath, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
59
+ return naturalPath;
60
+ const tag = shortHomeTag(homeDir);
61
+ const suffix = pidTag === undefined ? "" : `-${pidTag}`;
62
+ const fallback = join(tmpdir(), `kobe-${tag}-${role}${suffix}.sock`);
63
+ if (Buffer.byteLength(fallback, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
64
+ return fallback;
65
+ throw new Error(`kobe socket path exceeds ${SOCKET_PATH_SAFETY_LIMIT} bytes even after fallback: ${fallback}`);
66
+ }
67
+ function defaultDaemonSocketPath(homeDir) {
68
+ const explicit = homeDir ?? process.env.KOBE_HOME_DIR;
69
+ if (explicit && explicit.length > 0) {
70
+ return fitSocketPath(join(explicit, ".kobe", "daemon.sock"), explicit, "daemon");
71
+ }
54
72
  const runtimeDir = process.env.XDG_RUNTIME_DIR;
55
- if (runtimeDir && runtimeDir.length > 0)
56
- return join(runtimeDir, "kobe.sock");
57
- return join(homeDir, ".kobe", "daemon.sock");
73
+ if (runtimeDir && runtimeDir.length > 0) {
74
+ return fitSocketPath(join(runtimeDir, "kobe.sock"), runtimeDir, "daemon");
75
+ }
76
+ const home = homedir();
77
+ return fitSocketPath(join(home, ".kobe", "daemon.sock"), home, "daemon");
58
78
  }
59
79
  function defaultDaemonPidPath(homeDir = process.env.KOBE_HOME_DIR ?? homedir()) {
60
80
  return join(homeDir, ".kobe", "daemon.pid");
61
81
  }
82
+ var SOCKET_PATH_SAFETY_LIMIT = 100;
62
83
  var init_paths = () => {};
63
84
 
64
85
  // src/daemon/protocol.ts
@@ -78,6 +99,8 @@ function serializeTask(task) {
78
99
  pinned: task.pinned ?? false,
79
100
  permissionMode: task.permissionMode,
80
101
  model: task.model,
102
+ modelEffort: task.modelEffort,
103
+ vendor: task.vendor,
81
104
  createdAt: task.createdAt,
82
105
  updatedAt: task.updatedAt
83
106
  };
@@ -345,6 +368,86 @@ var init_daemon_process = __esm(() => {
345
368
  init_client();
346
369
  });
347
370
 
371
+ // src/session/usage-metrics.ts
372
+ function totalContextTokens(u) {
373
+ return u.input_tokens + (u.cache_read_input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0);
374
+ }
375
+ function parseTimestampMs(value) {
376
+ const ms = new Date(value).getTime();
377
+ return Number.isFinite(ms) ? ms : null;
378
+ }
379
+ function mergeIntervals(intervals) {
380
+ if (intervals.length === 0)
381
+ return [];
382
+ const sorted = [...intervals].sort((a, b) => a.startMs - b.startMs);
383
+ const first = sorted[0];
384
+ if (!first)
385
+ return [];
386
+ const merged = [{ startMs: first.startMs, endMs: first.endMs }];
387
+ for (let i = 1;i < sorted.length; i++) {
388
+ const current = sorted[i];
389
+ const last = merged[merged.length - 1];
390
+ if (!current || !last)
391
+ continue;
392
+ if (current.startMs <= last.endMs) {
393
+ last.endMs = Math.max(last.endMs, current.endMs);
394
+ } else {
395
+ merged.push({ startMs: current.startMs, endMs: current.endMs });
396
+ }
397
+ }
398
+ return merged;
399
+ }
400
+ function durationMs(intervals) {
401
+ return intervals.reduce((total, interval) => total + (interval.endMs - interval.startMs), 0);
402
+ }
403
+ function deriveSessionUsageMetrics(past) {
404
+ let latestUsage;
405
+ let latestUsageTimestampMs = null;
406
+ let lastUserTimestampMs = null;
407
+ let inputTokens = 0;
408
+ let outputTokens = 0;
409
+ const intervals = [];
410
+ for (const message of past) {
411
+ const timestampMs = parseTimestampMs(message.timestamp);
412
+ if (message.role === "user" && timestampMs !== null) {
413
+ lastUserTimestampMs = timestampMs;
414
+ continue;
415
+ }
416
+ if (message.role !== "assistant" || !message.usage)
417
+ continue;
418
+ if (timestampMs !== null && (latestUsageTimestampMs === null || timestampMs > latestUsageTimestampMs)) {
419
+ latestUsageTimestampMs = timestampMs;
420
+ latestUsage = message.usage;
421
+ } else if (latestUsage === undefined) {
422
+ latestUsage = message.usage;
423
+ }
424
+ inputTokens += message.usage.input_tokens;
425
+ outputTokens += message.usage.output_tokens;
426
+ if (timestampMs !== null && lastUserTimestampMs !== null && timestampMs > lastUserTimestampMs) {
427
+ intervals.push({ startMs: lastUserTimestampMs, endMs: timestampMs });
428
+ }
429
+ }
430
+ if (!latestUsage)
431
+ return;
432
+ const totalDurationMs = durationMs(mergeIntervals(intervals));
433
+ if (totalDurationMs <= 0)
434
+ return latestUsage;
435
+ return {
436
+ ...latestUsage,
437
+ total_speed_tokens_per_second: (inputTokens + outputTokens) / (totalDurationMs / 1000)
438
+ };
439
+ }
440
+ function withTotalSpeedForTurn(usage, startedAtIso, endedAtIso) {
441
+ const startMs = startedAtIso ? parseTimestampMs(startedAtIso) : null;
442
+ const endMs = parseTimestampMs(endedAtIso);
443
+ if (startMs === null || endMs === null || endMs <= startMs)
444
+ return usage;
445
+ return {
446
+ ...usage,
447
+ total_speed_tokens_per_second: (usage.input_tokens + usage.output_tokens) / ((endMs - startMs) / 1000)
448
+ };
449
+ }
450
+
348
451
  // src/engine/claude-code-local/binary.ts
349
452
  import { spawnSync } from "child_process";
350
453
  import { existsSync as existsSync2, statSync } from "fs";
@@ -449,10 +552,175 @@ var init_binary = __esm(() => {
449
552
  };
450
553
  });
451
554
 
555
+ // src/engine/claude-code-local/models.ts
556
+ function opus47EffortChoices(id, label) {
557
+ return CLAUDE_OPUS_EFFORT_LEVELS.map((effort) => ({
558
+ vendor: "claude",
559
+ id,
560
+ effort,
561
+ level: effort,
562
+ label: `${label} \xB7 ${effort}`,
563
+ hint: effort === "max" ? "deepest reasoning" : `${effort} effort`
564
+ }));
565
+ }
566
+ function parseContextWindowSize(modelIdentifier) {
567
+ const delimitedMatch = /(?:\(|\[)\s*(\d+(?:[,_]\d+)*(?:\.\d+)?)\s*([km])\s*(?:\)|\])/i.exec(modelIdentifier);
568
+ if (delimitedMatch?.[1] && delimitedMatch[2]) {
569
+ const parsed2 = Number.parseFloat(delimitedMatch[1].replace(/[,_]/g, ""));
570
+ if (Number.isFinite(parsed2) && parsed2 > 0) {
571
+ return Math.round(parsed2 * (delimitedMatch[2].toLowerCase() === "m" ? 1e6 : 1000));
572
+ }
573
+ }
574
+ const contextMatch = /\b(\d+(?:[,_]\d+)*(?:\.\d+)?)\s*([km])(?:\s*(?:token\s*)?context)?\b/i.exec(modelIdentifier);
575
+ if (!contextMatch?.[1] || !contextMatch[2])
576
+ return null;
577
+ const parsed = Number.parseFloat(contextMatch[1].replace(/[,_]/g, ""));
578
+ if (!Number.isFinite(parsed) || parsed <= 0)
579
+ return null;
580
+ return Math.round(parsed * (contextMatch[2].toLowerCase() === "m" ? 1e6 : 1000));
581
+ }
582
+ function claudeContextWindowFor(modelId) {
583
+ const parsed = parseContextWindowSize(modelId);
584
+ if (parsed !== null)
585
+ return parsed;
586
+ if (modelId.includes("[1m]"))
587
+ return LONG_CTX;
588
+ const inCatalog = CLAUDE_MODELS.some((m) => m.id === modelId);
589
+ if (inCatalog)
590
+ return STD_CTX;
591
+ if (modelId.includes("1m") || modelId.includes("[1M]"))
592
+ return LONG_CTX;
593
+ return STD_CTX;
594
+ }
595
+ var CLAUDE_OPUS_EFFORT_LEVELS, CLAUDE_MODELS, LONG_CTX = 1e6, STD_CTX = 200000;
596
+ var init_models = __esm(() => {
597
+ CLAUDE_OPUS_EFFORT_LEVELS = [
598
+ "low",
599
+ "medium",
600
+ "high",
601
+ "xhigh",
602
+ "max"
603
+ ];
604
+ CLAUDE_MODELS = [
605
+ { vendor: "claude", id: "claude-opus-4-7[1m]", label: "Opus 4.7 1M", hint: "long context, default" },
606
+ ...opus47EffortChoices("claude-opus-4-7[1m]", "Opus 4.7 1M"),
607
+ { vendor: "claude", id: "claude-opus-4-7", label: "Opus 4.7", hint: "most capable, slowest" },
608
+ ...opus47EffortChoices("claude-opus-4-7", "Opus 4.7"),
609
+ { vendor: "claude", id: "claude-sonnet-4-6[1m]", label: "sonnet 4.6 (1M)", hint: "long context" },
610
+ { vendor: "claude", id: "claude-sonnet-4-6", label: "sonnet 4.6" },
611
+ { vendor: "claude", id: "claude-haiku-4-5-20251001", label: "haiku 4.5", hint: "fastest, cheapest" }
612
+ ];
613
+ });
614
+
615
+ // src/engine/claude-code-local/settings.ts
616
+ import { readFileSync } from "fs";
617
+ import { homedir as homedir3 } from "os";
618
+ import { join as join3 } from "path";
619
+ function readClaudeSettings() {
620
+ if (cached !== undefined)
621
+ return cached;
622
+ try {
623
+ const raw = readFileSync(SETTINGS_PATH, "utf8");
624
+ const parsed = JSON.parse(raw);
625
+ if (!parsed || typeof parsed !== "object") {
626
+ cached = null;
627
+ return null;
628
+ }
629
+ const obj = parsed;
630
+ const model = typeof obj.model === "string" && obj.model.length > 0 ? obj.model : undefined;
631
+ cached = { model };
632
+ return cached;
633
+ } catch {
634
+ cached = null;
635
+ return null;
636
+ }
637
+ }
638
+ function resolveClaudeDefaultModelId() {
639
+ const settings = readClaudeSettings();
640
+ if (settings?.model && settings.model.length > 0)
641
+ return settings.model;
642
+ return CLAUDE_FALLBACK_DEFAULT_MODEL_ID;
643
+ }
644
+ var SETTINGS_PATH, cached, CLAUDE_FALLBACK_DEFAULT_MODEL_ID = "claude-opus-4-7[1m]";
645
+ var init_settings = __esm(() => {
646
+ SETTINGS_PATH = join3(homedir3(), ".claude", "settings.json");
647
+ });
648
+
649
+ // src/engine/claude-code-local/capabilities.ts
650
+ var claudeCapabilities, claudeIdentity;
651
+ var init_capabilities = __esm(() => {
652
+ init_models();
653
+ init_settings();
654
+ claudeCapabilities = {
655
+ vendorId: "claude",
656
+ label: "Claude Code",
657
+ models: CLAUDE_MODELS,
658
+ permissionModes: [
659
+ { id: "default", label: "default" },
660
+ { id: "plan", label: "plan mode" }
661
+ ],
662
+ defaultModelId: resolveClaudeDefaultModelId,
663
+ contextWindowFor: claudeContextWindowFor
664
+ };
665
+ claudeIdentity = {
666
+ vendorId: "claude",
667
+ productName: "Claude Code",
668
+ shortName: "Claude",
669
+ assistantName: "Claude",
670
+ inputPlaceholder: "Ask Claude\u2026"
671
+ };
672
+ });
673
+
674
+ // src/engine/claude-code-local/normalize.ts
675
+ function normalizeClaudeContent(content) {
676
+ if (typeof content === "string") {
677
+ return content.length > 0 ? [{ type: "text", text: content }] : [];
678
+ }
679
+ if (!Array.isArray(content))
680
+ return [];
681
+ const out = [];
682
+ for (const block of content) {
683
+ if (typeof block === "string") {
684
+ if (block.length > 0)
685
+ out.push({ type: "text", text: block });
686
+ continue;
687
+ }
688
+ if (!block || typeof block !== "object")
689
+ continue;
690
+ const b = block;
691
+ if (b.type === "text" && typeof b.text === "string") {
692
+ out.push({ type: "text", text: b.text });
693
+ continue;
694
+ }
695
+ if (b.type === "tool_use") {
696
+ out.push({
697
+ type: "tool_call",
698
+ callId: typeof b.id === "string" ? b.id : "",
699
+ name: typeof b.name === "string" ? b.name : "",
700
+ input: b.input
701
+ });
702
+ continue;
703
+ }
704
+ if (b.type === "tool_result") {
705
+ out.push({
706
+ type: "tool_result",
707
+ callId: typeof b.tool_use_id === "string" ? b.tool_use_id : "",
708
+ output: b.content,
709
+ isError: b.is_error === true
710
+ });
711
+ continue;
712
+ }
713
+ if (b.type === "thinking" && typeof b.thinking === "string") {
714
+ out.push({ type: "thinking", text: b.thinking });
715
+ }
716
+ }
717
+ return out;
718
+ }
719
+
452
720
  // src/engine/claude-code-local/history.ts
453
721
  import { randomUUID } from "crypto";
454
722
  import { appendFile, mkdir, readFile, readdir, unlink, writeFile } from "fs/promises";
455
- import { homedir as homedir3 } from "os";
723
+ import { homedir as homedir4 } from "os";
456
724
  import path2 from "path";
457
725
  function encodeCwd(cwd) {
458
726
  return cwd.replace(/[/.]/g, "-");
@@ -523,11 +791,11 @@ function extractMessage(record, fallbackSessionId) {
523
791
  return null;
524
792
  if (!("content" in inner))
525
793
  return null;
526
- const content = inner.content;
794
+ const blocks = normalizeClaudeContent(inner.content);
527
795
  const ts = typeof record.timestamp === "string" ? record.timestamp : new Date().toISOString();
528
796
  const sid = typeof record.sessionId === "string" ? record.sessionId : fallbackSessionId;
529
797
  const usage = extractUsage(inner.usage);
530
- return usage ? { role, content, timestamp: ts, sessionId: sid, usage } : { role, content, timestamp: ts, sessionId: sid };
798
+ return usage ? { role, blocks, timestamp: ts, sessionId: sid, usage } : { role, blocks, timestamp: ts, sessionId: sid };
531
799
  }
532
800
  function extractUsage(v) {
533
801
  if (!isObject(v))
@@ -622,7 +890,7 @@ var defaultDeps2;
622
890
  var init_history = __esm(() => {
623
891
  defaultDeps2 = {
624
892
  projectsDir() {
625
- return path2.join(homedir3(), ".claude", "projects");
893
+ return path2.join(homedir4(), ".claude", "projects");
626
894
  },
627
895
  async readdir(p) {
628
896
  try {
@@ -658,7 +926,12 @@ class SessionRegistry {
658
926
  }
659
927
  this.handles.set(handle.sessionId, handle);
660
928
  }
661
- unregister(sessionId) {
929
+ unregister(sessionId, proc) {
930
+ if (proc) {
931
+ const existing = this.handles.get(sessionId);
932
+ if (existing && existing.proc !== proc)
933
+ return;
934
+ }
662
935
  this.handles.delete(sessionId);
663
936
  }
664
937
  get(sessionId) {
@@ -706,7 +979,7 @@ function delay(ms) {
706
979
 
707
980
  // src/engine/claude-code-local/sessions.ts
708
981
  import { readFile as readFile2, readdir as readdir2, stat } from "fs/promises";
709
- import { homedir as homedir4 } from "os";
982
+ import { homedir as homedir5 } from "os";
710
983
  import path3 from "path";
711
984
  async function listSessionsForCwd(cwd, deps = defaultDeps3) {
712
985
  const projectDir = path3.join(deps.projectsDir(), encodeCwd(cwd));
@@ -758,17 +1031,11 @@ function extractFirstUserMessage(lines) {
758
1031
  return null;
759
1032
  }
760
1033
  function stringifyContent(content) {
761
- if (typeof content === "string")
762
- return content.trim();
763
- if (!Array.isArray(content))
764
- return "";
1034
+ const blocks = normalizeClaudeContent(content);
765
1035
  const parts = [];
766
- for (const block of content) {
767
- if (!isObject2(block))
768
- continue;
769
- if (block.type === "text" && typeof block.text === "string") {
770
- parts.push(block.text);
771
- }
1036
+ for (const b of blocks) {
1037
+ if (b.type === "text")
1038
+ parts.push(b.text);
772
1039
  }
773
1040
  return parts.join(" ").trim();
774
1041
  }
@@ -780,7 +1047,7 @@ var init_sessions = __esm(() => {
780
1047
  init_history();
781
1048
  defaultDeps3 = {
782
1049
  projectsDir() {
783
- return path3.join(homedir4(), ".claude", "projects");
1050
+ return path3.join(homedir5(), ".claude", "projects");
784
1051
  },
785
1052
  async readdir(p) {
786
1053
  try {
@@ -824,6 +1091,9 @@ function buildArgs(opts) {
824
1091
  if (opts.model) {
825
1092
  args.push("--model", opts.model);
826
1093
  }
1094
+ if (opts.modelEffort) {
1095
+ args.push("--effort", opts.modelEffort);
1096
+ }
827
1097
  if (opts.permissionMode) {
828
1098
  args.push("--permission-mode", opts.permissionMode);
829
1099
  }
@@ -924,6 +1194,13 @@ async function* parseStreamJson(lines, opts = {}) {
924
1194
  };
925
1195
  }
926
1196
  const subtype = typeof msg.subtype === "string" ? msg.subtype : "success";
1197
+ const isApiError = msg.is_error === true || typeof msg.api_error_status === "number";
1198
+ if (isApiError) {
1199
+ const status = typeof msg.api_error_status === "number" ? ` ${msg.api_error_status}` : "";
1200
+ const result = typeof msg.result === "string" ? msg.result.trim() : "";
1201
+ yield { type: "error", message: result ? `claude API error${status}: ${result}` : `claude API error${status}` };
1202
+ return;
1203
+ }
927
1204
  if (subtype === "success") {
928
1205
  yield { type: "done" };
929
1206
  } else {
@@ -973,6 +1250,8 @@ function stringifyErr(err) {
973
1250
 
974
1251
  // src/engine/claude-code-local/index.ts
975
1252
  class ClaudeCodeLocal {
1253
+ identity = claudeIdentity;
1254
+ capabilities = claudeCapabilities;
976
1255
  registry = new SessionRegistry;
977
1256
  running = new Map;
978
1257
  binaryPathResolver;
@@ -1015,7 +1294,9 @@ class ClaudeCodeLocal {
1015
1294
  };
1016
1295
  }
1017
1296
  async readHistory(sessionId) {
1018
- return readHistory(sessionId);
1297
+ const messages = await readHistory(sessionId);
1298
+ const usageMetrics = deriveSessionUsageMetrics(messages);
1299
+ return { messages, ...usageMetrics ? { usageMetrics } : {} };
1019
1300
  }
1020
1301
  async deleteHistory(sessionId) {
1021
1302
  return deleteHistory(sessionId);
@@ -1049,6 +1330,7 @@ class ClaudeCodeLocal {
1049
1330
  cwd: args.cwd,
1050
1331
  prompt: args.prompt,
1051
1332
  model: args.opts?.model,
1333
+ modelEffort: args.opts?.modelEffort,
1052
1334
  permissionMode: cliPermissionMode,
1053
1335
  env: args.opts?.env,
1054
1336
  resumeSessionId: args.resumeSessionId
@@ -1074,7 +1356,8 @@ class ClaudeCodeLocal {
1074
1356
  waiters: [],
1075
1357
  closed: false,
1076
1358
  completedNaturally: false,
1077
- prompt: args.prompt
1359
+ prompt: args.prompt,
1360
+ spawnedAtIso: new Date().toISOString()
1078
1361
  };
1079
1362
  this.running.set(sessionId, session);
1080
1363
  this.registry.register({
@@ -1103,7 +1386,8 @@ class ClaudeCodeLocal {
1103
1386
  });
1104
1387
  try {
1105
1388
  for await (const ev of events) {
1106
- queue.push(ev);
1389
+ const enriched = enrichUsageEvent(ev, session?.spawnedAtIso);
1390
+ queue.push(enriched);
1107
1391
  if (ev.type === "done" && session) {
1108
1392
  session.completedNaturally = true;
1109
1393
  }
@@ -1122,7 +1406,7 @@ class ClaudeCodeLocal {
1122
1406
  if (session) {
1123
1407
  session.closed = true;
1124
1408
  this.notify(session);
1125
- this.registry.unregister(session.sessionId);
1409
+ this.registry.unregister(session.sessionId, spawned.proc);
1126
1410
  }
1127
1411
  if (!bound) {
1128
1412
  rejectHandle(new Error("claude exited without emitting a session id"));
@@ -1153,20 +1437,1022 @@ function drainStream(stream) {
1153
1437
  s.on("data", () => {});
1154
1438
  s.on("error", () => {});
1155
1439
  }
1440
+ function enrichUsageEvent(ev, startedAtIso) {
1441
+ if (ev.type !== "usage")
1442
+ return ev;
1443
+ return { type: "usage", ...withTotalSpeedForTurn(ev, startedAtIso, new Date().toISOString()) };
1444
+ }
1156
1445
  var init_claude_code_local = __esm(() => {
1157
1446
  init_binary();
1447
+ init_capabilities();
1158
1448
  init_history();
1159
1449
  init_sessions();
1160
1450
  init_spawn();
1161
1451
  });
1162
1452
 
1453
+ // src/engine/codex-local/binary.ts
1454
+ import { spawnSync as spawnSync2 } from "child_process";
1455
+ import { existsSync as existsSync3, statSync as statSync2 } from "fs";
1456
+ import { homedir as homedir6 } from "os";
1457
+ import path4 from "path";
1458
+ async function findCodexBinary(deps = defaultDeps4) {
1459
+ const checked = [];
1460
+ const tryPath = (p) => {
1461
+ if (!p)
1462
+ return;
1463
+ checked.push(p);
1464
+ return deps.fileExists(p) ? p : undefined;
1465
+ };
1466
+ const whichResult = deps.which("codex");
1467
+ if (whichResult) {
1468
+ checked.push(`which:${whichResult}`);
1469
+ if (deps.fileExists(whichResult))
1470
+ return whichResult;
1471
+ }
1472
+ const home = deps.home();
1473
+ for (const p of ["/opt/homebrew/bin/codex", "/usr/local/bin/codex", "/usr/bin/codex", "/bin/codex"]) {
1474
+ const candidate = tryPath(p);
1475
+ if (candidate)
1476
+ return candidate;
1477
+ }
1478
+ const nvmBin = deps.env("NVM_BIN");
1479
+ if (nvmBin) {
1480
+ const candidate = tryPath(path4.join(nvmBin, "codex"));
1481
+ if (candidate)
1482
+ return candidate;
1483
+ }
1484
+ for (const rel of [".local/bin/codex", ".bun/bin/codex", "bin/codex"]) {
1485
+ const candidate = tryPath(path4.join(home, rel));
1486
+ if (candidate)
1487
+ return candidate;
1488
+ }
1489
+ throw new CodexBinaryNotFoundError(checked);
1490
+ }
1491
+ var CodexBinaryNotFoundError, defaultDeps4;
1492
+ var init_binary2 = __esm(() => {
1493
+ CodexBinaryNotFoundError = class CodexBinaryNotFoundError extends Error {
1494
+ checkedPaths;
1495
+ constructor(checkedPaths) {
1496
+ super(`Codex CLI binary not found. Checked: ${checkedPaths.join(", ")}. Ensure 'codex' is on PATH (e.g. \`brew install codex\` or the official installer).`);
1497
+ this.name = "CodexBinaryNotFoundError";
1498
+ this.checkedPaths = checkedPaths;
1499
+ }
1500
+ };
1501
+ defaultDeps4 = {
1502
+ fileExists(p) {
1503
+ try {
1504
+ return statSync2(p).isFile();
1505
+ } catch {
1506
+ return false;
1507
+ }
1508
+ },
1509
+ env(name) {
1510
+ return process.env[name];
1511
+ },
1512
+ home() {
1513
+ return homedir6();
1514
+ },
1515
+ which(name) {
1516
+ const cmd = process.platform === "win32" ? "where" : "which";
1517
+ const out = spawnSync2(cmd, [name], { encoding: "utf8" });
1518
+ if (out.status !== 0)
1519
+ return;
1520
+ const first = out.stdout.split(`
1521
+ `).map((l) => l.trim()).filter(Boolean)[0];
1522
+ if (!first)
1523
+ return;
1524
+ if (first.startsWith("codex:") && first.includes("aliased to")) {
1525
+ const aliasTarget = first.split("aliased to")[1]?.trim();
1526
+ return aliasTarget && existsSync3(aliasTarget) ? aliasTarget : undefined;
1527
+ }
1528
+ return first;
1529
+ },
1530
+ readdir(p) {
1531
+ try {
1532
+ const fs = __require("fs");
1533
+ return fs.readdirSync(p);
1534
+ } catch {
1535
+ return [];
1536
+ }
1537
+ }
1538
+ };
1539
+ });
1540
+
1541
+ // src/engine/codex-local/models.ts
1542
+ function codexContextWindowFor(_modelId) {
1543
+ return DEFAULT_CTX;
1544
+ }
1545
+ var CODEX_GPT55_EFFORT_LEVELS, CODEX_MODELS, CODEX_FALLBACK_DEFAULT_MODEL_ID = "gpt-5.4-mini", DEFAULT_CTX = 400000;
1546
+ var init_models2 = __esm(() => {
1547
+ CODEX_GPT55_EFFORT_LEVELS = [
1548
+ "none",
1549
+ "low",
1550
+ "medium",
1551
+ "high",
1552
+ "xhigh"
1553
+ ];
1554
+ CODEX_MODELS = [
1555
+ { vendor: "codex", id: "gpt-5.5", label: "GPT-5.5", hint: "latest, ChatGPT-account compatible" },
1556
+ ...CODEX_GPT55_EFFORT_LEVELS.map((effort) => ({
1557
+ vendor: "codex",
1558
+ id: "gpt-5.5",
1559
+ effort,
1560
+ level: effort,
1561
+ label: `GPT-5.5 \xB7 ${effort}`,
1562
+ hint: effort === "none" ? "no reasoning effort" : `${effort} reasoning`
1563
+ })),
1564
+ { vendor: "codex", id: "gpt-5.4", label: "GPT-5.4", hint: "stable" },
1565
+ { vendor: "codex", id: "gpt-5.4-mini", label: "GPT-5.4 mini", hint: "fastest, always supported" }
1566
+ ];
1567
+ });
1568
+
1569
+ // src/engine/codex-local/settings.ts
1570
+ import { readFileSync as readFileSync2 } from "fs";
1571
+ import { homedir as homedir7 } from "os";
1572
+ import { join as join4 } from "path";
1573
+ function readModelFromConfig() {
1574
+ try {
1575
+ const raw = readFileSync2(CONFIG_PATH, "utf8");
1576
+ let inTable = false;
1577
+ for (const line of raw.split(`
1578
+ `)) {
1579
+ const t = line.trim();
1580
+ if (t.startsWith("[") && t.endsWith("]")) {
1581
+ inTable = true;
1582
+ continue;
1583
+ }
1584
+ if (inTable)
1585
+ continue;
1586
+ const m = /^model\s*=\s*"([^"]+)"/.exec(t);
1587
+ if (m)
1588
+ return m[1] ?? null;
1589
+ }
1590
+ return null;
1591
+ } catch {
1592
+ return null;
1593
+ }
1594
+ }
1595
+ function resolveCodexDefaultModelId() {
1596
+ if (cached2 === undefined)
1597
+ cached2 = readModelFromConfig();
1598
+ return cached2 ?? CODEX_FALLBACK_DEFAULT_MODEL_ID;
1599
+ }
1600
+ var CONFIG_PATH, cached2;
1601
+ var init_settings2 = __esm(() => {
1602
+ init_models2();
1603
+ CONFIG_PATH = join4(homedir7(), ".codex", "config.toml");
1604
+ });
1605
+
1606
+ // src/engine/codex-local/capabilities.ts
1607
+ var codexCapabilities, codexIdentity;
1608
+ var init_capabilities2 = __esm(() => {
1609
+ init_models2();
1610
+ init_settings2();
1611
+ codexCapabilities = {
1612
+ vendorId: "codex",
1613
+ label: "Codex",
1614
+ models: CODEX_MODELS,
1615
+ permissionModes: [
1616
+ { id: "default", label: "full access" },
1617
+ { id: "plan", label: "plan mode" }
1618
+ ],
1619
+ defaultModelId: resolveCodexDefaultModelId,
1620
+ contextWindowFor: codexContextWindowFor
1621
+ };
1622
+ codexIdentity = {
1623
+ vendorId: "codex",
1624
+ productName: "Codex",
1625
+ shortName: "Codex",
1626
+ assistantName: "Codex",
1627
+ inputPlaceholder: "Ask Codex\u2026"
1628
+ };
1629
+ });
1630
+
1631
+ // src/engine/codex-local/normalize.ts
1632
+ function normalizeCodexContent(raw) {
1633
+ if (typeof raw === "string") {
1634
+ return raw.length > 0 ? [{ type: "text", text: raw }] : [];
1635
+ }
1636
+ if (!Array.isArray(raw))
1637
+ return [];
1638
+ const blocks = [];
1639
+ for (const item of raw) {
1640
+ if (typeof item === "string") {
1641
+ if (item.length > 0)
1642
+ blocks.push({ type: "text", text: item });
1643
+ continue;
1644
+ }
1645
+ if (!isObject4(item))
1646
+ continue;
1647
+ const t = typeof item.type === "string" ? item.type : undefined;
1648
+ if (t === "input_text" || t === "output_text") {
1649
+ const text = typeof item.text === "string" ? item.text : "";
1650
+ if (text.length > 0)
1651
+ blocks.push({ type: "text", text });
1652
+ continue;
1653
+ }
1654
+ if (t)
1655
+ blocks.push({ type: "text", text: `[codex: ${t}]` });
1656
+ }
1657
+ return blocks;
1658
+ }
1659
+ function isObject4(v) {
1660
+ return typeof v === "object" && v !== null && !Array.isArray(v);
1661
+ }
1662
+
1663
+ // src/engine/codex-local/synthetic.ts
1664
+ function isSyntheticCodexUserRow(blocks) {
1665
+ if (blocks.length === 0)
1666
+ return false;
1667
+ for (const b of blocks) {
1668
+ if (b.type !== "text")
1669
+ return false;
1670
+ const t = (b.text ?? "").trim();
1671
+ if (!isEnvironmentContextEnvelope(t) && !isInstructionsEnvelope(t))
1672
+ return false;
1673
+ }
1674
+ return true;
1675
+ }
1676
+ function visibleCodexUserText(content) {
1677
+ const blocks = normalizeCodexContent(content);
1678
+ if (isSyntheticCodexUserRow(blocks))
1679
+ return null;
1680
+ const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join(" ").trim();
1681
+ return text.length > 0 ? text : null;
1682
+ }
1683
+ function isEnvironmentContextEnvelope(text) {
1684
+ return text.startsWith("<environment_context>") && text.endsWith("</environment_context>");
1685
+ }
1686
+ function isInstructionsEnvelope(text) {
1687
+ return text.startsWith("# AGENTS.md instructions for ") && text.includes(`
1688
+ <INSTRUCTIONS>
1689
+ `) && text.endsWith("</INSTRUCTIONS>");
1690
+ }
1691
+ var init_synthetic = () => {};
1692
+
1693
+ // src/engine/codex-local/history.ts
1694
+ import { readFile as readFile3, readdir as readdir3, unlink as unlink2 } from "fs/promises";
1695
+ import { homedir as homedir8 } from "os";
1696
+ import path5 from "path";
1697
+ async function listRolloutFiles(deps = defaultDeps5) {
1698
+ const root = deps.sessionsDir();
1699
+ const years = (await deps.readdir(root)).sort().reverse();
1700
+ const out = [];
1701
+ for (const y of years) {
1702
+ const yp = path5.join(root, y);
1703
+ const months = (await deps.readdir(yp)).sort().reverse();
1704
+ for (const m of months) {
1705
+ const mp = path5.join(yp, m);
1706
+ const days = (await deps.readdir(mp)).sort().reverse();
1707
+ for (const d of days) {
1708
+ const dp = path5.join(mp, d);
1709
+ const files = (await deps.readdir(dp)).filter((f) => f.startsWith("rollout-") && f.endsWith(".jsonl"));
1710
+ files.sort().reverse();
1711
+ for (const f of files)
1712
+ out.push(path5.join(dp, f));
1713
+ }
1714
+ }
1715
+ }
1716
+ return out;
1717
+ }
1718
+ async function findRolloutFile(sessionId, deps = defaultDeps5) {
1719
+ const all = await listRolloutFiles(deps);
1720
+ for (const p of all) {
1721
+ if (path5.basename(p).endsWith(`-${sessionId}.jsonl`))
1722
+ return p;
1723
+ }
1724
+ return;
1725
+ }
1726
+ async function readHistoryWithMetrics(sessionId, deps = defaultDeps5) {
1727
+ const file = await findRolloutFile(sessionId, deps);
1728
+ if (!file)
1729
+ return { messages: [] };
1730
+ let raw;
1731
+ try {
1732
+ raw = await deps.readFile(file);
1733
+ } catch {
1734
+ return { messages: [] };
1735
+ }
1736
+ const messages = sortByTimestamp2(parseJsonl2(raw, sessionId));
1737
+ const usageMetrics = deriveCodexUsageMetrics(raw);
1738
+ return { messages, ...usageMetrics ? { usageMetrics } : {} };
1739
+ }
1740
+ async function deleteHistory2(sessionId, deps = defaultDeps5) {
1741
+ const file = await findRolloutFile(sessionId, deps);
1742
+ if (!file)
1743
+ return;
1744
+ try {
1745
+ await unlink2(file);
1746
+ } catch (err) {
1747
+ if (err.code === "ENOENT")
1748
+ return;
1749
+ throw err;
1750
+ }
1751
+ }
1752
+ function sortByTimestamp2(messages) {
1753
+ return messages.map((msg, idx) => ({ msg, idx })).sort((a, b) => {
1754
+ if (a.msg.timestamp < b.msg.timestamp)
1755
+ return -1;
1756
+ if (a.msg.timestamp > b.msg.timestamp)
1757
+ return 1;
1758
+ return a.idx - b.idx;
1759
+ }).map((entry) => entry.msg);
1760
+ }
1761
+ function parseJsonl2(raw, sessionId) {
1762
+ const out = [];
1763
+ for (const line of raw.split(`
1764
+ `)) {
1765
+ const trimmed = line.trim();
1766
+ if (!trimmed)
1767
+ continue;
1768
+ let parsed;
1769
+ try {
1770
+ parsed = JSON.parse(trimmed);
1771
+ } catch {
1772
+ continue;
1773
+ }
1774
+ if (!isObject5(parsed))
1775
+ continue;
1776
+ if (parsed.type !== "response_item")
1777
+ continue;
1778
+ const payload = isObject5(parsed.payload) ? parsed.payload : undefined;
1779
+ if (!payload)
1780
+ continue;
1781
+ if (payload.type !== "message")
1782
+ continue;
1783
+ const role = payload.role;
1784
+ if (role !== "user" && role !== "assistant" && role !== "system")
1785
+ continue;
1786
+ const blocks = normalizeCodexContent(payload.content);
1787
+ if (role === "user" && isSyntheticCodexUserRow(blocks))
1788
+ continue;
1789
+ const ts = typeof parsed.timestamp === "string" ? parsed.timestamp : new Date().toISOString();
1790
+ out.push({ role, blocks, timestamp: ts, sessionId });
1791
+ }
1792
+ return out;
1793
+ }
1794
+ function deriveCodexUsageMetrics(raw) {
1795
+ let latestUsage;
1796
+ let latestUsageTimestampMs = null;
1797
+ let lastUserTimestampMs = null;
1798
+ let inputTokens = 0;
1799
+ let outputTokens = 0;
1800
+ const intervals = [];
1801
+ for (const line of raw.split(`
1802
+ `)) {
1803
+ const trimmed = line.trim();
1804
+ if (!trimmed)
1805
+ continue;
1806
+ let parsed;
1807
+ try {
1808
+ parsed = JSON.parse(trimmed);
1809
+ } catch {
1810
+ continue;
1811
+ }
1812
+ if (!isObject5(parsed))
1813
+ continue;
1814
+ const timestampMs = typeof parsed.timestamp === "string" ? parseTimestampMs2(parsed.timestamp) : null;
1815
+ if (parsed.type === "response_item") {
1816
+ const payload = isObject5(parsed.payload) ? parsed.payload : undefined;
1817
+ if (payload?.type === "message" && payload.role === "user" && timestampMs !== null) {
1818
+ const blocks = normalizeCodexContent(payload.content);
1819
+ if (!isSyntheticCodexUserRow(blocks))
1820
+ lastUserTimestampMs = timestampMs;
1821
+ }
1822
+ continue;
1823
+ }
1824
+ if (parsed.type !== "turn.completed")
1825
+ continue;
1826
+ const usage = isObject5(parsed.usage) ? parsed.usage : undefined;
1827
+ if (!usage)
1828
+ continue;
1829
+ const snapshot = codexUsageToSnapshot(usage);
1830
+ if (!snapshot)
1831
+ continue;
1832
+ if (timestampMs !== null && (latestUsageTimestampMs === null || timestampMs > latestUsageTimestampMs)) {
1833
+ latestUsageTimestampMs = timestampMs;
1834
+ latestUsage = snapshot;
1835
+ } else if (latestUsage === undefined) {
1836
+ latestUsage = snapshot;
1837
+ }
1838
+ inputTokens += snapshot.input_tokens;
1839
+ outputTokens += snapshot.output_tokens;
1840
+ if (timestampMs !== null && lastUserTimestampMs !== null && timestampMs > lastUserTimestampMs) {
1841
+ intervals.push({ startMs: lastUserTimestampMs, endMs: timestampMs });
1842
+ }
1843
+ }
1844
+ if (!latestUsage)
1845
+ return;
1846
+ const durationMs2 = mergedDurationMs(intervals);
1847
+ if (durationMs2 <= 0)
1848
+ return latestUsage;
1849
+ return {
1850
+ ...latestUsage,
1851
+ total_speed_tokens_per_second: (inputTokens + outputTokens) / (durationMs2 / 1000)
1852
+ };
1853
+ }
1854
+ function codexUsageToSnapshot(usage) {
1855
+ const input = numberOr(usage.input_tokens, 0);
1856
+ const output = numberOr(usage.output_tokens, 0) + numberOr(usage.reasoning_output_tokens, 0);
1857
+ const cacheRead = typeof usage.cached_input_tokens === "number" ? usage.cached_input_tokens : undefined;
1858
+ if (input <= 0 && output <= 0 && cacheRead === undefined)
1859
+ return;
1860
+ return {
1861
+ input_tokens: input,
1862
+ output_tokens: output,
1863
+ ...cacheRead !== undefined ? { cache_read_input_tokens: cacheRead } : {}
1864
+ };
1865
+ }
1866
+ function parseTimestampMs2(value) {
1867
+ const ms = new Date(value).getTime();
1868
+ return Number.isFinite(ms) ? ms : null;
1869
+ }
1870
+ function mergedDurationMs(intervals) {
1871
+ if (intervals.length === 0)
1872
+ return 0;
1873
+ const sorted = [...intervals].sort((a, b) => a.startMs - b.startMs);
1874
+ let total = 0;
1875
+ let current = sorted[0];
1876
+ if (!current)
1877
+ return 0;
1878
+ for (let i = 1;i < sorted.length; i++) {
1879
+ const next = sorted[i];
1880
+ if (!next)
1881
+ continue;
1882
+ if (next.startMs <= current.endMs) {
1883
+ current = { startMs: current.startMs, endMs: Math.max(current.endMs, next.endMs) };
1884
+ } else {
1885
+ total += current.endMs - current.startMs;
1886
+ current = next;
1887
+ }
1888
+ }
1889
+ total += current.endMs - current.startMs;
1890
+ return total;
1891
+ }
1892
+ function numberOr(v, fallback) {
1893
+ return typeof v === "number" && Number.isFinite(v) ? v : fallback;
1894
+ }
1895
+ function isObject5(v) {
1896
+ return typeof v === "object" && v !== null && !Array.isArray(v);
1897
+ }
1898
+ var defaultDeps5;
1899
+ var init_history2 = __esm(() => {
1900
+ init_synthetic();
1901
+ defaultDeps5 = {
1902
+ sessionsDir() {
1903
+ return path5.join(homedir8(), ".codex", "sessions");
1904
+ },
1905
+ async readdir(p) {
1906
+ try {
1907
+ return await readdir3(p);
1908
+ } catch {
1909
+ return [];
1910
+ }
1911
+ },
1912
+ async readFile(p) {
1913
+ return await readFile3(p, "utf8");
1914
+ }
1915
+ };
1916
+ });
1917
+
1918
+ // src/engine/codex-local/sessions.ts
1919
+ import { open, stat as stat2 } from "fs/promises";
1920
+ async function listSessionsForCwd2(cwd, deps) {
1921
+ const files = await listRolloutFiles(deps);
1922
+ const out = [];
1923
+ for (const file of files) {
1924
+ const meta = await tryReadMeta(file);
1925
+ if (!meta)
1926
+ continue;
1927
+ if (meta.cwd !== cwd)
1928
+ continue;
1929
+ out.push({
1930
+ sessionId: meta.sessionId,
1931
+ mtimeMs: meta.mtimeMs,
1932
+ firstUserMessage: meta.firstUserMessage,
1933
+ messageCount: meta.messageCount
1934
+ });
1935
+ }
1936
+ out.sort((a, b) => b.mtimeMs - a.mtimeMs);
1937
+ return out;
1938
+ }
1939
+ async function tryReadMeta(file) {
1940
+ let st;
1941
+ try {
1942
+ st = await stat2(file);
1943
+ } catch {
1944
+ return null;
1945
+ }
1946
+ let sessionId;
1947
+ let cwd;
1948
+ let firstUser = null;
1949
+ let messageCount = 0;
1950
+ const handle = await open(file, "r").catch(() => null);
1951
+ if (!handle)
1952
+ return null;
1953
+ try {
1954
+ let buf = "";
1955
+ const processLine = (line) => {
1956
+ const parsed = safeParse(line);
1957
+ if (!parsed)
1958
+ return;
1959
+ if (parsed.type === "session_meta") {
1960
+ const payload = parsed.payload;
1961
+ if (isObject6(payload)) {
1962
+ if (typeof payload.id === "string")
1963
+ sessionId = payload.id;
1964
+ if (typeof payload.cwd === "string")
1965
+ cwd = payload.cwd;
1966
+ }
1967
+ return;
1968
+ }
1969
+ if (parsed.type === "response_item" && isObject6(parsed.payload)) {
1970
+ const p = parsed.payload;
1971
+ if (p.type === "message") {
1972
+ messageCount++;
1973
+ if (!firstUser && p.role === "user") {
1974
+ const text = visibleCodexUserText(p.content);
1975
+ if (text)
1976
+ firstUser = text.slice(0, PREVIEW_CHAR_CAP);
1977
+ }
1978
+ }
1979
+ }
1980
+ };
1981
+ const reader = handle.createReadStream({ encoding: "utf8" });
1982
+ for await (const chunk of reader) {
1983
+ buf += chunk;
1984
+ let nl = buf.indexOf(`
1985
+ `);
1986
+ while (nl !== -1) {
1987
+ const line = buf.slice(0, nl);
1988
+ buf = buf.slice(nl + 1);
1989
+ nl = buf.indexOf(`
1990
+ `);
1991
+ processLine(line);
1992
+ }
1993
+ }
1994
+ if (buf.trim())
1995
+ processLine(buf);
1996
+ } finally {
1997
+ await handle.close().catch(() => {});
1998
+ }
1999
+ if (!sessionId || !cwd)
2000
+ return null;
2001
+ return {
2002
+ sessionId,
2003
+ cwd,
2004
+ mtimeMs: st.mtimeMs,
2005
+ firstUserMessage: firstUser,
2006
+ messageCount
2007
+ };
2008
+ }
2009
+ function safeParse(line) {
2010
+ const t = line.trim();
2011
+ if (!t)
2012
+ return null;
2013
+ try {
2014
+ const v = JSON.parse(t);
2015
+ return isObject6(v) ? v : null;
2016
+ } catch {
2017
+ return null;
2018
+ }
2019
+ }
2020
+ function isObject6(v) {
2021
+ return typeof v === "object" && v !== null && !Array.isArray(v);
2022
+ }
2023
+ var PREVIEW_CHAR_CAP = 200;
2024
+ var init_sessions2 = __esm(() => {
2025
+ init_history2();
2026
+ init_synthetic();
2027
+ });
2028
+
2029
+ // src/engine/codex-local/spawn.ts
2030
+ import { spawn as spawn3 } from "child_process";
2031
+ function spawnCodexProcess(opts) {
2032
+ const args = buildArgs2(opts);
2033
+ const proc = spawn3(opts.binaryPath, args, {
2034
+ cwd: opts.cwd,
2035
+ env: { ...process.env, ...opts.env ?? {} },
2036
+ stdio: ["pipe", "pipe", "pipe"]
2037
+ });
2038
+ try {
2039
+ proc.stdin.end();
2040
+ } catch {}
2041
+ return {
2042
+ proc,
2043
+ stdout: proc.stdout,
2044
+ stderr: proc.stderr,
2045
+ args
2046
+ };
2047
+ }
2048
+ function buildArgs2(opts) {
2049
+ const isResume = !!opts.resumeSessionId;
2050
+ const args = ["exec"];
2051
+ if (isResume) {
2052
+ args.push("resume", opts.resumeSessionId);
2053
+ }
2054
+ args.push("--json", "--skip-git-repo-check");
2055
+ if (opts.model) {
2056
+ args.push("-m", opts.model);
2057
+ }
2058
+ if (opts.modelEffort) {
2059
+ args.push("-c", `model_reasoning_effort="${opts.modelEffort}"`);
2060
+ }
2061
+ if (!isResume) {
2062
+ args.push("-C", opts.cwd);
2063
+ if (opts.permissionMode === "plan")
2064
+ args.push("-s", "read-only");
2065
+ }
2066
+ if (opts.permissionMode !== "plan") {
2067
+ args.push("--dangerously-bypass-approvals-and-sandbox");
2068
+ }
2069
+ if (opts.extraArgs && opts.extraArgs.length > 0) {
2070
+ args.push(...opts.extraArgs);
2071
+ }
2072
+ args.push(opts.prompt);
2073
+ return args;
2074
+ }
2075
+ var init_spawn2 = () => {};
2076
+
2077
+ // src/engine/codex-local/stream.ts
2078
+ async function* parseStreamJson2(lines, opts = {}) {
2079
+ let sessionIdEmitted = false;
2080
+ const toolNameById = new Map;
2081
+ const startedByItemId = new Set;
2082
+ for await (const rawLine of lines) {
2083
+ const line = rawLine.trim();
2084
+ if (!line)
2085
+ continue;
2086
+ let msg;
2087
+ try {
2088
+ msg = JSON.parse(line);
2089
+ } catch (err) {
2090
+ yield { type: "error", message: `codex stream-json parse failed: ${stringifyErr2(err)}` };
2091
+ continue;
2092
+ }
2093
+ if (!isObject7(msg))
2094
+ continue;
2095
+ const type = typeof msg.type === "string" ? msg.type : undefined;
2096
+ if (!type)
2097
+ continue;
2098
+ if (type === "session_meta" || type === "thread.started") {
2099
+ const sid = codexSessionId(msg);
2100
+ if (sid && !sessionIdEmitted) {
2101
+ sessionIdEmitted = true;
2102
+ opts.onSessionId?.(sid);
2103
+ }
2104
+ continue;
2105
+ }
2106
+ if (type === "turn.started")
2107
+ continue;
2108
+ if (type === "item.started" || type === "item.completed") {
2109
+ const item = isObject7(msg.item) ? msg.item : undefined;
2110
+ if (!item)
2111
+ continue;
2112
+ const itemId = typeof item.id === "string" ? item.id : undefined;
2113
+ const itemType = typeof item.type === "string" ? item.type : "tool";
2114
+ if (itemType === "agent_message") {
2115
+ if (type !== "item.completed")
2116
+ continue;
2117
+ const text = typeof item.text === "string" ? item.text : "";
2118
+ if (text)
2119
+ yield { type: "assistant.delta", text };
2120
+ continue;
2121
+ }
2122
+ if (itemId) {
2123
+ toolNameById.set(itemId, itemType);
2124
+ }
2125
+ if (type === "item.started") {
2126
+ if (itemId)
2127
+ startedByItemId.add(itemId);
2128
+ const input = stripIdAndType(item);
2129
+ yield { type: "tool.start", name: itemType, input };
2130
+ continue;
2131
+ }
2132
+ if (itemId && !startedByItemId.has(itemId)) {
2133
+ const input = stripIdAndType(item);
2134
+ yield { type: "tool.start", name: itemType, input };
2135
+ }
2136
+ if (itemId)
2137
+ startedByItemId.delete(itemId);
2138
+ const output = stripIdAndType(item);
2139
+ yield { type: "tool.result", name: itemType, output };
2140
+ continue;
2141
+ }
2142
+ if (type === "turn.completed") {
2143
+ const usage = isObject7(msg.usage) ? msg.usage : undefined;
2144
+ if (usage) {
2145
+ const inTok = numberOr2(usage.input_tokens, 0);
2146
+ const outTok = numberOr2(usage.output_tokens, 0) + numberOr2(usage.reasoning_output_tokens, 0);
2147
+ const cacheRead = typeof usage.cached_input_tokens === "number" ? usage.cached_input_tokens : undefined;
2148
+ yield {
2149
+ type: "usage",
2150
+ input_tokens: inTok,
2151
+ output_tokens: outTok,
2152
+ ...cacheRead !== undefined ? { cache_read_input_tokens: cacheRead } : {}
2153
+ };
2154
+ }
2155
+ yield { type: "done" };
2156
+ return;
2157
+ }
2158
+ if (type === "error") {
2159
+ const message = typeof msg.message === "string" ? msg.message : "codex emitted an error";
2160
+ yield { type: "error", message };
2161
+ return;
2162
+ }
2163
+ }
2164
+ }
2165
+ async function* readLines2(stream) {
2166
+ let buf = "";
2167
+ for await (const chunk of stream) {
2168
+ const text = typeof chunk === "string" ? chunk : Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
2169
+ buf += text;
2170
+ let nl = buf.indexOf(`
2171
+ `);
2172
+ while (nl !== -1) {
2173
+ yield buf.slice(0, nl);
2174
+ buf = buf.slice(nl + 1);
2175
+ nl = buf.indexOf(`
2176
+ `);
2177
+ }
2178
+ }
2179
+ if (buf.length > 0)
2180
+ yield buf;
2181
+ }
2182
+ function isObject7(v) {
2183
+ return typeof v === "object" && v !== null && !Array.isArray(v);
2184
+ }
2185
+ function codexSessionId(msg) {
2186
+ if (msg.type === "session_meta") {
2187
+ const payload = isObject7(msg.payload) ? msg.payload : undefined;
2188
+ const id2 = payload?.id;
2189
+ return typeof id2 === "string" && id2.length > 0 ? id2 : undefined;
2190
+ }
2191
+ const id = msg.thread_id;
2192
+ return typeof id === "string" && id.length > 0 ? id : undefined;
2193
+ }
2194
+ function numberOr2(v, fallback) {
2195
+ return typeof v === "number" && Number.isFinite(v) ? v : fallback;
2196
+ }
2197
+ function stripIdAndType(item) {
2198
+ const { id: _id, type: _type, ...rest } = item;
2199
+ return rest;
2200
+ }
2201
+ function stringifyErr2(err) {
2202
+ if (err instanceof Error)
2203
+ return err.message;
2204
+ try {
2205
+ return JSON.stringify(err);
2206
+ } catch {
2207
+ return String(err);
2208
+ }
2209
+ }
2210
+
2211
+ // src/engine/codex-local/index.ts
2212
+ class CodexLocal {
2213
+ identity = codexIdentity;
2214
+ capabilities = codexCapabilities;
2215
+ registry = new SessionRegistry;
2216
+ running = new Map;
2217
+ binaryPathResolver;
2218
+ stopGraceMs;
2219
+ constructor(opts = {}) {
2220
+ this.binaryPathResolver = opts.binaryPathResolver ?? findCodexBinary;
2221
+ this.stopGraceMs = opts.stopGraceMs ?? 5000;
2222
+ }
2223
+ async spawn(cwd, prompt, opts) {
2224
+ return this.start({ cwd, prompt, opts });
2225
+ }
2226
+ async resume(sessionId, prompt, opts) {
2227
+ const cwd = opts?.cwd ?? opts?.env?.KOBE_RESUME_CWD ?? process.cwd();
2228
+ return this.start({ cwd, prompt, opts, resumeSessionId: sessionId });
2229
+ }
2230
+ stream(handle) {
2231
+ const sid = handle.sessionId;
2232
+ const self = this;
2233
+ return {
2234
+ async* [Symbol.asyncIterator]() {
2235
+ const session = self.running.get(sid);
2236
+ if (!session)
2237
+ return;
2238
+ let idx = 0;
2239
+ while (true) {
2240
+ if (idx < session.queue.length) {
2241
+ const ev = session.queue[idx++];
2242
+ if (!ev)
2243
+ continue;
2244
+ yield ev;
2245
+ if (ev.type === "done" || ev.type === "error")
2246
+ return;
2247
+ continue;
2248
+ }
2249
+ if (session.closed)
2250
+ return;
2251
+ await new Promise((resolve2) => session.waiters.push(resolve2));
2252
+ }
2253
+ }
2254
+ };
2255
+ }
2256
+ async readHistory(sessionId) {
2257
+ return readHistoryWithMetrics(sessionId);
2258
+ }
2259
+ async deleteHistory(sessionId) {
2260
+ return deleteHistory2(sessionId);
2261
+ }
2262
+ async listSessions(cwd) {
2263
+ return listSessionsForCwd2(cwd);
2264
+ }
2265
+ async stop(handle) {
2266
+ const sid = handle.sessionId;
2267
+ await this.registry.kill(sid, this.stopGraceMs);
2268
+ const session = this.running.get(sid);
2269
+ if (session) {
2270
+ session.closed = true;
2271
+ this.notify(session);
2272
+ this.running.delete(sid);
2273
+ }
2274
+ }
2275
+ async start(args) {
2276
+ const binaryPath = await this.binaryPathResolver();
2277
+ const spawned = spawnCodexProcess({
2278
+ binaryPath,
2279
+ cwd: args.cwd,
2280
+ prompt: args.prompt,
2281
+ model: args.opts?.model,
2282
+ modelEffort: args.opts?.modelEffort,
2283
+ permissionMode: args.opts?.permissionMode,
2284
+ env: args.opts?.env,
2285
+ resumeSessionId: args.resumeSessionId
2286
+ });
2287
+ let resolveHandle = () => {};
2288
+ let rejectHandle = () => {};
2289
+ const handlePromise = new Promise((res, rej) => {
2290
+ resolveHandle = res;
2291
+ rejectHandle = rej;
2292
+ });
2293
+ const queue = [];
2294
+ let session;
2295
+ let bound = false;
2296
+ let stderrTail = "";
2297
+ const bind = (sessionId) => {
2298
+ if (bound) {
2299
+ if (session && session.sessionId !== sessionId) {
2300
+ queue.push({
2301
+ type: "error",
2302
+ message: `codex resumed to a different session id (got ${sessionId}, expected ${session.sessionId})`
2303
+ });
2304
+ this.notify(session);
2305
+ }
2306
+ return;
2307
+ }
2308
+ bound = true;
2309
+ session = {
2310
+ sessionId,
2311
+ cwd: args.cwd,
2312
+ spawned,
2313
+ queue,
2314
+ waiters: [],
2315
+ closed: false,
2316
+ spawnedAtIso: new Date().toISOString()
2317
+ };
2318
+ this.running.set(sessionId, session);
2319
+ this.registry.register({
2320
+ sessionId,
2321
+ cwd: args.cwd,
2322
+ proc: spawned.proc,
2323
+ startedAt: Date.now(),
2324
+ prompt: args.prompt
2325
+ });
2326
+ resolveHandle({ sessionId, cwd: args.cwd });
2327
+ };
2328
+ if (args.resumeSessionId) {
2329
+ try {
2330
+ bind(args.resumeSessionId);
2331
+ } catch (err) {
2332
+ try {
2333
+ spawned.proc.kill("SIGKILL");
2334
+ } catch {}
2335
+ rejectHandle(err);
2336
+ throw err;
2337
+ }
2338
+ }
2339
+ captureStderrTail(spawned.stderr, (chunk) => {
2340
+ stderrTail = (stderrTail + chunk).slice(-STDERR_TAIL_CAP);
2341
+ });
2342
+ const exitInfo = {
2343
+ code: null,
2344
+ signal: null,
2345
+ seen: false
2346
+ };
2347
+ const exitObserved = new Promise((resolve2) => {
2348
+ spawned.proc.once("exit", (code, signal) => {
2349
+ exitInfo.code = code;
2350
+ exitInfo.signal = signal;
2351
+ exitInfo.seen = true;
2352
+ if (!bound) {
2353
+ rejectHandle(new Error(formatExitMsg("codex exited before session id was captured", code, signal, stderrTail)));
2354
+ }
2355
+ resolve2();
2356
+ });
2357
+ });
2358
+ (async () => {
2359
+ const events = parseStreamJson2(readLines2(spawned.stdout), {
2360
+ onSessionId: (sid) => bind(sid)
2361
+ });
2362
+ try {
2363
+ for await (const ev of events) {
2364
+ const enriched = enrichUsageEvent2(ev, session?.spawnedAtIso);
2365
+ queue.push(enriched);
2366
+ if (session)
2367
+ this.notify(session);
2368
+ if ((enriched.type === "done" || enriched.type === "error") && session) {
2369
+ this.registry.unregister(session.sessionId, spawned.proc);
2370
+ }
2371
+ }
2372
+ } catch (err) {
2373
+ const ev = {
2374
+ type: "error",
2375
+ message: `codex parser failure: ${err instanceof Error ? err.message : String(err)}`
2376
+ };
2377
+ queue.push(ev);
2378
+ if (session)
2379
+ this.notify(session);
2380
+ } finally {
2381
+ await Promise.race([exitObserved, new Promise((r) => setTimeout(r, 500))]);
2382
+ if (session) {
2383
+ const code = exitInfo.code;
2384
+ const lastEv = queue[queue.length - 1];
2385
+ if (exitInfo.seen && typeof code === "number" && code !== 0 && lastEv?.type !== "error") {
2386
+ queue.push({
2387
+ type: "error",
2388
+ message: formatExitMsg("codex exited", code, exitInfo.signal, stderrTail)
2389
+ });
2390
+ }
2391
+ session.closed = true;
2392
+ this.notify(session);
2393
+ this.registry.unregister(session.sessionId, spawned.proc);
2394
+ if (this.running.get(session.sessionId) === session) {
2395
+ this.running.delete(session.sessionId);
2396
+ }
2397
+ }
2398
+ if (!bound) {
2399
+ rejectHandle(new Error("codex exited without emitting a session id"));
2400
+ }
2401
+ }
2402
+ })();
2403
+ spawned.proc.once("error", (err) => {
2404
+ if (!bound)
2405
+ rejectHandle(err);
2406
+ });
2407
+ return handlePromise;
2408
+ }
2409
+ notify(session) {
2410
+ const waiters = session.waiters;
2411
+ session.waiters = [];
2412
+ for (const w of waiters)
2413
+ w();
2414
+ }
2415
+ }
2416
+ function captureStderrTail(stream, onChunk) {
2417
+ const s = stream;
2418
+ s.on("data", (chunk) => {
2419
+ onChunk(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
2420
+ });
2421
+ s.on("error", () => {});
2422
+ }
2423
+ function formatExitMsg(prefix, code, signal, stderrTail) {
2424
+ const parts = [prefix];
2425
+ if (typeof code === "number")
2426
+ parts.push(`(code=${code}${signal ? `, signal=${signal}` : ""})`);
2427
+ else if (signal)
2428
+ parts.push(`(signal=${signal})`);
2429
+ const detail = stderrTail.trim().split(/\r?\n/).filter(Boolean).slice(-3).join(" | ");
2430
+ if (detail)
2431
+ parts.push(`: ${detail}`);
2432
+ return parts.join(" ").replace(/ : /, ": ");
2433
+ }
2434
+ function enrichUsageEvent2(ev, startedAtIso) {
2435
+ if (ev.type !== "usage")
2436
+ return ev;
2437
+ return { type: "usage", ...withTotalSpeedForTurn(ev, startedAtIso, new Date().toISOString()) };
2438
+ }
2439
+ var STDERR_TAIL_CAP;
2440
+ var init_codex_local = __esm(() => {
2441
+ init_binary2();
2442
+ init_capabilities2();
2443
+ init_history2();
2444
+ init_sessions2();
2445
+ init_spawn2();
2446
+ STDERR_TAIL_CAP = 4 * 1024;
2447
+ });
2448
+
1163
2449
  // src/orchestrator/bridge/server.ts
1164
- import { mkdir as mkdir2, unlink as unlink2 } from "fs/promises";
2450
+ import { mkdir as mkdir2, unlink as unlink3 } from "fs/promises";
1165
2451
  import { createServer } from "net";
1166
2452
  import { dirname as dirname2 } from "path";
1167
2453
  async function startBridgeServer(orch, socketPath) {
1168
2454
  await mkdir2(dirname2(socketPath), { recursive: true });
1169
- await unlink2(socketPath).catch(() => {});
2455
+ await unlink3(socketPath).catch(() => {});
1170
2456
  const conns = new Set;
1171
2457
  const server = createServer((conn) => {
1172
2458
  conns.add(conn);
@@ -1207,7 +2493,7 @@ async function startBridgeServer(orch, socketPath) {
1207
2493
  conn.destroy();
1208
2494
  conns.clear();
1209
2495
  await new Promise((resolve2) => server.close(() => resolve2()));
1210
- await unlink2(socketPath).catch(() => {});
2496
+ await unlink3(socketPath).catch(() => {});
1211
2497
  }
1212
2498
  };
1213
2499
  }
@@ -1300,23 +2586,19 @@ __export(exports_bridge, {
1300
2586
  bridgeSocketPathForHome: () => bridgeSocketPathForHome
1301
2587
  });
1302
2588
  import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
1303
- import { homedir as homedir5, tmpdir as tmpdir2 } from "os";
1304
- import { join as join3 } from "path";
2589
+ import { homedir as homedir9 } from "os";
2590
+ import { join as join5 } from "path";
1305
2591
  import { fileURLToPath as fileURLToPath2 } from "url";
1306
2592
  function bridgeSocketPathForHome(home, pid = process.pid) {
1307
- const runDir = join3(home, ".kobe", "run");
1308
- const preferred = join3(runDir, `bridge-${pid}.sock`);
1309
- const macTempSocket = process.platform === "darwin" && preferred.startsWith(tmpdir2());
1310
- if (preferred.length <= UNIX_SOCKET_PATH_LIMIT && !macTempSocket)
1311
- return preferred;
1312
- const shortTmp = process.platform === "darwin" ? "/tmp" : tmpdir2();
1313
- return join3(shortTmp, `kobe-bridge-${pid}.sock`);
2593
+ const runDir = join5(home, ".kobe", "run");
2594
+ return fitSocketPath(join5(runDir, `bridge-${pid}.sock`), home, "bridge", pid);
1314
2595
  }
1315
2596
  async function startBridge(orch, opts = {}) {
1316
- const home = opts.homeDir ?? process.env.KOBE_HOME_DIR ?? homedir5();
1317
- const runDir = join3(home, ".kobe", "run");
2597
+ const home = opts.homeDir ?? process.env.KOBE_HOME_DIR ?? homedir9();
2598
+ const runDir = join5(home, ".kobe", "run");
1318
2599
  const socketPath = bridgeSocketPathForHome(home);
1319
- const mcpConfigPath = join3(runDir, `mcp-${process.pid}.json`);
2600
+ const mcpConfigPath = join5(runDir, `mcp-${process.pid}.json`);
2601
+ await mkdir3(runDir, { recursive: true });
1320
2602
  const server = await startBridgeServer(orch, socketPath);
1321
2603
  await mkdir3(runDir, { recursive: true });
1322
2604
  const moduleExt = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
@@ -1339,8 +2621,8 @@ async function startBridge(orch, opts = {}) {
1339
2621
  }
1340
2622
  };
1341
2623
  }
1342
- var UNIX_SOCKET_PATH_LIMIT = 103;
1343
2624
  var init_bridge = __esm(() => {
2625
+ init_paths();
1344
2626
  init_server();
1345
2627
  });
1346
2628
 
@@ -2572,134 +3854,83 @@ var init_dev = __esm(() => {
2572
3854
  }
2573
3855
  });
2574
3856
 
2575
- // src/engine/claude-settings.ts
2576
- import { readFileSync } from "fs";
2577
- import { homedir as homedir6 } from "os";
2578
- import { join as join4 } from "path";
2579
- function readClaudeSettings() {
2580
- if (cached !== undefined)
2581
- return cached;
2582
- try {
2583
- const raw = readFileSync(SETTINGS_PATH, "utf8");
2584
- const parsed = JSON.parse(raw);
2585
- if (!parsed || typeof parsed !== "object") {
2586
- cached = null;
2587
- return null;
2588
- }
2589
- const obj = parsed;
2590
- const model = typeof obj.model === "string" && obj.model.length > 0 ? obj.model : undefined;
2591
- cached = { model };
2592
- return cached;
2593
- } catch {
2594
- cached = null;
2595
- return null;
3857
+ // src/engine/registry.ts
3858
+ function getCapabilities(vendor) {
3859
+ return ENGINE_REGISTRY[vendor] ?? defaultCapabilities;
3860
+ }
3861
+ function getIdentity(vendor) {
3862
+ return ENGINE_IDENTITIES[vendor] ?? defaultIdentity;
3863
+ }
3864
+ function capabilitiesForModelId(modelId) {
3865
+ if (!modelId)
3866
+ return defaultCapabilities;
3867
+ for (const caps of Object.values(ENGINE_REGISTRY)) {
3868
+ if (!caps)
3869
+ continue;
3870
+ if (caps.models.some((m) => m.id === modelId))
3871
+ return caps;
2596
3872
  }
3873
+ return defaultCapabilities;
2597
3874
  }
2598
- function resolveDefaultModelId() {
2599
- const settings = readClaudeSettings();
2600
- if (settings?.model && settings.model.length > 0)
2601
- return settings.model;
2602
- return FALLBACK_DEFAULT_MODEL_ID;
2603
- }
2604
- var SETTINGS_PATH, cached, FALLBACK_DEFAULT_MODEL_ID = "claude-opus-4-7[1m]";
2605
- var init_claude_settings = __esm(() => {
2606
- SETTINGS_PATH = join4(homedir6(), ".claude", "settings.json");
2607
- });
2608
-
2609
- // src/session/usage-metrics.ts
2610
- function totalContextTokens(u) {
2611
- return u.input_tokens + (u.cache_read_input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0);
2612
- }
2613
- function parseTimestampMs(value) {
2614
- const ms = new Date(value).getTime();
2615
- return Number.isFinite(ms) ? ms : null;
2616
- }
2617
- function mergeIntervals(intervals) {
2618
- if (intervals.length === 0)
2619
- return [];
2620
- const sorted = [...intervals].sort((a, b) => a.startMs - b.startMs);
2621
- const first = sorted[0];
2622
- if (!first)
2623
- return [];
2624
- const merged = [{ startMs: first.startMs, endMs: first.endMs }];
2625
- for (let i = 1;i < sorted.length; i++) {
2626
- const current = sorted[i];
2627
- const last = merged[merged.length - 1];
2628
- if (!current || !last)
3875
+ function allModels() {
3876
+ const seen = new Set;
3877
+ const out = [];
3878
+ for (const caps of Object.values(ENGINE_REGISTRY)) {
3879
+ if (!caps)
2629
3880
  continue;
2630
- if (current.startMs <= last.endMs) {
2631
- last.endMs = Math.max(last.endMs, current.endMs);
2632
- } else {
2633
- merged.push({ startMs: current.startMs, endMs: current.endMs });
3881
+ for (const m of caps.models) {
3882
+ const key = `${m.vendor}:${m.id}:${m.effort ?? ""}`;
3883
+ if (seen.has(key))
3884
+ continue;
3885
+ seen.add(key);
3886
+ out.push(m);
2634
3887
  }
2635
3888
  }
2636
- return merged;
2637
- }
2638
- function durationMs(intervals) {
2639
- return intervals.reduce((total, interval) => total + (interval.endMs - interval.startMs), 0);
3889
+ return out;
2640
3890
  }
2641
- function deriveSessionUsageMetrics(past) {
2642
- let latestUsage;
2643
- let latestUsageTimestampMs = null;
2644
- let lastUserTimestampMs = null;
2645
- let inputTokens = 0;
2646
- let outputTokens = 0;
2647
- const intervals = [];
2648
- for (const message of past) {
2649
- const timestampMs = parseTimestampMs(message.timestamp);
2650
- if (message.role === "user" && timestampMs !== null) {
2651
- lastUserTimestampMs = timestampMs;
2652
- continue;
2653
- }
2654
- if (message.role !== "assistant" || !message.usage)
3891
+ function modelLabelFor(modelId, effort) {
3892
+ const resolved = modelId ?? defaultCapabilities.defaultModelId();
3893
+ for (const caps of Object.values(ENGINE_REGISTRY)) {
3894
+ if (!caps)
2655
3895
  continue;
2656
- if (timestampMs !== null && (latestUsageTimestampMs === null || timestampMs > latestUsageTimestampMs)) {
2657
- latestUsageTimestampMs = timestampMs;
2658
- latestUsage = message.usage;
2659
- } else if (latestUsage === undefined) {
2660
- latestUsage = message.usage;
2661
- }
2662
- inputTokens += message.usage.input_tokens;
2663
- outputTokens += message.usage.output_tokens;
2664
- if (timestampMs !== null && lastUserTimestampMs !== null && timestampMs > lastUserTimestampMs) {
2665
- intervals.push({ startMs: lastUserTimestampMs, endMs: timestampMs });
2666
- }
2667
- }
2668
- if (!latestUsage)
2669
- return;
2670
- const totalDurationMs = durationMs(mergeIntervals(intervals));
2671
- if (totalDurationMs <= 0)
2672
- return latestUsage;
2673
- return {
2674
- ...latestUsage,
2675
- total_speed_tokens_per_second: (inputTokens + outputTokens) / (totalDurationMs / 1000)
3896
+ const match = caps.models.find((m) => m.id === resolved && (effort === undefined || m.effort === effort));
3897
+ if (match)
3898
+ return match.label;
3899
+ }
3900
+ if (effort)
3901
+ return `${resolved} \xB7 ${effort}`;
3902
+ return resolved;
3903
+ }
3904
+ var ENGINE_REGISTRY, ENGINE_IDENTITIES, defaultCapabilities, defaultIdentity;
3905
+ var init_registry = __esm(() => {
3906
+ init_capabilities();
3907
+ init_capabilities2();
3908
+ ENGINE_REGISTRY = {
3909
+ claude: claudeCapabilities,
3910
+ codex: codexCapabilities
2676
3911
  };
2677
- }
2678
- function withTotalSpeedForTurn(usage, startedAtIso, endedAtIso) {
2679
- const startMs = startedAtIso ? parseTimestampMs(startedAtIso) : null;
2680
- const endMs = parseTimestampMs(endedAtIso);
2681
- if (startMs === null || endMs === null || endMs <= startMs)
2682
- return usage;
2683
- return {
2684
- ...usage,
2685
- total_speed_tokens_per_second: (usage.input_tokens + usage.output_tokens) / ((endMs - startMs) / 1000)
3912
+ ENGINE_IDENTITIES = {
3913
+ claude: claudeIdentity,
3914
+ codex: codexIdentity
2686
3915
  };
2687
- }
3916
+ defaultCapabilities = claudeCapabilities;
3917
+ defaultIdentity = claudeIdentity;
3918
+ });
2688
3919
 
2689
3920
  // src/env.ts
2690
- import { homedir as homedir7 } from "os";
2691
- import { join as join5 } from "path";
3921
+ import { homedir as homedir10 } from "os";
3922
+ import { join as join6 } from "path";
2692
3923
  function isDev() {
2693
3924
  return process.env.KOBE_DEV === "1";
2694
3925
  }
2695
3926
  function homeDir() {
2696
- return process.env.KOBE_HOME_DIR ?? homedir7();
3927
+ return process.env.KOBE_HOME_DIR ?? homedir10();
2697
3928
  }
2698
3929
  function kobeStateDir() {
2699
- return join5(homeDir(), ".kobe");
3930
+ return join6(homeDir(), ".kobe");
2700
3931
  }
2701
3932
  function kvStatePath() {
2702
- return join5(homeDir(), ".config", "kobe", "state.json");
3933
+ return join6(homeDir(), ".config", "kobe", "state.json");
2703
3934
  }
2704
3935
  var init_env = () => {};
2705
3936
 
@@ -2714,11 +3945,11 @@ __export(exports_repos, {
2714
3945
  getSavedRepos: () => getSavedRepos,
2715
3946
  addSavedRepo: () => addSavedRepo
2716
3947
  });
2717
- import { spawnSync as spawnSync2 } from "child_process";
2718
- import { mkdirSync, readFileSync as readFileSync2, realpathSync, renameSync, writeFileSync } from "fs";
3948
+ import { spawnSync as spawnSync3 } from "child_process";
3949
+ import { mkdirSync, readFileSync as readFileSync3, realpathSync, renameSync, writeFileSync } from "fs";
2719
3950
  import { dirname as dirname3 } from "path";
2720
3951
  function resolveRepoRoot(absPath) {
2721
- const r = spawnSync2("git", ["rev-parse", "--show-toplevel"], {
3952
+ const r = spawnSync3("git", ["rev-parse", "--show-toplevel"], {
2722
3953
  cwd: absPath,
2723
3954
  encoding: "utf8",
2724
3955
  shell: false
@@ -2752,7 +3983,7 @@ function statePath() {
2752
3983
  }
2753
3984
  function load() {
2754
3985
  try {
2755
- const text = readFileSync2(statePath(), "utf8");
3986
+ const text = readFileSync3(statePath(), "utf8");
2756
3987
  const parsed = JSON.parse(text);
2757
3988
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2758
3989
  return parsed;
@@ -2761,11 +3992,11 @@ function load() {
2761
3992
  return {};
2762
3993
  }
2763
3994
  function save(state) {
2764
- const path4 = statePath();
2765
- mkdirSync(dirname3(path4), { recursive: true });
2766
- const tmp = `${path4}.tmp`;
3995
+ const path6 = statePath();
3996
+ mkdirSync(dirname3(path6), { recursive: true });
3997
+ const tmp = `${path6}.tmp`;
2767
3998
  writeFileSync(tmp, JSON.stringify(state, null, 2), "utf8");
2768
- renameSync(tmp, path4);
3999
+ renameSync(tmp, path6);
2769
4000
  }
2770
4001
  function getSavedRepos() {
2771
4002
  const state = load();
@@ -2829,7 +4060,15 @@ function nextChatTabSeq(tabs) {
2829
4060
  max = t.seq;
2830
4061
  return max + 1;
2831
4062
  }
2832
- var toTaskId = (id) => id;
4063
+ function worktreeSlug(task) {
4064
+ if (task.kind === "main")
4065
+ return "";
4066
+ if (!task.worktreePath)
4067
+ return "";
4068
+ const match = task.worktreePath.match(/([^/\\]+)[/\\]*$/);
4069
+ return match ? match[1] ?? "" : "";
4070
+ }
4071
+ var toTaskId = (id) => id, DEFAULT_TASK_VENDOR = "claude";
2833
4072
 
2834
4073
  // src/orchestrator/index/ulid.ts
2835
4074
  function encodeTime(now, len) {
@@ -2891,7 +4130,7 @@ var init_ulid = __esm(() => {
2891
4130
  });
2892
4131
 
2893
4132
  // src/orchestrator/metadata-suggester.ts
2894
- import { spawn as spawn3 } from "child_process";
4133
+ import { spawn as spawn4 } from "child_process";
2895
4134
 
2896
4135
  class MetadataSuggester {
2897
4136
  binaryPromise = null;
@@ -2920,7 +4159,7 @@ class MetadataSuggester {
2920
4159
  return new Promise((resolve2) => {
2921
4160
  let proc;
2922
4161
  try {
2923
- proc = spawn3(binary, ["-p", builder(trimmed)], {
4162
+ proc = spawn4(binary, ["-p", builder(trimmed)], {
2924
4163
  stdio: ["ignore", "pipe", "ignore"],
2925
4164
  env: process.env
2926
4165
  });
@@ -3069,10 +4308,10 @@ class InMemoryPendingInputBroker {
3069
4308
  }
3070
4309
 
3071
4310
  // src/orchestrator/pr/build.ts
3072
- import { spawnSync as spawnSync3 } from "child_process";
4311
+ import { spawnSync as spawnSync4 } from "child_process";
3073
4312
  function git(cwd, args) {
3074
4313
  try {
3075
- const out = spawnSync3("git", args.slice(), {
4314
+ const out = spawnSync4("git", args.slice(), {
3076
4315
  cwd,
3077
4316
  encoding: "utf8",
3078
4317
  timeout: GIT_TIMEOUT_MS
@@ -3122,7 +4361,7 @@ var init_build = () => {};
3122
4361
 
3123
4362
  // src/orchestrator/pr/instructions.ts
3124
4363
  import { promises as fs } from "fs";
3125
- import path4 from "path";
4364
+ import path6 from "path";
3126
4365
  function dirtyCountSentence(n) {
3127
4366
  if (n <= 0)
3128
4367
  return "There are no uncommitted changes.";
@@ -3150,7 +4389,7 @@ function renderPRInstructions(template, state) {
3150
4389
  async function loadPRInstructionsTemplate(worktreePath) {
3151
4390
  if (!worktreePath)
3152
4391
  return DEFAULT_PR_INSTRUCTIONS_TEMPLATE;
3153
- const file = path4.join(worktreePath, ".kobe", "pr-instructions.md");
4392
+ const file = path6.join(worktreePath, ".kobe", "pr-instructions.md");
3154
4393
  try {
3155
4394
  const text = await fs.readFile(file, "utf8");
3156
4395
  if (text.length === 0)
@@ -3201,10 +4440,11 @@ class SessionPump {
3201
4440
  }
3202
4441
  async run(taskId, tabId, handle) {
3203
4442
  const tabKey = chatRunStateKey(taskId, tabId);
4443
+ const engine = this.env.engineFor(taskId, tabId);
3204
4444
  let killedForInput = false;
3205
4445
  let terminalEvent = null;
3206
4446
  try {
3207
- for await (const ev of this.env.engine.stream(handle)) {
4447
+ for await (const ev of engine.stream(handle)) {
3208
4448
  const inputReq = detectUserInputFromEngineEvent(ev);
3209
4449
  if (inputReq) {
3210
4450
  this.env.dispatch(taskId, tabId, ev);
@@ -3218,7 +4458,7 @@ class SessionPump {
3218
4458
  });
3219
4459
  killedForInput = true;
3220
4460
  try {
3221
- await this.env.engine.stop(handle);
4461
+ await engine.stop(handle);
3222
4462
  } catch {}
3223
4463
  break;
3224
4464
  }
@@ -3231,7 +4471,7 @@ class SessionPump {
3231
4471
  } finally {
3232
4472
  if (!killedForInput) {
3233
4473
  try {
3234
- await this.env.engine.stop(handle);
4474
+ await engine.stop(handle);
3235
4475
  } catch {}
3236
4476
  }
3237
4477
  }
@@ -3242,6 +4482,548 @@ var init_session_pump = __esm(() => {
3242
4482
  init_core();
3243
4483
  });
3244
4484
 
4485
+ // src/orchestrator/worktree/animal-names.ts
4486
+ var ANIMAL_NAMES;
4487
+ var init_animal_names = __esm(() => {
4488
+ ANIMAL_NAMES = [
4489
+ "aardvark",
4490
+ "addax",
4491
+ "adder",
4492
+ "agouti",
4493
+ "albatross",
4494
+ "alpaca",
4495
+ "anaconda",
4496
+ "anchovy",
4497
+ "anglerfish",
4498
+ "anole",
4499
+ "anteater",
4500
+ "antelope",
4501
+ "ape",
4502
+ "argali",
4503
+ "armadillo",
4504
+ "avocet",
4505
+ "axolotl",
4506
+ "baboon",
4507
+ "badger",
4508
+ "banteng",
4509
+ "barracuda",
4510
+ "basilisk",
4511
+ "bass",
4512
+ "bat",
4513
+ "beagle",
4514
+ "bear",
4515
+ "beaver",
4516
+ "bee",
4517
+ "beetle",
4518
+ "beluga",
4519
+ "betta",
4520
+ "bison",
4521
+ "blackbird",
4522
+ "blenny",
4523
+ "bluebird",
4524
+ "bluegill",
4525
+ "boar",
4526
+ "bobcat",
4527
+ "bonito",
4528
+ "bovid",
4529
+ "buffalo",
4530
+ "bullfrog",
4531
+ "bunting",
4532
+ "bushbuck",
4533
+ "butterfly",
4534
+ "buzzard",
4535
+ "caiman",
4536
+ "camel",
4537
+ "canary",
4538
+ "capybara",
4539
+ "caracal",
4540
+ "cardinal",
4541
+ "caribou",
4542
+ "carp",
4543
+ "cassowary",
4544
+ "caterpillar",
4545
+ "catfish",
4546
+ "chameleon",
4547
+ "chamois",
4548
+ "char",
4549
+ "cheetah",
4550
+ "chickadee",
4551
+ "chimp",
4552
+ "chinchilla",
4553
+ "chipmunk",
4554
+ "chough",
4555
+ "cicada",
4556
+ "civet",
4557
+ "clam",
4558
+ "clownfish",
4559
+ "cobra",
4560
+ "cockatoo",
4561
+ "condor",
4562
+ "coot",
4563
+ "copperhead",
4564
+ "coral",
4565
+ "cormorant",
4566
+ "cougar",
4567
+ "coyote",
4568
+ "crab",
4569
+ "crane",
4570
+ "crawfish",
4571
+ "crayfish",
4572
+ "cricket",
4573
+ "crocodile",
4574
+ "crow",
4575
+ "cuckoo",
4576
+ "curlew",
4577
+ "cuttlefish",
4578
+ "deer",
4579
+ "dikdik",
4580
+ "dingo",
4581
+ "dolphin",
4582
+ "donkey",
4583
+ "dormouse",
4584
+ "dotterel",
4585
+ "dove",
4586
+ "dragonfly",
4587
+ "dromedary",
4588
+ "duck",
4589
+ "dugong",
4590
+ "dunlin",
4591
+ "eagle",
4592
+ "echidna",
4593
+ "eel",
4594
+ "egret",
4595
+ "eider",
4596
+ "eland",
4597
+ "elephant",
4598
+ "elk",
4599
+ "emu",
4600
+ "ermine",
4601
+ "falcon",
4602
+ "fawn",
4603
+ "ferret",
4604
+ "finch",
4605
+ "firefly",
4606
+ "flamingo",
4607
+ "flounder",
4608
+ "fox",
4609
+ "frog",
4610
+ "gadwall",
4611
+ "gannet",
4612
+ "gaur",
4613
+ "gazelle",
4614
+ "gecko",
4615
+ "gemsbok",
4616
+ "gerbil",
4617
+ "gharial",
4618
+ "gibbon",
4619
+ "gnu",
4620
+ "goat",
4621
+ "goby",
4622
+ "godwit",
4623
+ "goldfish",
4624
+ "goose",
4625
+ "gopher",
4626
+ "goral",
4627
+ "gorilla",
4628
+ "goshawk",
4629
+ "grackle",
4630
+ "grasshopper",
4631
+ "grayling",
4632
+ "grebe",
4633
+ "grouse",
4634
+ "guanaco",
4635
+ "guillemot",
4636
+ "guineafowl",
4637
+ "guppy",
4638
+ "halibut",
4639
+ "hammerhead",
4640
+ "hamster",
4641
+ "hare",
4642
+ "harrier",
4643
+ "hartebeest",
4644
+ "hawk",
4645
+ "hedgehog",
4646
+ "heron",
4647
+ "herring",
4648
+ "hippo",
4649
+ "hornbill",
4650
+ "hornet",
4651
+ "horse",
4652
+ "hummingbird",
4653
+ "hyena",
4654
+ "ibex",
4655
+ "ibis",
4656
+ "iguana",
4657
+ "impala",
4658
+ "indri",
4659
+ "jacana",
4660
+ "jackal",
4661
+ "jaguar",
4662
+ "jay",
4663
+ "jellyfish",
4664
+ "jerboa",
4665
+ "junco",
4666
+ "kakapo",
4667
+ "kangaroo",
4668
+ "kestrel",
4669
+ "killdeer",
4670
+ "kingfisher",
4671
+ "kinglet",
4672
+ "kite",
4673
+ "kiwi",
4674
+ "koala",
4675
+ "kookaburra",
4676
+ "krait",
4677
+ "krill",
4678
+ "kudu",
4679
+ "ladybug",
4680
+ "lamprey",
4681
+ "langur",
4682
+ "lapwing",
4683
+ "lark",
4684
+ "lemming",
4685
+ "lemur",
4686
+ "leopard",
4687
+ "limpet",
4688
+ "lion",
4689
+ "lionfish",
4690
+ "lizard",
4691
+ "llama",
4692
+ "lobster",
4693
+ "locust",
4694
+ "loon",
4695
+ "lorikeet",
4696
+ "loris",
4697
+ "lynx",
4698
+ "macaque",
4699
+ "macaw",
4700
+ "mackerel",
4701
+ "magpie",
4702
+ "mallard",
4703
+ "mamba",
4704
+ "mammoth",
4705
+ "manatee",
4706
+ "mandrill",
4707
+ "manta",
4708
+ "mantis",
4709
+ "marlin",
4710
+ "marmoset",
4711
+ "marmot",
4712
+ "marten",
4713
+ "meerkat",
4714
+ "merganser",
4715
+ "mink",
4716
+ "minnow",
4717
+ "mole",
4718
+ "mongoose",
4719
+ "monitor",
4720
+ "monkey",
4721
+ "moose",
4722
+ "moth",
4723
+ "mouflon",
4724
+ "mouse",
4725
+ "mudpuppy",
4726
+ "mule",
4727
+ "muntjac",
4728
+ "musk",
4729
+ "muskox",
4730
+ "muskrat",
4731
+ "mussel",
4732
+ "mustang",
4733
+ "narwhal",
4734
+ "newt",
4735
+ "nightingale",
4736
+ "numbat",
4737
+ "nuthatch",
4738
+ "nyala",
4739
+ "ocelot",
4740
+ "octopus",
4741
+ "okapi",
4742
+ "opossum",
4743
+ "orangutan",
4744
+ "orca",
4745
+ "oriole",
4746
+ "oryx",
4747
+ "osprey",
4748
+ "ostrich",
4749
+ "otter",
4750
+ "owl",
4751
+ "oyster",
4752
+ "oystercatcher",
4753
+ "paca",
4754
+ "panda",
4755
+ "pangolin",
4756
+ "panther",
4757
+ "parakeet",
4758
+ "parrot",
4759
+ "parrotfish",
4760
+ "partridge",
4761
+ "peafowl",
4762
+ "pelican",
4763
+ "penguin",
4764
+ "perch",
4765
+ "petrel",
4766
+ "pheasant",
4767
+ "pigeon",
4768
+ "pika",
4769
+ "pike",
4770
+ "pintail",
4771
+ "pipefish",
4772
+ "platypus",
4773
+ "plover",
4774
+ "pollock",
4775
+ "pony",
4776
+ "porcupine",
4777
+ "porpoise",
4778
+ "possum",
4779
+ "ptarmigan",
4780
+ "puffin",
4781
+ "puma",
4782
+ "pupfish",
4783
+ "python",
4784
+ "quail",
4785
+ "quetzal",
4786
+ "quokka",
4787
+ "quoll",
4788
+ "rabbit",
4789
+ "raccoon",
4790
+ "rail",
4791
+ "rat",
4792
+ "rattler",
4793
+ "raven",
4794
+ "ray",
4795
+ "redpoll",
4796
+ "reindeer",
4797
+ "remora",
4798
+ "rhino",
4799
+ "roadrunner",
4800
+ "robin",
4801
+ "rook",
4802
+ "sable",
4803
+ "sailfish",
4804
+ "salamander",
4805
+ "salmon",
4806
+ "sandpiper",
4807
+ "sardine",
4808
+ "scallop",
4809
+ "scaup",
4810
+ "scorpion",
4811
+ "seahorse",
4812
+ "seal",
4813
+ "serval",
4814
+ "shark",
4815
+ "shearwater",
4816
+ "shrew",
4817
+ "shrike",
4818
+ "shrimp",
4819
+ "siskin",
4820
+ "sitatunga",
4821
+ "skate",
4822
+ "skink",
4823
+ "skua",
4824
+ "skunk",
4825
+ "sloth",
4826
+ "smelt",
4827
+ "snail",
4828
+ "snipe",
4829
+ "solenodon",
4830
+ "sparrow",
4831
+ "spider",
4832
+ "springbok",
4833
+ "squid",
4834
+ "squirrel",
4835
+ "starfish",
4836
+ "starling",
4837
+ "stilt",
4838
+ "stingray",
4839
+ "stoat",
4840
+ "stork",
4841
+ "sunbird",
4842
+ "sunfish",
4843
+ "swallow",
4844
+ "swan",
4845
+ "swift",
4846
+ "swordfish",
4847
+ "tahr",
4848
+ "takin",
4849
+ "tamarin",
4850
+ "tanager",
4851
+ "tapir",
4852
+ "tarantula",
4853
+ "tarpon",
4854
+ "tarsier",
4855
+ "teal",
4856
+ "tenrec",
4857
+ "terrapin",
4858
+ "tern",
4859
+ "tetra",
4860
+ "thrasher",
4861
+ "thrush",
4862
+ "tiger",
4863
+ "toad",
4864
+ "tortoise",
4865
+ "toucan",
4866
+ "treecreeper",
4867
+ "treefrog",
4868
+ "trout",
4869
+ "tuatara",
4870
+ "tuna",
4871
+ "turkey",
4872
+ "turtle",
4873
+ "urchin",
4874
+ "urial",
4875
+ "vicuna",
4876
+ "viper",
4877
+ "vole",
4878
+ "vulture",
4879
+ "wagtail",
4880
+ "wallaby",
4881
+ "walleye",
4882
+ "walrus",
4883
+ "warbler",
4884
+ "warthog",
4885
+ "waterbuck",
4886
+ "weasel",
4887
+ "wildebeest",
4888
+ "wolf",
4889
+ "wolverine",
4890
+ "wombat",
4891
+ "woodcock",
4892
+ "woodpecker",
4893
+ "wrasse",
4894
+ "wren",
4895
+ "yak",
4896
+ "zebra",
4897
+ "zebu",
4898
+ "zorilla"
4899
+ ];
4900
+ });
4901
+
4902
+ // src/orchestrator/worktree/paths.ts
4903
+ import fs2 from "fs";
4904
+ import path7 from "path";
4905
+ function worktreeRootFor(repo) {
4906
+ if (!path7.isAbsolute(repo)) {
4907
+ throw new Error(`worktreeRootFor: repo must be an absolute path, got: ${repo}`);
4908
+ }
4909
+ return path7.join(repo, KOBE_WORKTREE_ROOT_SUBPATH);
4910
+ }
4911
+ function worktreePathFor(repo, slug) {
4912
+ if (!slug || /[/\\\0]/.test(slug)) {
4913
+ throw new Error(`worktreePathFor: invalid slug: ${JSON.stringify(slug)}`);
4914
+ }
4915
+ return path7.join(worktreeRootFor(repo), slug);
4916
+ }
4917
+ function listWorktreeDirNames(repo) {
4918
+ const root = worktreeRootFor(repo);
4919
+ try {
4920
+ return fs2.readdirSync(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
4921
+ } catch {
4922
+ return [];
4923
+ }
4924
+ }
4925
+ function isKobeManagedPath(repo, candidate) {
4926
+ if (!path7.isAbsolute(repo) || !path7.isAbsolute(candidate))
4927
+ return false;
4928
+ const root = canonicalize(worktreeRootFor(repo));
4929
+ const target = canonicalize(candidate);
4930
+ const rel = path7.relative(root, target);
4931
+ return rel !== "" && !rel.startsWith("..") && !path7.isAbsolute(rel);
4932
+ }
4933
+ function canonicalize(p) {
4934
+ try {
4935
+ return fs2.realpathSync(p);
4936
+ } catch {
4937
+ return path7.resolve(p);
4938
+ }
4939
+ }
4940
+ var KOBE_WORKTREE_ROOT_SUBPATH = ".claude/worktrees";
4941
+ var init_paths2 = () => {};
4942
+
4943
+ // src/orchestrator/worktree/slug-allocator.ts
4944
+ class SlugAllocator {
4945
+ activeSlugs;
4946
+ random;
4947
+ pool;
4948
+ pendingByRepo = new Map;
4949
+ chain = Promise.resolve();
4950
+ constructor(activeSlugs, options = {}) {
4951
+ this.activeSlugs = activeSlugs;
4952
+ this.random = options.random ?? Math.random;
4953
+ this.pool = options.pool ?? ANIMAL_NAMES;
4954
+ if (this.pool.length === 0) {
4955
+ throw new Error("SlugAllocator: animal pool cannot be empty");
4956
+ }
4957
+ }
4958
+ async allocate(repo) {
4959
+ const previous = this.chain;
4960
+ let release;
4961
+ this.chain = new Promise((resolve2) => {
4962
+ release = resolve2;
4963
+ });
4964
+ await previous;
4965
+ try {
4966
+ return this.pickLocked(repo);
4967
+ } finally {
4968
+ release();
4969
+ }
4970
+ }
4971
+ commit(repo, slug) {
4972
+ this.deletePending(repo, slug);
4973
+ }
4974
+ cancel(repo, slug) {
4975
+ this.deletePending(repo, slug);
4976
+ }
4977
+ pickLocked(repo) {
4978
+ const occupied = this.occupiedSlugs(repo);
4979
+ const candidates = this.pool.filter((n) => !occupied.has(n));
4980
+ if (candidates.length > 0) {
4981
+ const pick = candidates[Math.floor(this.random() * candidates.length)];
4982
+ this.addPending(repo, pick);
4983
+ return pick;
4984
+ }
4985
+ const base = this.pool[Math.floor(this.random() * this.pool.length)];
4986
+ for (let v = 2;; v++) {
4987
+ const candidate = `${base}-v${v}`;
4988
+ if (!occupied.has(candidate)) {
4989
+ this.addPending(repo, candidate);
4990
+ return candidate;
4991
+ }
4992
+ }
4993
+ }
4994
+ occupiedSlugs(repo) {
4995
+ const set = new Set(this.pendingByRepo.get(repo) ?? []);
4996
+ for (const slug of this.activeSlugs(repo)) {
4997
+ if (slug)
4998
+ set.add(slug);
4999
+ }
5000
+ for (const dir of listWorktreeDirNames(repo)) {
5001
+ set.add(dir);
5002
+ }
5003
+ return set;
5004
+ }
5005
+ addPending(repo, slug) {
5006
+ let pending = this.pendingByRepo.get(repo);
5007
+ if (!pending) {
5008
+ pending = new Set;
5009
+ this.pendingByRepo.set(repo, pending);
5010
+ }
5011
+ pending.add(slug);
5012
+ }
5013
+ deletePending(repo, slug) {
5014
+ const pending = this.pendingByRepo.get(repo);
5015
+ if (!pending)
5016
+ return;
5017
+ pending.delete(slug);
5018
+ if (pending.size === 0)
5019
+ this.pendingByRepo.delete(repo);
5020
+ }
5021
+ }
5022
+ var init_slug_allocator = __esm(() => {
5023
+ init_animal_names();
5024
+ init_paths2();
5025
+ });
5026
+
3245
5027
  // src/orchestrator/core.ts
3246
5028
  var exports_core = {};
3247
5029
  __export(exports_core, {
@@ -3291,7 +5073,8 @@ function summarizeWorktreeError(raw, repo, baseRef) {
3291
5073
  }
3292
5074
 
3293
5075
  class Orchestrator {
3294
- engine;
5076
+ engines;
5077
+ fallbackEngine;
3295
5078
  store;
3296
5079
  worktrees;
3297
5080
  metadataSuggester;
@@ -3303,16 +5086,38 @@ class Orchestrator {
3303
5086
  requestIdCounter = 0;
3304
5087
  sessionPump;
3305
5088
  pendingWorktreeOpts = new Map;
5089
+ slugAllocator;
5090
+ ensureWorktreeLatches = new Map;
3306
5091
  tasksAcc;
3307
5092
  setTasks;
3308
5093
  unsubscribeStore;
3309
5094
  runStateAcc;
3310
5095
  setRunState;
3311
5096
  constructor(deps) {
3312
- this.engine = deps.engine;
5097
+ const built = {};
5098
+ let fallback;
5099
+ if (deps.engines) {
5100
+ for (const [vendor, eng] of Object.entries(deps.engines)) {
5101
+ if (!eng)
5102
+ continue;
5103
+ built[vendor] = eng;
5104
+ fallback ??= eng;
5105
+ }
5106
+ }
5107
+ if (deps.engine) {
5108
+ const v = deps.engine.capabilities.vendorId;
5109
+ built[v] ??= deps.engine;
5110
+ fallback ??= deps.engine;
5111
+ }
5112
+ if (!fallback) {
5113
+ throw new Error("Orchestrator: no usable engine found; both deps.engine and deps.engines were examined but contained no valid engines.");
5114
+ }
5115
+ this.engines = built;
5116
+ this.fallbackEngine = fallback;
3313
5117
  this.store = deps.store;
3314
5118
  this.worktrees = deps.worktrees;
3315
5119
  this.metadataSuggester = deps.metadataSuggester ?? new MetadataSuggester;
5120
+ this.slugAllocator = new SlugAllocator((repo) => this.store.list().filter((t) => t.repo === repo && !t.archived).map((t) => worktreeSlug(t)).filter((s) => s.length > 0));
3316
5121
  const [tasks, setTasks] = createSignal(this.store.list());
3317
5122
  this.tasksAcc = tasks;
3318
5123
  this.setTasks = (next) => setTasks(() => next);
@@ -3323,13 +5128,89 @@ class Orchestrator {
3323
5128
  this.runStateAcc = runState;
3324
5129
  this.setRunState = (next) => setRunState(() => next);
3325
5130
  this.sessionPump = new SessionPump({
3326
- engine: this.engine,
5131
+ engineFor: (taskId, tabId) => this.engineForTaskTabId(taskId, tabId),
3327
5132
  broker: this.pendingInputBroker,
3328
5133
  dispatch: (taskId, tabId, ev) => this.dispatchEvent(taskId, tabId, ev),
3329
5134
  nextRequestId: () => `req-${++this.requestIdCounter}`,
3330
5135
  onPendingInputChange: () => this.bumpRunState()
3331
5136
  });
3332
5137
  }
5138
+ engineForVendor(vendor) {
5139
+ const v = vendor ?? DEFAULT_TASK_VENDOR;
5140
+ return this.engines[v] ?? this.fallbackEngine;
5141
+ }
5142
+ engineForTask(task) {
5143
+ return this.engineForVendor(task.vendor);
5144
+ }
5145
+ vendorForTab(task, tab) {
5146
+ return tab.vendor ?? task.vendor ?? "claude";
5147
+ }
5148
+ modelForTab(task, tab, engine) {
5149
+ return tab.model ?? task.model ?? engine.capabilities.defaultModelId();
5150
+ }
5151
+ modelEffortForTab(task, tab) {
5152
+ return tab.modelEffort ?? task.modelEffort;
5153
+ }
5154
+ engineForTab(task, tab) {
5155
+ return this.engineForVendor(this.vendorForTab(task, tab));
5156
+ }
5157
+ async engineForTabRun(task, tab) {
5158
+ if (!tab.sessionId || tab.vendor)
5159
+ return this.engineForTab(task, tab);
5160
+ const resolved = await this.findEngineWithHistory(tab.sessionId, this.vendorForTab(task, tab));
5161
+ if (resolved.vendor && resolved.vendor !== tab.vendor) {
5162
+ await this.updateTab(task.id, tab.id, { vendor: resolved.vendor });
5163
+ }
5164
+ return resolved.engine;
5165
+ }
5166
+ async findEngineWithHistory(sessionId, preferredVendor) {
5167
+ const candidates = [];
5168
+ if (preferredVendor)
5169
+ candidates.push([preferredVendor, this.engineForVendor(preferredVendor)]);
5170
+ for (const [vendor, engine] of Object.entries(this.engines)) {
5171
+ if (!engine || vendor === preferredVendor)
5172
+ continue;
5173
+ candidates.push([vendor, engine]);
5174
+ }
5175
+ if (candidates.length === 0)
5176
+ candidates.push([undefined, this.fallbackEngine]);
5177
+ let fallback = candidates[0] ?? [undefined, this.fallbackEngine];
5178
+ let fallbackHistory;
5179
+ for (const [vendor, engine] of candidates) {
5180
+ try {
5181
+ const history = await engine.readHistory(sessionId);
5182
+ if (!fallbackHistory) {
5183
+ fallback = [vendor, engine];
5184
+ fallbackHistory = history;
5185
+ }
5186
+ if (history.messages.length > 0 || history.usageMetrics)
5187
+ return { engine, vendor, history };
5188
+ } catch {}
5189
+ }
5190
+ return { engine: fallback[1], vendor: fallback[0], history: fallbackHistory };
5191
+ }
5192
+ engineForTaskId(taskId) {
5193
+ const task = this.store.get(taskId);
5194
+ return task ? this.engineForTask(task) : this.fallbackEngine;
5195
+ }
5196
+ engineForTaskTabId(taskId, tabId) {
5197
+ const task = this.store.get(taskId);
5198
+ if (!task)
5199
+ return this.fallbackEngine;
5200
+ const tab = task.tabs.find((t) => t.id === tabId);
5201
+ return tab ? this.engineForTab(task, tab) : this.engineForTask(task);
5202
+ }
5203
+ engineForSessionId(sessionId) {
5204
+ for (const task of this.store.list()) {
5205
+ for (const tab of task.tabs) {
5206
+ if (tab.sessionId === sessionId)
5207
+ return this.engineForTab(task, tab);
5208
+ }
5209
+ if (task.sessionId === sessionId)
5210
+ return this.engineForTask(task);
5211
+ }
5212
+ return this.fallbackEngine;
5213
+ }
3333
5214
  bumpRunState() {
3334
5215
  const next = new Map;
3335
5216
  for (const tabKey2 of this.pendingInputBroker.awaitingTabKeys()) {
@@ -3434,26 +5315,49 @@ class Orchestrator {
3434
5315
  async ensureWorktree(task) {
3435
5316
  if (task.worktreePath)
3436
5317
  return task;
5318
+ const inflight = this.ensureWorktreeLatches.get(task.id);
5319
+ if (inflight) {
5320
+ await inflight;
5321
+ return this.requireTask(task.id);
5322
+ }
5323
+ const latch = this.doEnsureWorktree(task);
5324
+ this.ensureWorktreeLatches.set(task.id, latch);
5325
+ try {
5326
+ return await latch;
5327
+ } finally {
5328
+ this.ensureWorktreeLatches.delete(task.id);
5329
+ }
5330
+ }
5331
+ async doEnsureWorktree(task) {
3437
5332
  const opts = this.pendingWorktreeOpts.get(task.id);
3438
5333
  const branch = opts?.branch ?? `kobe/tmp-${task.id.slice(-8).toLowerCase()}`;
3439
5334
  const baseRef = opts?.baseRef;
5335
+ const slug = await this.slugAllocator.allocate(task.repo);
3440
5336
  let info;
3441
5337
  try {
3442
5338
  info = await this.worktrees.createForTask({
3443
5339
  repo: task.repo,
3444
- taskId: task.id,
5340
+ slug,
3445
5341
  branch,
3446
5342
  baseRef
3447
5343
  });
3448
5344
  } catch (err) {
5345
+ this.slugAllocator.cancel(task.repo, slug);
3449
5346
  const message = err instanceof Error ? err.message : String(err);
3450
5347
  throw new Error(summarizeWorktreeError(message, task.repo, baseRef ?? null), { cause: err });
3451
5348
  }
3452
- this.pendingWorktreeOpts.delete(task.id);
3453
- return await this.store.update(task.id, {
3454
- branch: info.branch,
3455
- worktreePath: info.path
3456
- });
5349
+ try {
5350
+ this.pendingWorktreeOpts.delete(task.id);
5351
+ const updated = await this.store.update(task.id, {
5352
+ branch: info.branch,
5353
+ worktreePath: info.path
5354
+ });
5355
+ this.slugAllocator.commit(task.repo, slug);
5356
+ return updated;
5357
+ } catch (err) {
5358
+ this.slugAllocator.cancel(task.repo, slug);
5359
+ throw err;
5360
+ }
3457
5361
  }
3458
5362
  async maybeRenameTempBranch(taskId, tabId, prompt) {
3459
5363
  if (!prompt || prompt.trim().length === 0)
@@ -3549,14 +5453,17 @@ class Orchestrator {
3549
5453
  if (prompt && prompt.trim().length > 0) {
3550
5454
  this.dispatchEvent(task.id, targetTab.id, { type: "user.inject", text: prompt });
3551
5455
  }
3552
- const modelToUse = task.model ?? resolveDefaultModelId();
5456
+ const engine = targetTab.sessionId ? await this.engineForTabRun(task, targetTab) : this.engineForTab(task, targetTab);
5457
+ const modelToUse = this.modelForTab(task, targetTab, engine);
5458
+ const modelEffortToUse = this.modelEffortForTab(task, targetTab);
3553
5459
  let handle;
3554
5460
  if (targetTab.sessionId) {
3555
- handle = await this.engine.resume(targetTab.sessionId, promptToSend, {
5461
+ handle = await engine.resume(targetTab.sessionId, promptToSend, {
3556
5462
  cwd: task.worktreePath,
3557
5463
  env: { KOBE_RESUME_CWD: task.worktreePath },
3558
5464
  permissionMode: task.permissionMode,
3559
- model: modelToUse
5465
+ model: modelToUse,
5466
+ modelEffort: modelEffortToUse
3560
5467
  });
3561
5468
  } else {
3562
5469
  let releaseLatch = () => {};
@@ -3565,9 +5472,10 @@ class Orchestrator {
3565
5472
  });
3566
5473
  this.firstSpawnLatches.set(key, latch);
3567
5474
  try {
3568
- handle = await this.engine.spawn(task.worktreePath, promptToSend, {
5475
+ handle = await engine.spawn(task.worktreePath, promptToSend, {
3569
5476
  permissionMode: task.permissionMode,
3570
- model: modelToUse
5477
+ model: modelToUse,
5478
+ modelEffort: modelEffortToUse
3571
5479
  });
3572
5480
  await this.updateTab(task.id, targetTab.id, { sessionId: handle.sessionId });
3573
5481
  if (task.title === PLACEHOLDER_TASK_TITLE && prompt && prompt.trim().length > 0) {
@@ -3640,7 +5548,7 @@ class Orchestrator {
3640
5548
  text: "(turn interrupted \u2014 sending new prompt)"
3641
5549
  });
3642
5550
  try {
3643
- await this.engine.stop(handle);
5551
+ await this.engineForTask(task).stop(handle);
3644
5552
  } finally {
3645
5553
  this.handles.delete(key);
3646
5554
  this.bumpRunState();
@@ -3682,11 +5590,13 @@ class Orchestrator {
3682
5590
  return;
3683
5591
  await this.store.update(task.id, { permissionMode: mode });
3684
5592
  }
3685
- async setModel(id, model) {
5593
+ async setModel(id, model, tabId, modelEffort) {
3686
5594
  const task = this.requireTask(id);
3687
- if (task.model === model)
5595
+ const tab = this.resolveTab(task, tabId);
5596
+ const vendor = model ? capabilitiesForModelId(model).vendorId : this.vendorForTab(task, tab);
5597
+ if (tab.model === model && tab.modelEffort === modelEffort && this.vendorForTab(task, tab) === vendor)
3688
5598
  return;
3689
- await this.store.update(task.id, { model });
5599
+ await this.updateTab(task.id, tab.id, { model, modelEffort, vendor });
3690
5600
  }
3691
5601
  async setTitle(id, title) {
3692
5602
  const task = this.requireTask(id);
@@ -3750,7 +5660,7 @@ class Orchestrator {
3750
5660
  }
3751
5661
  if (task.sessionId) {
3752
5662
  try {
3753
- await this.engine.deleteHistory(task.sessionId);
5663
+ await this.engineForTask(task).deleteHistory(task.sessionId);
3754
5664
  } catch (err) {
3755
5665
  console.error(`[kobe orchestrator] deleteTask: deleteHistory failed for ${task.id}:`, err);
3756
5666
  }
@@ -3759,15 +5669,15 @@ class Orchestrator {
3759
5669
  await this.store.remove(task.id);
3760
5670
  }
3761
5671
  async readHistory(sessionId) {
3762
- try {
3763
- return await this.engine.readHistory(sessionId);
3764
- } catch {
3765
- return [];
3766
- }
5672
+ return (await this.readHistoryWithMetrics(sessionId)).messages;
3767
5673
  }
3768
5674
  async readHistoryWithMetrics(sessionId) {
3769
- const messages = await this.readHistory(sessionId);
3770
- const usageMetrics = deriveSessionUsageMetrics(messages);
5675
+ const preferred = this.engineForSessionId(sessionId);
5676
+ const { history } = await this.findEngineWithHistory(sessionId, preferred.capabilities.vendorId);
5677
+ if (!history)
5678
+ return { messages: [] };
5679
+ const messages = [...history.messages];
5680
+ const usageMetrics = history.usageMetrics;
3771
5681
  return {
3772
5682
  messages,
3773
5683
  ...usageMetrics ? { usageMetrics } : {}
@@ -3777,14 +5687,16 @@ class Orchestrator {
3777
5687
  const task = this.requireTask(id);
3778
5688
  if (!task.worktreePath)
3779
5689
  return [];
5690
+ const tab = this.resolveTab(task);
3780
5691
  try {
3781
- return await this.engine.listSessions(task.worktreePath);
5692
+ return await this.engineForTab(task, tab).listSessions(task.worktreePath);
3782
5693
  } catch {
3783
5694
  return [];
3784
5695
  }
3785
5696
  }
3786
5697
  async openSessionInTab(id, sessionId, opts = {}) {
3787
5698
  const task = this.requireTask(id);
5699
+ const active = this.resolveTab(task);
3788
5700
  const existing = task.tabs.find((t) => t.sessionId === sessionId);
3789
5701
  if (existing) {
3790
5702
  await this.setActiveTab(task.id, existing.id);
@@ -3795,6 +5707,8 @@ class Orchestrator {
3795
5707
  sessionId,
3796
5708
  seq: nextChatTabSeq(task.tabs),
3797
5709
  createdAt: new Date().toISOString(),
5710
+ model: active.model ?? task.model,
5711
+ vendor: this.vendorForTab(task, active),
3798
5712
  ...opts.title ? { title: opts.title } : {}
3799
5713
  };
3800
5714
  await this.store.update(task.id, { tabs: [...task.tabs, tab], activeTabId: tab.id });
@@ -3822,11 +5736,14 @@ class Orchestrator {
3822
5736
  }
3823
5737
  async createTab(id, opts = {}) {
3824
5738
  const task = this.requireTask(id);
5739
+ const active = this.resolveTab(task);
3825
5740
  const tab = {
3826
5741
  id: ulid(),
3827
5742
  sessionId: null,
3828
5743
  seq: nextChatTabSeq(task.tabs),
3829
5744
  createdAt: new Date().toISOString(),
5745
+ model: active.model ?? task.model,
5746
+ vendor: this.vendorForTab(task, active),
3830
5747
  ...opts.title ? { title: opts.title } : {}
3831
5748
  };
3832
5749
  const tabs = [...task.tabs, tab];
@@ -3906,7 +5823,7 @@ class Orchestrator {
3906
5823
  if (!handle)
3907
5824
  return;
3908
5825
  try {
3909
- await this.engine.stop(handle);
5826
+ await this.engineForTaskId(taskId).stop(handle);
3910
5827
  } finally {
3911
5828
  this.handles.delete(key);
3912
5829
  this.bumpRunState();
@@ -3915,12 +5832,13 @@ class Orchestrator {
3915
5832
  async stopAllTabsForTask(taskId) {
3916
5833
  const prefix = `${taskId}:`;
3917
5834
  const keys = Array.from(this.handles.keys()).filter((k) => k.startsWith(prefix));
5835
+ const engine = this.engineForTaskId(taskId);
3918
5836
  for (const key of keys) {
3919
5837
  const handle = this.handles.get(key);
3920
5838
  if (!handle)
3921
5839
  continue;
3922
5840
  try {
3923
- await this.engine.stop(handle);
5841
+ await engine.stop(handle);
3924
5842
  } catch {}
3925
5843
  this.handles.delete(key);
3926
5844
  }
@@ -4046,12 +5964,13 @@ function deriveTitleFromPrompt(prompt) {
4046
5964
  var CONCURRENCY_CAP = 20, PLACEHOLDER_TASK_TITLE = "(new task)", IllegalTransitionError, ConcurrencyCapError, PRPreconditionError, TaskNotFoundError, CannotDeleteMainTaskError, TITLE_CHAR_CAP = 40;
4047
5965
  var init_core = __esm(() => {
4048
5966
  init_dev();
4049
- init_claude_settings();
5967
+ init_registry();
4050
5968
  init_repos();
4051
5969
  init_ulid();
4052
5970
  init_metadata_suggester();
4053
5971
  init_pr();
4054
5972
  init_session_pump();
5973
+ init_slug_allocator();
4055
5974
  IllegalTransitionError = class IllegalTransitionError extends Error {
4056
5975
  from;
4057
5976
  to;
@@ -4091,9 +6010,9 @@ var init_core = __esm(() => {
4091
6010
  });
4092
6011
 
4093
6012
  // src/orchestrator/index/store.ts
4094
- import { mkdir as mkdir4, open, readFile as readFile3, rename, unlink as unlink3 } from "fs/promises";
4095
- import { homedir as homedir8 } from "os";
4096
- import { dirname as dirname4, join as join6 } from "path";
6013
+ import { mkdir as mkdir4, open as open2, readFile as readFile4, rename, unlink as unlink4 } from "fs/promises";
6014
+ import { homedir as homedir11 } from "os";
6015
+ import { dirname as dirname4, join as join7 } from "path";
4097
6016
 
4098
6017
  class TaskIndexStore {
4099
6018
  homeDir;
@@ -4105,9 +6024,9 @@ class TaskIndexStore {
4105
6024
  listeners = new Set;
4106
6025
  saveChain = Promise.resolve();
4107
6026
  constructor(options = {}) {
4108
- this.homeDir = options.homeDir ?? homedir8();
4109
- this.kobeDir = join6(this.homeDir, ".kobe");
4110
- this.path = join6(this.kobeDir, "tasks.json");
6027
+ this.homeDir = options.homeDir ?? homedir11();
6028
+ this.kobeDir = join7(this.homeDir, ".kobe");
6029
+ this.path = join7(this.kobeDir, "tasks.json");
4111
6030
  this.tmpPath = `${this.path}.tmp`;
4112
6031
  }
4113
6032
  subscribe(listener) {
@@ -4132,7 +6051,7 @@ class TaskIndexStore {
4132
6051
  async load() {
4133
6052
  let raw;
4134
6053
  try {
4135
- raw = await readFile3(this.path, "utf8");
6054
+ raw = await readFile4(this.path, "utf8");
4136
6055
  } catch (err) {
4137
6056
  const code = err.code;
4138
6057
  if (code === "ENOENT") {
@@ -4169,7 +6088,7 @@ class TaskIndexStore {
4169
6088
  const payload = this.snapshot();
4170
6089
  const json = `${JSON.stringify(payload, null, 2)}
4171
6090
  `;
4172
- const handle = await open(this.tmpPath, "w", 420);
6091
+ const handle = await open2(this.tmpPath, "w", 420);
4173
6092
  try {
4174
6093
  await handle.writeFile(json, "utf8");
4175
6094
  await handle.sync();
@@ -4197,7 +6116,17 @@ class TaskIndexStore {
4197
6116
  activeTabId = activeIn && tabsIn.some((t) => t.id === activeIn) ? activeIn : tabsIn[0]?.id ?? "";
4198
6117
  } else {
4199
6118
  const tabId = ulid();
4200
- tabs = [{ id: tabId, sessionId: sessionId ?? null, seq: 1, createdAt: now }];
6119
+ tabs = [
6120
+ {
6121
+ id: tabId,
6122
+ sessionId: sessionId ?? null,
6123
+ seq: 1,
6124
+ createdAt: now,
6125
+ ...rest.model ? { model: rest.model } : {},
6126
+ ...rest.modelEffort ? { modelEffort: rest.modelEffort } : {},
6127
+ vendor: rest.vendor ?? DEFAULT_TASK_VENDOR
6128
+ }
6129
+ ];
4201
6130
  activeTabId = tabId;
4202
6131
  }
4203
6132
  const firstSession = tabs[0]?.sessionId ?? null;
@@ -4264,13 +6193,13 @@ class TaskIndexStore {
4264
6193
  }
4265
6194
  async _unlinkForTests() {
4266
6195
  try {
4267
- await unlink3(this.path);
6196
+ await unlink4(this.path);
4268
6197
  } catch (err) {
4269
6198
  if (err.code !== "ENOENT")
4270
6199
  throw err;
4271
6200
  }
4272
6201
  try {
4273
- await unlink3(this.tmpPath);
6202
+ await unlink4(this.tmpPath);
4274
6203
  } catch (err) {
4275
6204
  if (err.code !== "ENOENT")
4276
6205
  throw err;
@@ -4360,7 +6289,10 @@ function coerceTask(value) {
4360
6289
  sessionId: tt.sessionId ?? null,
4361
6290
  seq,
4362
6291
  createdAt: tt.createdAt,
4363
- ...typeof tt.title === "string" ? { title: tt.title } : {}
6292
+ ...typeof tt.title === "string" ? { title: tt.title } : {},
6293
+ ...typeof tt.model === "string" ? { model: tt.model } : {},
6294
+ ...isModelEffortLevel(tt.modelEffort) ? { modelEffort: tt.modelEffort } : {},
6295
+ ...isVendorId(tt.vendor) ? { vendor: tt.vendor } : {}
4364
6296
  };
4365
6297
  tabs.push(tab);
4366
6298
  }
@@ -4393,6 +6325,8 @@ function coerceTask(value) {
4393
6325
  kind: v.kind === "main" ? "main" : "task",
4394
6326
  permissionMode: isPermissionMode(v.permissionMode) ? v.permissionMode : undefined,
4395
6327
  model: typeof v.model === "string" ? v.model : undefined,
6328
+ modelEffort: isModelEffortLevel(v.modelEffort) ? v.modelEffort : undefined,
6329
+ vendor: resolveTaskVendor(v.vendor, typeof v.model === "string" ? v.model : undefined),
4396
6330
  createdAt: v.createdAt,
4397
6331
  updatedAt: v.updatedAt
4398
6332
  };
@@ -4400,20 +6334,36 @@ function coerceTask(value) {
4400
6334
  function isPermissionMode(v) {
4401
6335
  return v === "default" || v === "plan";
4402
6336
  }
6337
+ function isModelEffortLevel(v) {
6338
+ return v === "none" || v === "minimal" || v === "low" || v === "medium" || v === "high" || v === "xhigh" || v === "max";
6339
+ }
6340
+ function isVendorId(v) {
6341
+ return typeof v === "string" && v in ENGINE_REGISTRY;
6342
+ }
6343
+ function resolveTaskVendor(rawVendor, modelId) {
6344
+ const stored = isVendorId(rawVendor) ? rawVendor : DEFAULT_TASK_VENDOR;
6345
+ if (!modelId)
6346
+ return stored;
6347
+ const matched = Object.values(ENGINE_REGISTRY).some((caps) => caps?.models.some((m) => m.id === modelId));
6348
+ if (!matched)
6349
+ return stored;
6350
+ return capabilitiesForModelId(modelId).vendorId;
6351
+ }
4403
6352
  function isTaskStatus(s) {
4404
6353
  return s === "backlog" || s === "in_progress" || s === "in_review" || s === "done" || s === "canceled" || s === "error";
4405
6354
  }
4406
6355
  var init_store = __esm(() => {
6356
+ init_registry();
4407
6357
  init_ulid();
4408
6358
  });
4409
6359
 
4410
6360
  // src/orchestrator/worktree/git.ts
4411
- import { spawnSync as spawnSync4 } from "child_process";
6361
+ import { spawnSync as spawnSync5 } from "child_process";
4412
6362
  function git2(args, opts) {
4413
6363
  if (!opts.cwd) {
4414
6364
  throw new Error("git(): cwd is required; refusing to inherit from process.cwd()");
4415
6365
  }
4416
- const proc = spawnSync4("git", [...args], {
6366
+ const proc = spawnSync5("git", [...args], {
4417
6367
  cwd: opts.cwd,
4418
6368
  env: opts.env ? { ...process.env, ...opts.env } : process.env,
4419
6369
  encoding: "utf8",
@@ -4449,42 +6399,9 @@ var init_git = __esm(() => {
4449
6399
  };
4450
6400
  });
4451
6401
 
4452
- // src/orchestrator/worktree/paths.ts
4453
- import fs2 from "fs";
4454
- import path5 from "path";
4455
- function worktreeRootFor(repo) {
4456
- if (!path5.isAbsolute(repo)) {
4457
- throw new Error(`worktreeRootFor: repo must be an absolute path, got: ${repo}`);
4458
- }
4459
- return path5.join(repo, KOBE_WORKTREE_ROOT_SUBPATH);
4460
- }
4461
- function worktreePathFor(repo, taskId) {
4462
- if (!taskId || /[/\\\0]/.test(taskId)) {
4463
- throw new Error(`worktreePathFor: invalid taskId: ${JSON.stringify(taskId)}`);
4464
- }
4465
- return path5.join(worktreeRootFor(repo), taskId);
4466
- }
4467
- function isKobeManagedPath(repo, candidate) {
4468
- if (!path5.isAbsolute(repo) || !path5.isAbsolute(candidate))
4469
- return false;
4470
- const root = canonicalize(worktreeRootFor(repo));
4471
- const target = canonicalize(candidate);
4472
- const rel = path5.relative(root, target);
4473
- return rel !== "" && !rel.startsWith("..") && !path5.isAbsolute(rel);
4474
- }
4475
- function canonicalize(p) {
4476
- try {
4477
- return fs2.realpathSync(p);
4478
- } catch {
4479
- return path5.resolve(p);
4480
- }
4481
- }
4482
- var KOBE_WORKTREE_ROOT_SUBPATH = ".claude/worktrees";
4483
- var init_paths2 = () => {};
4484
-
4485
6402
  // src/orchestrator/worktree/manager.ts
4486
6403
  import fs3 from "fs";
4487
- import path6 from "path";
6404
+ import path8 from "path";
4488
6405
 
4489
6406
  class GitWorktreeManager {
4490
6407
  async create(repo, branch, worktreePath, baseRef) {
@@ -4502,7 +6419,7 @@ class GitWorktreeManager {
4502
6419
  }
4503
6420
  throw new Error(`create(): ${worktreePath} exists but is not a registered git worktree`);
4504
6421
  }
4505
- fs3.mkdirSync(path6.dirname(worktreePath), { recursive: true });
6422
+ fs3.mkdirSync(path8.dirname(worktreePath), { recursive: true });
4506
6423
  const branchExists = this.branchExists(repo, branch);
4507
6424
  const args = branchExists ? ["worktree", "add", worktreePath, branch] : baseRef ? ["worktree", "add", "-b", branch, worktreePath, baseRef] : ["worktree", "add", "-b", branch, worktreePath];
4508
6425
  git2(args, { cwd: repo });
@@ -4516,7 +6433,7 @@ class GitWorktreeManager {
4516
6433
  return info;
4517
6434
  }
4518
6435
  async createForTask(args) {
4519
- const target = worktreePathFor(args.repo, args.taskId);
6436
+ const target = worktreePathFor(args.repo, args.slug);
4520
6437
  return this.create(args.repo, args.branch, target, args.baseRef);
4521
6438
  }
4522
6439
  async remove(worktreePath, opts) {
@@ -4557,8 +6474,8 @@ class GitWorktreeManager {
4557
6474
  if (!entry.branch || entry.detached)
4558
6475
  continue;
4559
6476
  const canonEntry = canonicalize2(entry.path);
4560
- const rel = path6.relative(canonRoot, canonEntry);
4561
- const callerPath = path6.join(callerRoot, rel);
6477
+ const rel = path8.relative(canonRoot, canonEntry);
6478
+ const callerPath = path8.join(callerRoot, rel);
4562
6479
  const dirty = await this.isDirty(entry.path);
4563
6480
  infos.push({
4564
6481
  path: callerPath,
@@ -4619,9 +6536,9 @@ class GitWorktreeManager {
4619
6536
  const gitDir = out.stdout.trim();
4620
6537
  if (!gitDir)
4621
6538
  return null;
4622
- const absolute = path6.isAbsolute(gitDir) ? gitDir : path6.resolve(worktreePath, gitDir);
4623
- const base = path6.basename(absolute);
4624
- return base === ".git" ? path6.dirname(absolute) : absolute;
6539
+ const absolute = path8.isAbsolute(gitDir) ? gitDir : path8.resolve(worktreePath, gitDir);
6540
+ const base = path8.basename(absolute);
6541
+ return base === ".git" ? path8.dirname(absolute) : absolute;
4625
6542
  } catch (err) {
4626
6543
  if (err instanceof GitCommandError)
4627
6544
  return null;
@@ -4661,7 +6578,7 @@ function parsePorcelain(out) {
4661
6578
  return records;
4662
6579
  }
4663
6580
  function requireAbsolute(name, value) {
4664
- if (!value || !path6.isAbsolute(value)) {
6581
+ if (!value || !path8.isAbsolute(value)) {
4665
6582
  throw new Error(`${name} must be an absolute path, got: ${JSON.stringify(value)}`);
4666
6583
  }
4667
6584
  }
@@ -4669,7 +6586,7 @@ function canonicalize2(p) {
4669
6586
  try {
4670
6587
  return fs3.realpathSync(p);
4671
6588
  } catch {
4672
- return path6.resolve(p);
6589
+ return path8.resolve(p);
4673
6590
  }
4674
6591
  }
4675
6592
  var init_manager = __esm(() => {
@@ -4680,22 +6597,26 @@ var init_manager = __esm(() => {
4680
6597
  // src/bin/kobed.ts
4681
6598
  init_daemon_process();
4682
6599
  init_client();
4683
- import { unlink as unlink5 } from "fs/promises";
6600
+ import { unlink as unlink6 } from "fs/promises";
4684
6601
 
4685
6602
  // src/core/index.ts
4686
6603
  init_claude_code_local();
6604
+ init_codex_local();
4687
6605
  init_bridge();
4688
6606
  init_core();
4689
6607
  init_store();
4690
6608
  init_manager();
4691
- import { homedir as homedir9 } from "os";
6609
+ import { homedir as homedir12 } from "os";
4692
6610
  async function createKobeCore(options = {}) {
4693
- const homeDir2 = options.homeDir ?? process.env.KOBE_HOME_DIR ?? homedir9();
6611
+ const homeDir2 = options.homeDir ?? process.env.KOBE_HOME_DIR ?? homedir12();
4694
6612
  const store = new TaskIndexStore({ homeDir: homeDir2 });
4695
6613
  await store.load();
4696
6614
  const worktrees = new GitWorktreeManager;
4697
- const engine = options.engine ?? new ClaudeCodeLocal;
4698
- const orchestrator = new Orchestrator({ engine, store, worktrees });
6615
+ const engines = options.engines ?? (options.engine ? { [options.engine.capabilities.vendorId]: options.engine } : {
6616
+ claude: new ClaudeCodeLocal,
6617
+ codex: new CodexLocal
6618
+ });
6619
+ const orchestrator = new Orchestrator({ engines, store, worktrees });
4699
6620
  const bridge = options.startMcpBridge === false ? null : await startBridge(orchestrator, { homeDir: homeDir2 });
4700
6621
  return {
4701
6622
  homeDir: homeDir2,
@@ -4716,16 +6637,16 @@ init_paths();
4716
6637
  // src/daemon/server.ts
4717
6638
  init_repos();
4718
6639
  init_paths();
4719
- import { mkdir as mkdir5, readFile as readFile5, unlink as unlink4, writeFile as writeFile4 } from "fs/promises";
6640
+ import { mkdir as mkdir5, readFile as readFile6, unlink as unlink5, writeFile as writeFile4 } from "fs/promises";
4720
6641
  import { createServer as createServer2 } from "net";
4721
6642
  import { dirname as dirname5 } from "path";
4722
6643
 
4723
6644
  // src/engine/claude-code-local/plan-usage.ts
4724
6645
  import { execFile } from "child_process";
4725
- import { createHash } from "crypto";
4726
- import { readFile as readFile4 } from "fs/promises";
4727
- import { homedir as homedir10, userInfo } from "os";
4728
- import { join as join7 } from "path";
6646
+ import { createHash as createHash2 } from "crypto";
6647
+ import { readFile as readFile5 } from "fs/promises";
6648
+ import { homedir as homedir13, userInfo } from "os";
6649
+ import { join as join8 } from "path";
4729
6650
  import { promisify } from "util";
4730
6651
  var execFileAsync = promisify(execFile);
4731
6652
  var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
@@ -4736,7 +6657,7 @@ function keychainServiceName() {
4736
6657
  const configDir = process.env.CLAUDE_CONFIG_DIR;
4737
6658
  if (!configDir)
4738
6659
  return `${KEYCHAIN_BASE}${KEYCHAIN_SUFFIX}`;
4739
- const hash = createHash("sha256").update(configDir).digest("hex").slice(0, 8);
6660
+ const hash = createHash2("sha256").update(configDir).digest("hex").slice(0, 8);
4740
6661
  return `${KEYCHAIN_BASE}${KEYCHAIN_SUFFIX}-${hash}`;
4741
6662
  }
4742
6663
  function keychainAccount() {
@@ -4760,10 +6681,10 @@ async function readKeychainToken() {
4760
6681
  }
4761
6682
  }
4762
6683
  async function readPlainTextToken() {
4763
- const configDir = process.env.CLAUDE_CONFIG_DIR ?? join7(homedir10(), ".claude");
4764
- const path7 = join7(configDir, ".credentials.json");
6684
+ const configDir = process.env.CLAUDE_CONFIG_DIR ?? join8(homedir13(), ".claude");
6685
+ const path9 = join8(configDir, ".credentials.json");
4765
6686
  try {
4766
- const raw = await readFile4(path7, "utf8");
6687
+ const raw = await readFile5(path9, "utf8");
4767
6688
  return parseStoredOAuth(raw);
4768
6689
  } catch {
4769
6690
  return null;
@@ -4876,7 +6797,7 @@ function createPlanUsagePoller(options) {
4876
6797
  }
4877
6798
  // src/daemon/rc-bridge.ts
4878
6799
  init_binary();
4879
- import { spawn as spawn4 } from "child_process";
6800
+ import { spawn as spawn5 } from "child_process";
4880
6801
  var ENV_ID_RE = /Environment ID:\s*(env_[A-Za-z0-9]+)/;
4881
6802
  var DEEPLINK_RE = /https:\/\/claude\.ai\/code\?environment=([A-Za-z0-9_]+)/;
4882
6803
  var ANSI_RE = /\x1b\[[0-9;?]*[A-Za-z]/g;
@@ -4884,7 +6805,7 @@ function createRcBridge(options = {}) {
4884
6805
  const stopGraceMs = options.stopGraceMs ?? 5000;
4885
6806
  const readyTimeoutMs = options.readyTimeoutMs ?? 30000;
4886
6807
  const binaryPathResolver = options.binaryPathResolver ?? findClaudeBinary;
4887
- const spawner = options.spawner ?? ((cmd, args, cwd) => spawn4(cmd, [...args], {
6808
+ const spawner = options.spawner ?? ((cmd, args, cwd) => spawn5(cmd, [...args], {
4888
6809
  cwd,
4889
6810
  stdio: ["ignore", "pipe", "pipe"],
4890
6811
  env: { ...process.env }
@@ -5058,7 +6979,7 @@ async function startDaemonServer(orch, options = {}) {
5058
6979
  let nextClientId = 1;
5059
6980
  await mkdir5(dirname5(socketPath), { recursive: true });
5060
6981
  await mkdir5(dirname5(pidPath), { recursive: true });
5061
- await unlink4(socketPath).catch(() => {});
6982
+ await unlink5(socketPath).catch(() => {});
5062
6983
  const server = createServer2((socket) => {
5063
6984
  const client = {
5064
6985
  id: nextClientId++,
@@ -5103,8 +7024,8 @@ async function startDaemonServer(orch, options = {}) {
5103
7024
  client.socket.destroy();
5104
7025
  }
5105
7026
  await new Promise((resolve2) => server.close(() => resolve2()));
5106
- await unlink4(socketPath).catch(() => {});
5107
- await unlink4(pidPath).catch(() => {});
7027
+ await unlink5(socketPath).catch(() => {});
7028
+ await unlink5(pidPath).catch(() => {});
5108
7029
  }
5109
7030
  };
5110
7031
  planUsagePoller.start();
@@ -5218,7 +7139,11 @@ async function startDaemonServer(orch, options = {}) {
5218
7139
  }
5219
7140
  case "task.model": {
5220
7141
  const taskId = requireString2(payload, "taskId");
5221
- await orch.setModel(taskId, optionalString2(payload, "model"));
7142
+ const modelEffort = optionalString2(payload, "modelEffort");
7143
+ if (modelEffort !== undefined && modelEffort !== "none" && modelEffort !== "minimal" && modelEffort !== "low" && modelEffort !== "medium" && modelEffort !== "high" && modelEffort !== "xhigh" && modelEffort !== "max") {
7144
+ throw new Error("modelEffort must be a supported effort level");
7145
+ }
7146
+ await orch.setModel(taskId, optionalString2(payload, "model"), optionalString2(payload, "tabId"), modelEffort);
5222
7147
  broadcastTaskUpdated(orch, clients, taskId);
5223
7148
  return {};
5224
7149
  }
@@ -5417,7 +7342,7 @@ async function startDaemonServer(orch, options = {}) {
5417
7342
  }
5418
7343
  async function readPidFile(pidPath) {
5419
7344
  try {
5420
- const raw = await readFile5(pidPath, "utf8");
7345
+ const raw = await readFile6(pidPath, "utf8");
5421
7346
  const pid = Number(raw.trim());
5422
7347
  return Number.isFinite(pid) ? pid : null;
5423
7348
  } catch {
@@ -5455,8 +7380,7 @@ async function readTaskHistory(orch, taskId, requestedSessionId, limit, before)
5455
7380
  const sessionId = requestedSessionId ?? task?.tabs.find((t) => t.id === task.activeTabId)?.sessionId ?? task?.sessionId;
5456
7381
  if (!sessionId)
5457
7382
  return { messages: [], nextBefore: null, hasMore: false };
5458
- const messages = await orch.readHistory(sessionId);
5459
- const usageMetrics = deriveSessionUsageMetrics(messages);
7383
+ const { messages, usageMetrics } = await orch.readHistoryWithMetrics(sessionId);
5460
7384
  const beforeIdx = before ? messages.findIndex((m) => `${m.timestamp}:${m.sessionId}` === before) : -1;
5461
7385
  const end = beforeIdx >= 0 ? beforeIdx : messages.length;
5462
7386
  const start = Math.max(0, end - limit);
@@ -5602,7 +7526,7 @@ async function main() {
5602
7526
  await new Promise((resolve2) => setTimeout(resolve2, 100));
5603
7527
  } catch {}
5604
7528
  }
5605
- await unlink5(socketPath).catch(() => {});
7529
+ await unlink6(socketPath).catch(() => {});
5606
7530
  const next = await connectOrStartDaemon();
5607
7531
  next.close();
5608
7532
  console.log(`kobed: restarted, listening on ${socketPath}`);