@bman654/clodex 2.11.7 → 2.12.1

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/cli.js CHANGED
@@ -382,7 +382,7 @@ import { join } from "path";
382
382
  // package.json
383
383
  var package_default = {
384
384
  name: "@bman654/clodex",
385
- version: "2.11.7",
385
+ version: "2.12.1",
386
386
  publishConfig: {
387
387
  access: "public"
388
388
  },
@@ -3884,16 +3884,17 @@ function applyClodexPatches(source, config) {
3884
3884
  // src/patcher.ts
3885
3885
  import { createHash as createHash9 } from "crypto";
3886
3886
  import {
3887
+ chmodSync as chmodSync4,
3887
3888
  copyFileSync,
3888
3889
  existsSync as existsSync5,
3889
3890
  mkdtempSync,
3890
3891
  mkdirSync as mkdirSync5,
3891
3892
  readFileSync as readFileSync10,
3892
- renameSync as renameSync2,
3893
- rmSync,
3893
+ renameSync as renameSync3,
3894
+ rmSync as rmSync2,
3894
3895
  statSync as statSync8,
3895
3896
  unlinkSync as unlinkSync3,
3896
- writeFileSync as writeFileSync5,
3897
+ writeFileSync as writeFileSync6,
3897
3898
  openSync as openSync7,
3898
3899
  closeSync as closeSync7,
3899
3900
  realpathSync as realpathSync2
@@ -7772,7 +7773,7 @@ function writeAll(fd, bytes, position, path) {
7772
7773
 
7773
7774
  // src/patch-backup.ts
7774
7775
  import { createHash as createHash5 } from "crypto";
7775
- import { existsSync as existsSync4, readFileSync as readFileSync9, readdirSync, statSync as statSync7 } from "fs";
7776
+ import { existsSync as existsSync4, readFileSync as readFileSync9, readdirSync, renameSync as renameSync2, rmSync, statSync as statSync7, writeFileSync as writeFileSync5 } from "fs";
7776
7777
  import { homedir as homedir2 } from "os";
7777
7778
  import { join as join7 } from "path";
7778
7779
  var BACKUP_SHA_PREFIX_LENGTH = 16;
@@ -7790,9 +7791,60 @@ function backupVersionTag(version) {
7790
7791
  function contentAddressedBackupPath(version, sha256, dir = backupDir()) {
7791
7792
  return join7(dir, `claude-${backupVersionTag(version)}-${sha256.slice(0, BACKUP_SHA_PREFIX_LENGTH)}.orig`);
7792
7793
  }
7794
+ function legacyBackupPath(version, dir = backupDir()) {
7795
+ return join7(dir, `claude-${backupVersionTag(version)}.orig`);
7796
+ }
7793
7797
  function tweakccMirrorBackupPath(dir = backupDir()) {
7794
7798
  return join7(dir, "native-binary.backup");
7795
7799
  }
7800
+ function installProvenancePath(backupPath, binaryPath) {
7801
+ const tag = createHash5("sha256").update(binaryPath).digest("hex").slice(0, BACKUP_SHA_PREFIX_LENGTH);
7802
+ return `${backupPath}.for-${tag}.json`;
7803
+ }
7804
+ function installProvenancePattern(name) {
7805
+ return new RegExp(`^${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.for-[0-9a-f]{${BACKUP_SHA_PREFIX_LENGTH}}\\.json$`);
7806
+ }
7807
+ function readInstallProvenance(path) {
7808
+ let raw;
7809
+ try {
7810
+ raw = readFileSync9(path, "utf8");
7811
+ } catch {
7812
+ return null;
7813
+ }
7814
+ let parsed;
7815
+ try {
7816
+ parsed = JSON.parse(raw);
7817
+ } catch {
7818
+ return "damaged";
7819
+ }
7820
+ if (typeof parsed !== "object" || parsed === null) return "damaged";
7821
+ const { install, assumed } = parsed;
7822
+ if (typeof install !== "string" || install === "") return "damaged";
7823
+ if (typeof assumed !== "boolean") return "damaged";
7824
+ return { install, assumed };
7825
+ }
7826
+ function recordBackupProvenance(backupPath, binaryPath, opts) {
7827
+ const target = installProvenancePath(backupPath, binaryPath);
7828
+ const existing = readInstallProvenance(target);
7829
+ if (existing && existing !== "damaged" && existing.install === binaryPath) {
7830
+ if (existing.assumed === opts.assumed) return existing.assumed ? "assumed" : "established";
7831
+ if (!existing.assumed) return "established";
7832
+ }
7833
+ const record = { install: binaryPath, assumed: opts.assumed };
7834
+ const temp = `${target}.tmp-${process.pid}-${Date.now().toString(36)}`;
7835
+ try {
7836
+ writeFileSync5(temp, `${JSON.stringify(record, null, 2)}
7837
+ `);
7838
+ renameSync2(temp, target);
7839
+ return opts.assumed ? "assumed" : "established";
7840
+ } catch (err) {
7841
+ try {
7842
+ rmSync(temp, { force: true });
7843
+ } catch {
7844
+ }
7845
+ throw err;
7846
+ }
7847
+ }
7796
7848
  function scanPristineBackups(version, dir = backupDir()) {
7797
7849
  const tag = backupVersionTag(version);
7798
7850
  const pattern = new RegExp(`^claude-${tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:-([0-9a-f]{${BACKUP_SHA_PREFIX_LENGTH}}))?\\.orig$`);
@@ -7802,6 +7854,27 @@ function scanPristineBackups(version, dir = backupDir()) {
7802
7854
  } catch {
7803
7855
  return { valid: [], corrupt: [] };
7804
7856
  }
7857
+ const provenanceOf = (name) => {
7858
+ const recordPattern = installProvenancePattern(name);
7859
+ const installs = [];
7860
+ const assumedInstalls = [];
7861
+ const damagedProvenance = [];
7862
+ for (const entry of entries) {
7863
+ if (!recordPattern.test(entry)) continue;
7864
+ const path = join7(dir, entry);
7865
+ const record = readInstallProvenance(path);
7866
+ if (record === null || record === "damaged" || installProvenancePath(join7(dir, name), record.install) !== path) {
7867
+ damagedProvenance.push(path);
7868
+ continue;
7869
+ }
7870
+ (record.assumed ? assumedInstalls : installs).push(record.install);
7871
+ }
7872
+ return {
7873
+ installs: installs.sort(),
7874
+ assumedInstalls: assumedInstalls.sort(),
7875
+ damagedProvenance: damagedProvenance.sort()
7876
+ };
7877
+ };
7805
7878
  const valid = [];
7806
7879
  const corrupt = [];
7807
7880
  for (const entry of entries.sort()) {
@@ -7822,9 +7895,9 @@ function scanPristineBackups(version, dir = backupDir()) {
7822
7895
  corrupt.push(path);
7823
7896
  continue;
7824
7897
  }
7825
- valid.push({ path, kind: "content-addressed", sha256 });
7898
+ valid.push({ path, kind: "content-addressed", sha256, ...provenanceOf(entry) });
7826
7899
  } else {
7827
- valid.push({ path, kind: "legacy", sha256 });
7900
+ valid.push({ path, kind: "legacy", sha256, ...provenanceOf(entry) });
7828
7901
  }
7829
7902
  }
7830
7903
  return { valid, corrupt };
@@ -7846,7 +7919,7 @@ function looksLikeLegacyClodexPatch(source) {
7846
7919
  }
7847
7920
  function noBackupMessage(facts) {
7848
7921
  const corrupt = facts.corruptBackups?.length ? ` (${facts.corruptBackups.length} backup file(s) for this version failed integrity checks and were ignored)` : "";
7849
- return `claude ${facts.version} is already patched and no trustworthy pristine backup for that version exists in ${backupDir()}${corrupt}. Reinstall Claude Code to get a pristine binary, then run \`clodex patch\`.`;
7922
+ return `clodex holds no pristine backup of claude ${facts.version} it can attribute to ${facts.binaryPath}: none was found in ${backupDir()}${corrupt}. If that binary is patched, reinstall Claude Code to get a pristine one and run \`clodex patch\` again. If it was never patched, there is nothing to restore.`;
7850
7923
  }
7851
7924
  function identifiesABackup(manifest) {
7852
7925
  return manifest.pristineSha256 !== void 0 || manifest.backupPath !== void 0;
@@ -7860,8 +7933,26 @@ function recordedBackupGoneMessage(facts, manifest) {
7860
7933
  function otherInstallMessage(facts, otherBinaryPath) {
7861
7934
  return `The patch manifest records a different Claude Code install (${otherBinaryPath}), so nothing establishes that a pristine backup tagged claude ${facts.version} in ${backupDir()} belongs to ${facts.binaryPath}. Two installs of one Claude Code version are different files, so restoring by version tag alone would overwrite this install with another one's bytes. Set TWEAKCC_CC_INSTALLATION_PATH=${otherBinaryPath} to restore that install instead, or reinstall Claude Code to make this one pristine.`;
7862
7935
  }
7936
+ function otherInstallProvenanceMessage(facts, recorded) {
7937
+ const owners = [...new Set(recorded.flatMap((backup) => backup.installs))];
7938
+ return `The pristine backup(s) of claude ${facts.version} in ${backupDir()} are recorded as the pristine content of ${owners.join(", ")}, not of ${facts.binaryPath}. Two installs of one Claude Code version are different files, so restoring one of those would overwrite this install with bytes that were never its own. Set TWEAKCC_CC_INSTALLATION_PATH=${owners[0]} to restore that install instead, or reinstall Claude Code to make this one pristine.`;
7939
+ }
7940
+ function damagedProvenanceMessage(facts, damaged) {
7941
+ return `A pristine backup of claude ${facts.version} in ${backupDir()} carries a provenance record that clodex cannot read (${damaged.join(", ")}). Something recorded which install those bytes belong to and that record is damaged, so clodex will not fall back to matching on the claude version in the file name \u2014 on a machine with more than one install that would hand ${facts.binaryPath} bytes that were never its own. Delete the unreadable record to accept that fallback, or reinstall Claude Code to make this install pristine.`;
7942
+ }
7943
+ function contradictoryProvenanceMessage(facts, mine) {
7944
+ return `More than one pristine backup of claude ${facts.version} is recorded as the pristine content of ${facts.binaryPath} (${mine.map((backup) => backup.path).join(", ")}), and they do not hold the same bytes, so clodex cannot tell which one that install holds now \u2014 being snapshotted twice, at two different sets of unpatched bytes, reaches this state without either record being wrong. Delete whichever is stale to decide it: ${mine.map((backup) => installProvenancePath(backup.path, facts.binaryPath)).join(", ")}. Or reinstall the Claude Code at ${facts.binaryPath} and delete both.`;
7945
+ }
7946
+ function manifestContradictsRecordMessage(facts, manifestChoice, recorded) {
7947
+ return `The patch manifest names ${manifestChoice} as the pristine content of ${facts.binaryPath}, but ${recorded.map((backup) => backup.path).join(", ")} is recorded as that install's pristine content and holds different bytes. Nothing establishes which of them the install at that path is now \u2014 an executable replaced in place with a different build of the same claude version reaches exactly this state \u2014 so clodex will not pick one. Delete whichever of these records is stale to decide it: ${recorded.map((backup) => installProvenancePath(backup.path, facts.binaryPath)).join(", ")}. Or reinstall the Claude Code at ${facts.binaryPath} and delete both: a pristine install needs no backup, and \`clodex patch\` will record its own.`;
7948
+ }
7949
+ function assumedElsewhereMessage(facts, backups) {
7950
+ const others = [...new Set(backups.flatMap((backup) => backup.assumedInstalls))];
7951
+ return `The pristine backup(s) of claude ${facts.version} in ${backupDir()} have already been restored onto ${others.join(", ")} \u2014 not onto ${facts.binaryPath}. That was itself matched on the claude version in a file name, so it is not proof of ownership, but it is a reason not to hand the same bytes to a second install: two installs of one version are different files. Set TWEAKCC_CC_INSTALLATION_PATH=${others[0]} to restore that install, or reinstall Claude Code to make this one pristine.`;
7952
+ }
7863
7953
  function selectRestoreSource(facts) {
7864
7954
  const notes = [];
7955
+ let assumedForThisInstall = false;
7865
7956
  const recorded = facts.manifest;
7866
7957
  const speaksForThisVersion = !recorded?.claudeVersion || recorded.claudeVersion === facts.version;
7867
7958
  const manifest = recorded && recorded.binaryPath === facts.binaryPath && speaksForThisVersion ? recorded : null;
@@ -7873,10 +7964,46 @@ function selectRestoreSource(facts) {
7873
7964
  if (!chosen && manifest && identifiesABackup(manifest)) {
7874
7965
  return { action: "error", message: recordedBackupGoneMessage(facts, manifest) };
7875
7966
  }
7967
+ const manifestDescribesLiveBytes = !!manifest?.patchedSha256 && manifest.patchedSha256 === facts.liveSha256;
7968
+ if (chosen && !manifestDescribesLiveBytes) {
7969
+ const manifestChoice = chosen;
7970
+ const contradicting = facts.backups.filter(
7971
+ (backup) => backup.installs.includes(facts.binaryPath) && backup.sha256 !== manifestChoice.sha256
7972
+ );
7973
+ if (contradicting.length) {
7974
+ return {
7975
+ action: "error",
7976
+ message: manifestContradictsRecordMessage(facts, manifestChoice.path, contradicting)
7977
+ };
7978
+ }
7979
+ }
7980
+ if (!chosen) {
7981
+ const mine = facts.backups.filter((backup) => backup.installs.includes(facts.binaryPath));
7982
+ if (mine.length) {
7983
+ if (new Set(mine.map((backup) => backup.sha256)).size > 1) {
7984
+ return { action: "error", message: contradictoryProvenanceMessage(facts, mine) };
7985
+ }
7986
+ chosen = mine.find((backup) => backup.kind === "content-addressed") ?? mine[0];
7987
+ }
7988
+ }
7876
7989
  if (!chosen) {
7877
7990
  if (other) {
7878
7991
  return facts.backups.length ? { action: "error", message: otherInstallMessage(facts, other.binaryPath) } : { action: "error", message: noBackupMessage(facts) };
7879
7992
  }
7993
+ const recordedElsewhere = facts.backups.filter((backup) => backup.installs.length > 0);
7994
+ if (recordedElsewhere.length) {
7995
+ return { action: "error", message: otherInstallProvenanceMessage(facts, recordedElsewhere) };
7996
+ }
7997
+ const damaged = facts.backups.flatMap((backup) => backup.damagedProvenance);
7998
+ if (damaged.length) {
7999
+ return { action: "error", message: damagedProvenanceMessage(facts, damaged) };
8000
+ }
8001
+ const assumedElsewhere = facts.backups.filter(
8002
+ (backup) => backup.assumedInstalls.some((install) => install !== facts.binaryPath)
8003
+ );
8004
+ if (assumedElsewhere.length) {
8005
+ return { action: "error", message: assumedElsewhereMessage(facts, assumedElsewhere) };
8006
+ }
7880
8007
  const distinct = [...new Set(facts.backups.map((backup) => backup.sha256))];
7881
8008
  if (distinct.length > 1) {
7882
8009
  return {
@@ -7886,12 +8013,14 @@ function selectRestoreSource(facts) {
7886
8013
  }
7887
8014
  chosen = facts.backups.find((backup) => backup.kind === "content-addressed") ?? facts.backups[0];
7888
8015
  if (chosen) {
8016
+ assumedForThisInstall = true;
7889
8017
  notes.push(
7890
- `No patch manifest records ${facts.binaryPath}, so ${chosen.path} is being used as its pristine content on the strength of its claude ${facts.version} version tag alone. On a machine with more than one Claude Code install those bytes may belong to the other one \u2014 a clodex that had recorded this install would have refused rather than guess.`
8018
+ `Neither a patch manifest nor a provenance record ties ${chosen.path} to ${facts.binaryPath}, so it is being used as that install's pristine content on the strength of its claude ${facts.version} version tag alone \u2014 it predates the records clodex now writes. On a machine with more than one Claude Code install those bytes may belong to the other one, and clodex will not record this guess as provenance.`
7891
8019
  );
7892
8020
  }
7893
8021
  }
7894
8022
  if (!chosen) return { action: "error", message: noBackupMessage(facts) };
8023
+ if (manifest?.pristineProvenance === "assumed") assumedForThisInstall = true;
7895
8024
  return {
7896
8025
  action: "restore",
7897
8026
  backupPath: chosen.path,
@@ -7900,6 +8029,7 @@ function selectRestoreSource(facts) {
7900
8029
  // another version's binary, stored under a mislabeled name by an older
7901
8030
  // clodex. Executing it is the only evidence available; require it.
7902
8031
  probeVersion: chosen.kind === "legacy",
8032
+ assumedForThisInstall,
7903
8033
  notes
7904
8034
  };
7905
8035
  }
@@ -8847,7 +8977,7 @@ function instructionChangeSummary(previous, current) {
8847
8977
  const firstDiffLine = previous.slice(0, prefix).split("\n").length;
8848
8978
  return `instructions changed: previous_chars=${previous.length} current_chars=${current.length} common_prefix_chars=${prefix} common_suffix_chars=${suffix} first_diff_line=${firstDiffLine}`;
8849
8979
  }
8850
- function responsesWebSocketPartitionKey(wsUrl, payload, options = {}, authorizationFingerprint = "") {
8980
+ function responsesWebSocketPartitionKey(wsUrl, payload, options = {}, authorizationFingerprint = "", claudeAgentId = "") {
8851
8981
  const promptCacheKey = payload.prompt_cache_key;
8852
8982
  const model = payload.model;
8853
8983
  if (typeof promptCacheKey !== "string" || !promptCacheKey || typeof model !== "string" || !model) return void 0;
@@ -8860,7 +8990,8 @@ function responsesWebSocketPartitionKey(wsUrl, payload, options = {}, authorizat
8860
8990
  model,
8861
8991
  effort,
8862
8992
  promptCacheKey,
8863
- authorizationFingerprint
8993
+ authorizationFingerprint,
8994
+ claudeAgentId
8864
8995
  ].join("");
8865
8996
  return createHash6("sha256").update(material).digest("hex");
8866
8997
  }
@@ -10019,16 +10150,18 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
10019
10150
  }
10020
10151
  if (hasResponsesLiteHeader(headers)) payload = applyResponsesLiteShape(payload);
10021
10152
  const authorizationFingerprint = authorizationHeaderFingerprint(headers);
10153
+ const diagnosticCorrelation = diagnosticContext.getStore();
10154
+ const claudeAgentId = diagnosticCorrelation?.claudeAgentId ?? "";
10022
10155
  const partitionKey = responsesWebSocketPartitionKey(
10023
10156
  wsUrl,
10024
10157
  payload,
10025
10158
  options,
10026
- authorizationFingerprint
10159
+ authorizationFingerprint,
10160
+ claudeAgentId
10027
10161
  );
10028
10162
  const promptFingerprint = responsesWebSocketPromptFingerprint(payload);
10029
10163
  const promptFieldHashes = responsesWebSocketPromptFieldHashes(payload);
10030
10164
  const instructionsSnapshot = instructionsFromPayload(payload);
10031
- const diagnosticCorrelation = diagnosticContext.getStore();
10032
10165
  let now = resolvedOptions.now();
10033
10166
  const evictions = cleanupExpiredConnections(now);
10034
10167
  let canonicalClientItems;
@@ -10236,7 +10369,9 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
10236
10369
  accountIdHash: options.accountId ? createHash6("sha256").update(options.accountId).digest("hex").slice(0, 16) : "",
10237
10370
  model: typeof payload.model === "string" ? payload.model : void 0,
10238
10371
  effort: typeof payload.reasoning?.effort === "string" ? String(payload.reasoning.effort).trim().toLowerCase() : "",
10239
- promptCacheKey: typeof payload.prompt_cache_key === "string" ? payload.prompt_cache_key : void 0
10372
+ promptCacheKey: typeof payload.prompt_cache_key === "string" ? payload.prompt_cache_key : void 0,
10373
+ claudeAgentId: claudeAgentId || void 0,
10374
+ claudeParentAgentId: diagnosticCorrelation?.claudeParentAgentId
10240
10375
  },
10241
10376
  promptFingerprint,
10242
10377
  promptFieldHashes,
@@ -12134,6 +12269,18 @@ function extractClaudeSessionId(body, headerFallback) {
12134
12269
  }
12135
12270
  return validClaudeSessionId(headerFallback);
12136
12271
  }
12272
+ var CLAUDE_AGENT_ID_RE = /^[A-Za-z0-9._-]{1,128}$/;
12273
+ function extractClaudeAgentIds(headers) {
12274
+ const read = (name) => {
12275
+ const raw = headers[name];
12276
+ const value = (Array.isArray(raw) ? raw[0] : raw)?.trim();
12277
+ return value && CLAUDE_AGENT_ID_RE.test(value) ? value : void 0;
12278
+ };
12279
+ return {
12280
+ claudeAgentId: read("x-claude-code-agent-id"),
12281
+ claudeParentAgentId: read("x-claude-code-parent-agent-id")
12282
+ };
12283
+ }
12137
12284
  function claudeSessionPromptCacheKey(sessionId) {
12138
12285
  return "relay-session-" + createHash8("sha256").update(sessionId).digest("hex").slice(0, 32);
12139
12286
  }
@@ -13345,6 +13492,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
13345
13492
  const openAiOAuth = isOpenAiOAuthRoute(route);
13346
13493
  const claudeSessionIdHeader = Array.isArray(req.headers["x-claude-code-session-id"]) ? req.headers["x-claude-code-session-id"][0] : req.headers["x-claude-code-session-id"];
13347
13494
  const claudeSessionId = extractClaudeSessionId(anthropicBody, claudeSessionIdHeader);
13495
+ const claudeAgentIds = extractClaudeAgentIds(req.headers);
13348
13496
  const translationLifecycle = createTranslationLifecycle(
13349
13497
  inferenceLogPath,
13350
13498
  relayRequestId,
@@ -13418,7 +13566,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
13418
13566
  keepAlive.unref();
13419
13567
  try {
13420
13568
  await withResponsesWebSocketDiagnosticContext(
13421
- { requestId: relayRequestId, claudeSessionId },
13569
+ { requestId: relayRequestId, claudeSessionId, ...claudeAgentIds },
13422
13570
  () => streamAnthropicResponse(
13423
13571
  model,
13424
13572
  params,
@@ -13449,7 +13597,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
13449
13597
  res.end();
13450
13598
  } else {
13451
13599
  const anthropicResponse = await withResponsesWebSocketDiagnosticContext(
13452
- { requestId: relayRequestId, claudeSessionId },
13600
+ { requestId: relayRequestId, claudeSessionId, ...claudeAgentIds },
13453
13601
  () => generateAnthropicResponse(
13454
13602
  model,
13455
13603
  params,
@@ -13782,7 +13930,7 @@ function readPatchManifest(path = getPatchManifestPath()) {
13782
13930
  }
13783
13931
  function writePatchManifest(manifest, path = getPatchManifestPath()) {
13784
13932
  mkdirSync5(getAppHome(), { recursive: true, mode: 448 });
13785
- writeFileSync5(path, `${JSON.stringify(manifest, null, 2)}
13933
+ writeFileSync6(path, `${JSON.stringify(manifest, null, 2)}
13786
13934
  `, { encoding: "utf8", mode: 384 });
13787
13935
  }
13788
13936
  function buildPatchModelConfig(favorites, aliases, modelMetaFor) {
@@ -13940,7 +14088,7 @@ function tryAcquirePatchLock(lockPath = getPatchLockPath(), opts = {}) {
13940
14088
  try {
13941
14089
  const fd = openSync7(lockPath, "wx");
13942
14090
  const content = { pid: process.pid, startedAt: now };
13943
- writeFileSync5(fd, JSON.stringify(content));
14091
+ writeFileSync6(fd, JSON.stringify(content));
13944
14092
  closeSync7(fd);
13945
14093
  return () => {
13946
14094
  try {
@@ -13965,10 +14113,19 @@ function tryAcquirePatchLock(lockPath = getPatchLockPath(), opts = {}) {
13965
14113
  }
13966
14114
  return null;
13967
14115
  }
14116
+ var ignoredLaunchOverride = null;
14117
+ function takeIgnoredLaunchOverride() {
14118
+ const ignored = ignoredLaunchOverride;
14119
+ ignoredLaunchOverride = null;
14120
+ return ignored;
14121
+ }
13968
14122
  function resolveClaudeBinaryForPatch() {
13969
- const envOverride = process.env["TWEAKCC_CC_INSTALLATION_PATH"];
14123
+ const envOverride = process.env["TWEAKCC_CC_INSTALLATION_PATH"]?.trim() || null;
13970
14124
  const nativeSymlink = join8(homedir3(), ".local", "bin", "claude");
13971
- const source = envOverride?.trim() || (existsSync5(nativeSymlink) ? nativeSymlink : null) || findClaudeBinary();
14125
+ if (envOverride && !existsSync5(envOverride)) {
14126
+ return { ok: false, reason: "patch-target-missing", declaredPath: envOverride };
14127
+ }
14128
+ const source = envOverride || (existsSync5(nativeSymlink) ? nativeSymlink : null) || findClaudeBinary();
13972
14129
  if (!source) return { ok: false, reason: "binary-not-found" };
13973
14130
  let resolved;
13974
14131
  try {
@@ -13988,6 +14145,17 @@ function resolveClaudeBinaryForPatch() {
13988
14145
  };
13989
14146
  }
13990
14147
  resolved = followed.path;
14148
+ const launchOverride = process.env["CLODEX_CLAUDE_PATH"]?.trim() || null;
14149
+ let launchOverrideResolved = null;
14150
+ if (launchOverride) {
14151
+ try {
14152
+ const followedOverride = resolveThroughNpmShims(realpathSync2(launchOverride));
14153
+ launchOverrideResolved = followedOverride.ok ? followedOverride.path : realpathSync2(launchOverride);
14154
+ } catch {
14155
+ launchOverrideResolved = launchOverride;
14156
+ }
14157
+ }
14158
+ ignoredLaunchOverride = launchOverrideResolved && launchOverrideResolved !== resolved ? { used: resolved, ignored: launchOverride } : null;
13991
14159
  try {
13992
14160
  if (!statSync8(resolved).isFile()) return { ok: false, reason: "binary-not-found" };
13993
14161
  } catch {
@@ -14011,7 +14179,7 @@ function installScriptCommand(path) {
14011
14179
  }
14012
14180
  function describePatchTargetFailure(target, command = "patch") {
14013
14181
  if (target.reason === "launcher-unresolved") {
14014
- return `${target.shimPath} starts Claude Code but is not Claude Code itself, and clodex could not follow it: ${target.detail}. clodex will not patch a launcher script \u2014 that fails with "Unable to detect installation type". Set CLODEX_CLAUDE_PATH to the Claude Code program itself (for an npm install on Windows that is node_modules\\@anthropic-ai\\claude-code\\bin\\claude.exe under the directory holding the launcher), then run the command again.`;
14182
+ return `${target.shimPath} starts Claude Code but is not Claude Code itself, and clodex could not follow it: ${target.detail}. clodex will not patch a launcher script \u2014 that fails with "Unable to detect installation type". Set TWEAKCC_CC_INSTALLATION_PATH to the Claude Code program itself (for an npm install on Windows that is node_modules\\@anthropic-ai\\claude-code\\bin\\claude.exe under the directory holding the launcher), then run the command again.`;
14015
14183
  }
14016
14184
  if (target.reason === "native-binary-missing") {
14017
14185
  const installer = installScriptCommand(target.installScriptPath);
@@ -14020,6 +14188,9 @@ function describePatchTargetFailure(target, command = "patch") {
14020
14188
  const restore = command === "restore" ? ` If you need to restore by hand, pristine backups are in ${backupDir()}.` : "";
14021
14189
  return `${target.binaryPath} is Claude Code's npm placeholder, not its native binary, so the npm install is incomplete. ${remedy} ${retry}${restore} If this is a custom wrapper rather than the npm placeholder, set TWEAKCC_CC_INSTALLATION_PATH to the native Claude Code binary.`;
14022
14190
  }
14191
+ if (target.reason === "patch-target-missing") {
14192
+ return `TWEAKCC_CC_INSTALLATION_PATH is set to ${target.declaredPath}, which does not exist. clodex will not look for another Claude Code instead \u2014 patching or restoring a different install than the one you named is how one install's pristine bytes end up over another's. Point it at the Claude Code program, or unset it to let clodex find your install.`;
14193
+ }
14023
14194
  return target.reason === "binary-not-found" ? "claude binary not found. Install Claude Code or set TWEAKCC_CC_INSTALLATION_PATH." : `Could not determine the version of ${target.binaryPath} (\`claude --version\` failed). clodex will not patch a binary whose version it cannot read, because the version selects the pristine backup it patches from. If a previous patch left the install broken, \`clodex patch --restore\` still works \u2014 it reads the version from the patch manifest.`;
14024
14195
  }
14025
14196
  function summarizePatchResults(results) {
@@ -14042,14 +14213,15 @@ function verifyPristineSource(plan, version) {
14042
14213
  message: `Refusing to use ${plan.backupPath} as the pristine source for claude ${version}: it reports ${backupVersion ? `version ${backupVersion}` : "no version at all"}. That backup predates content-addressed backup names and does not hold this version's bytes. Remove it (or reinstall Claude Code), then run \`clodex patch\`.`
14043
14214
  };
14044
14215
  }
14045
- function publishBackupFile(from, to) {
14216
+ function publishFileByRename(from, to, mode) {
14046
14217
  const temp = `${to}.tmp-${process.pid}-${Date.now().toString(36)}`;
14047
14218
  try {
14048
14219
  copyFileSync(from, temp);
14049
- renameSync2(temp, to);
14220
+ if (mode !== void 0) chmodSync4(temp, mode);
14221
+ renameSync3(temp, to);
14050
14222
  } catch (err) {
14051
14223
  try {
14052
- rmSync(temp, { force: true });
14224
+ rmSync2(temp, { force: true });
14053
14225
  } catch {
14054
14226
  }
14055
14227
  throw err;
@@ -14113,6 +14285,7 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
14113
14285
  let patchedSha256;
14114
14286
  let backup;
14115
14287
  let pristineSha256;
14288
+ const pristine = { provenance: "established" };
14116
14289
  try {
14117
14290
  mkdirSync5(backupDir(), { recursive: true });
14118
14291
  const { tryDetectInstallation, readContent, writeContent } = await import("tweakcc");
@@ -14161,6 +14334,18 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
14161
14334
  }
14162
14335
  pristineSha256 = plan.pristineSha256;
14163
14336
  backup = plan.backupPath;
14337
+ const inheritsAGuess = (path) => {
14338
+ const existing = readInstallProvenance(installProvenancePath(path, binaryPath));
14339
+ return existing === "damaged" || existing !== null && existing.assumed;
14340
+ };
14341
+ const provenanceAssumed = plan.action === "restore" && plan.assumedForThisInstall || inheritsAGuess(plan.backupPath) || inheritsAGuess(contentAddressedBackupPath(version, plan.pristineSha256)) || inheritsAGuess(legacyBackupPath(version)) || facts.backups.some(
14342
+ (candidate) => candidate.sha256 === plan.pristineSha256 && candidate.assumedInstalls.includes(binaryPath)
14343
+ );
14344
+ const recordProvenance = (path) => {
14345
+ if (recordBackupProvenance(path, binaryPath, { assumed: provenanceAssumed }) === "assumed") {
14346
+ pristine.provenance = "assumed";
14347
+ }
14348
+ };
14164
14349
  if (!loaded) {
14165
14350
  loaded = await seedCandidate(backup);
14166
14351
  if (isPatchedClaudeSource(loaded.source)) {
@@ -14172,17 +14357,22 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
14172
14357
  `the patch candidate no longer matches the pristine bytes it was seeded from; refusing to publish it as ${plan.backupPath}`
14173
14358
  );
14174
14359
  }
14175
- publishBackupFile(candidatePath, plan.backupPath);
14360
+ recordProvenance(plan.backupPath);
14361
+ publishFileByRename(candidatePath, plan.backupPath);
14176
14362
  }
14177
14363
  const canonical = contentAddressedBackupPath(version, pristineSha256);
14178
14364
  if (canonical !== backup) {
14365
+ recordProvenance(backup);
14366
+ recordProvenance(canonical);
14179
14367
  const alreadyStored = facts.backups.some(
14180
14368
  (candidate) => candidate.path === canonical && candidate.sha256 === pristineSha256
14181
14369
  );
14182
- if (!alreadyStored) publishBackupFile(backup, canonical);
14370
+ if (!alreadyStored) publishFileByRename(backup, canonical);
14183
14371
  backup = canonical;
14184
14372
  }
14185
- publishBackupFile(backup, tweakccMirrorBackupPath());
14373
+ publishFileByRename(backup, tweakccMirrorBackupPath());
14374
+ recordProvenance(backup);
14375
+ if (plan.backupPath !== backup) recordProvenance(plan.backupPath);
14186
14376
  const builtIn = applyClodexPatches(loaded.source, desired.config);
14187
14377
  results = builtIn.results;
14188
14378
  const failedEffortPatches = requiredEffortPatchFailures(results);
@@ -14249,7 +14439,7 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
14249
14439
  else if (publishedBlob) resignMachOBinary(candidatePath);
14250
14440
  patchedSize = statSync8(candidatePath).size;
14251
14441
  patchedSha256 = sha256File(candidatePath);
14252
- renameSync2(candidatePath, binaryPath);
14442
+ renameSync3(candidatePath, binaryPath);
14253
14443
  } catch (err) {
14254
14444
  const detailLines = err instanceof PatchApplyError ? summarizePatchResults(err.results) : [];
14255
14445
  if (opts.trace && detailLines.length) {
@@ -14264,7 +14454,7 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
14264
14454
  } finally {
14265
14455
  if (candidateDir !== void 0) {
14266
14456
  try {
14267
- rmSync(candidateDir, { recursive: true, force: true });
14457
+ rmSync2(candidateDir, { recursive: true, force: true });
14268
14458
  } catch (err) {
14269
14459
  if (opts.trace) {
14270
14460
  process.stderr.write(
@@ -14287,6 +14477,7 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
14287
14477
  patchedSha256,
14288
14478
  backupPath: backup,
14289
14479
  pristineSha256,
14480
+ ...pristine.provenance === "assumed" ? { pristineProvenance: "assumed" } : {},
14290
14481
  patchedAt: (/* @__PURE__ */ new Date()).toISOString()
14291
14482
  };
14292
14483
  writePatchManifest(manifest);
@@ -14300,7 +14491,7 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
14300
14491
  };
14301
14492
  }
14302
14493
  function runRestoreCommand(target) {
14303
- if (!target.ok && (target.reason === "binary-not-found" || target.reason === "native-binary-missing")) {
14494
+ if (!target.ok && (target.reason === "binary-not-found" || target.reason === "patch-target-missing" || target.reason === "native-binary-missing")) {
14304
14495
  p2.log.error(describePatchTargetFailure(target, "restore"));
14305
14496
  return 1;
14306
14497
  }
@@ -14335,16 +14526,65 @@ function runRestoreCommand(target) {
14335
14526
  p2.log.error(verified.message);
14336
14527
  return 1;
14337
14528
  }
14338
- copyFileSync(plan.backupPath, binaryPath);
14529
+ if (manifest && manifest.backupPath && manifest.binaryPath === binaryPath && manifest.claudeVersion !== version && manifest.backupPath !== plan.backupPath) {
14530
+ try {
14531
+ if (existsSync5(manifest.backupPath)) {
14532
+ recordBackupProvenance(manifest.backupPath, manifest.binaryPath, {
14533
+ assumed: manifest.pristineProvenance === "assumed"
14534
+ });
14535
+ }
14536
+ } catch (err) {
14537
+ p2.log.warn(
14538
+ `Could not record in ${backupDir()} that ${manifest.backupPath} holds the pristine bytes of claude ${manifest.claudeVersion} at ${manifest.binaryPath} (${err instanceof Error ? err.message : String(err)}). That version may need to be reinstalled rather than restored.`
14539
+ );
14540
+ }
14541
+ }
14542
+ let recorded;
14543
+ try {
14544
+ recorded = recordBackupProvenance(plan.backupPath, binaryPath, { assumed: plan.assumedForThisInstall });
14545
+ } catch (err) {
14546
+ recorded = "failed";
14547
+ const detail = err instanceof Error ? err.message : String(err);
14548
+ if (plan.assumedForThisInstall) {
14549
+ p2.log.error(
14550
+ `Refusing to restore ${binaryPath} from ${plan.backupPath}: nothing but the claude ${version} version tag ties those bytes to this install, and clodex cannot record that in ${backupDir()} (${detail}). Writing them without that record would leave the machine unable to tell afterwards that the match was a guess. Fix the backup directory, or reinstall Claude Code to make this install pristine.`
14551
+ );
14552
+ return 1;
14553
+ }
14554
+ p2.log.warn(
14555
+ `Could not record in ${backupDir()} that ${plan.backupPath} holds the pristine bytes of ${binaryPath} (${detail}). Restoring anyway and keeping the patch manifest, so that record is not lost \u2014 a later restore would otherwise have nothing tying that backup to this install.`
14556
+ );
14557
+ }
14558
+ let publishedMode;
14339
14559
  try {
14340
- unlinkSync3(getPatchManifestPath());
14560
+ publishedMode = statSync8(binaryPath).mode;
14341
14561
  } catch {
14342
14562
  }
14563
+ try {
14564
+ publishFileByRename(plan.backupPath, binaryPath, publishedMode);
14565
+ } catch (err) {
14566
+ p2.log.warn(
14567
+ `Could not replace ${binaryPath} through a new file (${err instanceof Error ? err.message : String(err)}); writing the pristine bytes in place instead. If claude then fails to start with a code-signing error, copy the backup to a new file and move it over the binary by hand.`
14568
+ );
14569
+ copyFileSync(plan.backupPath, binaryPath);
14570
+ }
14571
+ if (recorded === "established" && (!manifest || manifest.binaryPath === binaryPath)) {
14572
+ try {
14573
+ unlinkSync3(getPatchManifestPath());
14574
+ } catch {
14575
+ }
14576
+ }
14343
14577
  p2.log.success(`Restored pristine claude ${version} from ${plan.backupPath}.`);
14344
14578
  return 0;
14345
14579
  }
14346
14580
  async function runPatchCommand(opts = {}) {
14347
14581
  const target = resolveClaudeBinaryForPatch();
14582
+ const ignoredOverride = takeIgnoredLaunchOverride();
14583
+ if (ignoredOverride) {
14584
+ p2.log.warn(
14585
+ `CLODEX_CLAUDE_PATH is set to ${ignoredOverride.ignored}, but it does not choose what gets patched \u2014 ${ignoredOverride.used} does. CLODEX_CLAUDE_PATH selects the claude that gets LAUNCHED; set TWEAKCC_CC_INSTALLATION_PATH to patch a specific install.`
14586
+ );
14587
+ }
14348
14588
  if (opts.restore) return runRestoreCommand(target);
14349
14589
  if (!target.ok) {
14350
14590
  p2.log.error(describePatchTargetFailure(target));
@@ -14387,6 +14627,19 @@ async function runPatchCommand(opts = {}) {
14387
14627
  p2.log.warn("Another clodex process is patching the claude binary right now \u2014 skipped.");
14388
14628
  return 1;
14389
14629
  }
14630
+ if (manifest && manifest.backupPath && (manifest.binaryPath !== binaryPath || manifest.claudeVersion !== version)) {
14631
+ try {
14632
+ if (existsSync5(manifest.backupPath)) {
14633
+ recordBackupProvenance(manifest.backupPath, manifest.binaryPath, {
14634
+ assumed: manifest.pristineProvenance === "assumed"
14635
+ });
14636
+ }
14637
+ } catch (err) {
14638
+ p2.log.warn(
14639
+ `Could not record in ${backupDir()} that ${manifest.backupPath} holds the pristine bytes of ${manifest.binaryPath} before replacing the patch manifest (${err instanceof Error ? err.message : String(err)}). That install may need to be reinstalled rather than restored.`
14640
+ );
14641
+ }
14642
+ }
14390
14643
  try {
14391
14644
  const outcome = await applyPatch(binaryPath, version, desired, configHash, {
14392
14645
  trace: opts.trace ?? false,
@@ -14776,9 +15029,9 @@ import {
14776
15029
  mkdirSync as mkdirSync6,
14777
15030
  openSync as openSync8,
14778
15031
  readFileSync as readFileSync11,
14779
- renameSync as renameSync3,
15032
+ renameSync as renameSync4,
14780
15033
  unlinkSync as unlinkSync4,
14781
- writeFileSync as writeFileSync6
15034
+ writeFileSync as writeFileSync7
14782
15035
  } from "fs";
14783
15036
  import { dirname as dirname6 } from "path";
14784
15037
  var JOURNAL_SCHEMA_VERSION = 1;
@@ -14910,13 +15163,13 @@ function writeJournalUnlocked(journal, path) {
14910
15163
  let fd;
14911
15164
  try {
14912
15165
  fd = openSync8(tmp, "wx", FILE_MODE4);
14913
- writeFileSync6(fd, `${JSON.stringify(journal, null, 2)}
15166
+ writeFileSync7(fd, `${JSON.stringify(journal, null, 2)}
14914
15167
  `);
14915
15168
  fsyncSync2(fd);
14916
15169
  closeSync8(fd);
14917
15170
  fd = void 0;
14918
15171
  assertRegistryWriteOwnership(path);
14919
- renameSync3(tmp, path);
15172
+ renameSync4(tmp, path);
14920
15173
  syncParentDirectory(path);
14921
15174
  } finally {
14922
15175
  if (fd !== void 0) closeSync8(fd);
@@ -18218,6 +18471,7 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
18218
18471
  const requestId = randomUUID5();
18219
18472
  const claudeSessionIdHeader = Array.isArray(req.headers["x-claude-code-session-id"]) ? req.headers["x-claude-code-session-id"][0] : req.headers["x-claude-code-session-id"];
18220
18473
  const claudeSessionId = extractClaudeSessionId(body, claudeSessionIdHeader);
18474
+ const claudeAgentIds = extractClaudeAgentIds(req.headers);
18221
18475
  if (options.webSocketDiagnosticsLogPath) {
18222
18476
  writeWebSocketDiagnosticRequestLog(options.webSocketDiagnosticsLogPath, {
18223
18477
  requestId,
@@ -18382,7 +18636,7 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
18382
18636
  res.write(chunk);
18383
18637
  };
18384
18638
  await withResponsesWebSocketDiagnosticContext(
18385
- { requestId, claudeSessionId },
18639
+ { requestId, claudeSessionId, ...claudeAgentIds },
18386
18640
  () => streamAnthropicResponse(languageModel, params, responseModelId, writeStreamChunk, void 0, {
18387
18641
  abortSignal: clientAbort.signal,
18388
18642
  initialInputTokens: estimateAnthropicInputTokens(body),
@@ -18398,7 +18652,7 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
18398
18652
  res.end();
18399
18653
  } else {
18400
18654
  const anthropicResponse = await withResponsesWebSocketDiagnosticContext(
18401
- { requestId, claudeSessionId },
18655
+ { requestId, claudeSessionId, ...claudeAgentIds },
18402
18656
  () => generateAnthropicResponse(languageModel, params, responseModelId, {
18403
18657
  forceStream: openAiOAuth,
18404
18658
  abortSignal: clientAbort.signal,
@@ -18888,7 +19142,7 @@ import { createBrotliDecompress, createGunzip, createInflate } from "zlib";
18888
19142
 
18889
19143
  // src/http-proxy/ca.ts
18890
19144
  import { randomBytes as randomBytes2 } from "crypto";
18891
- import { chmodSync as chmodSync4, existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "fs";
19145
+ import { chmodSync as chmodSync5, existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "fs";
18892
19146
  import { dirname as dirname7, join as join9, resolve as resolve3 } from "path";
18893
19147
  import forge from "node-forge";
18894
19148
  var CERT_DIR = "http-proxy";
@@ -18915,16 +19169,16 @@ function certPaths() {
18915
19169
  };
18916
19170
  }
18917
19171
  function writePrivate(path, value) {
18918
- writeFileSync7(path, value, { encoding: "utf8", mode: 384 });
18919
- chmodSync4(path, 384);
19172
+ writeFileSync8(path, value, { encoding: "utf8", mode: 384 });
19173
+ chmodSync5(path, 384);
18920
19174
  }
18921
19175
  function writePublic(path, value) {
18922
- writeFileSync7(path, value, { encoding: "utf8", mode: 420 });
18923
- chmodSync4(path, 420);
19176
+ writeFileSync8(path, value, { encoding: "utf8", mode: 420 });
19177
+ chmodSync5(path, 420);
18924
19178
  }
18925
19179
  function generateCertificates(paths) {
18926
19180
  mkdirSync7(paths.dir, { recursive: true, mode: 448 });
18927
- chmodSync4(paths.dir, 448);
19181
+ chmodSync5(paths.dir, 448);
18928
19182
  const caKeys = forge.pki.rsa.generateKeyPair(2048);
18929
19183
  const caCert = forge.pki.createCertificate();
18930
19184
  caCert.publicKey = caKeys.publicKey;
@@ -19491,6 +19745,9 @@ function forwardToAdapter(req, res, rawBody, adapter, adapterRequest = http2.req
19491
19745
  "Content-Length": String(rawBody.length),
19492
19746
  "x-api-key": adapter.token,
19493
19747
  ...typeof req.headers["x-claude-code-session-id"] === "string" ? { "x-claude-code-session-id": req.headers["x-claude-code-session-id"] } : {},
19748
+ // Subagent identity: the relay partitions ChatGPT WebSocket heads by it.
19749
+ ...typeof req.headers["x-claude-code-agent-id"] === "string" ? { "x-claude-code-agent-id": req.headers["x-claude-code-agent-id"] } : {},
19750
+ ...typeof req.headers["x-claude-code-parent-agent-id"] === "string" ? { "x-claude-code-parent-agent-id": req.headers["x-claude-code-parent-agent-id"] } : {},
19494
19751
  ...lifecycle ? { "x-relay-request-id": lifecycle.requestId } : {}
19495
19752
  }
19496
19753
  }, (upstreamRes) => {