@sma1lboy/kobe 0.5.19 → 0.5.21

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
@@ -65,6 +65,9 @@ function fitSocketPath(naturalPath, homeDir, role, pidTag) {
65
65
  throw new Error(`kobe socket path exceeds ${SOCKET_PATH_SAFETY_LIMIT} bytes even after fallback: ${fallback}`);
66
66
  }
67
67
  function defaultDaemonSocketPath(homeDir) {
68
+ const override = process.env.KOBE_DAEMON_SOCKET_PATH;
69
+ if (override && override.length > 0)
70
+ return override;
68
71
  const explicit = homeDir ?? process.env.KOBE_HOME_DIR;
69
72
  if (explicit && explicit.length > 0) {
70
73
  return fitSocketPath(join(explicit, ".kobe", "daemon.sock"), explicit, "daemon");
@@ -77,6 +80,9 @@ function defaultDaemonSocketPath(homeDir) {
77
80
  return fitSocketPath(join(home, ".kobe", "daemon.sock"), home, "daemon");
78
81
  }
79
82
  function defaultDaemonPidPath(homeDir = process.env.KOBE_HOME_DIR ?? homedir()) {
83
+ const override = process.env.KOBE_DAEMON_PID_PATH;
84
+ if (override && override.length > 0)
85
+ return override;
80
86
  return join(homeDir, ".kobe", "daemon.pid");
81
87
  }
82
88
  var SOCKET_PATH_SAFETY_LIMIT = 100;
@@ -180,7 +186,11 @@ class KobeDaemonClient {
180
186
  this.socket = null;
181
187
  }
182
188
  forceDisconnect() {
183
- this.socket?.destroy();
189
+ const socket = this.socket;
190
+ if (!socket)
191
+ return;
192
+ this.socket = null;
193
+ socket.destroy();
184
194
  }
185
195
  on(name, handler) {
186
196
  let set = this.handlers.get(name);
@@ -241,7 +251,7 @@ class KobeDaemonClient {
241
251
  });
242
252
  }
243
253
  onSocketClose(which) {
244
- if (this.socket !== which && this.socket !== null)
254
+ if (this.socket !== which)
245
255
  return;
246
256
  this.socket = null;
247
257
  for (const pending of this.pending.values())
@@ -300,6 +310,8 @@ var init_client = () => {};
300
310
  // src/client/daemon-process.ts
301
311
  import { spawn } from "child_process";
302
312
  import { existsSync } from "fs";
313
+ import { unlink } from "fs/promises";
314
+ import { homedir as homedir2 } from "os";
303
315
  import { dirname, join as join2, resolve } from "path";
304
316
  import { fileURLToPath } from "url";
305
317
  async function ensureDaemonReachable() {
@@ -332,6 +344,53 @@ async function connectOrStartDaemon() {
332
344
  await client.connect();
333
345
  return client;
334
346
  }
347
+ async function connectOrStartOwnedDaemon() {
348
+ const homeDir = process.env.KOBE_HOME_DIR ?? homedir2();
349
+ const socketPath = fitSocketPath(join2(homeDir, ".kobe", `daemon-${process.pid}.sock`), homeDir, "daemon", process.pid);
350
+ const pidPath = join2(homeDir, ".kobe", `daemon-${process.pid}.pid`);
351
+ await ensureOwnedDaemonReachable(socketPath, pidPath);
352
+ const client = new KobeDaemonClient(socketPath);
353
+ await client.connect();
354
+ return {
355
+ client,
356
+ socketPath,
357
+ pidPath,
358
+ stop: async () => {
359
+ try {
360
+ await client.request("daemon.stop");
361
+ } catch {} finally {
362
+ client.close();
363
+ }
364
+ }
365
+ };
366
+ }
367
+ async function ensureOwnedDaemonReachable(socketPath, pidPath) {
368
+ await unlink(socketPath).catch(() => {});
369
+ const { entry, runWithBun } = resolveKobedEntry();
370
+ const env = {
371
+ ...process.env,
372
+ KOBE_DAEMON_SOCKET_PATH: socketPath,
373
+ KOBE_DAEMON_PID_PATH: pidPath
374
+ };
375
+ const child = runWithBun ? spawn(process.execPath, [entry, "start"], {
376
+ detached: true,
377
+ stdio: "ignore",
378
+ env
379
+ }) : spawn(entry, ["start"], {
380
+ detached: true,
381
+ stdio: "ignore",
382
+ env
383
+ });
384
+ child.unref();
385
+ const deadline = Date.now() + 5000;
386
+ while (Date.now() < deadline) {
387
+ if (await testCanConnect(socketPath)) {
388
+ return;
389
+ }
390
+ await new Promise((resolveTimer) => setTimeout(resolveTimer, 100));
391
+ }
392
+ throw new Error(`kobe: owned daemon did not start at ${socketPath}`);
393
+ }
335
394
  async function testCanConnect(socketPath) {
336
395
  const probe = new KobeDaemonClient(socketPath);
337
396
  try {
@@ -370,6 +429,8 @@ var init_daemon_process = __esm(() => {
370
429
 
371
430
  // src/session/usage-metrics.ts
372
431
  function totalContextTokens(u) {
432
+ if (typeof u.context_tokens === "number" && Number.isFinite(u.context_tokens))
433
+ return Math.max(0, u.context_tokens);
373
434
  return u.input_tokens + (u.cache_read_input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0);
374
435
  }
375
436
  function parseTimestampMs(value) {
@@ -451,7 +512,7 @@ function withTotalSpeedForTurn(usage, startedAtIso, endedAtIso) {
451
512
  // src/engine/claude-code-local/binary.ts
452
513
  import { spawnSync } from "child_process";
453
514
  import { existsSync as existsSync2, statSync } from "fs";
454
- import { homedir as homedir2 } from "os";
515
+ import { homedir as homedir3 } from "os";
455
516
  import path from "path";
456
517
  async function findClaudeBinary(deps = defaultDeps) {
457
518
  const checked = [];
@@ -524,7 +585,7 @@ var init_binary = __esm(() => {
524
585
  return process.env[name];
525
586
  },
526
587
  home() {
527
- return homedir2();
588
+ return homedir3();
528
589
  },
529
590
  which(name) {
530
591
  const cmd = process.platform === "win32" ? "where" : "which";
@@ -614,7 +675,7 @@ var init_models = __esm(() => {
614
675
 
615
676
  // src/engine/claude-code-local/settings.ts
616
677
  import { readFileSync } from "fs";
617
- import { homedir as homedir3 } from "os";
678
+ import { homedir as homedir4 } from "os";
618
679
  import { join as join3 } from "path";
619
680
  function readClaudeSettings() {
620
681
  if (cached !== undefined)
@@ -643,7 +704,7 @@ function resolveClaudeDefaultModelId() {
643
704
  }
644
705
  var SETTINGS_PATH, cached, CLAUDE_FALLBACK_DEFAULT_MODEL_ID = "claude-opus-4-7[1m]";
645
706
  var init_settings = __esm(() => {
646
- SETTINGS_PATH = join3(homedir3(), ".claude", "settings.json");
707
+ SETTINGS_PATH = join3(homedir4(), ".claude", "settings.json");
647
708
  });
648
709
 
649
710
  // src/engine/claude-code-local/capabilities.ts
@@ -719,8 +780,8 @@ function normalizeClaudeContent(content) {
719
780
 
720
781
  // src/engine/claude-code-local/history.ts
721
782
  import { randomUUID } from "crypto";
722
- import { appendFile, mkdir, readFile, readdir, unlink, writeFile } from "fs/promises";
723
- import { homedir as homedir4 } from "os";
783
+ import { appendFile, mkdir, readFile, readdir, unlink as unlink2, writeFile } from "fs/promises";
784
+ import { homedir as homedir5 } from "os";
724
785
  import path2 from "path";
725
786
  function encodeCwd(cwd) {
726
787
  return cwd.replace(/[/.]/g, "-");
@@ -746,7 +807,7 @@ async function deleteHistory(sessionId, deps = defaultDeps2) {
746
807
  for (const dir of projectDirs) {
747
808
  const candidate = path2.join(root, dir, `${sessionId}.jsonl`);
748
809
  try {
749
- await unlink(candidate);
810
+ await unlink2(candidate);
750
811
  } catch (err) {
751
812
  if (err.code === "ENOENT")
752
813
  continue;
@@ -890,7 +951,7 @@ var defaultDeps2;
890
951
  var init_history = __esm(() => {
891
952
  defaultDeps2 = {
892
953
  projectsDir() {
893
- return path2.join(homedir4(), ".claude", "projects");
954
+ return path2.join(homedir5(), ".claude", "projects");
894
955
  },
895
956
  async readdir(p) {
896
957
  try {
@@ -979,7 +1040,7 @@ function delay(ms) {
979
1040
 
980
1041
  // src/engine/claude-code-local/sessions.ts
981
1042
  import { readFile as readFile2, readdir as readdir2, stat } from "fs/promises";
982
- import { homedir as homedir5 } from "os";
1043
+ import { homedir as homedir6 } from "os";
983
1044
  import path3 from "path";
984
1045
  async function listSessionsForCwd(cwd, deps = defaultDeps3) {
985
1046
  const projectDir = path3.join(deps.projectsDir(), encodeCwd(cwd));
@@ -1047,7 +1108,7 @@ var init_sessions = __esm(() => {
1047
1108
  init_history();
1048
1109
  defaultDeps3 = {
1049
1110
  projectsDir() {
1050
- return path3.join(homedir5(), ".claude", "projects");
1111
+ return path3.join(homedir6(), ".claude", "projects");
1051
1112
  },
1052
1113
  async readdir(p) {
1053
1114
  try {
@@ -1450,10 +1511,363 @@ var init_claude_code_local = __esm(() => {
1450
1511
  init_spawn();
1451
1512
  });
1452
1513
 
1514
+ // src/env.ts
1515
+ import { homedir as homedir7 } from "os";
1516
+ import { join as join4 } from "path";
1517
+ function isDev() {
1518
+ return process.env.KOBE_DEV === "1";
1519
+ }
1520
+ function homeDir() {
1521
+ return process.env.KOBE_HOME_DIR ?? homedir7();
1522
+ }
1523
+ function kobeStateDir() {
1524
+ return join4(homeDir(), ".kobe");
1525
+ }
1526
+ function kvStatePath() {
1527
+ return join4(homeDir(), ".config", "kobe", "state.json");
1528
+ }
1529
+ var init_env = () => {};
1530
+
1531
+ // src/engine/codex-local/app-server.ts
1532
+ import { spawn as spawn3 } from "child_process";
1533
+ import { readFileSync as readFileSync2 } from "fs";
1534
+ function resolveCodexBackend(env = process.env, readPreference = readCodexBackendPreference) {
1535
+ if (env.KOBE_CODEX_BACKEND === "exec")
1536
+ return "exec";
1537
+ if (env.KOBE_CODEX_BACKEND === "app-server" || env.KOBE_CODEX_APP_SERVER === "1")
1538
+ return "app-server";
1539
+ return readPreference() ?? "app-server";
1540
+ }
1541
+ function readCodexBackendPreference() {
1542
+ try {
1543
+ const parsed = JSON.parse(readFileSync2(kvStatePath(), "utf8"));
1544
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
1545
+ return;
1546
+ const raw = parsed[CODEX_BACKEND_KV_KEY];
1547
+ if (raw === "exec" || raw === "app-server")
1548
+ return raw;
1549
+ } catch {}
1550
+ return;
1551
+ }
1552
+ function spawnCodexAppServerTurn(opts) {
1553
+ const args = buildAppServerArgs(opts);
1554
+ const proc = spawn3(opts.binaryPath, args, {
1555
+ cwd: opts.cwd,
1556
+ env: { ...process.env, ...opts.env ?? {} },
1557
+ stdio: ["pipe", "pipe", "pipe"]
1558
+ });
1559
+ const rpc = new AppServerRpc(proc, opts);
1560
+ rpc.run();
1561
+ return {
1562
+ proc,
1563
+ stdout: proc.stdout,
1564
+ stderr: proc.stderr,
1565
+ args,
1566
+ ready: rpc.ready,
1567
+ closed: rpc.closed
1568
+ };
1569
+ }
1570
+ function buildAppServerArgs(opts) {
1571
+ const mode = permissionPayloads(opts.permissionMode);
1572
+ return ["app-server", "-c", `approval_policy="${mode.approvalPolicy}"`, "-c", `sandbox_mode="${mode.threadSandbox}"`];
1573
+ }
1574
+ function codexAppServerUsageToSnapshot(params) {
1575
+ const root = asObject(params);
1576
+ const tokenUsage = asObject(root?.tokenUsage ?? root?.token_usage);
1577
+ const last = asObject(tokenUsage?.last);
1578
+ if (!last)
1579
+ return null;
1580
+ const totalTokens = numberOr(last.totalTokens ?? last.total_tokens, 0);
1581
+ const inputTokens = numberOr(last.inputTokens ?? last.input_tokens, 0);
1582
+ const cachedInputTokens = numberOr(last.cachedInputTokens ?? last.cached_input_tokens, 0);
1583
+ const outputTokens = numberOr(last.outputTokens ?? last.output_tokens, 0);
1584
+ const contextWindow = numberOr(tokenUsage?.modelContextWindow ?? tokenUsage?.model_context_window, 0);
1585
+ if (totalTokens <= 0 && inputTokens <= 0 && outputTokens <= 0 && cachedInputTokens <= 0)
1586
+ return null;
1587
+ return {
1588
+ type: "usage",
1589
+ input_tokens: Math.max(0, inputTokens - cachedInputTokens),
1590
+ output_tokens: outputTokens,
1591
+ ...cachedInputTokens > 0 ? { cache_read_input_tokens: cachedInputTokens } : {},
1592
+ ...totalTokens > 0 ? { context_tokens: totalTokens } : {},
1593
+ ...contextWindow > 0 ? { context_window_tokens: contextWindow } : {}
1594
+ };
1595
+ }
1596
+ function codexAppServerItemNotificationToEvents(method, params) {
1597
+ if (method !== "item/started" && method !== "item/completed")
1598
+ return [];
1599
+ const p = asObject(params);
1600
+ const item = asObject(p?.item);
1601
+ const itemType = typeof item?.type === "string" ? item.type : "tool";
1602
+ if (isNonToolTranscriptItem(itemType))
1603
+ return [];
1604
+ const payload = stripItemHousekeeping(item ?? {});
1605
+ if (method === "item/started")
1606
+ return [{ type: "tool.start", name: itemType, input: payload }];
1607
+ return [{ type: "tool.result", name: itemType, output: payload }];
1608
+ }
1609
+
1610
+ class AppServerRpc {
1611
+ proc;
1612
+ opts;
1613
+ pending = new Map;
1614
+ readBuffer = "";
1615
+ nextId = 1;
1616
+ readyResolve = () => {};
1617
+ readyReject = () => {};
1618
+ ready = new Promise((resolve2, reject) => {
1619
+ this.readyResolve = resolve2;
1620
+ this.readyReject = reject;
1621
+ });
1622
+ closed;
1623
+ constructor(proc, opts) {
1624
+ this.proc = proc;
1625
+ this.opts = opts;
1626
+ this.closed = new Promise((resolve2) => {
1627
+ proc.once("exit", (code, signal) => resolve2({ code, signal }));
1628
+ });
1629
+ proc.stdout.setEncoding("utf8");
1630
+ proc.stdout.on("data", (chunk) => this.onStdout(chunk));
1631
+ proc.stdout.on("error", () => {});
1632
+ proc.stderr.on("error", () => {});
1633
+ proc.once("error", (err) => {
1634
+ this.readyReject(err);
1635
+ this.rejectAll(err);
1636
+ });
1637
+ }
1638
+ async run() {
1639
+ try {
1640
+ await this.call("initialize", {
1641
+ clientInfo: {
1642
+ name: "kobe",
1643
+ title: "kobe",
1644
+ version: "0.0.0"
1645
+ },
1646
+ capabilities: { experimentalApi: true }
1647
+ });
1648
+ this.notify("initialized", undefined);
1649
+ const sessionId = this.opts.resumeSessionId ? await this.resumeThread(this.opts.resumeSessionId) : await this.startThread();
1650
+ this.opts.onSessionId(sessionId);
1651
+ this.readyResolve(sessionId);
1652
+ await this.startTurn(sessionId);
1653
+ } catch (err) {
1654
+ this.readyReject(err);
1655
+ this.opts.onEvent({ type: "error", message: `codex app-server failure: ${stringifyErr2(err)}` });
1656
+ this.close();
1657
+ }
1658
+ }
1659
+ async startThread() {
1660
+ const mode = permissionPayloads(this.opts.permissionMode);
1661
+ const result = asObject(await this.call("thread/start", {
1662
+ cwd: this.opts.cwd,
1663
+ model: this.opts.model ?? null,
1664
+ approvalPolicy: mode.approvalPolicy,
1665
+ sandbox: mode.threadSandbox,
1666
+ ephemeral: false
1667
+ }));
1668
+ const thread = asObject(result?.thread);
1669
+ const id = typeof thread?.id === "string" ? thread.id : undefined;
1670
+ if (!id)
1671
+ throw new Error("thread/start did not return a thread id");
1672
+ return id;
1673
+ }
1674
+ async resumeThread(sessionId) {
1675
+ const mode = permissionPayloads(this.opts.permissionMode);
1676
+ const result = asObject(await this.call("thread/resume", {
1677
+ threadId: sessionId,
1678
+ cwd: this.opts.cwd,
1679
+ model: this.opts.model ?? null,
1680
+ approvalPolicy: mode.approvalPolicy,
1681
+ sandbox: mode.threadSandbox
1682
+ }));
1683
+ const thread = asObject(result?.thread);
1684
+ const id = typeof thread?.id === "string" ? thread.id : undefined;
1685
+ if (!id)
1686
+ throw new Error("thread/resume did not return a thread id");
1687
+ return id;
1688
+ }
1689
+ async startTurn(sessionId) {
1690
+ const mode = permissionPayloads(this.opts.permissionMode);
1691
+ await this.call("turn/start", {
1692
+ threadId: sessionId,
1693
+ input: [{ type: "text", text: this.opts.prompt, text_elements: [] }],
1694
+ cwd: this.opts.cwd,
1695
+ model: this.opts.model ?? null,
1696
+ effort: this.opts.modelEffort ?? null,
1697
+ approvalPolicy: mode.approvalPolicy,
1698
+ sandboxPolicy: mode.turnSandboxPolicy
1699
+ });
1700
+ }
1701
+ call(method, params) {
1702
+ const id = this.nextId++;
1703
+ this.send({ jsonrpc: "2.0", id, method, params });
1704
+ return new Promise((resolve2, reject) => {
1705
+ this.pending.set(id, { resolve: resolve2, reject });
1706
+ });
1707
+ }
1708
+ notify(method, params) {
1709
+ this.send(params === undefined ? { jsonrpc: "2.0", method } : { jsonrpc: "2.0", method, params });
1710
+ }
1711
+ send(payload) {
1712
+ this.proc.stdin.write(`${JSON.stringify(payload)}
1713
+ `);
1714
+ }
1715
+ onStdout(chunk) {
1716
+ this.readBuffer += chunk;
1717
+ let lineEnd = this.readBuffer.indexOf(`
1718
+ `);
1719
+ while (lineEnd !== -1) {
1720
+ const line = this.readBuffer.slice(0, lineEnd).trim();
1721
+ this.readBuffer = this.readBuffer.slice(lineEnd + 1);
1722
+ if (line.length > 0)
1723
+ this.handleLine(line);
1724
+ lineEnd = this.readBuffer.indexOf(`
1725
+ `);
1726
+ }
1727
+ }
1728
+ handleLine(line) {
1729
+ let msg;
1730
+ try {
1731
+ msg = JSON.parse(line);
1732
+ } catch {
1733
+ return;
1734
+ }
1735
+ const record = asObject(msg);
1736
+ if (!record)
1737
+ return;
1738
+ const id = typeof record.id === "number" ? record.id : undefined;
1739
+ const method = typeof record.method === "string" ? record.method : undefined;
1740
+ if (id !== undefined && this.pending.has(id)) {
1741
+ const pending = this.pending.get(id);
1742
+ this.pending.delete(id);
1743
+ if (!pending)
1744
+ return;
1745
+ const error = asObject(record.error);
1746
+ if (error)
1747
+ pending.reject(new Error(typeof error.message === "string" ? error.message : "app-server request failed"));
1748
+ else
1749
+ pending.resolve(record.result);
1750
+ return;
1751
+ }
1752
+ if (id !== undefined && method) {
1753
+ this.rejectServerRequest(id, method);
1754
+ return;
1755
+ }
1756
+ if (!method)
1757
+ return;
1758
+ this.handleNotification(method, record.params);
1759
+ }
1760
+ handleNotification(method, params) {
1761
+ if (method === "thread/tokenUsage/updated") {
1762
+ const usage = codexAppServerUsageToSnapshot(params);
1763
+ if (usage)
1764
+ this.opts.onEvent(usage);
1765
+ return;
1766
+ }
1767
+ if (method === "item/agentMessage/delta") {
1768
+ const p = asObject(params);
1769
+ const text = typeof p?.delta === "string" ? p.delta : typeof p?.text === "string" ? p.text : "";
1770
+ if (text)
1771
+ this.opts.onEvent({ type: "assistant.delta", text });
1772
+ return;
1773
+ }
1774
+ if (method === "item/started" || method === "item/completed") {
1775
+ for (const event of codexAppServerItemNotificationToEvents(method, params))
1776
+ this.opts.onEvent(event);
1777
+ return;
1778
+ }
1779
+ if (method === "turn/completed") {
1780
+ const p = asObject(params);
1781
+ const turn = asObject(p?.turn);
1782
+ const error = turn?.error;
1783
+ if (error) {
1784
+ this.opts.onEvent({ type: "error", message: stringifyErr2(error) });
1785
+ } else {
1786
+ this.opts.onEvent({ type: "done" });
1787
+ }
1788
+ this.close();
1789
+ return;
1790
+ }
1791
+ if (method === "error") {
1792
+ const p = asObject(params);
1793
+ const message = typeof p?.message === "string" ? p.message : "codex app-server emitted an error";
1794
+ this.opts.onEvent({ type: "error", message });
1795
+ this.close();
1796
+ }
1797
+ }
1798
+ rejectServerRequest(id, method) {
1799
+ this.send({
1800
+ jsonrpc: "2.0",
1801
+ id,
1802
+ error: {
1803
+ code: -32000,
1804
+ message: `kobe app-server backend does not handle server request ${method}`
1805
+ }
1806
+ });
1807
+ }
1808
+ close() {
1809
+ try {
1810
+ this.proc.stdin.end();
1811
+ } catch {}
1812
+ setTimeout(() => {
1813
+ if (!this.proc.killed) {
1814
+ try {
1815
+ this.proc.kill("SIGTERM");
1816
+ } catch {}
1817
+ }
1818
+ }, 50).unref();
1819
+ }
1820
+ rejectAll(err) {
1821
+ for (const pending of this.pending.values())
1822
+ pending.reject(err);
1823
+ this.pending.clear();
1824
+ }
1825
+ }
1826
+ function permissionPayloads(mode) {
1827
+ if (mode === "plan") {
1828
+ return {
1829
+ approvalPolicy: "never",
1830
+ threadSandbox: "read-only",
1831
+ turnSandboxPolicy: { type: "readOnly", networkAccess: true }
1832
+ };
1833
+ }
1834
+ return {
1835
+ approvalPolicy: "never",
1836
+ threadSandbox: "danger-full-access",
1837
+ turnSandboxPolicy: { type: "dangerFullAccess" }
1838
+ };
1839
+ }
1840
+ function asObject(v) {
1841
+ return typeof v === "object" && v !== null && !Array.isArray(v) ? v : null;
1842
+ }
1843
+ function numberOr(v, fallback) {
1844
+ return typeof v === "number" && Number.isFinite(v) ? v : fallback;
1845
+ }
1846
+ function stripItemHousekeeping(item) {
1847
+ const { id: _id, type: _type, ...rest } = item;
1848
+ return rest;
1849
+ }
1850
+ function isNonToolTranscriptItem(itemType) {
1851
+ return itemType === "agentMessage" || itemType === "agent_message" || itemType === "userMessage" || itemType === "user_message";
1852
+ }
1853
+ function stringifyErr2(err) {
1854
+ if (err instanceof Error)
1855
+ return err.message;
1856
+ try {
1857
+ return JSON.stringify(err);
1858
+ } catch {
1859
+ return String(err);
1860
+ }
1861
+ }
1862
+ var CODEX_BACKEND_KV_KEY = "codex.backend";
1863
+ var init_app_server = __esm(() => {
1864
+ init_env();
1865
+ });
1866
+
1453
1867
  // src/engine/codex-local/binary.ts
1454
1868
  import { spawnSync as spawnSync2 } from "child_process";
1455
1869
  import { existsSync as existsSync3, statSync as statSync2 } from "fs";
1456
- import { homedir as homedir6 } from "os";
1870
+ import { homedir as homedir8 } from "os";
1457
1871
  import path4 from "path";
1458
1872
  async function findCodexBinary(deps = defaultDeps4) {
1459
1873
  const checked = [];
@@ -1510,7 +1924,7 @@ var init_binary2 = __esm(() => {
1510
1924
  return process.env[name];
1511
1925
  },
1512
1926
  home() {
1513
- return homedir6();
1927
+ return homedir8();
1514
1928
  },
1515
1929
  which(name) {
1516
1930
  const cmd = process.platform === "win32" ? "where" : "which";
@@ -1540,9 +1954,9 @@ var init_binary2 = __esm(() => {
1540
1954
 
1541
1955
  // src/engine/codex-local/models.ts
1542
1956
  function codexContextWindowFor(_modelId) {
1543
- return DEFAULT_CTX;
1957
+ return 0;
1544
1958
  }
1545
- var CODEX_GPT55_EFFORT_LEVELS, CODEX_MODELS, CODEX_FALLBACK_DEFAULT_MODEL_ID = "gpt-5.4-mini", DEFAULT_CTX = 400000;
1959
+ var CODEX_GPT55_EFFORT_LEVELS, CODEX_MODELS, CODEX_FALLBACK_DEFAULT_MODEL_ID = "gpt-5.4-mini";
1546
1960
  var init_models2 = __esm(() => {
1547
1961
  CODEX_GPT55_EFFORT_LEVELS = [
1548
1962
  "none",
@@ -1567,12 +1981,12 @@ var init_models2 = __esm(() => {
1567
1981
  });
1568
1982
 
1569
1983
  // 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";
1984
+ import { readFileSync as readFileSync3 } from "fs";
1985
+ import { homedir as homedir9 } from "os";
1986
+ import { join as join5 } from "path";
1573
1987
  function readModelFromConfig() {
1574
1988
  try {
1575
- const raw = readFileSync2(CONFIG_PATH, "utf8");
1989
+ const raw = readFileSync3(CONFIG_PATH, "utf8");
1576
1990
  let inTable = false;
1577
1991
  for (const line of raw.split(`
1578
1992
  `)) {
@@ -1600,7 +2014,7 @@ function resolveCodexDefaultModelId() {
1600
2014
  var CONFIG_PATH, cached2;
1601
2015
  var init_settings2 = __esm(() => {
1602
2016
  init_models2();
1603
- CONFIG_PATH = join4(homedir7(), ".codex", "config.toml");
2017
+ CONFIG_PATH = join5(homedir9(), ".codex", "config.toml");
1604
2018
  });
1605
2019
 
1606
2020
  // src/engine/codex-local/capabilities.ts
@@ -1690,9 +2104,31 @@ function isInstructionsEnvelope(text) {
1690
2104
  }
1691
2105
  var init_synthetic = () => {};
1692
2106
 
2107
+ // src/engine/codex-local/usage.ts
2108
+ function codexUsageToSnapshot(usage, opts = {}) {
2109
+ const totalInput = numberOr2(usage.input_tokens, 0);
2110
+ const cachedInput = numberOr2(usage.cached_input_tokens, 0);
2111
+ const output = numberOr2(usage.output_tokens, 0);
2112
+ const nonCachedInput = Math.max(0, totalInput - cachedInput);
2113
+ if (totalInput <= 0 && output <= 0 && cachedInput <= 0)
2114
+ return;
2115
+ return {
2116
+ input_tokens: nonCachedInput,
2117
+ output_tokens: output,
2118
+ ...cachedInput > 0 ? { cache_read_input_tokens: cachedInput } : {},
2119
+ ...validPositive(opts.contextWindowTokens) ? { context_window_tokens: opts.contextWindowTokens } : {}
2120
+ };
2121
+ }
2122
+ function numberOr2(v, fallback) {
2123
+ return typeof v === "number" && Number.isFinite(v) ? v : fallback;
2124
+ }
2125
+ function validPositive(v) {
2126
+ return typeof v === "number" && Number.isFinite(v) && v > 0;
2127
+ }
2128
+
1693
2129
  // 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";
2130
+ import { readFile as readFile3, readdir as readdir3, unlink as unlink3 } from "fs/promises";
2131
+ import { homedir as homedir10 } from "os";
1696
2132
  import path5 from "path";
1697
2133
  async function listRolloutFiles(deps = defaultDeps5) {
1698
2134
  const root = deps.sessionsDir();
@@ -1742,7 +2178,7 @@ async function deleteHistory2(sessionId, deps = defaultDeps5) {
1742
2178
  if (!file)
1743
2179
  return;
1744
2180
  try {
1745
- await unlink2(file);
2181
+ await unlink3(file);
1746
2182
  } catch (err) {
1747
2183
  if (err.code === "ENOENT")
1748
2184
  return;
@@ -1794,10 +2230,6 @@ function parseJsonl2(raw, sessionId) {
1794
2230
  function deriveCodexUsageMetrics(raw) {
1795
2231
  let latestUsage;
1796
2232
  let latestUsageTimestampMs = null;
1797
- let lastUserTimestampMs = null;
1798
- let inputTokens = 0;
1799
- let outputTokens = 0;
1800
- const intervals = [];
1801
2233
  for (const line of raw.split(`
1802
2234
  `)) {
1803
2235
  const trimmed = line.trim();
@@ -1812,15 +2244,8 @@ function deriveCodexUsageMetrics(raw) {
1812
2244
  if (!isObject5(parsed))
1813
2245
  continue;
1814
2246
  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
- }
2247
+ if (parsed.type === "response_item")
1822
2248
  continue;
1823
- }
1824
2249
  if (parsed.type !== "turn.completed")
1825
2250
  continue;
1826
2251
  const usage = isObject5(parsed.usage) ? parsed.usage : undefined;
@@ -1835,63 +2260,13 @@ function deriveCodexUsageMetrics(raw) {
1835
2260
  } else if (latestUsage === undefined) {
1836
2261
  latestUsage = snapshot;
1837
2262
  }
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
2263
  }
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
- };
2264
+ return latestUsage;
1865
2265
  }
1866
2266
  function parseTimestampMs2(value) {
1867
2267
  const ms = new Date(value).getTime();
1868
2268
  return Number.isFinite(ms) ? ms : null;
1869
2269
  }
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
2270
  function isObject5(v) {
1896
2271
  return typeof v === "object" && v !== null && !Array.isArray(v);
1897
2272
  }
@@ -1900,7 +2275,7 @@ var init_history2 = __esm(() => {
1900
2275
  init_synthetic();
1901
2276
  defaultDeps5 = {
1902
2277
  sessionsDir() {
1903
- return path5.join(homedir8(), ".codex", "sessions");
2278
+ return path5.join(homedir10(), ".codex", "sessions");
1904
2279
  },
1905
2280
  async readdir(p) {
1906
2281
  try {
@@ -1915,6 +2290,52 @@ var init_history2 = __esm(() => {
1915
2290
  };
1916
2291
  });
1917
2292
 
2293
+ // src/engine/codex-local/openrouter.ts
2294
+ async function resolveOpenRouterContextWindow(modelId) {
2295
+ const id = openRouterModelId(modelId);
2296
+ if (!id)
2297
+ return;
2298
+ const models = await loadOpenRouterModels();
2299
+ return models.get(id)?.contextLength;
2300
+ }
2301
+ function openRouterModelId(modelId) {
2302
+ const id = modelId?.trim();
2303
+ if (!id)
2304
+ return null;
2305
+ return id.includes("/") ? id : `openai/${id}`;
2306
+ }
2307
+ async function loadOpenRouterModels() {
2308
+ cachePromise ??= fetchOpenRouterModels().catch(() => new Map);
2309
+ return cachePromise;
2310
+ }
2311
+ async function fetchOpenRouterModels() {
2312
+ const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
2313
+ const res = await fetch(MODELS_URL, {
2314
+ signal,
2315
+ headers: { "user-agent": "kobe-codex-openrouter-context" }
2316
+ });
2317
+ if (!res.ok)
2318
+ return new Map;
2319
+ const body = await res.json();
2320
+ if (!isObject6(body) || !Array.isArray(body.data))
2321
+ return new Map;
2322
+ const out = new Map;
2323
+ for (const item of body.data) {
2324
+ if (!isObject6(item))
2325
+ continue;
2326
+ const id = typeof item.id === "string" ? item.id : undefined;
2327
+ const contextLength = typeof item.context_length === "number" ? item.context_length : undefined;
2328
+ if (!id || !contextLength || !Number.isFinite(contextLength) || contextLength <= 0)
2329
+ continue;
2330
+ out.set(id, { id, contextLength });
2331
+ }
2332
+ return out;
2333
+ }
2334
+ function isObject6(v) {
2335
+ return typeof v === "object" && v !== null && !Array.isArray(v);
2336
+ }
2337
+ var MODELS_URL = "https://openrouter.ai/api/v1/models", FETCH_TIMEOUT_MS = 2500, cachePromise;
2338
+
1918
2339
  // src/engine/codex-local/sessions.ts
1919
2340
  import { open, stat as stat2 } from "fs/promises";
1920
2341
  async function listSessionsForCwd2(cwd, deps) {
@@ -1958,7 +2379,7 @@ async function tryReadMeta(file) {
1958
2379
  return;
1959
2380
  if (parsed.type === "session_meta") {
1960
2381
  const payload = parsed.payload;
1961
- if (isObject6(payload)) {
2382
+ if (isObject7(payload)) {
1962
2383
  if (typeof payload.id === "string")
1963
2384
  sessionId = payload.id;
1964
2385
  if (typeof payload.cwd === "string")
@@ -1966,7 +2387,7 @@ async function tryReadMeta(file) {
1966
2387
  }
1967
2388
  return;
1968
2389
  }
1969
- if (parsed.type === "response_item" && isObject6(parsed.payload)) {
2390
+ if (parsed.type === "response_item" && isObject7(parsed.payload)) {
1970
2391
  const p = parsed.payload;
1971
2392
  if (p.type === "message") {
1972
2393
  messageCount++;
@@ -2012,12 +2433,12 @@ function safeParse(line) {
2012
2433
  return null;
2013
2434
  try {
2014
2435
  const v = JSON.parse(t);
2015
- return isObject6(v) ? v : null;
2436
+ return isObject7(v) ? v : null;
2016
2437
  } catch {
2017
2438
  return null;
2018
2439
  }
2019
2440
  }
2020
- function isObject6(v) {
2441
+ function isObject7(v) {
2021
2442
  return typeof v === "object" && v !== null && !Array.isArray(v);
2022
2443
  }
2023
2444
  var PREVIEW_CHAR_CAP = 200;
@@ -2027,10 +2448,10 @@ var init_sessions2 = __esm(() => {
2027
2448
  });
2028
2449
 
2029
2450
  // src/engine/codex-local/spawn.ts
2030
- import { spawn as spawn3 } from "child_process";
2451
+ import { spawn as spawn4 } from "child_process";
2031
2452
  function spawnCodexProcess(opts) {
2032
2453
  const args = buildArgs2(opts);
2033
- const proc = spawn3(opts.binaryPath, args, {
2454
+ const proc = spawn4(opts.binaryPath, args, {
2034
2455
  cwd: opts.cwd,
2035
2456
  env: { ...process.env, ...opts.env ?? {} },
2036
2457
  stdio: ["pipe", "pipe", "pipe"]
@@ -2087,10 +2508,10 @@ async function* parseStreamJson2(lines, opts = {}) {
2087
2508
  try {
2088
2509
  msg = JSON.parse(line);
2089
2510
  } catch (err) {
2090
- yield { type: "error", message: `codex stream-json parse failed: ${stringifyErr2(err)}` };
2511
+ yield { type: "error", message: `codex stream-json parse failed: ${stringifyErr3(err)}` };
2091
2512
  continue;
2092
2513
  }
2093
- if (!isObject7(msg))
2514
+ if (!isObject8(msg))
2094
2515
  continue;
2095
2516
  const type = typeof msg.type === "string" ? msg.type : undefined;
2096
2517
  if (!type)
@@ -2106,7 +2527,7 @@ async function* parseStreamJson2(lines, opts = {}) {
2106
2527
  if (type === "turn.started")
2107
2528
  continue;
2108
2529
  if (type === "item.started" || type === "item.completed") {
2109
- const item = isObject7(msg.item) ? msg.item : undefined;
2530
+ const item = isObject8(msg.item) ? msg.item : undefined;
2110
2531
  if (!item)
2111
2532
  continue;
2112
2533
  const itemId = typeof item.id === "string" ? item.id : undefined;
@@ -2140,17 +2561,12 @@ async function* parseStreamJson2(lines, opts = {}) {
2140
2561
  continue;
2141
2562
  }
2142
2563
  if (type === "turn.completed") {
2143
- const usage = isObject7(msg.usage) ? msg.usage : undefined;
2564
+ const usage = isObject8(msg.usage) ? msg.usage : undefined;
2144
2565
  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
- };
2566
+ const contextWindowTokens = await opts.contextWindowTokens?.();
2567
+ const snapshot = codexUsageToSnapshot(usage, { contextWindowTokens });
2568
+ if (snapshot)
2569
+ yield { type: "usage", ...snapshot };
2154
2570
  }
2155
2571
  yield { type: "done" };
2156
2572
  return;
@@ -2179,26 +2595,23 @@ async function* readLines2(stream) {
2179
2595
  if (buf.length > 0)
2180
2596
  yield buf;
2181
2597
  }
2182
- function isObject7(v) {
2598
+ function isObject8(v) {
2183
2599
  return typeof v === "object" && v !== null && !Array.isArray(v);
2184
2600
  }
2185
2601
  function codexSessionId(msg) {
2186
2602
  if (msg.type === "session_meta") {
2187
- const payload = isObject7(msg.payload) ? msg.payload : undefined;
2603
+ const payload = isObject8(msg.payload) ? msg.payload : undefined;
2188
2604
  const id2 = payload?.id;
2189
2605
  return typeof id2 === "string" && id2.length > 0 ? id2 : undefined;
2190
2606
  }
2191
2607
  const id = msg.thread_id;
2192
2608
  return typeof id === "string" && id.length > 0 ? id : undefined;
2193
2609
  }
2194
- function numberOr2(v, fallback) {
2195
- return typeof v === "number" && Number.isFinite(v) ? v : fallback;
2196
- }
2197
2610
  function stripIdAndType(item) {
2198
2611
  const { id: _id, type: _type, ...rest } = item;
2199
2612
  return rest;
2200
2613
  }
2201
- function stringifyErr2(err) {
2614
+ function stringifyErr3(err) {
2202
2615
  if (err instanceof Error)
2203
2616
  return err.message;
2204
2617
  try {
@@ -2207,6 +2620,7 @@ function stringifyErr2(err) {
2207
2620
  return String(err);
2208
2621
  }
2209
2622
  }
2623
+ var init_stream = () => {};
2210
2624
 
2211
2625
  // src/engine/codex-local/index.ts
2212
2626
  class CodexLocal {
@@ -2216,9 +2630,11 @@ class CodexLocal {
2216
2630
  running = new Map;
2217
2631
  binaryPathResolver;
2218
2632
  stopGraceMs;
2633
+ backend;
2219
2634
  constructor(opts = {}) {
2220
2635
  this.binaryPathResolver = opts.binaryPathResolver ?? findCodexBinary;
2221
2636
  this.stopGraceMs = opts.stopGraceMs ?? 5000;
2637
+ this.backend = opts.backend ?? resolveCodexBackend();
2222
2638
  }
2223
2639
  async spawn(cwd, prompt, opts) {
2224
2640
  return this.start({ cwd, prompt, opts });
@@ -2273,7 +2689,100 @@ class CodexLocal {
2273
2689
  }
2274
2690
  }
2275
2691
  async start(args) {
2692
+ if (this.backend === "app-server")
2693
+ return this.startAppServer(args);
2694
+ return this.startExec(args);
2695
+ }
2696
+ async startAppServer(args) {
2697
+ const binaryPath = await this.binaryPathResolver();
2698
+ const queue = [];
2699
+ let session;
2700
+ let bound = false;
2701
+ let terminalSeen = false;
2702
+ let stderrTail = "";
2703
+ let resolveHandle = () => {};
2704
+ let rejectHandle = () => {};
2705
+ const handlePromise = new Promise((res, rej) => {
2706
+ resolveHandle = res;
2707
+ rejectHandle = rej;
2708
+ });
2709
+ const bind = (sessionId) => {
2710
+ if (bound)
2711
+ return;
2712
+ bound = true;
2713
+ session = {
2714
+ sessionId,
2715
+ cwd: args.cwd,
2716
+ spawned,
2717
+ queue,
2718
+ waiters: [],
2719
+ closed: false,
2720
+ spawnedAtIso: new Date().toISOString()
2721
+ };
2722
+ this.running.set(sessionId, session);
2723
+ this.registry.register({
2724
+ sessionId,
2725
+ cwd: args.cwd,
2726
+ proc: spawned.proc,
2727
+ startedAt: Date.now(),
2728
+ prompt: args.prompt
2729
+ });
2730
+ resolveHandle({ sessionId, cwd: args.cwd });
2731
+ };
2732
+ const emit = (ev) => {
2733
+ if (ev.type === "done" || ev.type === "error")
2734
+ terminalSeen = true;
2735
+ queue.push(ev);
2736
+ if (session) {
2737
+ this.notify(session);
2738
+ if (ev.type === "done" || ev.type === "error") {
2739
+ this.registry.unregister(session.sessionId, spawned.proc);
2740
+ }
2741
+ }
2742
+ };
2743
+ const spawned = spawnCodexAppServerTurn({
2744
+ binaryPath,
2745
+ cwd: args.cwd,
2746
+ prompt: args.prompt,
2747
+ model: args.opts?.model,
2748
+ modelEffort: args.opts?.modelEffort,
2749
+ permissionMode: args.opts?.permissionMode,
2750
+ env: args.opts?.env,
2751
+ resumeSessionId: args.resumeSessionId,
2752
+ onSessionId: bind,
2753
+ onEvent: emit
2754
+ });
2755
+ captureStderrTail(spawned.stderr, (chunk) => {
2756
+ stderrTail = (stderrTail + chunk).slice(-STDERR_TAIL_CAP);
2757
+ });
2758
+ spawned.ready.catch((err) => {
2759
+ if (!bound)
2760
+ rejectHandle(err);
2761
+ });
2762
+ spawned.closed.then(({ code, signal }) => {
2763
+ if (session) {
2764
+ if (!terminalSeen && typeof code === "number" && code !== 0) {
2765
+ queue.push({
2766
+ type: "error",
2767
+ message: formatExitMsg("codex app-server exited", code, signal, stderrTail)
2768
+ });
2769
+ }
2770
+ session.closed = true;
2771
+ this.notify(session);
2772
+ this.registry.unregister(session.sessionId, spawned.proc);
2773
+ if (this.running.get(session.sessionId) === session) {
2774
+ this.running.delete(session.sessionId);
2775
+ }
2776
+ }
2777
+ if (!bound) {
2778
+ rejectHandle(new Error(formatExitMsg("codex app-server exited before session id was captured", code, signal, stderrTail)));
2779
+ }
2780
+ });
2781
+ return handlePromise;
2782
+ }
2783
+ async startExec(args) {
2276
2784
  const binaryPath = await this.binaryPathResolver();
2785
+ const modelId = args.opts?.model ?? codexCapabilities.defaultModelId();
2277
2786
  const spawned = spawnCodexProcess({
2278
2787
  binaryPath,
2279
2788
  cwd: args.cwd,
@@ -2294,6 +2803,7 @@ class CodexLocal {
2294
2803
  let session;
2295
2804
  let bound = false;
2296
2805
  let stderrTail = "";
2806
+ const contextWindowPromise = resolveOpenRouterContextWindow(modelId);
2297
2807
  const bind = (sessionId) => {
2298
2808
  if (bound) {
2299
2809
  if (session && session.sessionId !== sessionId) {
@@ -2357,15 +2867,15 @@ class CodexLocal {
2357
2867
  });
2358
2868
  (async () => {
2359
2869
  const events = parseStreamJson2(readLines2(spawned.stdout), {
2360
- onSessionId: (sid) => bind(sid)
2870
+ onSessionId: (sid) => bind(sid),
2871
+ contextWindowTokens: () => contextWindowPromise
2361
2872
  });
2362
2873
  try {
2363
2874
  for await (const ev of events) {
2364
- const enriched = enrichUsageEvent2(ev, session?.spawnedAtIso);
2365
- queue.push(enriched);
2875
+ queue.push(ev);
2366
2876
  if (session)
2367
2877
  this.notify(session);
2368
- if ((enriched.type === "done" || enriched.type === "error") && session) {
2878
+ if ((ev.type === "done" || ev.type === "error") && session) {
2369
2879
  this.registry.unregister(session.sessionId, spawned.proc);
2370
2880
  }
2371
2881
  }
@@ -2431,28 +2941,25 @@ function formatExitMsg(prefix, code, signal, stderrTail) {
2431
2941
  parts.push(`: ${detail}`);
2432
2942
  return parts.join(" ").replace(/ : /, ": ");
2433
2943
  }
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
2944
  var STDERR_TAIL_CAP;
2440
2945
  var init_codex_local = __esm(() => {
2946
+ init_app_server();
2441
2947
  init_binary2();
2442
2948
  init_capabilities2();
2443
2949
  init_history2();
2444
2950
  init_sessions2();
2445
2951
  init_spawn2();
2952
+ init_stream();
2446
2953
  STDERR_TAIL_CAP = 4 * 1024;
2447
2954
  });
2448
2955
 
2449
2956
  // src/orchestrator/bridge/server.ts
2450
- import { mkdir as mkdir2, unlink as unlink3 } from "fs/promises";
2957
+ import { mkdir as mkdir2, unlink as unlink4 } from "fs/promises";
2451
2958
  import { createServer } from "net";
2452
2959
  import { dirname as dirname2 } from "path";
2453
2960
  async function startBridgeServer(orch, socketPath) {
2454
2961
  await mkdir2(dirname2(socketPath), { recursive: true });
2455
- await unlink3(socketPath).catch(() => {});
2962
+ await unlink4(socketPath).catch(() => {});
2456
2963
  const conns = new Set;
2457
2964
  const server = createServer((conn) => {
2458
2965
  conns.add(conn);
@@ -2493,7 +3000,7 @@ async function startBridgeServer(orch, socketPath) {
2493
3000
  conn.destroy();
2494
3001
  conns.clear();
2495
3002
  await new Promise((resolve2) => server.close(() => resolve2()));
2496
- await unlink3(socketPath).catch(() => {});
3003
+ await unlink4(socketPath).catch(() => {});
2497
3004
  }
2498
3005
  };
2499
3006
  }
@@ -2585,19 +3092,19 @@ __export(exports_bridge, {
2585
3092
  startBridge: () => startBridge,
2586
3093
  bridgeSocketPathForHome: () => bridgeSocketPathForHome
2587
3094
  });
2588
- import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
2589
- import { homedir as homedir9 } from "os";
2590
- import { join as join5 } from "path";
3095
+ import { mkdir as mkdir3, unlink as unlink5, writeFile as writeFile2 } from "fs/promises";
3096
+ import { homedir as homedir11 } from "os";
3097
+ import { join as join6 } from "path";
2591
3098
  import { fileURLToPath as fileURLToPath2 } from "url";
2592
3099
  function bridgeSocketPathForHome(home, pid = process.pid) {
2593
- const runDir = join5(home, ".kobe", "run");
2594
- return fitSocketPath(join5(runDir, `bridge-${pid}.sock`), home, "bridge", pid);
3100
+ const runDir = join6(home, ".kobe", "run");
3101
+ return fitSocketPath(join6(runDir, `bridge-${pid}.sock`), home, "bridge", pid);
2595
3102
  }
2596
3103
  async function startBridge(orch, opts = {}) {
2597
- const home = opts.homeDir ?? process.env.KOBE_HOME_DIR ?? homedir9();
2598
- const runDir = join5(home, ".kobe", "run");
3104
+ const home = opts.homeDir ?? process.env.KOBE_HOME_DIR ?? homedir11();
3105
+ const runDir = join6(home, ".kobe", "run");
2599
3106
  const socketPath = bridgeSocketPathForHome(home);
2600
- const mcpConfigPath = join5(runDir, `mcp-${process.pid}.json`);
3107
+ const mcpConfigPath = join6(runDir, `mcp-${process.pid}.json`);
2601
3108
  await mkdir3(runDir, { recursive: true });
2602
3109
  const server = await startBridgeServer(orch, socketPath);
2603
3110
  await mkdir3(runDir, { recursive: true });
@@ -2618,6 +3125,7 @@ async function startBridge(orch, opts = {}) {
2618
3125
  mcpConfigPath,
2619
3126
  async close() {
2620
3127
  await server.close();
3128
+ await unlink5(mcpConfigPath).catch(() => {});
2621
3129
  }
2622
3130
  };
2623
3131
  }
@@ -3925,23 +4433,6 @@ var init_registry = __esm(() => {
3925
4433
  defaultIdentity = claudeIdentity;
3926
4434
  });
3927
4435
 
3928
- // src/env.ts
3929
- import { homedir as homedir10 } from "os";
3930
- import { join as join6 } from "path";
3931
- function isDev() {
3932
- return process.env.KOBE_DEV === "1";
3933
- }
3934
- function homeDir() {
3935
- return process.env.KOBE_HOME_DIR ?? homedir10();
3936
- }
3937
- function kobeStateDir() {
3938
- return join6(homeDir(), ".kobe");
3939
- }
3940
- function kvStatePath() {
3941
- return join6(homeDir(), ".config", "kobe", "state.json");
3942
- }
3943
- var init_env = () => {};
3944
-
3945
4436
  // src/state/repos.ts
3946
4437
  var exports_repos = {};
3947
4438
  __export(exports_repos, {
@@ -3954,7 +4445,7 @@ __export(exports_repos, {
3954
4445
  addSavedRepo: () => addSavedRepo
3955
4446
  });
3956
4447
  import { spawnSync as spawnSync3 } from "child_process";
3957
- import { mkdirSync, readFileSync as readFileSync3, realpathSync, renameSync, writeFileSync } from "fs";
4448
+ import { mkdirSync, readFileSync as readFileSync4, realpathSync, renameSync, writeFileSync } from "fs";
3958
4449
  import { dirname as dirname3 } from "path";
3959
4450
  function resolveRepoRoot(absPath) {
3960
4451
  const r = spawnSync3("git", ["rev-parse", "--show-toplevel"], {
@@ -3991,7 +4482,7 @@ function statePath() {
3991
4482
  }
3992
4483
  function load() {
3993
4484
  try {
3994
- const text = readFileSync3(statePath(), "utf8");
4485
+ const text = readFileSync4(statePath(), "utf8");
3995
4486
  const parsed = JSON.parse(text);
3996
4487
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
3997
4488
  return parsed;
@@ -4138,7 +4629,7 @@ var init_ulid = __esm(() => {
4138
4629
  });
4139
4630
 
4140
4631
  // src/orchestrator/metadata-suggester.ts
4141
- import { spawn as spawn4 } from "child_process";
4632
+ import { spawn as spawn5 } from "child_process";
4142
4633
 
4143
4634
  class MetadataSuggester {
4144
4635
  binaryPromise = null;
@@ -4167,7 +4658,7 @@ class MetadataSuggester {
4167
4658
  return new Promise((resolve2) => {
4168
4659
  let proc;
4169
4660
  try {
4170
- proc = spawn4(binary, ["-p", builder(trimmed)], {
4661
+ proc = spawn5(binary, ["-p", builder(trimmed)], {
4171
4662
  stdio: ["ignore", "pipe", "ignore"],
4172
4663
  env: process.env
4173
4664
  });
@@ -6018,8 +6509,8 @@ var init_core = __esm(() => {
6018
6509
  });
6019
6510
 
6020
6511
  // src/orchestrator/index/store.ts
6021
- import { mkdir as mkdir4, open as open2, readFile as readFile4, rename, unlink as unlink4 } from "fs/promises";
6022
- import { homedir as homedir11 } from "os";
6512
+ import { mkdir as mkdir4, open as open2, readFile as readFile4, rename, unlink as unlink6 } from "fs/promises";
6513
+ import { homedir as homedir12 } from "os";
6023
6514
  import { dirname as dirname4, join as join7 } from "path";
6024
6515
 
6025
6516
  class TaskIndexStore {
@@ -6032,7 +6523,7 @@ class TaskIndexStore {
6032
6523
  listeners = new Set;
6033
6524
  saveChain = Promise.resolve();
6034
6525
  constructor(options = {}) {
6035
- this.homeDir = options.homeDir ?? homedir11();
6526
+ this.homeDir = options.homeDir ?? homedir12();
6036
6527
  this.kobeDir = join7(this.homeDir, ".kobe");
6037
6528
  this.path = join7(this.kobeDir, "tasks.json");
6038
6529
  this.tmpPath = `${this.path}.tmp`;
@@ -6201,13 +6692,13 @@ class TaskIndexStore {
6201
6692
  }
6202
6693
  async _unlinkForTests() {
6203
6694
  try {
6204
- await unlink4(this.path);
6695
+ await unlink6(this.path);
6205
6696
  } catch (err) {
6206
6697
  if (err.code !== "ENOENT")
6207
6698
  throw err;
6208
6699
  }
6209
6700
  try {
6210
- await unlink4(this.tmpPath);
6701
+ await unlink6(this.tmpPath);
6211
6702
  } catch (err) {
6212
6703
  if (err.code !== "ENOENT")
6213
6704
  throw err;
@@ -6605,7 +7096,7 @@ var init_manager = __esm(() => {
6605
7096
  // src/bin/kobed.ts
6606
7097
  init_daemon_process();
6607
7098
  init_client();
6608
- import { unlink as unlink6 } from "fs/promises";
7099
+ import { unlink as unlink8 } from "fs/promises";
6609
7100
 
6610
7101
  // src/core/index.ts
6611
7102
  init_claude_code_local();
@@ -6614,9 +7105,9 @@ init_bridge();
6614
7105
  init_core();
6615
7106
  init_store();
6616
7107
  init_manager();
6617
- import { homedir as homedir12 } from "os";
7108
+ import { homedir as homedir13 } from "os";
6618
7109
  async function createKobeCore(options = {}) {
6619
- const homeDir2 = options.homeDir ?? process.env.KOBE_HOME_DIR ?? homedir12();
7110
+ const homeDir2 = options.homeDir ?? process.env.KOBE_HOME_DIR ?? homedir13();
6620
7111
  const store = new TaskIndexStore({ homeDir: homeDir2 });
6621
7112
  await store.load();
6622
7113
  const worktrees = new GitWorktreeManager;
@@ -6645,7 +7136,7 @@ init_paths();
6645
7136
  // src/daemon/server.ts
6646
7137
  init_repos();
6647
7138
  init_paths();
6648
- import { mkdir as mkdir5, readFile as readFile6, unlink as unlink5, writeFile as writeFile4 } from "fs/promises";
7139
+ import { mkdir as mkdir5, readFile as readFile6, unlink as unlink7, writeFile as writeFile4 } from "fs/promises";
6649
7140
  import { createServer as createServer2 } from "net";
6650
7141
  import { dirname as dirname5 } from "path";
6651
7142
 
@@ -6653,12 +7144,12 @@ import { dirname as dirname5 } from "path";
6653
7144
  import { execFile } from "child_process";
6654
7145
  import { createHash as createHash2 } from "crypto";
6655
7146
  import { readFile as readFile5 } from "fs/promises";
6656
- import { homedir as homedir13, userInfo } from "os";
7147
+ import { homedir as homedir14, userInfo } from "os";
6657
7148
  import { join as join8 } from "path";
6658
7149
  import { promisify } from "util";
6659
7150
  var execFileAsync = promisify(execFile);
6660
7151
  var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
6661
- var FETCH_TIMEOUT_MS = 5000;
7152
+ var FETCH_TIMEOUT_MS2 = 5000;
6662
7153
  var KEYCHAIN_BASE = "Claude Code";
6663
7154
  var KEYCHAIN_SUFFIX = "-credentials";
6664
7155
  function keychainServiceName() {
@@ -6689,7 +7180,7 @@ async function readKeychainToken() {
6689
7180
  }
6690
7181
  }
6691
7182
  async function readPlainTextToken() {
6692
- const configDir = process.env.CLAUDE_CONFIG_DIR ?? join8(homedir13(), ".claude");
7183
+ const configDir = process.env.CLAUDE_CONFIG_DIR ?? join8(homedir14(), ".claude");
6693
7184
  const path9 = join8(configDir, ".credentials.json");
6694
7185
  try {
6695
7186
  const raw = await readFile5(path9, "utf8");
@@ -6730,7 +7221,7 @@ async function fetchPlanUsage(now = Date.now()) {
6730
7221
  if (typeof token.expiresAt === "number" && token.expiresAt > 0 && token.expiresAt < now)
6731
7222
  return null;
6732
7223
  const ctrl = new AbortController;
6733
- const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
7224
+ const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS2);
6734
7225
  try {
6735
7226
  const res = await fetch(USAGE_URL, {
6736
7227
  signal: ctrl.signal,
@@ -6805,7 +7296,7 @@ function createPlanUsagePoller(options) {
6805
7296
  }
6806
7297
  // src/daemon/rc-bridge.ts
6807
7298
  init_binary();
6808
- import { spawn as spawn5 } from "child_process";
7299
+ import { spawn as spawn6 } from "child_process";
6809
7300
  var ENV_ID_RE = /Environment ID:\s*(env_[A-Za-z0-9]+)/;
6810
7301
  var DEEPLINK_RE = /https:\/\/claude\.ai\/code\?environment=([A-Za-z0-9_]+)/;
6811
7302
  var ANSI_RE = /\x1b\[[0-9;?]*[A-Za-z]/g;
@@ -6813,7 +7304,7 @@ function createRcBridge(options = {}) {
6813
7304
  const stopGraceMs = options.stopGraceMs ?? 5000;
6814
7305
  const readyTimeoutMs = options.readyTimeoutMs ?? 30000;
6815
7306
  const binaryPathResolver = options.binaryPathResolver ?? findClaudeBinary;
6816
- const spawner = options.spawner ?? ((cmd, args, cwd) => spawn5(cmd, [...args], {
7307
+ const spawner = options.spawner ?? ((cmd, args, cwd) => spawn6(cmd, [...args], {
6817
7308
  cwd,
6818
7309
  stdio: ["ignore", "pipe", "pipe"],
6819
7310
  env: { ...process.env }
@@ -6987,7 +7478,7 @@ async function startDaemonServer(orch, options = {}) {
6987
7478
  let nextClientId = 1;
6988
7479
  await mkdir5(dirname5(socketPath), { recursive: true });
6989
7480
  await mkdir5(dirname5(pidPath), { recursive: true });
6990
- await unlink5(socketPath).catch(() => {});
7481
+ await unlink7(socketPath).catch(() => {});
6991
7482
  const server = createServer2((socket) => {
6992
7483
  const client = {
6993
7484
  id: nextClientId++,
@@ -7032,8 +7523,8 @@ async function startDaemonServer(orch, options = {}) {
7032
7523
  client.socket.destroy();
7033
7524
  }
7034
7525
  await new Promise((resolve2) => server.close(() => resolve2()));
7035
- await unlink5(socketPath).catch(() => {});
7036
- await unlink5(pidPath).catch(() => {});
7526
+ await unlink7(socketPath).catch(() => {});
7527
+ await unlink7(pidPath).catch(() => {});
7037
7528
  }
7038
7529
  };
7039
7530
  planUsagePoller.start();
@@ -7251,6 +7742,7 @@ async function startDaemonServer(orch, options = {}) {
7251
7742
  const tabId = optionalString2(payload, "tabId");
7252
7743
  const text = optionalString2(payload, "text");
7253
7744
  await orch.runTask(taskId, text, tabId);
7745
+ broadcastTaskUpdated(orch, clients, taskId);
7254
7746
  const task = orch.getTask(taskId);
7255
7747
  if (task)
7256
7748
  broadcast(clients, {
@@ -7534,7 +8026,7 @@ async function main() {
7534
8026
  await new Promise((resolve2) => setTimeout(resolve2, 100));
7535
8027
  } catch {}
7536
8028
  }
7537
- await unlink6(socketPath).catch(() => {});
8029
+ await unlink8(socketPath).catch(() => {});
7538
8030
  const next = await connectOrStartDaemon();
7539
8031
  next.close();
7540
8032
  console.log(`kobed: restarted, listening on ${socketPath}`);