@ai-matrx/coding-sessions 0.1.81 → 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 +1 @@
1
- export { c as CodingSessionsEngine, d as CodingSessionsEngineOptions, E as EFFORT_LEVELS, P as PreferenceStore, e as ProviderHomes, R as RAW_TRANSCRIPT_MAX_BYTES, f as RawBackupPolicy, g as RawBackupSink, h as RunStatus, i as buildCleanEnv, j as claudeDefaults, k as codexDefaults, l as createAiMatrxRawBackupSink, m as createCodingSessionsEngine, n as createRawBackupScheduler, o as defaultProviderHomes, p as describeActivity, q as describeProviderStatus, r as discoverLocalSessions, s as endsWithOpenToolCall, t as findDrivingProcess, u as findSessionFile, v as isAllowedEnvName, w as listProcesses, x as readSessionFile, y as searchLocalChats, z as sessionActivity, A as snippetAround } from '../index-RYOb-YZT.cjs';
1
+ export { c as CodingSessionsEngine, d as CodingSessionsEngineOptions, E as EFFORT_LEVELS, P as PreferenceStore, e as ProviderHomes, R as RAW_TRANSCRIPT_MAX_BYTES, f as RawBackupPolicy, g as RawBackupSink, h as RunStatus, i as buildCleanEnv, j as claudeDefaults, k as codexDefaults, l as createAiMatrxRawBackupSink, m as createCodingSessionsEngine, n as createRawBackupScheduler, o as defaultProviderHomes, p as describeActivity, q as describeProviderStatus, r as discoverLocalSessions, s as endsWithOpenToolCall, t as findDrivingProcess, u as findSessionFile, v as isAllowedEnvName, w as listProcesses, x as readSessionFile, y as searchLocalChats, z as sessionActivity, A as snippetAround } from '../index-DQ0teQvw.cjs';
@@ -1 +1 @@
1
- export { c as CodingSessionsEngine, d as CodingSessionsEngineOptions, E as EFFORT_LEVELS, P as PreferenceStore, e as ProviderHomes, R as RAW_TRANSCRIPT_MAX_BYTES, f as RawBackupPolicy, g as RawBackupSink, h as RunStatus, i as buildCleanEnv, j as claudeDefaults, k as codexDefaults, l as createAiMatrxRawBackupSink, m as createCodingSessionsEngine, n as createRawBackupScheduler, o as defaultProviderHomes, p as describeActivity, q as describeProviderStatus, r as discoverLocalSessions, s as endsWithOpenToolCall, t as findDrivingProcess, u as findSessionFile, v as isAllowedEnvName, w as listProcesses, x as readSessionFile, y as searchLocalChats, z as sessionActivity, A as snippetAround } from '../index-RYOb-YZT.js';
1
+ export { c as CodingSessionsEngine, d as CodingSessionsEngineOptions, E as EFFORT_LEVELS, P as PreferenceStore, e as ProviderHomes, R as RAW_TRANSCRIPT_MAX_BYTES, f as RawBackupPolicy, g as RawBackupSink, h as RunStatus, i as buildCleanEnv, j as claudeDefaults, k as codexDefaults, l as createAiMatrxRawBackupSink, m as createCodingSessionsEngine, n as createRawBackupScheduler, o as defaultProviderHomes, p as describeActivity, q as describeProviderStatus, r as discoverLocalSessions, s as endsWithOpenToolCall, t as findDrivingProcess, u as findSessionFile, v as isAllowedEnvName, w as listProcesses, x as readSessionFile, y as searchLocalChats, z as sessionActivity, A as snippetAround } from '../index-DQ0teQvw.js';
@@ -1,7 +1,7 @@
1
1
  // src/engine/index.ts
2
- import { execFile as execFile2, fork } from "child_process";
2
+ import { execFile as execFile3, fork } from "child_process";
3
3
  import { randomUUID as randomUUID3 } from "crypto";
4
- import { accessSync, constants as fsConstants, existsSync } from "fs";
4
+ import { existsSync } from "fs";
5
5
  import { formatFileSize } from "@ai-matrx/kit/format";
6
6
 
7
7
  // src/engine/raw-backup.ts
