codex-to-im 1.0.60 → 1.0.61

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/daemon.mjs CHANGED
@@ -324,9 +324,9 @@ __export(codex_provider_exports, {
324
324
  CodexProvider: () => CodexProvider,
325
325
  mapCodexUsage: () => mapCodexUsage
326
326
  });
327
- import fs15 from "node:fs";
327
+ import fs17 from "node:fs";
328
328
  import os4 from "node:os";
329
- import path17 from "node:path";
329
+ import path18 from "node:path";
330
330
  function isRecord(value) {
331
331
  return typeof value === "object" && value !== null && !Array.isArray(value);
332
332
  }
@@ -543,13 +543,13 @@ var init_codex_provider = __esm({
543
543
  { type: "text", text: params.prompt }
544
544
  ];
545
545
  for (const file of imageFiles) {
546
- if (file.filePath && fs15.existsSync(file.filePath)) {
546
+ if (file.filePath && fs17.existsSync(file.filePath)) {
547
547
  parts.push({ type: "local_image", path: file.filePath });
548
548
  continue;
549
549
  }
550
550
  const ext = MIME_EXT[file.type] || ".png";
551
- const tmpPath = path17.join(os4.tmpdir(), `cti-img-${Date.now()}-${Math.random().toString(36).slice(2)}${ext}`);
552
- fs15.writeFileSync(tmpPath, Buffer.from(file.data, "base64"));
551
+ const tmpPath = path18.join(os4.tmpdir(), `cti-img-${Date.now()}-${Math.random().toString(36).slice(2)}${ext}`);
552
+ fs17.writeFileSync(tmpPath, Buffer.from(file.data, "base64"));
553
553
  tempFiles.push(tmpPath);
554
554
  parts.push({ type: "local_image", path: tmpPath });
555
555
  }
@@ -715,7 +715,7 @@ var init_codex_provider = __esm({
715
715
  } finally {
716
716
  for (const tmp of tempFiles) {
717
717
  try {
718
- fs15.unlinkSync(tmp);
718
+ fs17.unlinkSync(tmp);
719
719
  } catch {
720
720
  }
721
721
  }
@@ -845,9 +845,9 @@ var init_codex_provider = __esm({
845
845
  });
846
846
 
847
847
  // src/main.ts
848
- import fs16 from "node:fs";
849
- import path18 from "node:path";
850
- import crypto10 from "node:crypto";
848
+ import fs18 from "node:fs";
849
+ import path19 from "node:path";
850
+ import crypto11 from "node:crypto";
851
851
 
852
852
  // src/lib/bridge/context.ts
853
853
  var CONTEXT_KEY = "__bridge_context__";
@@ -3286,20 +3286,20 @@ ${trimmedResponse}`;
3286
3286
  buffer = Buffer.concat(chunks);
3287
3287
  } catch (streamErr) {
3288
3288
  console.warn("[feishu-adapter] Stream read failed, falling back to writeFile:", streamErr instanceof Error ? streamErr.message : streamErr);
3289
- const fs17 = await import("fs");
3289
+ const fs19 = await import("fs");
3290
3290
  const os5 = await import("os");
3291
- const path19 = await import("path");
3292
- const tmpPath = path19.join(os5.tmpdir(), `feishu-dl-${crypto3.randomUUID()}`);
3291
+ const path20 = await import("path");
3292
+ const tmpPath = path20.join(os5.tmpdir(), `feishu-dl-${crypto3.randomUUID()}`);
3293
3293
  try {
3294
3294
  await res.writeFile(tmpPath);
3295
- buffer = fs17.readFileSync(tmpPath);
3295
+ buffer = fs19.readFileSync(tmpPath);
3296
3296
  if (buffer.length > MAX_FILE_SIZE) {
3297
3297
  console.warn(`[feishu-adapter] Resource too large (>${MAX_FILE_SIZE} bytes), key: ${fileKey}`);
3298
3298
  return null;
3299
3299
  }
3300
3300
  } finally {
3301
3301
  try {
3302
- fs17.unlinkSync(tmpPath);
3302
+ fs19.unlinkSync(tmpPath);
3303
3303
  } catch {
3304
3304
  }
3305
3305
  }
@@ -10837,8 +10837,10 @@ var IGNORED_RESPONSE_ITEM_TYPES = /* @__PURE__ */ new Set([
10837
10837
  "web_search_call"
10838
10838
  ]);
10839
10839
  var IGNORED_TOP_LEVEL_TYPES = /* @__PURE__ */ new Set([
10840
+ "compacted",
10840
10841
  "session_meta",
10841
- "turn_context"
10842
+ "turn_context",
10843
+ "world_state"
10842
10844
  ]);
10843
10845
  var TERMINAL_COMPLETION_EVENT_TYPES = /* @__PURE__ */ new Set([
10844
10846
  "task_complete",
@@ -10865,6 +10867,10 @@ function extractTerminalCompletionText(payload) {
10865
10867
  }
10866
10868
  return "";
10867
10869
  }
10870
+ function isSyntheticDesktopUserContext(text2) {
10871
+ const normalized = text2.trim();
10872
+ return normalized.startsWith("<environment_context>") && normalized.endsWith("</environment_context>");
10873
+ }
10868
10874
  function isIgnoredMirrorLineKind(line) {
10869
10875
  if (isSessionEventLine(line)) {
10870
10876
  const payloadType = typeof line.payload?.type === "string" ? line.payload.type.trim() : "";
@@ -11177,7 +11183,7 @@ function pushDesktopMirrorEventRecord(records, parsed, rawLine, activeTurnId) {
11177
11183
  }
11178
11184
  if (parsed.payload?.type === "user_message") {
11179
11185
  const text2 = extractNormalizedStructuredText(parsed.payload.message);
11180
- if (!text2) return true;
11186
+ if (!text2 || isSyntheticDesktopUserContext(text2)) return true;
11181
11187
  records.push({
11182
11188
  signature,
11183
11189
  type: "message",
@@ -11234,7 +11240,7 @@ function pushDesktopMirrorResponseRecord(records, parsed, rawLine, activeTurnId,
11234
11240
  }
11235
11241
  if (parsed.payload?.type === "message" && parsed.payload.role === "user") {
11236
11242
  const text2 = extractDesktopMessageText(parsed);
11237
- if (!text2) return true;
11243
+ if (!text2 || isSyntheticDesktopUserContext(text2)) return true;
11238
11244
  const previous = records.at(-1);
11239
11245
  if (previous?.type === "message" && previous.role === "user" && previous.content === text2 && previous.turnId === (activeTurnId || void 0)) {
11240
11246
  return true;
@@ -11249,6 +11255,9 @@ function pushDesktopMirrorResponseRecord(records, parsed, rawLine, activeTurnId,
11249
11255
  });
11250
11256
  return true;
11251
11257
  }
11258
+ if (parsed.payload?.type === "message") {
11259
+ return true;
11260
+ }
11252
11261
  if (parsed.payload?.type === "tool_search_call") {
11253
11262
  const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
11254
11263
  records.push({
@@ -13165,6 +13174,10 @@ function filterSuppressedMirrorRecords(store, sessionId, records, config2, nowMs
13165
13174
  break;
13166
13175
  }
13167
13176
  if (record.type === "message" && record.role === "user") {
13177
+ if (isSyntheticDesktopUserContext(record.content || "")) {
13178
+ handled = true;
13179
+ break;
13180
+ }
13168
13181
  if (suppression.promptText && normalizedContent === suppression.promptText) {
13169
13182
  suppression.awaitingPromptMatch = false;
13170
13183
  suppression.droppingTurn = true;
@@ -16677,6 +16690,44 @@ async function runMirrorReconcileBatch(deps) {
16677
16690
 
16678
16691
  // src/lib/bridge/mirror-runtime.ts
16679
16692
  function createMirrorRuntime(getState2, options, deps) {
16693
+ function isAtOrBefore(timestamp, boundary) {
16694
+ if (!timestamp) return true;
16695
+ const timestampMs = Date.parse(timestamp);
16696
+ const boundaryMs = Date.parse(boundary);
16697
+ if (Number.isFinite(timestampMs) && Number.isFinite(boundaryMs)) {
16698
+ return timestampMs <= boundaryMs;
16699
+ }
16700
+ return timestamp <= boundary;
16701
+ }
16702
+ function discardClaimedMirrorTurn(subscription, routeResult) {
16703
+ if (!routeResult.terminalClaimed) return;
16704
+ const claimedTurnId = routeResult.claimedTurnId;
16705
+ if (claimedTurnId) {
16706
+ const claimedStreamKey = buildMirrorStreamKey(subscription.sessionId, claimedTurnId, "");
16707
+ subscription.bufferedRecords = subscription.bufferedRecords.filter(
16708
+ (record) => record.turnId !== claimedTurnId
16709
+ );
16710
+ subscription.pendingDeliveries = subscription.pendingDeliveries.filter(
16711
+ (turn) => turn.streamKey !== claimedStreamKey
16712
+ );
16713
+ if (subscription.pendingTurn?.turnId === claimedTurnId) {
16714
+ deps.stopMirrorStreaming(subscription, "interrupted");
16715
+ subscription.pendingTurn = null;
16716
+ }
16717
+ }
16718
+ const claimedAt = routeResult.claimedAt;
16719
+ if (!claimedAt) return;
16720
+ const retainedAtOrBeforeClaim = [
16721
+ ...subscription.bufferedRecords.map((record) => record.timestamp),
16722
+ ...subscription.pendingDeliveries.map((turn) => turn.timestamp),
16723
+ ...routeResult.unclaimed.map((record) => record.timestamp),
16724
+ ...subscription.pendingTurn ? [subscription.pendingTurn.lastActivityAt] : []
16725
+ ].some((timestamp) => isAtOrBefore(timestamp, claimedAt));
16726
+ if (retainedAtOrBeforeClaim) return;
16727
+ if (!subscription.lastDeliveredAt || !isAtOrBefore(claimedAt, subscription.lastDeliveredAt)) {
16728
+ subscription.lastDeliveredAt = claimedAt;
16729
+ }
16730
+ }
16680
16731
  function closeMirrorWatcher(subscription) {
16681
16732
  if (subscription.watcher) {
16682
16733
  try {
@@ -16864,7 +16915,14 @@ function createMirrorRuntime(getState2, options, deps) {
16864
16915
  `[bridge-manager] Unhandled desktop mirror event for thread ${subscription.threadId}: ${kind}`
16865
16916
  );
16866
16917
  }
16867
- const routeResult = deliverableRecords.length > 0 && deps.routeDesktopRecords ? await deps.routeDesktopRecords(subscription.sessionId, subscription.threadId, deliverableRecords) : { claimed: [], unclaimed: deliverableRecords, terminalClaimed: false };
16918
+ const routeResult = deliverableRecords.length > 0 && deps.routeDesktopRecords ? await deps.routeDesktopRecords(subscription.sessionId, subscription.threadId, deliverableRecords) : {
16919
+ claimed: [],
16920
+ unclaimed: deliverableRecords,
16921
+ terminalClaimed: false,
16922
+ claimedTurnId: null,
16923
+ claimedAt: null
16924
+ };
16925
+ discardClaimedMirrorTurn(subscription, routeResult);
16868
16926
  const mirrorRecords = routeResult.unclaimed;
16869
16927
  if (mirrorRecords.length > 0) {
16870
16928
  deps.observeSessionHealthRecords(subscription.sessionId, subscription.threadId, mirrorRecords);
@@ -17930,7 +17988,9 @@ async function routeDesktopRecords(sessionId, desktopThreadId, records, coordina
17930
17988
  return {
17931
17989
  claimed: [],
17932
17990
  unclaimed: records,
17933
- terminalClaimed: false
17991
+ terminalClaimed: false,
17992
+ claimedTurnId: null,
17993
+ claimedAt: null
17934
17994
  };
17935
17995
  }
17936
17996
  const claim = await coordinator.claimDesktopTerminal(
@@ -17940,7 +18000,9 @@ async function routeDesktopRecords(sessionId, desktopThreadId, records, coordina
17940
18000
  return {
17941
18001
  claimed: [],
17942
18002
  unclaimed: records,
17943
- terminalClaimed: false
18003
+ terminalClaimed: false,
18004
+ claimedTurnId: null,
18005
+ claimedAt: null
17944
18006
  };
17945
18007
  }
17946
18008
  const claimedTurnId = terminalRecord.turnId;
@@ -17949,7 +18011,9 @@ async function routeDesktopRecords(sessionId, desktopThreadId, records, coordina
17949
18011
  return {
17950
18012
  claimed,
17951
18013
  unclaimed: records.filter((record) => !claimedSet.has(record.signature)),
17952
- terminalClaimed: true
18014
+ terminalClaimed: true,
18015
+ claimedTurnId: claimedTurnId || null,
18016
+ claimedAt: terminalRecord.timestamp || null
17953
18017
  };
17954
18018
  }
17955
18019
 
@@ -18579,40 +18643,76 @@ function persistSdkSessionUpdate(sessionId, sdkSessionId, hasError) {
18579
18643
  }
18580
18644
 
18581
18645
  // src/store.ts
18646
+ import fs13 from "node:fs";
18647
+ import path15 from "node:path";
18648
+ import crypto10 from "node:crypto";
18649
+
18650
+ // src/atomic-file.ts
18651
+ import crypto9 from "node:crypto";
18582
18652
  import fs12 from "node:fs";
18583
18653
  import path14 from "node:path";
18584
- import crypto9 from "node:crypto";
18585
- var DATA_DIR2 = path14.join(CTI_HOME, "data");
18586
- var MESSAGES_DIR = path14.join(DATA_DIR2, "messages");
18587
- var SESSIONS_PATH = path14.join(DATA_DIR2, "sessions.json");
18588
- var BINDINGS_PATH = path14.join(DATA_DIR2, "bindings.json");
18589
- var PERMISSIONS_PATH = path14.join(DATA_DIR2, "permissions.json");
18590
- var OFFSETS_PATH = path14.join(DATA_DIR2, "offsets.json");
18591
- var DEDUP_PATH = path14.join(DATA_DIR2, "dedup.json");
18592
- var AUDIT_PATH = path14.join(DATA_DIR2, "audit.json");
18593
- var DEFAULT_DEDUP_TTL_MS = 5 * 60 * 1e3;
18594
- var INBOUND_DEDUP_TTL_MS = 24 * 60 * 60 * 1e3;
18595
- function ensureDir2(dir) {
18596
- fs12.mkdirSync(dir, { recursive: true });
18654
+ var RETRYABLE_RENAME_CODES = /* @__PURE__ */ new Set(["EACCES", "EBUSY", "EPERM"]);
18655
+ var WAIT_ARRAY2 = new Int32Array(new SharedArrayBuffer(4));
18656
+ function defaultSleepSync(delayMs) {
18657
+ Atomics.wait(WAIT_ARRAY2, 0, 0, Math.max(1, delayMs));
18658
+ }
18659
+ function isRetryableRenameError(error) {
18660
+ return RETRYABLE_RENAME_CODES.has(error?.code || "");
18661
+ }
18662
+ function renameFileWithRetrySync(source, destination, options = {}) {
18663
+ const maxAttempts = Math.max(1, options.maxAttempts ?? 8);
18664
+ const baseDelayMs = Math.max(1, options.baseDelayMs ?? 15);
18665
+ const renameSync = options.renameSync ?? fs12.renameSync;
18666
+ const sleepSync2 = options.sleepSync ?? defaultSleepSync;
18667
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
18668
+ try {
18669
+ renameSync(source, destination);
18670
+ return;
18671
+ } catch (error) {
18672
+ if (!isRetryableRenameError(error) || attempt === maxAttempts - 1) {
18673
+ throw error;
18674
+ }
18675
+ sleepSync2(Math.min(250, baseDelayMs * 2 ** attempt));
18676
+ }
18677
+ }
18597
18678
  }
18598
- function atomicWrite2(filePath, data) {
18599
- const tmp = `${filePath}.${process.pid}.${crypto9.randomUUID()}.tmp`;
18679
+ function atomicWriteFileSync(filePath, data) {
18680
+ fs12.mkdirSync(path14.dirname(filePath), { recursive: true });
18681
+ const tmpPath = `${filePath}.${process.pid}.${crypto9.randomUUID()}.tmp`;
18600
18682
  try {
18601
- fs12.writeFileSync(tmp, data, "utf-8");
18602
- fs12.renameSync(tmp, filePath);
18683
+ fs12.writeFileSync(tmpPath, data, "utf-8");
18684
+ renameFileWithRetrySync(tmpPath, filePath);
18603
18685
  } finally {
18604
18686
  try {
18605
- fs12.rmSync(tmp, { force: true });
18687
+ fs12.rmSync(tmpPath, { force: true });
18606
18688
  } catch {
18607
18689
  }
18608
18690
  }
18609
18691
  }
18692
+
18693
+ // src/store.ts
18694
+ var DATA_DIR2 = path15.join(CTI_HOME, "data");
18695
+ var MESSAGES_DIR = path15.join(DATA_DIR2, "messages");
18696
+ var SESSIONS_PATH = path15.join(DATA_DIR2, "sessions.json");
18697
+ var BINDINGS_PATH = path15.join(DATA_DIR2, "bindings.json");
18698
+ var PERMISSIONS_PATH = path15.join(DATA_DIR2, "permissions.json");
18699
+ var OFFSETS_PATH = path15.join(DATA_DIR2, "offsets.json");
18700
+ var DEDUP_PATH = path15.join(DATA_DIR2, "dedup.json");
18701
+ var AUDIT_PATH = path15.join(DATA_DIR2, "audit.json");
18702
+ var DEFAULT_DEDUP_TTL_MS = 5 * 60 * 1e3;
18703
+ var INBOUND_DEDUP_TTL_MS = 24 * 60 * 60 * 1e3;
18704
+ function ensureDir2(dir) {
18705
+ fs13.mkdirSync(dir, { recursive: true });
18706
+ }
18707
+ function atomicWrite2(filePath, data) {
18708
+ atomicWriteFileSync(filePath, data);
18709
+ }
18610
18710
  function dedupTtlMs(key) {
18611
18711
  return key.startsWith("inbound:") ? INBOUND_DEDUP_TTL_MS : DEFAULT_DEDUP_TTL_MS;
18612
18712
  }
18613
18713
  function readJson2(filePath, fallback) {
18614
18714
  try {
18615
- const raw = fs12.readFileSync(filePath, "utf-8");
18715
+ const raw = fs13.readFileSync(filePath, "utf-8");
18616
18716
  return JSON.parse(raw);
18617
18717
  } catch (error) {
18618
18718
  if (error.code === "ENOENT") return fallback;
@@ -18623,7 +18723,7 @@ function writeJson(filePath, data) {
18623
18723
  atomicWrite2(filePath, JSON.stringify(data, null, 2));
18624
18724
  }
18625
18725
  function uuid() {
18626
- return crypto9.randomUUID();
18726
+ return crypto10.randomUUID();
18627
18727
  }
18628
18728
  function now() {
18629
18729
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -18786,14 +18886,14 @@ var JsonFileStore = class {
18786
18886
  }
18787
18887
  persistMessages(sessionId) {
18788
18888
  const msgs = this.messages.get(sessionId) || [];
18789
- writeJson(path14.join(MESSAGES_DIR, `${sessionId}.json`), msgs);
18889
+ writeJson(path15.join(MESSAGES_DIR, `${sessionId}.json`), msgs);
18790
18890
  }
18791
18891
  loadMessages(sessionId) {
18792
18892
  if (this.messages.has(sessionId)) {
18793
18893
  return this.messages.get(sessionId);
18794
18894
  }
18795
18895
  const msgs = readJson2(
18796
- path14.join(MESSAGES_DIR, `${sessionId}.json`),
18896
+ path15.join(MESSAGES_DIR, `${sessionId}.json`),
18797
18897
  []
18798
18898
  );
18799
18899
  this.messages.set(sessionId, msgs);
@@ -18816,7 +18916,7 @@ var JsonFileStore = class {
18816
18916
  });
18817
18917
  }
18818
18918
  mutateMessages(sessionId, mutation) {
18819
- const messagePath = path14.join(MESSAGES_DIR, `${sessionId}.json`);
18919
+ const messagePath = path15.join(MESSAGES_DIR, `${sessionId}.json`);
18820
18920
  return withFileLock(messagePath, () => {
18821
18921
  const messages = readJson2(messagePath, []);
18822
18922
  this.messages.set(sessionId, messages);
@@ -18975,7 +19075,7 @@ var JsonFileStore = class {
18975
19075
  });
18976
19076
  }
18977
19077
  deleteSession(sessionId) {
18978
- const messagePath = path14.join(MESSAGES_DIR, `${sessionId}.json`);
19078
+ const messagePath = path15.join(MESSAGES_DIR, `${sessionId}.json`);
18979
19079
  withFileLocks([SESSIONS_PATH, BINDINGS_PATH, messagePath], () => {
18980
19080
  this.reloadSessions();
18981
19081
  this.reloadBindings();
@@ -18987,7 +19087,7 @@ var JsonFileStore = class {
18987
19087
  }
18988
19088
  this.messages.delete(sessionId);
18989
19089
  try {
18990
- fs12.rmSync(messagePath, { force: true });
19090
+ fs13.rmSync(messagePath, { force: true });
18991
19091
  } catch {
18992
19092
  }
18993
19093
  this.persistSessions();
@@ -19001,7 +19101,7 @@ var JsonFileStore = class {
19001
19101
  });
19002
19102
  }
19003
19103
  getMessages(sessionId, opts) {
19004
- const messagePath = path14.join(MESSAGES_DIR, `${sessionId}.json`);
19104
+ const messagePath = path15.join(MESSAGES_DIR, `${sessionId}.json`);
19005
19105
  const msgs = readJson2(messagePath, []);
19006
19106
  this.messages.set(sessionId, msgs);
19007
19107
  if (opts?.limit && opts.limit > 0) {
@@ -19242,8 +19342,8 @@ var PendingPermissions = class {
19242
19342
  };
19243
19343
 
19244
19344
  // src/logger.ts
19245
- import fs13 from "node:fs";
19246
- import path15 from "node:path";
19345
+ import fs14 from "node:fs";
19346
+ import path16 from "node:path";
19247
19347
  import { inspect as inspect2 } from "node:util";
19248
19348
  var MASK_PATTERNS = [
19249
19349
  /(?:token|secret|password|api_key)["']?\s*[:=]\s*["']?([^\s"',]+)/gi,
@@ -19261,8 +19361,8 @@ function maskSecrets(text2) {
19261
19361
  }
19262
19362
  return result;
19263
19363
  }
19264
- var LOG_DIR = path15.join(CTI_HOME, "logs");
19265
- var LOG_PATH = path15.join(LOG_DIR, "bridge.log");
19364
+ var LOG_DIR = path16.join(CTI_HOME, "logs");
19365
+ var LOG_PATH = path16.join(LOG_DIR, "bridge.log");
19266
19366
  var MAX_LOG_SIZE = 10 * 1024 * 1024;
19267
19367
  var MAX_ROTATED = 3;
19268
19368
  var logStream = null;
@@ -19283,11 +19383,11 @@ function formatLogArg(value) {
19283
19383
  return String(value);
19284
19384
  }
19285
19385
  function openLogStream() {
19286
- return fs13.createWriteStream(LOG_PATH, { flags: "a" });
19386
+ return fs14.createWriteStream(LOG_PATH, { flags: "a" });
19287
19387
  }
19288
19388
  function rotateIfNeeded() {
19289
19389
  try {
19290
- const stat = fs13.statSync(LOG_PATH);
19390
+ const stat = fs14.statSync(LOG_PATH);
19291
19391
  if (stat.size < MAX_LOG_SIZE) return;
19292
19392
  } catch {
19293
19393
  return;
@@ -19297,17 +19397,17 @@ function rotateIfNeeded() {
19297
19397
  logStream = null;
19298
19398
  }
19299
19399
  const path32 = `${LOG_PATH}.${MAX_ROTATED}`;
19300
- if (fs13.existsSync(path32)) fs13.unlinkSync(path32);
19400
+ if (fs14.existsSync(path32)) fs14.unlinkSync(path32);
19301
19401
  for (let i = MAX_ROTATED - 1; i >= 1; i--) {
19302
19402
  const src = `${LOG_PATH}.${i}`;
19303
19403
  const dst = `${LOG_PATH}.${i + 1}`;
19304
- if (fs13.existsSync(src)) fs13.renameSync(src, dst);
19404
+ if (fs14.existsSync(src)) fs14.renameSync(src, dst);
19305
19405
  }
19306
- fs13.renameSync(LOG_PATH, `${LOG_PATH}.1`);
19406
+ fs14.renameSync(LOG_PATH, `${LOG_PATH}.1`);
19307
19407
  logStream = openLogStream();
19308
19408
  }
19309
19409
  function setupLogger() {
19310
- fs13.mkdirSync(LOG_DIR, { recursive: true });
19410
+ fs14.mkdirSync(LOG_DIR, { recursive: true });
19311
19411
  logStream = openLogStream();
19312
19412
  const write = (level, args) => {
19313
19413
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -19323,13 +19423,13 @@ function setupLogger() {
19323
19423
  }
19324
19424
 
19325
19425
  // src/bridge-instance-lock.ts
19326
- import fs14 from "node:fs";
19327
- import path16 from "node:path";
19328
- var runtimeDir = path16.join(CTI_HOME, "runtime");
19329
- var bridgeInstanceLockFile = path16.join(runtimeDir, "bridge.instance.lock");
19426
+ import fs15 from "node:fs";
19427
+ import path17 from "node:path";
19428
+ var runtimeDir = path17.join(CTI_HOME, "runtime");
19429
+ var bridgeInstanceLockFile = path17.join(runtimeDir, "bridge.instance.lock");
19330
19430
  function readJsonFile(filePath, fallback) {
19331
19431
  try {
19332
- return JSON.parse(fs14.readFileSync(filePath, "utf-8"));
19432
+ return JSON.parse(fs15.readFileSync(filePath, "utf-8"));
19333
19433
  } catch {
19334
19434
  return fallback;
19335
19435
  }
@@ -19361,8 +19461,8 @@ function tryAcquireBridgeInstanceLock(options = {}) {
19361
19461
  };
19362
19462
  for (let attempt = 0; attempt < 2; attempt += 1) {
19363
19463
  try {
19364
- fs14.mkdirSync(path16.dirname(filePath), { recursive: true });
19365
- fs14.writeFileSync(filePath, JSON.stringify(payload, null, 2), { encoding: "utf-8", flag: "wx" });
19464
+ fs15.mkdirSync(path17.dirname(filePath), { recursive: true });
19465
+ fs15.writeFileSync(filePath, JSON.stringify(payload, null, 2), { encoding: "utf-8", flag: "wx" });
19366
19466
  return { acquired: true };
19367
19467
  } catch (error) {
19368
19468
  const code2 = error.code;
@@ -19372,7 +19472,7 @@ function tryAcquireBridgeInstanceLock(options = {}) {
19372
19472
  return { acquired: false, holderPid: existing2.pid };
19373
19473
  }
19374
19474
  try {
19375
- fs14.unlinkSync(filePath);
19475
+ fs15.unlinkSync(filePath);
19376
19476
  } catch {
19377
19477
  }
19378
19478
  }
@@ -19387,37 +19487,46 @@ function releaseBridgeInstanceLock(filePath = bridgeInstanceLockFile, ownerPid =
19387
19487
  const existing = readBridgeInstanceLock(filePath);
19388
19488
  if (!existing) {
19389
19489
  try {
19390
- fs14.unlinkSync(filePath);
19490
+ fs15.unlinkSync(filePath);
19391
19491
  } catch {
19392
19492
  }
19393
19493
  return;
19394
19494
  }
19395
19495
  if (existing.pid !== ownerPid) return;
19396
19496
  try {
19397
- fs14.unlinkSync(filePath);
19497
+ fs15.unlinkSync(filePath);
19398
19498
  } catch {
19399
19499
  }
19400
19500
  }
19401
19501
 
19402
- // src/main.ts
19403
- var RUNTIME_DIR = path18.join(CTI_HOME, "runtime");
19404
- var STATUS_FILE = path18.join(RUNTIME_DIR, "status.json");
19405
- var PID_FILE = path18.join(RUNTIME_DIR, "bridge.pid");
19406
- async function resolveProvider() {
19407
- const { CodexProvider: CodexProvider2 } = await Promise.resolve().then(() => (init_codex_provider(), codex_provider_exports));
19408
- return new CodexProvider2();
19409
- }
19410
- function writeStatus(info) {
19411
- fs16.mkdirSync(RUNTIME_DIR, { recursive: true });
19502
+ // src/runtime-status.ts
19503
+ import fs16 from "node:fs";
19504
+ function writeRuntimeStatus(filePath, info, options = {}) {
19412
19505
  let existing = {};
19413
19506
  try {
19414
- existing = JSON.parse(fs16.readFileSync(STATUS_FILE, "utf-8"));
19507
+ existing = JSON.parse(fs16.readFileSync(filePath, "utf-8"));
19415
19508
  } catch {
19416
19509
  }
19510
+ const existingRunId = typeof existing.runId === "string" ? existing.runId : "";
19511
+ if (options.expectedRunId && existingRunId && existingRunId !== options.expectedRunId && options.allowRunIdTakeover !== true) {
19512
+ return false;
19513
+ }
19417
19514
  const merged = { ...existing, ...info };
19418
- const tmp = STATUS_FILE + ".tmp";
19419
- fs16.writeFileSync(tmp, JSON.stringify(merged, null, 2), "utf-8");
19420
- fs16.renameSync(tmp, STATUS_FILE);
19515
+ atomicWriteFileSync(filePath, JSON.stringify(merged, null, 2));
19516
+ return true;
19517
+ }
19518
+
19519
+ // src/main.ts
19520
+ var RUNTIME_DIR = path19.join(CTI_HOME, "runtime");
19521
+ var STATUS_FILE = path19.join(RUNTIME_DIR, "status.json");
19522
+ var PID_FILE = path19.join(RUNTIME_DIR, "bridge.pid");
19523
+ var runId = crypto11.randomUUID();
19524
+ async function resolveProvider() {
19525
+ const { CodexProvider: CodexProvider2 } = await Promise.resolve().then(() => (init_codex_provider(), codex_provider_exports));
19526
+ return new CodexProvider2();
19527
+ }
19528
+ function writeStatus(info, options = {}) {
19529
+ return writeRuntimeStatus(STATUS_FILE, info, options);
19421
19530
  }
19422
19531
  function getRunningChannels() {
19423
19532
  return getStatus().adapters.map((adapter) => adapter.channelType).sort();
@@ -19429,10 +19538,6 @@ async function main() {
19429
19538
  const lockState = tryAcquireBridgeInstanceLock();
19430
19539
  if (!lockState.acquired) {
19431
19540
  const holderPid = lockState.holderPid;
19432
- writeStatus({
19433
- running: true,
19434
- ...Number.isFinite(holderPid) && holderPid ? { pid: holderPid } : {}
19435
- });
19436
19541
  console.log(
19437
19542
  `[codex-to-im] Another bridge daemon is already running${holderPid ? ` (PID: ${holderPid})` : ""}. Exiting duplicate launcher.`
19438
19543
  );
@@ -19444,9 +19549,15 @@ async function main() {
19444
19549
  releaseBridgeInstanceLock(void 0, process.pid);
19445
19550
  instanceLockHeld = false;
19446
19551
  };
19552
+ writeStatus({
19553
+ running: false,
19554
+ pid: process.pid,
19555
+ runId,
19556
+ channels: [],
19557
+ adapters: []
19558
+ }, { expectedRunId: runId, allowRunIdTakeover: true });
19447
19559
  const config2 = loadConfig();
19448
19560
  setupLogger();
19449
- const runId = crypto10.randomUUID();
19450
19561
  console.log(`[codex-to-im] Starting bridge (run_id: ${runId})`);
19451
19562
  const settings = configToSettings(config2);
19452
19563
  const store = new JsonFileStore(settings, { dynamicSettings: true });
@@ -19462,8 +19573,8 @@ async function main() {
19462
19573
  permissions: gateway,
19463
19574
  lifecycle: {
19464
19575
  onBridgeStart: () => {
19465
- fs16.mkdirSync(RUNTIME_DIR, { recursive: true });
19466
- fs16.writeFileSync(PID_FILE, String(process.pid), "utf-8");
19576
+ fs18.mkdirSync(RUNTIME_DIR, { recursive: true });
19577
+ fs18.writeFileSync(PID_FILE, String(process.pid), "utf-8");
19467
19578
  const channels = getRunningChannels();
19468
19579
  writeStatus({
19469
19580
  running: true,
@@ -19472,7 +19583,7 @@ async function main() {
19472
19583
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
19473
19584
  channels,
19474
19585
  adapters: getAdapterStatuses()
19475
- });
19586
+ }, { expectedRunId: runId });
19476
19587
  console.log(`[codex-to-im] Bridge started (PID: ${process.pid}, channels: ${channels.join(", ")})`);
19477
19588
  },
19478
19589
  onBridgeAdaptersChanged: (channels) => {
@@ -19482,41 +19593,60 @@ async function main() {
19482
19593
  runId,
19483
19594
  channels,
19484
19595
  adapters: getAdapterStatuses()
19485
- });
19596
+ }, { expectedRunId: runId });
19486
19597
  console.log(`[codex-to-im] Active channels updated: ${channels.join(", ") || "none"}`);
19487
19598
  },
19488
19599
  onBridgeStop: () => {
19489
19600
  releaseInstanceLock();
19490
- writeStatus({ running: false, channels: [], adapters: [] });
19601
+ writeStatus({ running: false, channels: [], adapters: [] }, { expectedRunId: runId });
19491
19602
  console.log("[codex-to-im] Bridge stopped");
19492
19603
  }
19493
19604
  }
19494
19605
  });
19495
19606
  await start();
19496
19607
  let shuttingDown = false;
19497
- const shutdown = async (signal) => {
19608
+ const shutdown = async (reason, exitCode = 0) => {
19498
19609
  if (shuttingDown) return;
19499
19610
  shuttingDown = true;
19500
- const reason = signal ? `signal: ${signal}` : "shutdown requested";
19501
19611
  console.log(`[codex-to-im] Shutting down (${reason})...`);
19502
19612
  pendingPerms.denyAll();
19503
- await stop();
19504
- releaseInstanceLock();
19505
- writeStatus({ running: false, lastExitReason: reason });
19506
- process.exit(0);
19613
+ try {
19614
+ await stop();
19615
+ } catch (error) {
19616
+ console.error("[codex-to-im] Graceful bridge stop failed:", error instanceof Error ? error.stack || error.message : error);
19617
+ } finally {
19618
+ releaseInstanceLock();
19619
+ try {
19620
+ writeStatus({ running: false, lastExitReason: reason }, { expectedRunId: runId });
19621
+ } catch (error) {
19622
+ console.error("[codex-to-im] Failed to write final runtime status:", error instanceof Error ? error.message : error);
19623
+ }
19624
+ process.exit(exitCode);
19625
+ }
19507
19626
  };
19508
- process.on("SIGTERM", () => shutdown("SIGTERM"));
19509
- process.on("SIGINT", () => shutdown("SIGINT"));
19510
- process.on("SIGHUP", () => shutdown("SIGHUP"));
19627
+ process.on("SIGTERM", () => {
19628
+ void shutdown("signal: SIGTERM");
19629
+ });
19630
+ process.on("SIGINT", () => {
19631
+ void shutdown("signal: SIGINT");
19632
+ });
19633
+ process.on("SIGHUP", () => {
19634
+ void shutdown("signal: SIGHUP");
19635
+ });
19511
19636
  process.on("unhandledRejection", (reason) => {
19512
19637
  console.error("[codex-to-im] unhandledRejection:", reason instanceof Error ? reason.stack || reason.message : reason);
19513
- writeStatus({ running: false, lastExitReason: `unhandledRejection: ${reason instanceof Error ? reason.message : String(reason)}` });
19638
+ writeStatus({
19639
+ running: true,
19640
+ pid: process.pid,
19641
+ runId,
19642
+ channels: getRunningChannels(),
19643
+ adapters: getAdapterStatuses(),
19644
+ lastExitReason: `unhandledRejection: ${reason instanceof Error ? reason.message : String(reason)}`
19645
+ }, { expectedRunId: runId });
19514
19646
  });
19515
19647
  process.on("uncaughtException", (err) => {
19516
19648
  console.error("[codex-to-im] uncaughtException:", err.stack || err.message);
19517
- releaseInstanceLock();
19518
- writeStatus({ running: false, lastExitReason: `uncaughtException: ${err.message}` });
19519
- process.exit(1);
19649
+ void shutdown(`uncaughtException: ${err.message}`, 1);
19520
19650
  });
19521
19651
  process.on("beforeExit", (code2) => {
19522
19652
  console.log(`[codex-to-im] beforeExit (code: ${code2})`);
@@ -19532,7 +19662,10 @@ main().catch((err) => {
19532
19662
  console.error("[codex-to-im] Fatal error:", err instanceof Error ? err.stack || err.message : err);
19533
19663
  releaseBridgeInstanceLock(void 0, process.pid);
19534
19664
  try {
19535
- writeStatus({ running: false, lastExitReason: `fatal: ${err instanceof Error ? err.message : String(err)}` });
19665
+ writeStatus(
19666
+ { running: false, lastExitReason: `fatal: ${err instanceof Error ? err.message : String(err)}` },
19667
+ { expectedRunId: runId }
19668
+ );
19536
19669
  } catch {
19537
19670
  }
19538
19671
  process.exit(1);
@@ -1572,10 +1572,10 @@ var require_segments = __commonJS({
1572
1572
  const segs = getSegmentsFromString(data, Utils.isKanjiModeEnabled());
1573
1573
  const nodes = buildNodes(segs);
1574
1574
  const graph = buildGraph(nodes, version);
1575
- const path10 = dijkstra.find_path(graph.map, "start", "end");
1575
+ const path11 = dijkstra.find_path(graph.map, "start", "end");
1576
1576
  const optimizedSegs = [];
1577
- for (let i = 1; i < path10.length - 1; i++) {
1578
- optimizedSegs.push(graph.table[path10[i]].node);
1577
+ for (let i = 1; i < path11.length - 1; i++) {
1578
+ optimizedSegs.push(graph.table[path11[i]].node);
1579
1579
  }
1580
1580
  return exports.fromArray(mergeSegments(optimizedSegs));
1581
1581
  };
@@ -4011,7 +4011,7 @@ var require_utils2 = __commonJS({
4011
4011
  // node_modules/qrcode/lib/renderer/png.js
4012
4012
  var require_png2 = __commonJS({
4013
4013
  "node_modules/qrcode/lib/renderer/png.js"(exports) {
4014
- var fs10 = __require("fs");
4014
+ var fs11 = __require("fs");
4015
4015
  var PNG = require_png().PNG;
4016
4016
  var Utils = require_utils2();
4017
4017
  exports.render = function render(qrData, options) {
@@ -4052,7 +4052,7 @@ var require_png2 = __commonJS({
4052
4052
  });
4053
4053
  png.pack();
4054
4054
  };
4055
- exports.renderToFile = function renderToFile(path10, qrData, options, cb) {
4055
+ exports.renderToFile = function renderToFile(path11, qrData, options, cb) {
4056
4056
  if (typeof cb === "undefined") {
4057
4057
  cb = options;
4058
4058
  options = void 0;
@@ -4063,7 +4063,7 @@ var require_png2 = __commonJS({
4063
4063
  called = true;
4064
4064
  cb.apply(null, args);
4065
4065
  };
4066
- const stream = fs10.createWriteStream(path10);
4066
+ const stream = fs11.createWriteStream(path11);
4067
4067
  stream.on("error", done);
4068
4068
  stream.on("close", done);
4069
4069
  exports.renderToFileStream(stream, qrData, options);
@@ -4125,14 +4125,14 @@ var require_utf8 = __commonJS({
4125
4125
  }
4126
4126
  return output;
4127
4127
  };
4128
- exports.renderToFile = function renderToFile(path10, qrData, options, cb) {
4128
+ exports.renderToFile = function renderToFile(path11, qrData, options, cb) {
4129
4129
  if (typeof cb === "undefined") {
4130
4130
  cb = options;
4131
4131
  options = void 0;
4132
4132
  }
4133
- const fs10 = __require("fs");
4133
+ const fs11 = __require("fs");
4134
4134
  const utf8 = exports.render(qrData, options);
4135
- fs10.writeFile(path10, utf8, cb);
4135
+ fs11.writeFile(path11, utf8, cb);
4136
4136
  };
4137
4137
  }
4138
4138
  });
@@ -4253,7 +4253,7 @@ var require_svg_tag = __commonJS({
4253
4253
  return str;
4254
4254
  }
4255
4255
  function qrToPath(data, size, margin) {
4256
- let path10 = "";
4256
+ let path11 = "";
4257
4257
  let moveBy = 0;
4258
4258
  let newRow = false;
4259
4259
  let lineLength = 0;
@@ -4264,19 +4264,19 @@ var require_svg_tag = __commonJS({
4264
4264
  if (data[i]) {
4265
4265
  lineLength++;
4266
4266
  if (!(i > 0 && col > 0 && data[i - 1])) {
4267
- path10 += newRow ? svgCmd("M", col + margin, 0.5 + row + margin) : svgCmd("m", moveBy, 0);
4267
+ path11 += newRow ? svgCmd("M", col + margin, 0.5 + row + margin) : svgCmd("m", moveBy, 0);
4268
4268
  moveBy = 0;
4269
4269
  newRow = false;
4270
4270
  }
4271
4271
  if (!(col + 1 < size && data[i + 1])) {
4272
- path10 += svgCmd("h", lineLength);
4272
+ path11 += svgCmd("h", lineLength);
4273
4273
  lineLength = 0;
4274
4274
  }
4275
4275
  } else {
4276
4276
  moveBy++;
4277
4277
  }
4278
4278
  }
4279
- return path10;
4279
+ return path11;
4280
4280
  }
4281
4281
  exports.render = function render(qrData, options, cb) {
4282
4282
  const opts = Utils.getOptions(options);
@@ -4284,10 +4284,10 @@ var require_svg_tag = __commonJS({
4284
4284
  const data = qrData.modules.data;
4285
4285
  const qrcodesize = size + opts.margin * 2;
4286
4286
  const bg = !opts.color.light.a ? "" : "<path " + getColorAttrib(opts.color.light, "fill") + ' d="M0 0h' + qrcodesize + "v" + qrcodesize + 'H0z"/>';
4287
- const path10 = "<path " + getColorAttrib(opts.color.dark, "stroke") + ' d="' + qrToPath(data, size, opts.margin) + '"/>';
4287
+ const path11 = "<path " + getColorAttrib(opts.color.dark, "stroke") + ' d="' + qrToPath(data, size, opts.margin) + '"/>';
4288
4288
  const viewBox = 'viewBox="0 0 ' + qrcodesize + " " + qrcodesize + '"';
4289
4289
  const width = !opts.width ? "" : 'width="' + opts.width + '" height="' + opts.width + '" ';
4290
- const svgTag = '<svg xmlns="http://www.w3.org/2000/svg" ' + width + viewBox + ' shape-rendering="crispEdges">' + bg + path10 + "</svg>\n";
4290
+ const svgTag = '<svg xmlns="http://www.w3.org/2000/svg" ' + width + viewBox + ' shape-rendering="crispEdges">' + bg + path11 + "</svg>\n";
4291
4291
  if (typeof cb === "function") {
4292
4292
  cb(null, svgTag);
4293
4293
  }
@@ -4301,15 +4301,15 @@ var require_svg = __commonJS({
4301
4301
  "node_modules/qrcode/lib/renderer/svg.js"(exports) {
4302
4302
  var svgTagRenderer = require_svg_tag();
4303
4303
  exports.render = svgTagRenderer.render;
4304
- exports.renderToFile = function renderToFile(path10, qrData, options, cb) {
4304
+ exports.renderToFile = function renderToFile(path11, qrData, options, cb) {
4305
4305
  if (typeof cb === "undefined") {
4306
4306
  cb = options;
4307
4307
  options = void 0;
4308
4308
  }
4309
- const fs10 = __require("fs");
4309
+ const fs11 = __require("fs");
4310
4310
  const svgTag = exports.render(qrData, options);
4311
4311
  const xmlStr = '<?xml version="1.0" encoding="utf-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">' + svgTag;
4312
- fs10.writeFile(path10, xmlStr, cb);
4312
+ fs11.writeFile(path11, xmlStr, cb);
4313
4313
  };
4314
4314
  }
4315
4315
  });
@@ -4467,8 +4467,8 @@ var require_server = __commonJS({
4467
4467
  cb
4468
4468
  };
4469
4469
  }
4470
- function getTypeFromFilename(path10) {
4471
- return path10.slice((path10.lastIndexOf(".") - 1 >>> 0) + 2).toLowerCase();
4470
+ function getTypeFromFilename(path11) {
4471
+ return path11.slice((path11.lastIndexOf(".") - 1 >>> 0) + 2).toLowerCase();
4472
4472
  }
4473
4473
  function getRendererFromType(type) {
4474
4474
  switch (type) {
@@ -4532,17 +4532,17 @@ var require_server = __commonJS({
4532
4532
  const renderer = getRendererFromType(params.opts.type);
4533
4533
  return render(renderer.renderToBuffer, text2, params);
4534
4534
  };
4535
- exports.toFile = function toFile(path10, text2, opts, cb) {
4536
- if (typeof path10 !== "string" || !(typeof text2 === "string" || typeof text2 === "object")) {
4535
+ exports.toFile = function toFile(path11, text2, opts, cb) {
4536
+ if (typeof path11 !== "string" || !(typeof text2 === "string" || typeof text2 === "object")) {
4537
4537
  throw new Error("Invalid argument");
4538
4538
  }
4539
4539
  if (arguments.length < 3 && !canPromise()) {
4540
4540
  throw new Error("Too few arguments provided");
4541
4541
  }
4542
4542
  const params = checkParams(text2, opts, cb);
4543
- const type = params.opts.type || getTypeFromFilename(path10);
4543
+ const type = params.opts.type || getTypeFromFilename(path11);
4544
4544
  const renderer = getRendererFromType(type);
4545
- const renderToFile = renderer.renderToFile.bind(null, path10);
4545
+ const renderToFile = renderer.renderToFile.bind(null, path11);
4546
4546
  return render(renderToFile, text2, params);
4547
4547
  };
4548
4548
  exports.toFileStream = function toFileStream(stream, text2, opts) {
@@ -4566,7 +4566,7 @@ var require_lib = __commonJS({
4566
4566
 
4567
4567
  // src/ui-server.ts
4568
4568
  import http from "node:http";
4569
- import crypto8 from "node:crypto";
4569
+ import crypto9 from "node:crypto";
4570
4570
  import net from "node:net";
4571
4571
  import os5 from "node:os";
4572
4572
 
@@ -6438,40 +6438,76 @@ function isCodexIntegrationInstalled() {
6438
6438
  }
6439
6439
 
6440
6440
  // src/store.ts
6441
+ import fs7 from "node:fs";
6442
+ import path7 from "node:path";
6443
+ import crypto5 from "node:crypto";
6444
+
6445
+ // src/atomic-file.ts
6446
+ import crypto4 from "node:crypto";
6441
6447
  import fs6 from "node:fs";
6442
6448
  import path6 from "node:path";
6443
- import crypto4 from "node:crypto";
6444
- var DATA_DIR = path6.join(CTI_HOME, "data");
6445
- var MESSAGES_DIR = path6.join(DATA_DIR, "messages");
6446
- var SESSIONS_PATH = path6.join(DATA_DIR, "sessions.json");
6447
- var BINDINGS_PATH = path6.join(DATA_DIR, "bindings.json");
6448
- var PERMISSIONS_PATH = path6.join(DATA_DIR, "permissions.json");
6449
- var OFFSETS_PATH = path6.join(DATA_DIR, "offsets.json");
6450
- var DEDUP_PATH = path6.join(DATA_DIR, "dedup.json");
6451
- var AUDIT_PATH = path6.join(DATA_DIR, "audit.json");
6452
- var DEFAULT_DEDUP_TTL_MS = 5 * 60 * 1e3;
6453
- var INBOUND_DEDUP_TTL_MS = 24 * 60 * 60 * 1e3;
6454
- function ensureDir(dir) {
6455
- fs6.mkdirSync(dir, { recursive: true });
6449
+ var RETRYABLE_RENAME_CODES = /* @__PURE__ */ new Set(["EACCES", "EBUSY", "EPERM"]);
6450
+ var WAIT_ARRAY2 = new Int32Array(new SharedArrayBuffer(4));
6451
+ function defaultSleepSync(delayMs) {
6452
+ Atomics.wait(WAIT_ARRAY2, 0, 0, Math.max(1, delayMs));
6453
+ }
6454
+ function isRetryableRenameError(error) {
6455
+ return RETRYABLE_RENAME_CODES.has(error?.code || "");
6456
+ }
6457
+ function renameFileWithRetrySync(source, destination, options = {}) {
6458
+ const maxAttempts = Math.max(1, options.maxAttempts ?? 8);
6459
+ const baseDelayMs = Math.max(1, options.baseDelayMs ?? 15);
6460
+ const renameSync = options.renameSync ?? fs6.renameSync;
6461
+ const sleepSync2 = options.sleepSync ?? defaultSleepSync;
6462
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
6463
+ try {
6464
+ renameSync(source, destination);
6465
+ return;
6466
+ } catch (error) {
6467
+ if (!isRetryableRenameError(error) || attempt === maxAttempts - 1) {
6468
+ throw error;
6469
+ }
6470
+ sleepSync2(Math.min(250, baseDelayMs * 2 ** attempt));
6471
+ }
6472
+ }
6456
6473
  }
6457
- function atomicWrite(filePath, data) {
6458
- const tmp = `${filePath}.${process.pid}.${crypto4.randomUUID()}.tmp`;
6474
+ function atomicWriteFileSync(filePath, data) {
6475
+ fs6.mkdirSync(path6.dirname(filePath), { recursive: true });
6476
+ const tmpPath = `${filePath}.${process.pid}.${crypto4.randomUUID()}.tmp`;
6459
6477
  try {
6460
- fs6.writeFileSync(tmp, data, "utf-8");
6461
- fs6.renameSync(tmp, filePath);
6478
+ fs6.writeFileSync(tmpPath, data, "utf-8");
6479
+ renameFileWithRetrySync(tmpPath, filePath);
6462
6480
  } finally {
6463
6481
  try {
6464
- fs6.rmSync(tmp, { force: true });
6482
+ fs6.rmSync(tmpPath, { force: true });
6465
6483
  } catch {
6466
6484
  }
6467
6485
  }
6468
6486
  }
6487
+
6488
+ // src/store.ts
6489
+ var DATA_DIR = path7.join(CTI_HOME, "data");
6490
+ var MESSAGES_DIR = path7.join(DATA_DIR, "messages");
6491
+ var SESSIONS_PATH = path7.join(DATA_DIR, "sessions.json");
6492
+ var BINDINGS_PATH = path7.join(DATA_DIR, "bindings.json");
6493
+ var PERMISSIONS_PATH = path7.join(DATA_DIR, "permissions.json");
6494
+ var OFFSETS_PATH = path7.join(DATA_DIR, "offsets.json");
6495
+ var DEDUP_PATH = path7.join(DATA_DIR, "dedup.json");
6496
+ var AUDIT_PATH = path7.join(DATA_DIR, "audit.json");
6497
+ var DEFAULT_DEDUP_TTL_MS = 5 * 60 * 1e3;
6498
+ var INBOUND_DEDUP_TTL_MS = 24 * 60 * 60 * 1e3;
6499
+ function ensureDir(dir) {
6500
+ fs7.mkdirSync(dir, { recursive: true });
6501
+ }
6502
+ function atomicWrite(filePath, data) {
6503
+ atomicWriteFileSync(filePath, data);
6504
+ }
6469
6505
  function dedupTtlMs(key) {
6470
6506
  return key.startsWith("inbound:") ? INBOUND_DEDUP_TTL_MS : DEFAULT_DEDUP_TTL_MS;
6471
6507
  }
6472
6508
  function readJson(filePath, fallback) {
6473
6509
  try {
6474
- const raw = fs6.readFileSync(filePath, "utf-8");
6510
+ const raw = fs7.readFileSync(filePath, "utf-8");
6475
6511
  return JSON.parse(raw);
6476
6512
  } catch (error) {
6477
6513
  if (error.code === "ENOENT") return fallback;
@@ -6482,7 +6518,7 @@ function writeJson(filePath, data) {
6482
6518
  atomicWrite(filePath, JSON.stringify(data, null, 2));
6483
6519
  }
6484
6520
  function uuid() {
6485
- return crypto4.randomUUID();
6521
+ return crypto5.randomUUID();
6486
6522
  }
6487
6523
  function now() {
6488
6524
  return (/* @__PURE__ */ new Date()).toISOString();
@@ -6645,14 +6681,14 @@ var JsonFileStore = class {
6645
6681
  }
6646
6682
  persistMessages(sessionId) {
6647
6683
  const msgs = this.messages.get(sessionId) || [];
6648
- writeJson(path6.join(MESSAGES_DIR, `${sessionId}.json`), msgs);
6684
+ writeJson(path7.join(MESSAGES_DIR, `${sessionId}.json`), msgs);
6649
6685
  }
6650
6686
  loadMessages(sessionId) {
6651
6687
  if (this.messages.has(sessionId)) {
6652
6688
  return this.messages.get(sessionId);
6653
6689
  }
6654
6690
  const msgs = readJson(
6655
- path6.join(MESSAGES_DIR, `${sessionId}.json`),
6691
+ path7.join(MESSAGES_DIR, `${sessionId}.json`),
6656
6692
  []
6657
6693
  );
6658
6694
  this.messages.set(sessionId, msgs);
@@ -6675,7 +6711,7 @@ var JsonFileStore = class {
6675
6711
  });
6676
6712
  }
6677
6713
  mutateMessages(sessionId, mutation) {
6678
- const messagePath = path6.join(MESSAGES_DIR, `${sessionId}.json`);
6714
+ const messagePath = path7.join(MESSAGES_DIR, `${sessionId}.json`);
6679
6715
  return withFileLock(messagePath, () => {
6680
6716
  const messages = readJson(messagePath, []);
6681
6717
  this.messages.set(sessionId, messages);
@@ -6834,7 +6870,7 @@ var JsonFileStore = class {
6834
6870
  });
6835
6871
  }
6836
6872
  deleteSession(sessionId) {
6837
- const messagePath = path6.join(MESSAGES_DIR, `${sessionId}.json`);
6873
+ const messagePath = path7.join(MESSAGES_DIR, `${sessionId}.json`);
6838
6874
  withFileLocks([SESSIONS_PATH, BINDINGS_PATH, messagePath], () => {
6839
6875
  this.reloadSessions();
6840
6876
  this.reloadBindings();
@@ -6846,7 +6882,7 @@ var JsonFileStore = class {
6846
6882
  }
6847
6883
  this.messages.delete(sessionId);
6848
6884
  try {
6849
- fs6.rmSync(messagePath, { force: true });
6885
+ fs7.rmSync(messagePath, { force: true });
6850
6886
  } catch {
6851
6887
  }
6852
6888
  this.persistSessions();
@@ -6860,7 +6896,7 @@ var JsonFileStore = class {
6860
6896
  });
6861
6897
  }
6862
6898
  getMessages(sessionId, opts) {
6863
- const messagePath = path6.join(MESSAGES_DIR, `${sessionId}.json`);
6899
+ const messagePath = path7.join(MESSAGES_DIR, `${sessionId}.json`);
6864
6900
  const msgs = readJson(messagePath, []);
6865
6901
  this.messages.set(sessionId, msgs);
6866
6902
  if (opts?.limit && opts.limit > 0) {
@@ -7064,13 +7100,13 @@ var JsonFileStore = class {
7064
7100
 
7065
7101
  // src/weixin-login.ts
7066
7102
  var import_qrcode = __toESM(require_lib(), 1);
7067
- import fs8 from "node:fs";
7068
- import path8 from "node:path";
7069
- import crypto7 from "node:crypto";
7103
+ import fs9 from "node:fs";
7104
+ import path9 from "node:path";
7105
+ import crypto8 from "node:crypto";
7070
7106
  import { spawn as spawn2 } from "node:child_process";
7071
7107
 
7072
7108
  // src/adapters/weixin/weixin-api.ts
7073
- import crypto5 from "node:crypto";
7109
+ import crypto6 from "node:crypto";
7074
7110
 
7075
7111
  // src/adapters/weixin/weixin-types.ts
7076
7112
  var DEFAULT_BASE_URL = "https://ilinkai.weixin.qq.com";
@@ -7119,32 +7155,32 @@ async function pollLoginQrStatus(qrcode, baseUrl) {
7119
7155
  }
7120
7156
 
7121
7157
  // src/weixin-store.ts
7122
- import fs7 from "node:fs";
7123
- import crypto6 from "node:crypto";
7124
- import path7 from "node:path";
7125
- var DATA_DIR2 = path7.join(CTI_HOME, "data");
7126
- var ACCOUNTS_PATH = path7.join(DATA_DIR2, "weixin-accounts.json");
7127
- var CONTEXT_TOKENS_PATH = path7.join(DATA_DIR2, "weixin-context-tokens.json");
7158
+ import fs8 from "node:fs";
7159
+ import crypto7 from "node:crypto";
7160
+ import path8 from "node:path";
7161
+ var DATA_DIR2 = path8.join(CTI_HOME, "data");
7162
+ var ACCOUNTS_PATH = path8.join(DATA_DIR2, "weixin-accounts.json");
7163
+ var CONTEXT_TOKENS_PATH = path8.join(DATA_DIR2, "weixin-context-tokens.json");
7128
7164
  var DEFAULT_BASE_URL2 = "https://ilinkai.weixin.qq.com";
7129
7165
  var DEFAULT_CDN_BASE_URL2 = "https://novac2c.cdn.weixin.qq.com/c2c";
7130
7166
  function ensureDir2(dir) {
7131
- fs7.mkdirSync(dir, { recursive: true });
7167
+ fs8.mkdirSync(dir, { recursive: true });
7132
7168
  }
7133
7169
  function atomicWrite2(filePath, data) {
7134
- const tmpPath = `${filePath}.${process.pid}.${crypto6.randomUUID()}.tmp`;
7170
+ const tmpPath = `${filePath}.${process.pid}.${crypto7.randomUUID()}.tmp`;
7135
7171
  try {
7136
- fs7.writeFileSync(tmpPath, data, "utf-8");
7137
- fs7.renameSync(tmpPath, filePath);
7172
+ fs8.writeFileSync(tmpPath, data, "utf-8");
7173
+ fs8.renameSync(tmpPath, filePath);
7138
7174
  } finally {
7139
7175
  try {
7140
- fs7.rmSync(tmpPath, { force: true });
7176
+ fs8.rmSync(tmpPath, { force: true });
7141
7177
  } catch {
7142
7178
  }
7143
7179
  }
7144
7180
  }
7145
7181
  function readJson2(filePath, fallback) {
7146
7182
  try {
7147
- const raw = fs7.readFileSync(filePath, "utf-8");
7183
+ const raw = fs8.readFileSync(filePath, "utf-8");
7148
7184
  return JSON.parse(raw);
7149
7185
  } catch (error) {
7150
7186
  if (error.code === "ENOENT") return fallback;
@@ -7236,11 +7272,11 @@ var MAX_REFRESHES = 3;
7236
7272
  var QR_TTL_MS = 5 * 6e4;
7237
7273
  var POLL_INTERVAL_MS = 3e3;
7238
7274
  var WEB_SESSION_TTL_MS = 15 * 6e4;
7239
- var RUNTIME_DIR = path8.join(CTI_HOME, "runtime");
7240
- var HTML_PATH = path8.join(RUNTIME_DIR, "weixin-login.html");
7275
+ var RUNTIME_DIR = path9.join(CTI_HOME, "runtime");
7276
+ var HTML_PATH = path9.join(RUNTIME_DIR, "weixin-login.html");
7241
7277
  var webLoginSessions = /* @__PURE__ */ new Map();
7242
7278
  function ensureRuntimeDir() {
7243
- fs8.mkdirSync(RUNTIME_DIR, { recursive: true });
7279
+ fs9.mkdirSync(RUNTIME_DIR, { recursive: true });
7244
7280
  }
7245
7281
  function escapeHtml(text2) {
7246
7282
  return text2.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
@@ -7553,7 +7589,7 @@ function buildWeixinLoginPopupHtml(sessionId) {
7553
7589
  async function writeQrHtml(session) {
7554
7590
  ensureRuntimeDir();
7555
7591
  const qrSvg = await buildQrSvg(session.qrImageUrl);
7556
- fs8.writeFileSync(HTML_PATH, buildQrHtml(qrSvg), "utf-8");
7592
+ fs9.writeFileSync(HTML_PATH, buildQrHtml(qrSvg), "utf-8");
7557
7593
  }
7558
7594
  function openQrHtml() {
7559
7595
  try {
@@ -7742,7 +7778,7 @@ async function startWeixinLoginWebSession(options = {}) {
7742
7778
  ensureRuntimeDir();
7743
7779
  const config = options.config || {};
7744
7780
  const seed = await createSession(0, config.baseUrl);
7745
- const sessionId = crypto7.randomUUID();
7781
+ const sessionId = crypto8.randomUUID();
7746
7782
  const qrSvg = await buildQrSvg(seed.qrImageUrl);
7747
7783
  const session = {
7748
7784
  id: sessionId,
@@ -7814,7 +7850,7 @@ async function runWeixinLogin(config = {}) {
7814
7850
  }
7815
7851
  var isMainModule = (() => {
7816
7852
  const entry = process.argv[1];
7817
- return !!entry && path8.resolve(entry) === path8.resolve(new URL(import.meta.url).pathname);
7853
+ return !!entry && path9.resolve(entry) === path9.resolve(new URL(import.meta.url).pathname);
7818
7854
  })();
7819
7855
  if (isMainModule) {
7820
7856
  runWeixinLogin().catch((err) => {
@@ -7824,11 +7860,11 @@ if (isMainModule) {
7824
7860
  }
7825
7861
 
7826
7862
  // src/codex-models.ts
7827
- import fs9 from "node:fs";
7863
+ import fs10 from "node:fs";
7828
7864
  import os4 from "node:os";
7829
- import path9 from "node:path";
7830
- var DEFAULT_CODEX_CONFIG_PATH = path9.join(os4.homedir(), ".codex", "config.toml");
7831
- var DEFAULT_CODEX_MODELS_CACHE_PATH = path9.join(os4.homedir(), ".codex", "models_cache.json");
7865
+ import path10 from "node:path";
7866
+ var DEFAULT_CODEX_CONFIG_PATH = path10.join(os4.homedir(), ".codex", "config.toml");
7867
+ var DEFAULT_CODEX_MODELS_CACHE_PATH = path10.join(os4.homedir(), ".codex", "models_cache.json");
7832
7868
  var REASONING_LEVEL_DESCRIPTIONS = {
7833
7869
  low: "Fast responses with lighter reasoning",
7834
7870
  medium: "Balances speed and reasoning depth for everyday tasks",
@@ -7877,7 +7913,7 @@ var KNOWN_CODEX_MODEL_FALLBACKS = [
7877
7913
  ];
7878
7914
  function readConfiguredCodexModel(configPath = DEFAULT_CODEX_CONFIG_PATH) {
7879
7915
  try {
7880
- const raw = fs9.readFileSync(configPath, "utf-8");
7916
+ const raw = fs10.readFileSync(configPath, "utf-8");
7881
7917
  let inSection = false;
7882
7918
  for (const line of raw.split(/\r?\n/)) {
7883
7919
  const trimmed = line.trim();
@@ -7921,7 +7957,7 @@ function parseSupportedReasoningLevels(value) {
7921
7957
  }
7922
7958
  function listCachedCodexModels(cachePath = DEFAULT_CODEX_MODELS_CACHE_PATH) {
7923
7959
  try {
7924
- const raw = fs9.readFileSync(cachePath, "utf-8");
7960
+ const raw = fs10.readFileSync(cachePath, "utf-8");
7925
7961
  const parsed = JSON.parse(raw);
7926
7962
  if (!Array.isArray(parsed.models)) return [];
7927
7963
  const seen = /* @__PURE__ */ new Set();
@@ -8220,14 +8256,14 @@ function channelToPayload(channel) {
8220
8256
  };
8221
8257
  }
8222
8258
  function generateAccessToken() {
8223
- return crypto8.randomBytes(18).toString("base64url");
8259
+ return crypto9.randomBytes(18).toString("base64url");
8224
8260
  }
8225
8261
  function timingSafeMatch(left, right) {
8226
8262
  if (!left || !right) return false;
8227
8263
  const leftBuffer = Buffer.from(left);
8228
8264
  const rightBuffer = Buffer.from(right);
8229
8265
  if (leftBuffer.length !== rightBuffer.length) return false;
8230
- return crypto8.timingSafeEqual(leftBuffer, rightBuffer);
8266
+ return crypto9.timingSafeEqual(leftBuffer, rightBuffer);
8231
8267
  }
8232
8268
  function parseCookies(request) {
8233
8269
  const header = request.headers.cookie;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-to-im",
3
- "version": "1.0.60",
3
+ "version": "1.0.61",
4
4
  "description": "Installable Codex-to-IM bridge with local setup UI and background service",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/zhangle1987/codex-to-im#readme",