@ai-matrx/coding-sessions 0.1.80 → 0.1.83

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { C as CodingSessionsMethod, a as CodingSessionsApi, b as CodingEvent, c as CodingSessionsEngine, d as CodingSessionsEngineOptions } from '../index-RYOb-YZT.cjs';
1
+ import { C as CodingSessionsMethod, a as CodingSessionsApi, b as CodingEvent, c as CodingSessionsEngine, d as CodingSessionsEngineOptions } from '../index-DQ0teQvw.cjs';
2
2
 
3
3
  /**
4
4
  * THE HOST BRIDGE — the one seam between the engine and any transport.
@@ -1,4 +1,4 @@
1
- import { C as CodingSessionsMethod, a as CodingSessionsApi, b as CodingEvent, c as CodingSessionsEngine, d as CodingSessionsEngineOptions } from '../index-RYOb-YZT.js';
1
+ import { C as CodingSessionsMethod, a as CodingSessionsApi, b as CodingEvent, c as CodingSessionsEngine, d as CodingSessionsEngineOptions } from '../index-DQ0teQvw.js';
2
2
 
3
3
  /**
4
4
  * THE HOST BRIDGE — the one seam between the engine and any transport.
@@ -3,14 +3,14 @@ import { randomBytes, timingSafeEqual } from "crypto";
3
3
  import { existsSync as existsSync2 } from "fs";
4
4
  import fs7 from "fs/promises";
5
5
  import http from "http";
6
- import path7 from "path";
6
+ import path8 from "path";
7
7
  import { fileURLToPath as fileURLToPath2 } from "url";
8
8
  import { WebSocketServer } from "ws";
9
9
 
10
10
  // src/engine/index.ts
11
- import { execFile as execFile2, fork } from "child_process";
11
+ import { execFile as execFile3, fork } from "child_process";
12
12
  import { randomUUID as randomUUID3 } from "crypto";
13
- import { accessSync, constants as fsConstants, existsSync } from "fs";
13
+ import { existsSync } from "fs";
14
14
  import { formatFileSize } from "@ai-matrx/kit/format";
15
15
 
16
16
  // src/engine/raw-backup.ts
@@ -72,8 +72,8 @@ function createRawBackupScheduler(options) {
72
72
 
73
73
  // src/engine/index.ts
74
74
  import fs6 from "fs/promises";
75
- import os4 from "os";
76
- import path6 from "path";
75
+ import os5 from "os";
76
+ import path7 from "path";
77
77
  import { fileURLToPath } from "url";
78
78
 
79
79
  // src/normalize/claude.ts
@@ -739,9 +739,177 @@ function describeProblem(provider, problem) {
739
739
  };
740
740
  }
741
741
 
742
- // src/engine/clean-env.ts
742
+ // src/engine/binaries.ts
743
+ import { execFile } from "child_process";
744
+ import { accessSync, constants as fsConstants, readdirSync, readFileSync, realpathSync } from "fs";
743
745
  import os from "os";
744
746
  import path from "path";
747
+ var SOURCE_LABEL = {
748
+ codex: { "desktop-app": "from ChatGPT app", path: "from PATH", "npm-global": "from npm", configured: "set by host" },
749
+ claude: { "desktop-app": "from Claude app", path: "from PATH", "npm-global": "from npm", configured: "set by host" }
750
+ };
751
+ function sourceLabel(provider, source) {
752
+ return SOURCE_LABEL[provider][source];
753
+ }
754
+ function parseVersion(text) {
755
+ const match = /(\d+)\.(\d+)\.(\d+)(?:[-.][0-9A-Za-z.]+)?/.exec(text ?? "");
756
+ return match ? match[0] : null;
757
+ }
758
+ function compareVersions(a, b) {
759
+ const [aCore = "", aPre] = a.split("-", 2);
760
+ const [bCore = "", bPre] = b.split("-", 2);
761
+ const pa = aCore.split(".").map((n) => Number.parseInt(n, 10) || 0);
762
+ const pb = bCore.split(".").map((n) => Number.parseInt(n, 10) || 0);
763
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
764
+ const d = (pa[i] ?? 0) - (pb[i] ?? 0);
765
+ if (d !== 0) return d < 0 ? -1 : 1;
766
+ }
767
+ if (aPre && !bPre) return -1;
768
+ if (!aPre && bPre) return 1;
769
+ return 0;
770
+ }
771
+ function pickBinary(candidates) {
772
+ let best = null;
773
+ for (const c of candidates) {
774
+ if (!best) {
775
+ best = c;
776
+ continue;
777
+ }
778
+ if (c.version && (!best.version || compareVersions(c.version, best.version) > 0)) best = c;
779
+ }
780
+ return best;
781
+ }
782
+ function executable(file) {
783
+ try {
784
+ accessSync(file, fsConstants.X_OK);
785
+ return true;
786
+ } catch {
787
+ return false;
788
+ }
789
+ }
790
+ function exeName(name, platform) {
791
+ return platform === "win32" ? [`${name}.exe`, `${name}.cmd`, name] : [name];
792
+ }
793
+ function allOnPath(name, pathValue, platform = process.platform) {
794
+ const found = [];
795
+ for (const dir of pathValue.split(path.delimiter)) {
796
+ if (!dir) continue;
797
+ for (const file of exeName(name, platform)) {
798
+ const candidate = path.join(dir, file);
799
+ if (executable(candidate)) {
800
+ found.push(candidate);
801
+ break;
802
+ }
803
+ }
804
+ }
805
+ return found;
806
+ }
807
+ function desktopAppCandidates(provider, platform, home, applications) {
808
+ if (platform !== "darwin") return [];
809
+ if (provider === "codex") {
810
+ return applications.map(
811
+ (apps) => path.join(apps, "ChatGPT.app", "Contents", "Resources", "codex-cli", "bin", "codex")
812
+ );
813
+ }
814
+ const root = path.join(home, "Library", "Application Support", "Claude", "claude-code");
815
+ let versions = [];
816
+ try {
817
+ versions = readdirSync(root);
818
+ } catch {
819
+ return [];
820
+ }
821
+ return versions.map((v) => path.join(root, v, "claude.app", "Contents", "MacOS", "claude"));
822
+ }
823
+ function npmGlobalPrefix(env) {
824
+ const fromEnv = env.npm_config_prefix ?? env.NPM_CONFIG_PREFIX;
825
+ if (fromEnv) return Promise.resolve(fromEnv);
826
+ const npm = allOnPath("npm", env.PATH ?? "")[0];
827
+ if (!npm) return Promise.resolve(null);
828
+ return new Promise((resolve) => {
829
+ const child = execFile(npm, ["prefix", "-g"], { timeout: 5e3, env }, (error, stdout) => {
830
+ resolve(error ? null : String(stdout).trim() || null);
831
+ });
832
+ child.stdin?.end();
833
+ });
834
+ }
835
+ function declaredVersion(provider, source, file) {
836
+ if (source !== "desktop-app") return null;
837
+ if (provider === "codex") {
838
+ try {
839
+ const pkg = JSON.parse(readFileSync(path.join(path.dirname(path.dirname(file)), "codex-package.json"), "utf8"));
840
+ return typeof pkg.version === "string" ? parseVersion(pkg.version) : null;
841
+ } catch {
842
+ return null;
843
+ }
844
+ }
845
+ return parseVersion(path.basename(path.resolve(file, "..", "..", "..", "..")));
846
+ }
847
+ function runVersion(file, env) {
848
+ return new Promise((resolve) => {
849
+ const child = execFile(file, ["--version"], { timeout: 8e3, env }, (error, stdout) => {
850
+ resolve(error ? null : parseVersion(String(stdout)));
851
+ });
852
+ child.stdin?.end();
853
+ });
854
+ }
855
+ async function discoverBinaries(provider, options) {
856
+ const platform = options.platform ?? process.platform;
857
+ const home = options.home ?? options.env.HOME ?? os.homedir();
858
+ const listed2 = [
859
+ ...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications", path.join(home, "Applications")]).filter(executable).map((p) => ({ path: p, source: "desktop-app" })),
860
+ ...allOnPath(provider, options.env.PATH ?? "", platform).map((p) => ({ path: p, source: "path" }))
861
+ ];
862
+ const prefix = await npmGlobalPrefix(options.env);
863
+ if (prefix) {
864
+ const dir = platform === "win32" ? prefix : path.join(prefix, "bin");
865
+ for (const file of exeName(provider, platform)) {
866
+ const candidate = path.join(dir, file);
867
+ if (executable(candidate)) {
868
+ listed2.push({ path: candidate, source: "npm-global" });
869
+ break;
870
+ }
871
+ }
872
+ }
873
+ const seen = /* @__PURE__ */ new Set();
874
+ const unique = listed2.filter((c) => {
875
+ let real = c.path;
876
+ try {
877
+ real = realpathSync(c.path);
878
+ } catch {
879
+ }
880
+ if (seen.has(real)) return false;
881
+ seen.add(real);
882
+ return true;
883
+ });
884
+ return Promise.all(
885
+ unique.map(async (c) => ({
886
+ ...c,
887
+ version: declaredVersion(provider, c.source, c.path) ?? await runVersion(c.path, options.env)
888
+ }))
889
+ );
890
+ }
891
+ async function resolveBinary(provider, options) {
892
+ const candidates = options.discover === false ? [] : await discoverBinaries(provider, options);
893
+ const best = pickBinary(candidates);
894
+ const inUse = options.forced ? {
895
+ path: options.forced,
896
+ source: "configured",
897
+ version: candidates.find((c) => c.path === options.forced)?.version ?? (options.discover === false ? null : await runVersion(options.forced, options.env))
898
+ } : best;
899
+ if (!inUse) return null;
900
+ const newer = best && best.path !== inUse.path && best.version && (!inUse.version || compareVersions(best.version, inUse.version) > 0) ? { path: best.path, version: best.version, sourceLabel: sourceLabel(provider, best.source) } : null;
901
+ return {
902
+ path: inUse.path,
903
+ version: inUse.version,
904
+ source: inUse.source,
905
+ sourceLabel: sourceLabel(provider, inUse.source),
906
+ newer
907
+ };
908
+ }
909
+
910
+ // src/engine/clean-env.ts
911
+ import os2 from "os";
912
+ import path2 from "path";
745
913
  var ALLOWED_NAMES = /* @__PURE__ */ new Set([
746
914
  "HOME",
747
915
  "USER",
@@ -763,11 +931,11 @@ var ALLOWED_NAMES = /* @__PURE__ */ new Set([
763
931
  var ALLOWED_PREFIXES = ["LC_"];
764
932
  function wellKnownBinDirs(home) {
765
933
  return [
766
- path.join(home, ".local", "bin"),
767
- path.join(home, ".claude", "local"),
768
- path.join(home, ".npm-global", "bin"),
769
- path.join(home, ".bun", "bin"),
770
- path.join(home, ".volta", "bin"),
934
+ path2.join(home, ".local", "bin"),
935
+ path2.join(home, ".claude", "local"),
936
+ path2.join(home, ".npm-global", "bin"),
937
+ path2.join(home, ".bun", "bin"),
938
+ path2.join(home, ".volta", "bin"),
771
939
  "/opt/homebrew/bin",
772
940
  "/usr/local/bin",
773
941
  "/usr/bin",
@@ -785,14 +953,14 @@ function buildCleanEnv(hostEnv, extra = {}) {
785
953
  if (typeof value !== "string") continue;
786
954
  if (isAllowedEnvName(name)) out[name] = value;
787
955
  }
788
- const home = out.HOME ?? os.homedir();
956
+ const home = out.HOME ?? os2.homedir();
789
957
  out.HOME = home;
790
- const hostPath = (hostEnv.PATH ?? "").split(path.delimiter).filter(Boolean);
958
+ const hostPath = (hostEnv.PATH ?? "").split(path2.delimiter).filter(Boolean);
791
959
  const merged = [];
792
960
  for (const dir of [...hostPath, ...wellKnownBinDirs(home)]) {
793
961
  if (!merged.includes(dir)) merged.push(dir);
794
962
  }
795
- out.PATH = merged.join(path.delimiter);
963
+ out.PATH = merged.join(path2.delimiter);
796
964
  for (const [name, value] of Object.entries(extra)) out[name] = value;
797
965
  return out;
798
966
  }
@@ -800,16 +968,16 @@ function buildCleanEnv(hostEnv, extra = {}) {
800
968
  // src/engine/files.ts
801
969
  import { createReadStream } from "fs";
802
970
  import fs2 from "fs/promises";
803
- import os2 from "os";
804
- import path2 from "path";
971
+ import os3 from "os";
972
+ import path3 from "path";
805
973
 
806
974
  // src/engine/activity.ts
807
- import { execFile } from "child_process";
975
+ import { execFile as execFile2 } from "child_process";
808
976
  var RECENT_WRITE_MS = 2 * 60 * 1e3;
809
977
  var OPEN_TOOL_WINDOW_MS = 15 * 60 * 1e3;
810
978
  function listProcesses() {
811
979
  return new Promise((resolve) => {
812
- execFile("ps", ["-axww", "-o", "pid=,args="], { maxBuffer: 64 * 1024 * 1024, timeout: 5e3 }, (error, stdout) => {
980
+ execFile2("ps", ["-axww", "-o", "pid=,args="], { maxBuffer: 64 * 1024 * 1024, timeout: 5e3 }, (error, stdout) => {
813
981
  if (error) {
814
982
  resolve([]);
815
983
  return;
@@ -868,11 +1036,11 @@ function describeActivity(input) {
868
1036
  }
869
1037
 
870
1038
  // src/engine/files.ts
871
- function defaultProviderHomes(home = os2.homedir()) {
1039
+ function defaultProviderHomes(home = os3.homedir()) {
872
1040
  return {
873
- claudeProjects: path2.join(home, ".claude", "projects"),
874
- codexSessions: path2.join(home, ".codex", "sessions"),
875
- codexIndex: path2.join(home, ".codex", "session_index.jsonl")
1041
+ claudeProjects: path3.join(home, ".claude", "projects"),
1042
+ codexSessions: path3.join(home, ".codex", "sessions"),
1043
+ codexIndex: path3.join(home, ".codex", "session_index.jsonl")
876
1044
  };
877
1045
  }
878
1046
  var UUIDISH = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
@@ -897,10 +1065,10 @@ async function claudeCandidates(root) {
897
1065
  const out = [];
898
1066
  for (const project of await safeReaddir(root)) {
899
1067
  if (!project.isDirectory()) continue;
900
- const dir = path2.join(root, project.name);
1068
+ const dir = path3.join(root, project.name);
901
1069
  const entries = await safeReaddir(dir);
902
1070
  const stats = await Promise.all(
903
- entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude", path2.join(dir, e.name)))
1071
+ entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude", path3.join(dir, e.name)))
904
1072
  );
905
1073
  for (const s of stats) if (s) out.push(s);
906
1074
  }
@@ -910,14 +1078,14 @@ async function codexCandidates(root) {
910
1078
  const out = [];
911
1079
  for (const year of await safeReaddir(root)) {
912
1080
  if (!year.isDirectory()) continue;
913
- for (const month of await safeReaddir(path2.join(root, year.name))) {
1081
+ for (const month of await safeReaddir(path3.join(root, year.name))) {
914
1082
  if (!month.isDirectory()) continue;
915
- for (const day of await safeReaddir(path2.join(root, year.name, month.name))) {
1083
+ for (const day of await safeReaddir(path3.join(root, year.name, month.name))) {
916
1084
  if (!day.isDirectory()) continue;
917
- const dir = path2.join(root, year.name, month.name, day.name);
1085
+ const dir = path3.join(root, year.name, month.name, day.name);
918
1086
  const entries = await safeReaddir(dir);
919
1087
  const stats = await Promise.all(
920
- entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", path2.join(dir, e.name)))
1088
+ entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", path3.join(dir, e.name)))
921
1089
  );
922
1090
  for (const s of stats) if (s) out.push(s);
923
1091
  }
@@ -1178,7 +1346,7 @@ async function describeSessionFile(candidate, codexNames) {
1178
1346
  // src/engine/preferences.ts
1179
1347
  import { randomUUID } from "crypto";
1180
1348
  import fs3 from "fs/promises";
1181
- import path3 from "path";
1349
+ import path4 from "path";
1182
1350
  var PreferenceStore = class {
1183
1351
  constructor(dir) {
1184
1352
  this.dir = dir;
@@ -1187,7 +1355,7 @@ var PreferenceStore = class {
1187
1355
  data = {};
1188
1356
  writing = Promise.resolve();
1189
1357
  get file() {
1190
- return path3.join(this.dir, "preferences.json");
1358
+ return path4.join(this.dir, "preferences.json");
1191
1359
  }
1192
1360
  async load() {
1193
1361
  try {
@@ -1391,7 +1559,7 @@ async function searchLocalChats(query, options = {}) {
1391
1559
  // src/engine/store.ts
1392
1560
  import { randomUUID as randomUUID2 } from "crypto";
1393
1561
  import fs4 from "fs/promises";
1394
- import path4 from "path";
1562
+ import path5 from "path";
1395
1563
  var MAX_RUNS = 2e3;
1396
1564
  var RunIndex = class {
1397
1565
  constructor(dir) {
@@ -1401,7 +1569,7 @@ var RunIndex = class {
1401
1569
  data = { version: 1, sessions: [], runs: [] };
1402
1570
  writing = Promise.resolve();
1403
1571
  get file() {
1404
- return path4.join(this.dir, "index.json");
1572
+ return path5.join(this.dir, "index.json");
1405
1573
  }
1406
1574
  async load() {
1407
1575
  await fs4.mkdir(this.dir, { recursive: true });
@@ -1460,7 +1628,7 @@ var RunIndex = class {
1460
1628
  /** A stable Paperclip agent/company identity for this install, so the
1461
1629
  * driver's scratch state is reused rather than growing per run. */
1462
1630
  async identity() {
1463
- const file = path4.join(this.dir, "identity.json");
1631
+ const file = path5.join(this.dir, "identity.json");
1464
1632
  try {
1465
1633
  const parsed = JSON.parse(await fs4.readFile(file, "utf8"));
1466
1634
  if (typeof parsed.companyId === "string" && typeof parsed.agentId === "string") {
@@ -1476,8 +1644,8 @@ var RunIndex = class {
1476
1644
 
1477
1645
  // src/engine/user-defaults.ts
1478
1646
  import fs5 from "fs/promises";
1479
- import os3 from "os";
1480
- import path5 from "path";
1647
+ import os4 from "os";
1648
+ import path6 from "path";
1481
1649
  async function readJson(file) {
1482
1650
  try {
1483
1651
  const parsed = JSON.parse(await fs5.readFile(file, "utf8"));
@@ -1489,10 +1657,10 @@ async function readJson(file) {
1489
1657
  function str3(value) {
1490
1658
  return typeof value === "string" && value.trim() ? value.trim() : null;
1491
1659
  }
1492
- async function claudeDefaults(cwd, home = os3.homedir()) {
1660
+ async function claudeDefaults(cwd, home = os4.homedir()) {
1493
1661
  const files = [
1494
- ...cwd ? [path5.join(cwd, ".claude", "settings.local.json"), path5.join(cwd, ".claude", "settings.json")] : [],
1495
- path5.join(home, ".claude", "settings.json")
1662
+ ...cwd ? [path6.join(cwd, ".claude", "settings.local.json"), path6.join(cwd, ".claude", "settings.json")] : [],
1663
+ path6.join(home, ".claude", "settings.json")
1496
1664
  ];
1497
1665
  let model = null;
1498
1666
  let effort = null;
@@ -1505,10 +1673,10 @@ async function claudeDefaults(cwd, home = os3.homedir()) {
1505
1673
  }
1506
1674
  return { model, effort };
1507
1675
  }
1508
- async function codexDefaults(home = os3.homedir()) {
1676
+ async function codexDefaults(home = os4.homedir()) {
1509
1677
  let text;
1510
1678
  try {
1511
- text = await fs5.readFile(path5.join(home, ".codex", "config.toml"), "utf8");
1679
+ text = await fs5.readFile(path6.join(home, ".codex", "config.toml"), "utf8");
1512
1680
  } catch {
1513
1681
  return { model: null, effort: null };
1514
1682
  }
@@ -1532,45 +1700,33 @@ var EFFORT_LEVELS = {
1532
1700
  // src/engine/index.ts
1533
1701
  var RAW_TRANSCRIPT_MAX_BYTES = 200 * 1048576;
1534
1702
  function moduleDir() {
1535
- return path6.dirname(fileURLToPath(import.meta.url));
1703
+ return path7.dirname(fileURLToPath(import.meta.url));
1536
1704
  }
1537
1705
  function resolveWorkerPath() {
1538
1706
  const dir = moduleDir();
1539
1707
  for (const candidate of [
1540
- path6.join(dir, "worker.js"),
1541
- path6.join(dir, "worker.ts"),
1542
- path6.join(dir, "..", "engine", "worker.js"),
1543
- path6.join(dir, "..", "engine", "worker.ts")
1708
+ path7.join(dir, "worker.js"),
1709
+ path7.join(dir, "worker.ts"),
1710
+ path7.join(dir, "..", "engine", "worker.js"),
1711
+ path7.join(dir, "..", "engine", "worker.ts")
1544
1712
  ]) {
1545
1713
  if (existsSync(candidate)) return candidate;
1546
1714
  }
1547
1715
  throw new Error(`[coding-sessions] The driver worker is missing next to ${dir}.`);
1548
1716
  }
1549
1717
  async function installShim(dataDir) {
1550
- const source = path6.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
1718
+ const source = path7.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
1551
1719
  if (!existsSync(source)) throw new Error(`[coding-sessions] The CLI shim is missing at ${source}.`);
1552
1720
  const body = await fs6.readFile(source, "utf8");
1553
- const target = path6.join(dataDir, "bin", "cli-shim.sh");
1721
+ const target = path7.join(dataDir, "bin", "cli-shim.sh");
1554
1722
  const current = await fs6.readFile(target, "utf8").catch(() => null);
1555
1723
  if (current !== body) {
1556
- await fs6.mkdir(path6.dirname(target), { recursive: true });
1724
+ await fs6.mkdir(path7.dirname(target), { recursive: true });
1557
1725
  await fs6.writeFile(target, body, { mode: 493 });
1558
1726
  }
1559
1727
  await fs6.chmod(target, 493);
1560
1728
  return target;
1561
1729
  }
1562
- function whichOnPath(name, pathValue) {
1563
- for (const dir of pathValue.split(path6.delimiter)) {
1564
- if (!dir) continue;
1565
- const candidate = path6.join(dir, name);
1566
- try {
1567
- accessSync(candidate, fsConstants.X_OK);
1568
- return candidate;
1569
- } catch {
1570
- }
1571
- }
1572
- return null;
1573
- }
1574
1730
  function nowIso() {
1575
1731
  return (/* @__PURE__ */ new Date()).toISOString();
1576
1732
  }
@@ -1609,12 +1765,14 @@ function describeProviderStatus(provider, probe, lastProblem = null, quota = [])
1609
1765
  function describeAuthStatus(provider, probe) {
1610
1766
  const label = PROVIDER_LABEL[provider];
1611
1767
  const installed = probe?.installed === true;
1612
- const version = typeof probe?.version === "string" ? probe.version : null;
1768
+ const binary = probe?.binary ?? null;
1769
+ const version = binary?.version ?? (typeof probe?.version === "string" ? probe.version : null);
1613
1770
  const base = {
1614
1771
  provider,
1615
1772
  label,
1616
1773
  installed,
1617
1774
  version,
1775
+ binary,
1618
1776
  loggedIn: false,
1619
1777
  billing: "unknown",
1620
1778
  plan: null,
@@ -1666,8 +1824,8 @@ function describeAuthStatus(provider, probe) {
1666
1824
  };
1667
1825
  }
1668
1826
  async function createCodingSessionsEngine(options = {}) {
1669
- const dataDir = options.dataDir ?? path6.join(os4.homedir(), ".matrx", "coding-sessions");
1670
- const stateDir = path6.join(dataDir, "paperclip-state");
1827
+ const dataDir = options.dataDir ?? path7.join(os5.homedir(), ".matrx", "coding-sessions");
1828
+ const stateDir = path7.join(dataDir, "paperclip-state");
1671
1829
  const homes = options.homes ?? defaultProviderHomes();
1672
1830
  const index = new RunIndex(dataDir);
1673
1831
  await index.load();
@@ -1681,7 +1839,7 @@ async function createCodingSessionsEngine(options = {}) {
1681
1839
  onError: (error) => console.warn("[coding-sessions] raw transcript backup failed:", error)
1682
1840
  }) : null;
1683
1841
  const discoveredSeen = /* @__PURE__ */ new Map();
1684
- const healthFile = path6.join(dataDir, "provider-health.json");
1842
+ const healthFile = path7.join(dataDir, "provider-health.json");
1685
1843
  let health = {};
1686
1844
  try {
1687
1845
  health = JSON.parse(await fs6.readFile(healthFile, "utf8"));
@@ -1716,7 +1874,24 @@ async function createCodingSessionsEngine(options = {}) {
1716
1874
  // Marks the forked process as a driver worker (see worker.ts).
1717
1875
  MATRX_CODING_SESSIONS_WORKER: "1"
1718
1876
  });
1719
- const commandFor = (provider) => options.commands?.[provider] ?? whichOnPath(provider, workerEnv.PATH ?? "");
1877
+ const binaryHome = options.hostEnv?.HOME ?? os5.homedir();
1878
+ const binaryCache = /* @__PURE__ */ new Map();
1879
+ function binaryFor(provider, fresh = false) {
1880
+ const cached = binaryCache.get(provider);
1881
+ if (cached && !fresh && Date.now() - cached.at < 5 * 6e4) return cached.value;
1882
+ const forced = options.commands?.[provider];
1883
+ const value = resolveBinary(provider, {
1884
+ env: workerEnv,
1885
+ home: binaryHome,
1886
+ forced,
1887
+ // A forced command needs no discovery to run; the status check still
1888
+ // looks, so it can say when a newer copy exists.
1889
+ discover: !forced || fresh
1890
+ }).catch(() => forced ? { path: forced, version: null, source: "configured", sourceLabel: "set by host", newer: null } : null);
1891
+ if (!forced || !fresh) binaryCache.set(provider, { at: Date.now(), value });
1892
+ return value;
1893
+ }
1894
+ const commandFor = async (provider) => (await binaryFor(provider))?.path ?? null;
1720
1895
  const workerPath = resolveWorkerPath();
1721
1896
  const shimPath = await installShim(dataDir);
1722
1897
  const listeners = /* @__PURE__ */ new Set();
@@ -1787,7 +1962,7 @@ async function createCodingSessionsEngine(options = {}) {
1787
1962
  }
1788
1963
  async function startRun(session, prompt) {
1789
1964
  const provider = session.provider;
1790
- const realCommand = commandFor(provider);
1965
+ const realCommand = await commandFor(provider);
1791
1966
  const runId = randomUUID3();
1792
1967
  const model = session.model ?? (provider === "claude" ? (await userDefaults(provider, session.cwd)).model ?? null : null);
1793
1968
  const run = {
@@ -2019,7 +2194,7 @@ async function createCodingSessionsEngine(options = {}) {
2019
2194
  }, 3e3);
2020
2195
  }
2021
2196
  async function pruneDriverScratch() {
2022
- const runsDir = path6.join(
2197
+ const runsDir = path7.join(
2023
2198
  stateDir,
2024
2199
  "instances",
2025
2200
  "default",
@@ -2032,10 +2207,11 @@ async function createCodingSessionsEngine(options = {}) {
2032
2207
  );
2033
2208
  const entries = await fs6.readdir(runsDir).catch(() => []);
2034
2209
  await Promise.all(
2035
- entries.filter((name) => !live.has(name)).map((name) => fs6.rm(path6.join(runsDir, name), { recursive: true, force: true }).catch(() => void 0))
2210
+ entries.filter((name) => !live.has(name)).map((name) => fs6.rm(path7.join(runsDir, name), { recursive: true, force: true }).catch(() => void 0))
2036
2211
  );
2037
2212
  }
2038
2213
  async function probe() {
2214
+ const [claudeBinary, codexBinary] = await Promise.all([binaryFor("claude", true), binaryFor("codex", true)]);
2039
2215
  return new Promise((resolve) => {
2040
2216
  const child = forkWorker();
2041
2217
  let answered = false;
@@ -2051,8 +2227,8 @@ async function createCodingSessionsEngine(options = {}) {
2051
2227
  answered = true;
2052
2228
  clearTimeout(timer);
2053
2229
  resolve({
2054
- claude: message.claude ?? {},
2055
- codex: message.codex ?? {}
2230
+ claude: { ...message.claude ?? {}, binary: claudeBinary },
2231
+ codex: { ...message.codex ?? {}, binary: codexBinary }
2056
2232
  });
2057
2233
  }
2058
2234
  });
@@ -2064,7 +2240,7 @@ async function createCodingSessionsEngine(options = {}) {
2064
2240
  });
2065
2241
  child.send({
2066
2242
  kind: "probe",
2067
- commands: { claude: commandFor("claude"), codex: commandFor("codex") },
2243
+ commands: { claude: claudeBinary?.path ?? null, codex: codexBinary?.path ?? null },
2068
2244
  quota: options.quota !== false
2069
2245
  });
2070
2246
  });
@@ -2090,7 +2266,7 @@ async function createCodingSessionsEngine(options = {}) {
2090
2266
  function resolveFolder(raw) {
2091
2267
  const trimmed = raw.trim();
2092
2268
  if (!trimmed) return null;
2093
- return path6.resolve(trimmed.replace(/^~(?=$|\/)/, os4.homedir()));
2269
+ return path7.resolve(trimmed.replace(/^~(?=$|\/)/, os5.homedir()));
2094
2270
  }
2095
2271
  async function checkFolder(raw) {
2096
2272
  const target = resolveFolder(raw);
@@ -2099,7 +2275,7 @@ async function createCodingSessionsEngine(options = {}) {
2099
2275
  return { path: target, exists: stat !== null, isDirectory: stat?.isDirectory() === true };
2100
2276
  }
2101
2277
  async function assertFolder(cwd) {
2102
- const resolved = path6.resolve(cwd.replace(/^~(?=$|\/)/, os4.homedir()));
2278
+ const resolved = path7.resolve(cwd.replace(/^~(?=$|\/)/, os5.homedir()));
2103
2279
  const stat = await fs6.stat(resolved).catch(() => null);
2104
2280
  if (!stat?.isDirectory()) {
2105
2281
  throw new Error(`The folder ${resolved} doesn't exist on this computer.`);
@@ -2253,7 +2429,7 @@ async function createCodingSessionsEngine(options = {}) {
2253
2429
  };
2254
2430
  }
2255
2431
  const content = await fs6.readFile(file, "utf8");
2256
- return { available: true, fileName: path6.basename(file), bytes: stat.size, content };
2432
+ return { available: true, fileName: path7.basename(file), bytes: stat.size, content };
2257
2433
  },
2258
2434
  async start(input) {
2259
2435
  const prompt = input.prompt.trim();
@@ -2374,7 +2550,7 @@ function openFolderIn(folder, target) {
2374
2550
  args = ["-a", app.app, folder];
2375
2551
  }
2376
2552
  return new Promise((resolve, reject) => {
2377
- execFile2(command, args, { timeout: 1e4 }, (error) => {
2553
+ execFile3(command, args, { timeout: 1e4 }, (error) => {
2378
2554
  if (!error) return resolve();
2379
2555
  const label = target === "finder" ? "the folder" : OPEN_APP[target]?.label ?? target;
2380
2556
  reject(
@@ -2451,14 +2627,14 @@ var MIME = {
2451
2627
  ".map": "application/json"
2452
2628
  };
2453
2629
  function moduleDir2() {
2454
- return path7.dirname(fileURLToPath2(import.meta.url));
2630
+ return path8.dirname(fileURLToPath2(import.meta.url));
2455
2631
  }
2456
2632
  function defaultUiDir() {
2457
2633
  for (const candidate of [
2458
- path7.resolve(moduleDir2(), "..", "ui"),
2459
- path7.resolve(moduleDir2(), "..", "..", "dist", "ui")
2634
+ path8.resolve(moduleDir2(), "..", "ui"),
2635
+ path8.resolve(moduleDir2(), "..", "..", "dist", "ui")
2460
2636
  ]) {
2461
- if (existsSync2(path7.join(candidate, "index.html"))) return candidate;
2637
+ if (existsSync2(path8.join(candidate, "index.html"))) return candidate;
2462
2638
  }
2463
2639
  return null;
2464
2640
  }
@@ -2481,19 +2657,19 @@ async function serveStatic(uiDir, req, res) {
2481
2657
  return;
2482
2658
  }
2483
2659
  const requested = decodeURIComponent(url.pathname);
2484
- let file = path7.resolve(uiDir, `.${requested}`);
2660
+ let file = path8.resolve(uiDir, `.${requested}`);
2485
2661
  if (!file.startsWith(uiDir)) {
2486
2662
  res.writeHead(403).end();
2487
2663
  return;
2488
2664
  }
2489
2665
  const stat = await fs7.stat(file).catch(() => null);
2490
- if (!stat || stat.isDirectory()) file = path7.join(uiDir, "index.html");
2666
+ if (!stat || stat.isDirectory()) file = path8.join(uiDir, "index.html");
2491
2667
  const body = await fs7.readFile(file).catch(() => null);
2492
2668
  if (!body) {
2493
2669
  res.writeHead(404).end();
2494
2670
  return;
2495
2671
  }
2496
- const ext = path7.extname(file);
2672
+ const ext = path8.extname(file);
2497
2673
  res.writeHead(200, {
2498
2674
  "content-type": MIME[ext] ?? "application/octet-stream",
2499
2675
  "cache-control": ext === ".html" ? "no-store" : "public, max-age=31536000, immutable",