@@ -100,8 +100,8 @@ function createAiMatrxRawBackupSink(options) {
100
100
 
101
101
  // src/engine/index.ts
102
102
  import fs6 from "fs/promises";
103
- import os4 from "os";
104
- import path6 from "path";
103
+ import os5 from "os";
104
+ import path7 from "path";
105
105
  import { fileURLToPath } from "url";
106
106
 
107
107
  // src/normalize/claude.ts
@@ -767,9 +767,177 @@ function describeProblem(provider, problem) {
767
767
  };
768
768
  }
769
769
 
770
- // src/engine/clean-env.ts
770
+ // src/engine/binaries.ts
771
+ import { execFile } from "child_process";
772
+ import { accessSync, constants as fsConstants, readdirSync, readFileSync, realpathSync } from "fs";
771
773
  import os from "os";
772
774
  import path from "path";
775
+ var SOURCE_LABEL = {
776
+ codex: { "desktop-app": "from ChatGPT app", path: "from PATH", "npm-global": "from npm", configured: "set by host" },
777
+ claude: { "desktop-app": "from Claude app", path: "from PATH", "npm-global": "from npm", configured: "set by host" }
778
+ };
779
+ function sourceLabel(provider, source) {
780
+ return SOURCE_LABEL[provider][source];
781
+ }
782
+ function parseVersion(text) {
783
+ const match = /(\d+)\.(\d+)\.(\d+)(?:[-.][0-9A-Za-z.]+)?/.exec(text ?? "");
784
+ return match ? match[0] : null;
785
+ }
786
+ function compareVersions(a, b) {
787
+ const [aCore = "", aPre] = a.split("-", 2);
788
+ const [bCore = "", bPre] = b.split("-", 2);
789
+ const pa = aCore.split(".").map((n) => Number.parseInt(n, 10) || 0);
790
+ const pb = bCore.split(".").map((n) => Number.parseInt(n, 10) || 0);
791
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
792
+ const d = (pa[i] ?? 0) - (pb[i] ?? 0);
793
+ if (d !== 0) return d < 0 ? -1 : 1;
794
+ }
795
+ if (aPre && !bPre) return -1;
796
+ if (!aPre && bPre) return 1;
797
+ return 0;
798
+ }
799
+ function pickBinary(candidates) {
800
+ let best = null;
801
+ for (const c of candidates) {
802
+ if (!best) {
803
+ best = c;
804
+ continue;
805
+ }
806
+ if (c.version && (!best.version || compareVersions(c.version, best.version) > 0)) best = c;
807
+ }
808
+ return best;
809
+ }
810
+ function executable(file) {
811
+ try {
812
+ accessSync(file, fsConstants.X_OK);
813
+ return true;
814
+ } catch {
815
+ return false;
816
+ }
817
+ }
818
+ function exeName(name, platform) {
819
+ return platform === "win32" ? [`${name}.exe`, `${name}.cmd`, name] : [name];
820
+ }
821
+ function allOnPath(name, pathValue, platform = process.platform) {
822
+ const found = [];
823
+ for (const dir of pathValue.split(path.delimiter)) {
824
+ if (!dir) continue;
825
+ for (const file of exeName(name, platform)) {
826
+ const candidate = path.join(dir, file);
827
+ if (executable(candidate)) {
828
+ found.push(candidate);
829
+ break;
830
+ }
831
+ }
832
+ }
833
+ return found;
834
+ }
835
+ function desktopAppCandidates(provider, platform, home, applications) {
836
+ if (platform !== "darwin") return [];
837
+ if (provider === "codex") {
838
+ return applications.map(
839
+ (apps) => path.join(apps, "ChatGPT.app", "Contents", "Resources", "codex-cli", "bin", "codex")
840
+ );
841
+ }
842
+ const root = path.join(home, "Library", "Application Support", "Claude", "claude-code");
843
+ let versions = [];
844
+ try {
845
+ versions = readdirSync(root);
846
+ } catch {
847
+ return [];
848
+ }
849
+ return versions.map((v) => path.join(root, v, "claude.app", "Contents", "MacOS", "claude"));
850
+ }
851
+ function npmGlobalPrefix(env) {
852
+ const fromEnv = env.npm_config_prefix ?? env.NPM_CONFIG_PREFIX;
853
+ if (fromEnv) return Promise.resolve(fromEnv);
854
+ const npm = allOnPath("npm", env.PATH ?? "")[0];
855
+ if (!npm) return Promise.resolve(null);
856
+ return new Promise((resolve) => {
857
+ const child = execFile(npm, ["prefix", "-g"], { timeout: 5e3, env }, (error, stdout) => {
858
+ resolve(error ? null : String(stdout).trim() || null);
859
+ });
860
+ child.stdin?.end();
861
+ });
862
+ }
863
+ function declaredVersion(provider, source, file) {
864
+ if (source !== "desktop-app") return null;
865
+ if (provider === "codex") {
866
+ try {
867
+ const pkg = JSON.parse(readFileSync(path.join(path.dirname(path.dirname(file)), "codex-package.json"), "utf8"));
868
+ return typeof pkg.version === "string" ? parseVersion(pkg.version) : null;
869
+ } catch {
870
+ return null;
871
+ }
872
+ }
873
+ return parseVersion(path.basename(path.resolve(file, "..", "..", "..", "..")));
874
+ }
875
+ function runVersion(file, env) {
876
+ return new Promise((resolve) => {
877
+ const child = execFile(file, ["--version"], { timeout: 8e3, env }, (error, stdout) => {
878
+ resolve(error ? null : parseVersion(String(stdout)));
879
+ });
880
+ child.stdin?.end();
881
+ });
882
+ }
883
+ async function discoverBinaries(provider, options) {
884
+ const platform = options.platform ?? process.platform;
885
+ const home = options.home ?? options.env.HOME ?? os.homedir();
886
+ const listed2 = [
887
+ ...desktopAppCandidates(provider, platform, home, options.applications ?? ["/Applications", path.join(home, "Applications")]).filter(executable).map((p) => ({ path: p, source: "desktop-app" })),
888
+ ...allOnPath(provider, options.env.PATH ?? "", platform).map((p) => ({ path: p, source: "path" }))
889
+ ];
890
+ const prefix = await npmGlobalPrefix(options.env);
891
+ if (prefix) {
892
+ const dir = platform === "win32" ? prefix : path.join(prefix, "bin");
893
+ for (const file of exeName(provider, platform)) {
894
+ const candidate = path.join(dir, file);
895
+ if (executable(candidate)) {
896
+ listed2.push({ path: candidate, source: "npm-global" });
897
+ break;
898
+ }
899
+ }
900
+ }
901
+ const seen = /* @__PURE__ */ new Set();
902
+ const unique = listed2.filter((c) => {
903
+ let real = c.path;
904
+ try {
905
+ real = realpathSync(c.path);
906
+ } catch {
907
+ }
908
+ if (seen.has(real)) return false;
909
+ seen.add(real);
910
+ return true;
911
+ });
912
+ return Promise.all(
913
+ unique.map(async (c) => ({
914
+ ...c,
915
+ version: declaredVersion(provider, c.source, c.path) ?? await runVersion(c.path, options.env)
916
+ }))
917
+ );
918
+ }
919
+ async function resolveBinary(provider, options) {
920
+ const candidates = options.discover === false ? [] : await discoverBinaries(provider, options);
921
+ const best = pickBinary(candidates);
922
+ const inUse = options.forced ? {
923
+ path: options.forced,
924
+ source: "configured",
925
+ version: candidates.find((c) => c.path === options.forced)?.version ?? (options.discover === false ? null : await runVersion(options.forced, options.env))
926
+ } : best;
927
+ if (!inUse) return null;
928
+ 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;
929
+ return {
930
+ path: inUse.path,
931
+ version: inUse.version,
932
+ source: inUse.source,
933
+ sourceLabel: sourceLabel(provider, inUse.source),
934
+ newer
935
+ };
936
+ }
937
+
938
+ // src/engine/clean-env.ts
939
+ import os2 from "os";
940
+ import path2 from "path";
773
941
  var ALLOWED_NAMES = /* @__PURE__ */ new Set([
774
942
  "HOME",
775
943
  "USER",
@@ -791,11 +959,11 @@ var ALLOWED_NAMES = /* @__PURE__ */ new Set([
791
959
  var ALLOWED_PREFIXES = ["LC_"];
792
960
  function wellKnownBinDirs(home) {
793
961
  return [
794
- path.join(home, ".local", "bin"),
795
- path.join(home, ".claude", "local"),
796
- path.join(home, ".npm-global", "bin"),
797
- path.join(home, ".bun", "bin"),
798
- path.join(home, ".volta", "bin"),
962
+ path2.join(home, ".local", "bin"),
963
+ path2.join(home, ".claude", "local"),
964
+ path2.join(home, ".npm-global", "bin"),
965
+ path2.join(home, ".bun", "bin"),
966
+ path2.join(home, ".volta", "bin"),
799
967
  "/opt/homebrew/bin",
800
968
  "/usr/local/bin",
801
969
  "/usr/bin",
@@ -813,14 +981,14 @@ function buildCleanEnv(hostEnv, extra = {}) {
813
981
  if (typeof value !== "string") continue;
814
982
  if (isAllowedEnvName(name)) out[name] = value;
815
983
  }
816
- const home = out.HOME ?? os.homedir();
984
+ const home = out.HOME ?? os2.homedir();
817
985
  out.HOME = home;
818
- const hostPath = (hostEnv.PATH ?? "").split(path.delimiter).filter(Boolean);
986
+ const hostPath = (hostEnv.PATH ?? "").split(path2.delimiter).filter(Boolean);
819
987
  const merged = [];
820
988
  for (const dir of [...hostPath, ...wellKnownBinDirs(home)]) {
821
989
  if (!merged.includes(dir)) merged.push(dir);
822
990
  }
823
- out.PATH = merged.join(path.delimiter);
991
+ out.PATH = merged.join(path2.delimiter);
824
992
  for (const [name, value] of Object.entries(extra)) out[name] = value;
825
993
  return out;
826
994
  }
@@ -828,16 +996,16 @@ function buildCleanEnv(hostEnv, extra = {}) {
828
996
  // src/engine/files.ts
829
997
  import { createReadStream } from "fs";
830
998
  import fs2 from "fs/promises";
831
- import os2 from "os";
832
- import path2 from "path";
999
+ import os3 from "os";
1000
+ import path3 from "path";
833
1001
 
834
1002
  // src/engine/activity.ts
835
- import { execFile } from "child_process";
1003
+ import { execFile as execFile2 } from "child_process";
836
1004
  var RECENT_WRITE_MS = 2 * 60 * 1e3;
837
1005
  var OPEN_TOOL_WINDOW_MS = 15 * 60 * 1e3;
838
1006
  function listProcesses() {
839
1007
  return new Promise((resolve) => {
840
- execFile("ps", ["-axww", "-o", "pid=,args="], { maxBuffer: 64 * 1024 * 1024, timeout: 5e3 }, (error, stdout) => {
1008
+ execFile2("ps", ["-axww", "-o", "pid=,args="], { maxBuffer: 64 * 1024 * 1024, timeout: 5e3 }, (error, stdout) => {
841
1009
  if (error) {
842
1010
  resolve([]);
843
1011
  return;
@@ -896,11 +1064,11 @@ function describeActivity(input) {
896
1064
  }
897
1065
 
898
1066
  // src/engine/files.ts
899
- function defaultProviderHomes(home = os2.homedir()) {
1067
+ function defaultProviderHomes(home = os3.homedir()) {
900
1068
  return {
901
- claudeProjects: path2.join(home, ".claude", "projects"),
902
- codexSessions: path2.join(home, ".codex", "sessions"),
903
- codexIndex: path2.join(home, ".codex", "session_index.jsonl")
1069
+ claudeProjects: path3.join(home, ".claude", "projects"),
1070
+ codexSessions: path3.join(home, ".codex", "sessions"),
1071
+ codexIndex: path3.join(home, ".codex", "session_index.jsonl")
904
1072
  };
905
1073
  }
906
1074
  var UUIDISH = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
@@ -925,10 +1093,10 @@ async function claudeCandidates(root) {
925
1093
  const out = [];
926
1094
  for (const project of await safeReaddir(root)) {
927
1095
  if (!project.isDirectory()) continue;
928
- const dir = path2.join(root, project.name);
1096
+ const dir = path3.join(root, project.name);
929
1097
  const entries = await safeReaddir(dir);
930
1098
  const stats = await Promise.all(
931
- entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude", path2.join(dir, e.name)))
1099
+ entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => statCandidate("claude", path3.join(dir, e.name)))
932
1100
  );
933
1101
  for (const s of stats) if (s) out.push(s);
934
1102
  }
@@ -938,14 +1106,14 @@ async function codexCandidates(root) {
938
1106
  const out = [];
939
1107
  for (const year of await safeReaddir(root)) {
940
1108
  if (!year.isDirectory()) continue;
941
- for (const month of await safeReaddir(path2.join(root, year.name))) {
1109
+ for (const month of await safeReaddir(path3.join(root, year.name))) {
942
1110
  if (!month.isDirectory()) continue;
943
- for (const day of await safeReaddir(path2.join(root, year.name, month.name))) {
1111
+ for (const day of await safeReaddir(path3.join(root, year.name, month.name))) {
944
1112
  if (!day.isDirectory()) continue;
945
- const dir = path2.join(root, year.name, month.name, day.name);
1113
+ const dir = path3.join(root, year.name, month.name, day.name);
946
1114
  const entries = await safeReaddir(dir);
947
1115
  const stats = await Promise.all(
948
- entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", path2.join(dir, e.name)))
1116
+ entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", path3.join(dir, e.name)))
949
1117
  );
950
1118
  for (const s of stats) if (s) out.push(s);
951
1119
  }
@@ -1206,7 +1374,7 @@ async function describeSessionFile(candidate, codexNames) {
1206
1374
  // src/engine/preferences.ts
1207
1375
  import { randomUUID } from "crypto";
1208
1376
  import fs3 from "fs/promises";
1209
- import path3 from "path";
1377
+ import path4 from "path";
1210
1378
  var PreferenceStore = class {
1211
1379
  constructor(dir) {
1212
1380
  this.dir = dir;
@@ -1215,7 +1383,7 @@ var PreferenceStore = class {
1215
1383
  data = {};
1216
1384
  writing = Promise.resolve();
1217
1385
  get file() {
1218
- return path3.join(this.dir, "preferences.json");
1386
+ return path4.join(this.dir, "preferences.json");
1219
1387
  }
1220
1388
  async load() {
1221
1389
  try {
@@ -1419,7 +1587,7 @@ async function searchLocalChats(query, options = {}) {
1419
1587
  // src/engine/store.ts
1420
1588
  import { randomUUID as randomUUID2 } from "crypto";
1421
1589
  import fs4 from "fs/promises";
1422
- import path4 from "path";
1590
+ import path5 from "path";
1423
1591
  var MAX_RUNS = 2e3;
1424
1592
  var RunIndex = class {
1425
1593
  constructor(dir) {
@@ -1429,7 +1597,7 @@ var RunIndex = class {
1429
1597
  data = { version: 1, sessions: [], runs: [] };
1430
1598
  writing = Promise.resolve();
1431
1599
  get file() {
1432
- return path4.join(this.dir, "index.json");
1600
+ return path5.join(this.dir, "index.json");
1433
1601
  }
1434
1602
  async load() {
1435
1603
  await fs4.mkdir(this.dir, { recursive: true });
@@ -1488,7 +1656,7 @@ var RunIndex = class {
1488
1656
  /** A stable Paperclip agent/company identity for this install, so the
1489
1657
  * driver's scratch state is reused rather than growing per run. */
1490
1658
  async identity() {
1491
- const file = path4.join(this.dir, "identity.json");
1659
+ const file = path5.join(this.dir, "identity.json");
1492
1660
  try {
1493
1661
  const parsed = JSON.parse(await fs4.readFile(file, "utf8"));
1494
1662
  if (typeof parsed.companyId === "string" && typeof parsed.agentId === "string") {
@@ -1504,8 +1672,8 @@ var RunIndex = class {
1504
1672
 
1505
1673
  // src/engine/user-defaults.ts
1506
1674
  import fs5 from "fs/promises";
1507
- import os3 from "os";
1508
- import path5 from "path";
1675
+ import os4 from "os";
1676
+ import path6 from "path";
1509
1677
  async function readJson(file) {
1510
1678
  try {
1511
1679
  const parsed = JSON.parse(await fs5.readFile(file, "utf8"));
@@ -1517,10 +1685,10 @@ async function readJson(file) {
1517
1685
  function str3(value) {
1518
1686
  return typeof value === "string" && value.trim() ? value.trim() : null;
1519
1687
  }
1520
- async function claudeDefaults(cwd, home = os3.homedir()) {
1688
+ async function claudeDefaults(cwd, home = os4.homedir()) {
1521
1689
  const files = [
1522
- ...cwd ? [path5.join(cwd, ".claude", "settings.local.json"), path5.join(cwd, ".claude", "settings.json")] : [],
1523
- path5.join(home, ".claude", "settings.json")
1690
+ ...cwd ? [path6.join(cwd, ".claude", "settings.local.json"), path6.join(cwd, ".claude", "settings.json")] : [],
1691
+ path6.join(home, ".claude", "settings.json")
1524
1692
  ];
1525
1693
  let model = null;
1526
1694
  let effort = null;
@@ -1533,10 +1701,10 @@ async function claudeDefaults(cwd, home = os3.homedir()) {
1533
1701
  }
1534
1702
  return { model, effort };
1535
1703
  }
1536
- async function codexDefaults(home = os3.homedir()) {
1704
+ async function codexDefaults(home = os4.homedir()) {
1537
1705
  let text;
1538
1706
  try {
1539
- text = await fs5.readFile(path5.join(home, ".codex", "config.toml"), "utf8");
1707
+ text = await fs5.readFile(path6.join(home, ".codex", "config.toml"), "utf8");
1540
1708
  } catch {
1541
1709
  return { model: null, effort: null };
1542
1710
  }
@@ -1560,45 +1728,33 @@ var EFFORT_LEVELS = {
1560
1728
  // src/engine/index.ts
1561
1729
  var RAW_TRANSCRIPT_MAX_BYTES = 200 * 1048576;
1562
1730
  function moduleDir() {
1563
- return path6.dirname(fileURLToPath(import.meta.url));
1731
+ return path7.dirname(fileURLToPath(import.meta.url));
1564
1732
  }
1565
1733
  function resolveWorkerPath() {
1566
1734
  const dir = moduleDir();
1567
1735
  for (const candidate of [
1568
- path6.join(dir, "worker.js"),
1569
- path6.join(dir, "worker.ts"),
1570
- path6.join(dir, "..", "engine", "worker.js"),
1571
- path6.join(dir, "..", "engine", "worker.ts")
1736
+ path7.join(dir, "worker.js"),
1737
+ path7.join(dir, "worker.ts"),
1738
+ path7.join(dir, "..", "engine", "worker.js"),
1739
+ path7.join(dir, "..", "engine", "worker.ts")
1572
1740
  ]) {
1573
1741
  if (existsSync(candidate)) return candidate;
1574
1742
  }
1575
1743
  throw new Error(`[coding-sessions] The driver worker is missing next to ${dir}.`);
1576
1744
  }
1577
1745
  async function installShim(dataDir) {
1578
- const source = path6.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
1746
+ const source = path7.resolve(moduleDir(), "..", "..", "bin", "cli-shim.sh");
1579
1747
  if (!existsSync(source)) throw new Error(`[coding-sessions] The CLI shim is missing at ${source}.`);
1580
1748
  const body = await fs6.readFile(source, "utf8");
1581
- const target = path6.join(dataDir, "bin", "cli-shim.sh");
1749
+ const target = path7.join(dataDir, "bin", "cli-shim.sh");
1582
1750
  const current = await fs6.readFile(target, "utf8").catch(() => null);
1583
1751
  if (current !== body) {
1584
- await fs6.mkdir(path6.dirname(target), { recursive: true });
1752
+ await fs6.mkdir(path7.dirname(target), { recursive: true });
1585
1753
  await fs6.writeFile(target, body, { mode: 493 });
1586
1754
  }
1587
1755
  await fs6.chmod(target, 493);
1588
1756
  return target;
1589
1757
  }
1590
- function whichOnPath(name, pathValue) {
1591
- for (const dir of pathValue.split(path6.delimiter)) {
1592
- if (!dir) continue;
1593
- const candidate = path6.join(dir, name);
1594
- try {
1595
- accessSync(candidate, fsConstants.X_OK);
1596
- return candidate;
1597
- } catch {
1598
- }
1599
- }
1600
- return null;
1601
- }
1602
1758
  function nowIso() {
1603
1759
  return (/* @__PURE__ */ new Date()).toISOString();
1604
1760
  }
@@ -1637,12 +1793,14 @@ function describeProviderStatus(provider, probe, lastProblem = null, quota = [])
1637
1793
  function describeAuthStatus(provider, probe) {
1638
1794
  const label = PROVIDER_LABEL[provider];
1639
1795
  const installed = probe?.installed === true;
1640
- const version = typeof probe?.version === "string" ? probe.version : null;
1796
+ const binary = probe?.binary ?? null;
1797
+ const version = binary?.version ?? (typeof probe?.version === "string" ? probe.version : null);
1641
1798
  const base = {
1642
1799
  provider,
1643
1800
  label,
1644
1801
  installed,
1645
1802
  version,
1803
+ binary,
1646
1804
  loggedIn: false,
1647
1805
  billing: "unknown",
1648
1806
  plan: null,
@@ -1694,8 +1852,8 @@ function describeAuthStatus(provider, probe) {
1694
1852
  };
1695
1853
  }
1696
1854
  async function createCodingSessionsEngine(options = {}) {
1697
- const dataDir = options.dataDir ?? path6.join(os4.homedir(), ".matrx", "coding-sessions");
1698
- const stateDir = path6.join(dataDir, "paperclip-state");
1855
+ const dataDir = options.dataDir ?? path7.join(os5.homedir(), ".matrx", "coding-sessions");
1856
+ const stateDir = path7.join(dataDir, "paperclip-state");
1699
1857
  const homes = options.homes ?? defaultProviderHomes();
1700
1858
  const index = new RunIndex(dataDir);
1701
1859
  await index.load();
@@ -1709,7 +1867,7 @@ async function createCodingSessionsEngine(options = {}) {
1709
1867
  onError: (error) => console.warn("[coding-sessions] raw transcript backup failed:", error)
1710
1868
  }) : null;
1711
1869
  const discoveredSeen = /* @__PURE__ */ new Map();
1712
- const healthFile = path6.join(dataDir, "provider-health.json");
1870
+ const healthFile = path7.join(dataDir, "provider-health.json");
1713
1871
  let health = {};
1714
1872
  try {
1715
1873
  health = JSON.parse(await fs6.readFile(healthFile, "utf8"));
@@ -1744,7 +1902,24 @@ async function createCodingSessionsEngine(options = {}) {
1744
1902
  // Marks the forked process as a driver worker (see worker.ts).
1745
1903
  MATRX_CODING_SESSIONS_WORKER: "1"
1746
1904
  });
1747
- const commandFor = (provider) => options.commands?.[provider] ?? whichOnPath(provider, workerEnv.PATH ?? "");
1905
+ const binaryHome = options.hostEnv?.HOME ?? os5.homedir();
1906
+ const binaryCache = /* @__PURE__ */ new Map();
1907
+ function binaryFor(provider, fresh = false) {
1908
+ const cached = binaryCache.get(provider);
1909
+ if (cached && !fresh && Date.now() - cached.at < 5 * 6e4) return cached.value;
1910
+ const forced = options.commands?.[provider];
1911
+ const value = resolveBinary(provider, {
1912
+ env: workerEnv,
1913
+ home: binaryHome,
1914
+ forced,
1915
+ // A forced command needs no discovery to run; the status check still
1916
+ // looks, so it can say when a newer copy exists.
1917
+ discover: !forced || fresh
1918
+ }).catch(() => forced ? { path: forced, version: null, source: "configured", sourceLabel: "set by host", newer: null } : null);
1919
+ if (!forced || !fresh) binaryCache.set(provider, { at: Date.now(), value });
1920
+ return value;
1921
+ }
1922
+ const commandFor = async (provider) => (await binaryFor(provider))?.path ?? null;
1748
1923
  const workerPath = resolveWorkerPath();
1749
1924
  const shimPath = await installShim(dataDir);
1750
1925
  const listeners = /* @__PURE__ */ new Set();
@@ -1815,7 +1990,7 @@ async function createCodingSessionsEngine(options = {}) {
1815
1990
  }
1816
1991
  async function startRun(session, prompt) {
1817
1992
  const provider = session.provider;
1818
- const realCommand = commandFor(provider);
1993
+ const realCommand = await commandFor(provider);
1819
1994
  const runId = randomUUID3();
1820
1995
  const model = session.model ?? (provider === "claude" ? (await userDefaults(provider, session.cwd)).model ?? null : null);
1821
1996
  const run = {
@@ -2047,7 +2222,7 @@ async function createCodingSessionsEngine(options = {}) {
2047
2222
  }, 3e3);
2048
2223
  }
2049
2224
  async function pruneDriverScratch() {
2050
- const runsDir = path6.join(
2225
+ const runsDir = path7.join(
2051
2226
  stateDir,
2052
2227
  "instances",
2053
2228
  "default",
@@ -2060,10 +2235,11 @@ async function createCodingSessionsEngine(options = {}) {
2060
2235
  );
2061
2236
  const entries = await fs6.readdir(runsDir).catch(() => []);
2062
2237
  await Promise.all(
2063
- entries.filter((name) => !live.has(name)).map((name) => fs6.rm(path6.join(runsDir, name), { recursive: true, force: true }).catch(() => void 0))
2238
+ entries.filter((name) => !live.has(name)).map((name) => fs6.rm(path7.join(runsDir, name), { recursive: true, force: true }).catch(() => void 0))
2064
2239
  );
2065
2240
  }
2066
2241
  async function probe() {
2242
+ const [claudeBinary, codexBinary] = await Promise.all([binaryFor("claude", true), binaryFor("codex", true)]);
2067
2243
  return new Promise((resolve) => {
2068
2244
  const child = forkWorker();
2069
2245
  let answered = false;
@@ -2079,8 +2255,8 @@ async function createCodingSessionsEngine(options = {}) {
2079
2255
  answered = true;
2080
2256
  clearTimeout(timer);
2081
2257
  resolve({
2082
- claude: message.claude ?? {},
2083
- codex: message.codex ?? {}
2258
+ claude: { ...message.claude ?? {}, binary: claudeBinary },
2259
+ codex: { ...message.codex ?? {}, binary: codexBinary }
2084
2260
  });
2085
2261
  }
2086
2262
  });
@@ -2092,7 +2268,7 @@ async function createCodingSessionsEngine(options = {}) {
2092
2268
  });
2093
2269
  child.send({
2094
2270
  kind: "probe",
2095
- commands: { claude: commandFor("claude"), codex: commandFor("codex") },
2271
+ commands: { claude: claudeBinary?.path ?? null, codex: codexBinary?.path ?? null },
2096
2272
  quota: options.quota !== false
2097
2273
  });
2098
2274
  });
@@ -2118,7 +2294,7 @@ async function createCodingSessionsEngine(options = {}) {
2118
2294
  function resolveFolder(raw) {
2119
2295
  const trimmed = raw.trim();
2120
2296
  if (!trimmed) return null;
2121
- return path6.resolve(trimmed.replace(/^~(?=$|\/)/, os4.homedir()));
2297
+ return path7.resolve(trimmed.replace(/^~(?=$|\/)/, os5.homedir()));
2122
2298
  }
2123
2299
  async function checkFolder(raw) {
2124
2300
  const target = resolveFolder(raw);
@@ -2127,7 +2303,7 @@ async function createCodingSessionsEngine(options = {}) {
2127
2303
  return { path: target, exists: stat !== null, isDirectory: stat?.isDirectory() === true };
2128
2304
  }
2129
2305
  async function assertFolder(cwd) {
2130
- const resolved = path6.resolve(cwd.replace(/^~(?=$|\/)/, os4.homedir()));
2306
+ const resolved = path7.resolve(cwd.replace(/^~(?=$|\/)/, os5.homedir()));
2131
2307
  const stat = await fs6.stat(resolved).catch(() => null);
2132
2308
  if (!stat?.isDirectory()) {
2133
2309
  throw new Error(`The folder ${resolved} doesn't exist on this computer.`);
@@ -2281,7 +2457,7 @@ async function createCodingSessionsEngine(options = {}) {
2281
2457
  };
2282
2458
  }
2283
2459
  const content = await fs6.readFile(file, "utf8");
2284
- return { available: true, fileName: path6.basename(file), bytes: stat.size, content };
2460
+ return { available: true, fileName: path7.basename(file), bytes: stat.size, content };
2285
2461
  },
2286
2462
  async start(input) {
2287
2463
  const prompt = input.prompt.trim();
@@ -2402,7 +2578,7 @@ function openFolderIn(folder, target) {
2402
2578
  args = ["-a", app.app, folder];
2403
2579
  }
2404
2580
  return new Promise((resolve, reject) => {
2405
- execFile2(command, args, { timeout: 1e4 }, (error) => {
2581
+ execFile3(command, args, { timeout: 1e4 }, (error) => {
2406
2582
  if (!error) return resolve();
2407
2583
  const label = target === "finder" ? "the folder" : OPEN_APP[target]?.label ?? target;
2408
2584
  reject(