codex-to-im 1.0.28 → 1.0.29

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.mjs CHANGED
@@ -86,6 +86,30 @@ function isProcessAlive(pid) {
86
86
  return false;
87
87
  }
88
88
  }
89
+ function collectTrackedBridgePids(bridgePid, statusPid) {
90
+ const unique = /* @__PURE__ */ new Set();
91
+ for (const pid of [bridgePid, statusPid]) {
92
+ if (Number.isFinite(pid) && pid > 0) {
93
+ unique.add(pid);
94
+ }
95
+ }
96
+ return [...unique];
97
+ }
98
+ function resolveTrackedBridgePid(bridgePid, statusPid, isAlive = isProcessAlive) {
99
+ if (isAlive(bridgePid)) return bridgePid;
100
+ if (isAlive(statusPid)) return statusPid;
101
+ return bridgePid ?? statusPid;
102
+ }
103
+ function getTrackedBridgePids(status) {
104
+ const resolvedStatus = status ?? readJsonFile(bridgeStatusFile, { running: false });
105
+ return collectTrackedBridgePids(readPid(bridgePidFile), resolvedStatus.pid);
106
+ }
107
+ function clearBridgePidFile() {
108
+ try {
109
+ fs2.unlinkSync(bridgePidFile);
110
+ } catch {
111
+ }
112
+ }
89
113
  function sleep(ms) {
90
114
  return new Promise((resolve) => setTimeout(resolve, ms));
91
115
  }
@@ -246,7 +270,7 @@ function getCurrentUiServerUrl() {
246
270
  }
247
271
  function getBridgeStatus() {
248
272
  const status = readJsonFile(bridgeStatusFile, { running: false });
249
- const pid = readPid(bridgePidFile) ?? status.pid;
273
+ const pid = resolveTrackedBridgePid(readPid(bridgePidFile), status.pid);
250
274
  if (!isProcessAlive(pid)) {
251
275
  return {
252
276
  ...status,
@@ -311,7 +335,11 @@ async function waitForUiServer(timeoutMs = 15e3) {
311
335
  async function startBridge() {
312
336
  ensureDirs();
313
337
  const current = getBridgeStatus();
314
- if (current.running) return current;
338
+ const extraAlivePids = getTrackedBridgePids(current).filter((pid) => pid !== current.pid && isProcessAlive(pid));
339
+ if (current.running && extraAlivePids.length === 0) return current;
340
+ if (current.running && extraAlivePids.length > 0) {
341
+ await stopBridge();
342
+ }
315
343
  const daemonEntry = path2.join(packageRoot, "dist", "daemon.mjs");
316
344
  if (!fs2.existsSync(daemonEntry)) {
317
345
  throw new Error(`Daemon bundle not found at ${daemonEntry}. Run npm run build first.`);
@@ -333,31 +361,38 @@ async function startBridge() {
333
361
  return status;
334
362
  }
335
363
  async function stopBridge() {
336
- const status = getBridgeStatus();
337
- if (!status.pid || !isProcessAlive(status.pid)) {
338
- return { ...status, running: false };
339
- }
340
- if (process.platform === "win32") {
341
- await new Promise((resolve) => {
342
- const killer = spawn("cmd", ["/c", "taskkill", "/PID", String(status.pid), "/T", "/F"], {
343
- stdio: "ignore",
344
- ...WINDOWS_HIDE
364
+ const status = readJsonFile(bridgeStatusFile, { running: false });
365
+ const pids = getTrackedBridgePids(status).filter((pid) => isProcessAlive(pid));
366
+ if (pids.length === 0) {
367
+ clearBridgePidFile();
368
+ return { ...getBridgeStatus(), running: false };
369
+ }
370
+ for (const pid of pids) {
371
+ if (process.platform === "win32") {
372
+ await new Promise((resolve) => {
373
+ const killer = spawn("cmd", ["/c", "taskkill", "/PID", String(pid), "/T", "/F"], {
374
+ stdio: "ignore",
375
+ ...WINDOWS_HIDE
376
+ });
377
+ killer.on("exit", () => resolve());
378
+ killer.on("error", () => resolve());
345
379
  });
346
- killer.on("exit", () => resolve());
347
- killer.on("error", () => resolve());
348
- });
349
- } else {
350
- try {
351
- process.kill(status.pid, "SIGTERM");
352
- } catch {
380
+ } else {
381
+ try {
382
+ process.kill(pid, "SIGTERM");
383
+ } catch {
384
+ }
353
385
  }
354
386
  }
355
387
  const startedAt = Date.now();
356
388
  while (Date.now() - startedAt < 1e4) {
357
- const next = getBridgeStatus();
358
- if (!next.running) return next;
389
+ if (pids.every((pid) => !isProcessAlive(pid))) {
390
+ clearBridgePidFile();
391
+ return getBridgeStatus();
392
+ }
359
393
  await sleep(300);
360
394
  }
395
+ clearBridgePidFile();
361
396
  return getBridgeStatus();
362
397
  }
363
398
  async function getBridgeAutostartStatus() {
package/dist/daemon.mjs CHANGED
@@ -4255,6 +4255,7 @@ function getRegisteredTypes() {
4255
4255
  // src/lib/bridge/bridge-manager.ts
4256
4256
  import fs8 from "node:fs";
4257
4257
  import path11 from "node:path";
4258
+ import { inspect } from "node:util";
4258
4259
 
4259
4260
  // src/lib/bridge/adapters/telegram-adapter.ts
4260
4261
  import crypto from "crypto";
@@ -14233,12 +14234,6 @@ var WeixinAdapter = class extends BaseChannelAdapter {
14233
14234
  }
14234
14235
  }
14235
14236
  validateConfig() {
14236
- const linkedAccounts = this.filterConfiguredAccounts(
14237
- listWeixinAccounts().filter((account) => account.enabled && account.token)
14238
- );
14239
- if (linkedAccounts.length === 0) {
14240
- return "No linked WeChat account. Run the WeChat QR login helper first.";
14241
- }
14242
14237
  return null;
14243
14238
  }
14244
14239
  isAuthorized(_userId, _chatId) {
@@ -17018,6 +17013,9 @@ var MODE_OPTIONS_TEXT = "\u53EF\u9009\uFF1A`code`\uFF08\u76F4\u63A5\u6267\u884C\
17018
17013
  var REASONING_OPTIONS_TEXT = "\u53EF\u9009\uFF1A`1=minimal` `2=low` `3=medium` `4=high` `5=xhigh`";
17019
17014
  var DEFAULT_DESKTOP_THREAD_LIST_LIMIT = 10;
17020
17015
  var MAX_DESKTOP_THREAD_LIST_LIMIT = 200;
17016
+ var DANGLING_MIRROR_THREAD_RETRY_LIMIT = 3;
17017
+ var MIRROR_FAILURE_SUSPEND_MS = 6e4;
17018
+ var MIRROR_FAILURE_SUSPEND_THRESHOLD = 3;
17021
17019
  var MIRROR_POLL_INTERVAL_MS = 2500;
17022
17020
  var MIRROR_WATCH_DEBOUNCE_MS = 350;
17023
17021
  var MIRROR_EVENT_BATCH_LIMIT = 8;
@@ -17027,6 +17025,7 @@ var INTERACTIVE_IDLE_REMINDER_MS = 6e5;
17027
17025
  var MIRROR_IDLE_TIMEOUT_MS = 6e5;
17028
17026
  var AVAILABLE_CODEX_MODELS = listSelectableCodexModels();
17029
17027
  var AVAILABLE_CODEX_MODEL_MAP = new Map(AVAILABLE_CODEX_MODELS.map((model) => [model.slug, model]));
17028
+ var INVALID_ADAPTER_WARNING_CACHE = /* @__PURE__ */ new Map();
17030
17029
  function generateDraftId() {
17031
17030
  return Math.floor(Math.random() * 2147483646) + 1;
17032
17031
  }
@@ -17109,8 +17108,41 @@ function resolveByIndexOrPrefix(raw, items, getId) {
17109
17108
  }
17110
17109
  return { match: null, ambiguous: false };
17111
17110
  }
17111
+ function describeUnknownError(error) {
17112
+ if (error instanceof Error) {
17113
+ return error.stack || `${error.name}: ${error.message}`;
17114
+ }
17115
+ if (error === null) return "null";
17116
+ if (typeof error === "undefined") return "undefined";
17117
+ if (typeof error === "object") {
17118
+ const ctor = error?.constructor?.name;
17119
+ const rendered = inspect(error, {
17120
+ depth: 4,
17121
+ breakLength: Infinity,
17122
+ compact: true
17123
+ });
17124
+ return ctor && ctor !== "Object" ? `${ctor} ${rendered}` : rendered;
17125
+ }
17126
+ return String(error);
17127
+ }
17112
17128
  function getDisplayedDesktopThreads(limit = DEFAULT_DESKTOP_THREAD_LIST_LIMIT) {
17113
- return listDesktopSessions(limit);
17129
+ try {
17130
+ return listDesktopSessions(limit);
17131
+ } catch (error) {
17132
+ console.error("[bridge-manager] Failed to list desktop sessions:", error);
17133
+ return null;
17134
+ }
17135
+ }
17136
+ function getDesktopSessionByThreadIdSafe(threadId, context) {
17137
+ try {
17138
+ return getDesktopSessionByThreadId(threadId);
17139
+ } catch (error) {
17140
+ console.error(
17141
+ `[bridge-manager] Failed to load desktop thread ${threadId} during ${context}:`,
17142
+ error
17143
+ );
17144
+ return null;
17145
+ }
17114
17146
  }
17115
17147
  function parseDesktopThreadListArgs(args) {
17116
17148
  const trimmed = args.trim().toLowerCase();
@@ -17522,7 +17554,7 @@ async function summarizeHistory(currentBinding) {
17522
17554
  }
17523
17555
  function getDesktopThreadTitle(threadId) {
17524
17556
  if (!threadId) return null;
17525
- return getDesktopSessionByThreadId(threadId)?.title || null;
17557
+ return getDesktopSessionByThreadIdSafe(threadId, "status lookup")?.title || null;
17526
17558
  }
17527
17559
  function formatCommandPath(cwd) {
17528
17560
  return cwd?.trim() || "~";
@@ -17684,6 +17716,7 @@ function getState() {
17684
17716
  g[GLOBAL_KEY] = {
17685
17717
  adapters: /* @__PURE__ */ new Map(),
17686
17718
  adapterMeta: /* @__PURE__ */ new Map(),
17719
+ invalidAdapters: /* @__PURE__ */ new Map(),
17687
17720
  running: false,
17688
17721
  startedAt: null,
17689
17722
  loopAborts: /* @__PURE__ */ new Map(),
@@ -17706,6 +17739,9 @@ function getState() {
17706
17739
  if (!g[GLOBAL_KEY].mirrorSubscriptions) {
17707
17740
  g[GLOBAL_KEY].mirrorSubscriptions = /* @__PURE__ */ new Map();
17708
17741
  }
17742
+ if (!g[GLOBAL_KEY].invalidAdapters) {
17743
+ g[GLOBAL_KEY].invalidAdapters = /* @__PURE__ */ new Map();
17744
+ }
17709
17745
  if (!g[GLOBAL_KEY].queuedCounts) {
17710
17746
  g[GLOBAL_KEY].queuedCounts = /* @__PURE__ */ new Map();
17711
17747
  }
@@ -18065,7 +18101,9 @@ function scheduleMirrorWake(delayMs = MIRROR_WATCH_DEBOUNCE_MS) {
18065
18101
  if (state.mirrorWakeTimer) return;
18066
18102
  state.mirrorWakeTimer = setTimeout(() => {
18067
18103
  state.mirrorWakeTimer = null;
18068
- void reconcileMirrorSubscriptions();
18104
+ void reconcileMirrorSubscriptions().catch((err) => {
18105
+ console.error("[bridge-manager] Mirror wake reconcile failed:", describeUnknownError(err));
18106
+ });
18069
18107
  }, delayMs);
18070
18108
  }
18071
18109
  function watchMirrorFile(subscription, filePath) {
@@ -18103,6 +18141,16 @@ function syncMirrorSessionState(sessionId) {
18103
18141
  mirror_last_event_at: deliveredAt
18104
18142
  });
18105
18143
  }
18144
+ function syncMirrorSessionStateSafe(sessionId, context) {
18145
+ try {
18146
+ syncMirrorSessionState(sessionId);
18147
+ } catch (error) {
18148
+ console.error(
18149
+ `[bridge-manager] Failed to sync mirror session state for ${sessionId} during ${context}:`,
18150
+ describeUnknownError(error)
18151
+ );
18152
+ }
18153
+ }
18106
18154
  function getMirrorAssistantRuntimeLabel() {
18107
18155
  const { store } = getBridgeContext();
18108
18156
  const runtime = (store.getSetting("bridge_runtime") || "codex").trim().toLowerCase();
@@ -18467,7 +18515,17 @@ function removeMirrorSubscription(bindingId) {
18467
18515
  stopMirrorStreaming(existing);
18468
18516
  closeMirrorWatcher(existing);
18469
18517
  state.mirrorSubscriptions.delete(bindingId);
18470
- syncMirrorSessionState(existing.sessionId);
18518
+ syncMirrorSessionStateSafe(existing.sessionId, "mirror subscription removal");
18519
+ }
18520
+ function clearDanglingMirrorThread(subscription, reason) {
18521
+ const { store } = getBridgeContext();
18522
+ const session = store.getSession(subscription.sessionId);
18523
+ const currentThreadId = session?.sdk_session_id || subscription.threadId;
18524
+ console.warn(
18525
+ `[bridge-manager] Clearing dangling desktop thread ${currentThreadId} for session ${subscription.sessionId}: ${reason}`
18526
+ );
18527
+ store.updateSdkSessionId(subscription.sessionId, "");
18528
+ removeMirrorSubscription(subscription.bindingId);
18471
18529
  }
18472
18530
  function upsertMirrorSubscription(binding) {
18473
18531
  const { store } = getBridgeContext();
@@ -18482,7 +18540,7 @@ function upsertMirrorSubscription(binding) {
18482
18540
  removeMirrorSubscription(binding.id);
18483
18541
  return;
18484
18542
  }
18485
- const desktopSession = getDesktopSessionByThreadId(threadId);
18543
+ const desktopSession = getDesktopSessionByThreadIdSafe(threadId, "mirror subscription sync");
18486
18544
  const filePath = desktopSession?.filePath || null;
18487
18545
  const existing = state.mirrorSubscriptions.get(binding.id);
18488
18546
  if (!existing) {
@@ -18507,11 +18565,14 @@ function upsertMirrorSubscription(binding) {
18507
18565
  trailingText: "",
18508
18566
  activeMirrorTurnId: null,
18509
18567
  bufferedRecords: [],
18510
- pendingTurn: null
18568
+ pendingTurn: null,
18569
+ missingThreadPolls: 0,
18570
+ consecutiveFailures: 0,
18571
+ suspendedUntil: null
18511
18572
  };
18512
18573
  watchMirrorFile(created, filePath);
18513
18574
  state.mirrorSubscriptions.set(binding.id, created);
18514
- syncMirrorSessionState(binding.codepilotSessionId);
18575
+ syncMirrorSessionStateSafe(binding.codepilotSessionId, "mirror subscription create");
18515
18576
  return;
18516
18577
  }
18517
18578
  const previousSessionId = existing.sessionId;
@@ -18529,18 +18590,23 @@ function upsertMirrorSubscription(binding) {
18529
18590
  existing.lastDeliveredAt = session.mirror_last_event_at || null;
18530
18591
  existing.dirty = true;
18531
18592
  existing.pendingTurn = null;
18593
+ existing.missingThreadPolls = 0;
18594
+ existing.consecutiveFailures = 0;
18595
+ existing.suspendedUntil = null;
18532
18596
  resetMirrorReadState(existing);
18533
18597
  } else if (filePathChanged) {
18534
18598
  stopMirrorStreaming(existing);
18535
18599
  existing.dirty = true;
18536
18600
  existing.pendingTurn = null;
18601
+ existing.consecutiveFailures = 0;
18602
+ existing.suspendedUntil = null;
18537
18603
  resetMirrorReadState(existing);
18538
18604
  }
18539
18605
  watchMirrorFile(existing, filePath);
18540
18606
  if (previousSessionId !== binding.codepilotSessionId) {
18541
- syncMirrorSessionState(previousSessionId);
18607
+ syncMirrorSessionStateSafe(previousSessionId, "mirror subscription rebind previous session");
18542
18608
  }
18543
- syncMirrorSessionState(binding.codepilotSessionId);
18609
+ syncMirrorSessionStateSafe(binding.codepilotSessionId, "mirror subscription upsert");
18544
18610
  }
18545
18611
  function syncMirrorSubscriptionSet() {
18546
18612
  const { store } = getBridgeContext();
@@ -18554,7 +18620,14 @@ function syncMirrorSubscriptionSet() {
18554
18620
  const desiredIds = /* @__PURE__ */ new Set();
18555
18621
  for (const binding of desiredBindings) {
18556
18622
  desiredIds.add(binding.id);
18557
- upsertMirrorSubscription(binding);
18623
+ try {
18624
+ upsertMirrorSubscription(binding);
18625
+ } catch (error) {
18626
+ console.error(
18627
+ `[bridge-manager] Failed to sync mirror subscription for binding ${binding.id}:`,
18628
+ error
18629
+ );
18630
+ }
18558
18631
  }
18559
18632
  for (const bindingId of Array.from(state.mirrorSubscriptions.keys())) {
18560
18633
  if (!desiredIds.has(bindingId)) {
@@ -18569,7 +18642,29 @@ async function reconcileMirrorSubscription(subscription) {
18569
18642
  removeMirrorSubscription(subscription.bindingId);
18570
18643
  return;
18571
18644
  }
18572
- const desktopSession = getDesktopSessionByThreadId(subscription.threadId);
18645
+ if (subscription.suspendedUntil && Date.now() < subscription.suspendedUntil) {
18646
+ syncMirrorSessionStateSafe(subscription.sessionId, "mirror suspension");
18647
+ return;
18648
+ }
18649
+ if (subscription.suspendedUntil) {
18650
+ subscription.suspendedUntil = null;
18651
+ }
18652
+ const desktopSession = getDesktopSessionByThreadIdSafe(
18653
+ subscription.threadId,
18654
+ "mirror reconcile"
18655
+ );
18656
+ if (!desktopSession) {
18657
+ subscription.missingThreadPolls += 1;
18658
+ if (subscription.missingThreadPolls >= DANGLING_MIRROR_THREAD_RETRY_LIMIT) {
18659
+ clearDanglingMirrorThread(
18660
+ subscription,
18661
+ "desktop thread no longer exists locally"
18662
+ );
18663
+ return;
18664
+ }
18665
+ } else {
18666
+ subscription.missingThreadPolls = 0;
18667
+ }
18573
18668
  const filePathChanged = subscription.filePath !== (desktopSession?.filePath || null);
18574
18669
  subscription.filePath = desktopSession?.filePath || null;
18575
18670
  subscription.status = subscription.filePath ? "watching" : "stale";
@@ -18580,7 +18675,7 @@ async function reconcileMirrorSubscription(subscription) {
18580
18675
  watchMirrorFile(subscription, subscription.filePath);
18581
18676
  subscription.lastReconciledAt = nowIso2();
18582
18677
  if (!subscription.filePath) {
18583
- syncMirrorSessionState(subscription.sessionId);
18678
+ syncMirrorSessionStateSafe(subscription.sessionId, "mirror reconcile without file");
18584
18679
  return;
18585
18680
  }
18586
18681
  const snapshot = statMirrorFile(subscription.filePath);
@@ -18588,12 +18683,12 @@ async function reconcileMirrorSubscription(subscription) {
18588
18683
  subscription.status = "stale";
18589
18684
  subscription.dirty = true;
18590
18685
  resetMirrorReadState(subscription);
18591
- syncMirrorSessionState(subscription.sessionId);
18686
+ syncMirrorSessionStateSafe(subscription.sessionId, "mirror reconcile missing snapshot");
18592
18687
  return;
18593
18688
  }
18594
18689
  const unchanged = !subscription.dirty && subscription.fileIdentity === snapshot.identity && subscription.fileSize === snapshot.size && subscription.fileMtimeMs === snapshot.mtimeMs;
18595
18690
  if (unchanged && !hasPendingMirrorWork(subscription)) {
18596
- syncMirrorSessionState(subscription.sessionId);
18691
+ syncMirrorSessionStateSafe(subscription.sessionId, "mirror reconcile unchanged snapshot");
18597
18692
  return;
18598
18693
  }
18599
18694
  let deliverableRecords = [];
@@ -18648,13 +18743,13 @@ async function reconcileMirrorSubscription(subscription) {
18648
18743
  console.warn("[bridge-manager] Mirror delivery failed:", error instanceof Error ? error.message : error);
18649
18744
  }
18650
18745
  }
18651
- syncMirrorSessionState(subscription.sessionId);
18746
+ syncMirrorSessionStateSafe(subscription.sessionId, "mirror reconcile active task");
18652
18747
  return;
18653
18748
  }
18654
18749
  const finalizedTurns = timedOutTurn ? [timedOutTurn] : [];
18655
18750
  finalizedTurns.push(...consumeBufferedMirrorTurns(subscription));
18656
18751
  if (finalizedTurns.length === 0) {
18657
- syncMirrorSessionState(subscription.sessionId);
18752
+ syncMirrorSessionStateSafe(subscription.sessionId, "mirror reconcile no finalized turns");
18658
18753
  return;
18659
18754
  }
18660
18755
  try {
@@ -18663,29 +18758,66 @@ async function reconcileMirrorSubscription(subscription) {
18663
18758
  subscription.dirty = true;
18664
18759
  console.warn("[bridge-manager] Mirror delivery failed:", error instanceof Error ? error.message : error);
18665
18760
  }
18666
- syncMirrorSessionState(subscription.sessionId);
18761
+ syncMirrorSessionStateSafe(subscription.sessionId, "mirror reconcile delivered turns");
18667
18762
  }
18668
18763
  async function reconcileMirrorSubscriptions() {
18669
18764
  const state = getState();
18670
18765
  if (!state.running || state.mirrorSyncInFlight) return;
18671
18766
  state.mirrorSyncInFlight = true;
18767
+ let stage = "sync-start";
18672
18768
  try {
18673
- syncMirrorSubscriptionSet();
18674
- for (const subscription of state.mirrorSubscriptions.values()) {
18769
+ try {
18770
+ stage = "sync-subscription-set";
18771
+ syncMirrorSubscriptionSet();
18772
+ } catch (error) {
18773
+ console.error("[bridge-manager] Mirror subscription set reconcile failed:", error);
18774
+ return;
18775
+ }
18776
+ stage = "snapshot-subscriptions";
18777
+ const subscriptions = Array.from(state.mirrorSubscriptions.values());
18778
+ for (const subscription of subscriptions) {
18779
+ stage = `subscription:${subscription.bindingId}`;
18675
18780
  try {
18676
18781
  await reconcileMirrorSubscription(subscription);
18782
+ subscription.consecutiveFailures = 0;
18783
+ subscription.suspendedUntil = null;
18677
18784
  } catch (error) {
18678
- stopMirrorStreaming(subscription, "interrupted");
18679
- resetMirrorReadState(subscription);
18680
- subscription.status = "stale";
18681
- subscription.dirty = false;
18682
- console.error(
18683
- `[bridge-manager] Mirror reconcile failed for thread ${subscription.threadId}:`,
18684
- error instanceof Error ? error.stack || error.message : error
18685
- );
18686
- syncMirrorSessionState(subscription.sessionId);
18785
+ try {
18786
+ stopMirrorStreaming(subscription, "interrupted");
18787
+ subscription.pendingTurn = null;
18788
+ subscription.bufferedRecords = [];
18789
+ subscription.status = "stale";
18790
+ subscription.dirty = false;
18791
+ subscription.consecutiveFailures += 1;
18792
+ if (subscription.consecutiveFailures >= MIRROR_FAILURE_SUSPEND_THRESHOLD) {
18793
+ subscription.suspendedUntil = Date.now() + MIRROR_FAILURE_SUSPEND_MS;
18794
+ console.warn(
18795
+ `[bridge-manager] Mirror subscription for thread ${subscription.threadId} is suspended for ${Math.round(MIRROR_FAILURE_SUSPEND_MS / 1e3)}s after ${subscription.consecutiveFailures} consecutive failures`
18796
+ );
18797
+ }
18798
+ console.error(
18799
+ `[bridge-manager] Mirror reconcile failed for thread ${subscription.threadId}:`,
18800
+ describeUnknownError(error)
18801
+ );
18802
+ syncMirrorSessionStateSafe(subscription.sessionId, "mirror reconcile failure");
18803
+ } catch (recoveryError) {
18804
+ console.error(
18805
+ `[bridge-manager] Mirror reconcile recovery failed for thread ${subscription.threadId}:`,
18806
+ describeUnknownError(recoveryError)
18807
+ );
18808
+ console.error(
18809
+ `[bridge-manager] Original mirror reconcile error for thread ${subscription.threadId}:`,
18810
+ describeUnknownError(error)
18811
+ );
18812
+ }
18687
18813
  }
18688
18814
  }
18815
+ stage = "sync-complete";
18816
+ } catch (error) {
18817
+ console.error(
18818
+ `[bridge-manager] Mirror reconcile failed during ${stage}:`,
18819
+ describeUnknownError(error)
18820
+ );
18689
18821
  } finally {
18690
18822
  state.mirrorSyncInFlight = false;
18691
18823
  }
@@ -18774,6 +18906,8 @@ function listEnabledAdapterInstances() {
18774
18906
  async function stopAdapterInstance(channelType) {
18775
18907
  const state = getState();
18776
18908
  const adapter = state.adapters.get(channelType);
18909
+ state.invalidAdapters.delete(channelType);
18910
+ INVALID_ADAPTER_WARNING_CACHE.delete(channelType);
18777
18911
  if (!adapter) return;
18778
18912
  state.loopAborts.get(channelType)?.abort();
18779
18913
  state.loopAborts.delete(channelType);
@@ -18799,6 +18933,14 @@ async function syncConfiguredAdapters(options) {
18799
18933
  await stopAdapterInstance(existingKey);
18800
18934
  changed = true;
18801
18935
  }
18936
+ for (const invalidKey of Array.from(state.invalidAdapters.keys())) {
18937
+ if (desiredKeys.has(invalidKey)) continue;
18938
+ state.invalidAdapters.delete(invalidKey);
18939
+ }
18940
+ for (const invalidKey of Array.from(INVALID_ADAPTER_WARNING_CACHE.keys())) {
18941
+ if (desiredKeys.has(invalidKey)) continue;
18942
+ INVALID_ADAPTER_WARNING_CACHE.delete(invalidKey);
18943
+ }
18802
18944
  for (const instance of desiredInstances) {
18803
18945
  const existing = state.adapters.get(instance.id);
18804
18946
  const desiredFingerprint = desiredFingerprints.get(instance.id) || "";
@@ -18812,9 +18954,16 @@ async function syncConfiguredAdapters(options) {
18812
18954
  if (!adapter) continue;
18813
18955
  const configError = adapter.validateConfig();
18814
18956
  if (configError) {
18815
- console.warn(`[bridge-manager] ${instance.id} adapter not valid:`, configError);
18957
+ const invalidSignature = `${desiredFingerprint}:${configError}`;
18958
+ if (INVALID_ADAPTER_WARNING_CACHE.get(instance.id) !== invalidSignature) {
18959
+ console.warn(`[bridge-manager] ${instance.id} adapter not valid:`, configError);
18960
+ INVALID_ADAPTER_WARNING_CACHE.set(instance.id, invalidSignature);
18961
+ state.invalidAdapters.set(instance.id, invalidSignature);
18962
+ }
18816
18963
  continue;
18817
18964
  }
18965
+ state.invalidAdapters.delete(instance.id);
18966
+ INVALID_ADAPTER_WARNING_CACHE.delete(instance.id);
18818
18967
  try {
18819
18968
  state.adapters.set(instance.id, adapter);
18820
18969
  state.adapterMeta.set(instance.id, {
@@ -18874,11 +19023,11 @@ async function start() {
18874
19023
  }, 5e3);
18875
19024
  state.mirrorPollTimer = setInterval(() => {
18876
19025
  void reconcileMirrorSubscriptions().catch((err) => {
18877
- console.error("[bridge-manager] Mirror reconcile failed:", err);
19026
+ console.error("[bridge-manager] Mirror reconcile failed:", describeUnknownError(err));
18878
19027
  });
18879
19028
  }, MIRROR_POLL_INTERVAL_MS);
18880
19029
  void reconcileMirrorSubscriptions().catch((err) => {
18881
- console.error("[bridge-manager] Initial mirror reconcile failed:", err);
19030
+ console.error("[bridge-manager] Initial mirror reconcile failed:", describeUnknownError(err));
18882
19031
  });
18883
19032
  console.log(`[bridge-manager] Bridge started with ${startedCount} adapter(s)`);
18884
19033
  }
@@ -18911,6 +19060,8 @@ async function stop() {
18911
19060
  state.mirrorSuppressUntil.clear();
18912
19061
  state.mirrorIgnoredTurnIds.clear();
18913
19062
  state.queuedCounts.clear();
19063
+ state.invalidAdapters.clear();
19064
+ INVALID_ADAPTER_WARNING_CACHE.clear();
18914
19065
  for (const sessionId of activeSessionIds) {
18915
19066
  syncSessionRuntimeState(sessionId);
18916
19067
  }
@@ -19403,6 +19554,10 @@ async function handleCommand(adapter, msg, text2) {
19403
19554
  }
19404
19555
  if (args === "all") {
19405
19556
  const desktopSessions = getDisplayedDesktopThreads(MAX_DESKTOP_THREAD_LIST_LIMIT);
19557
+ if (!desktopSessions) {
19558
+ response = "\u8BFB\u53D6\u684C\u9762\u4F1A\u8BDD\u5217\u8868\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
19559
+ break;
19560
+ }
19406
19561
  if (desktopSessions.length === 0) {
19407
19562
  response = "\u6CA1\u6709\u627E\u5230\u684C\u9762\u4F1A\u8BDD\u3002\u5148\u5728 Codex Desktop App \u4E2D\u6253\u5F00\u4E00\u4E2A\u4F1A\u8BDD\uFF0C\u518D\u56DE\u6765\u8BD5\u4E00\u6B21\u3002";
19408
19563
  break;
@@ -19415,6 +19570,10 @@ async function handleCommand(adapter, msg, text2) {
19415
19570
  break;
19416
19571
  }
19417
19572
  const displayedThreads = getDisplayedDesktopThreads(MAX_DESKTOP_THREAD_LIST_LIMIT);
19573
+ if (!displayedThreads) {
19574
+ response = "\u8BFB\u53D6\u684C\u9762\u4F1A\u8BDD\u5217\u8868\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
19575
+ break;
19576
+ }
19418
19577
  const threadPick = resolveByIndexOrPrefix(args, displayedThreads, (session) => session.threadId);
19419
19578
  if (threadPick.ambiguous) {
19420
19579
  response = "\u5339\u914D\u5230\u591A\u4E2A\u684C\u9762\u4F1A\u8BDD\uFF0C\u8BF7\u5148\u53D1\u9001 `/t` \u67E5\u770B\u5217\u8868\uFF0C\u518D\u7528 `/t 1` \u8FD9\u79CD\u5E8F\u53F7\u5207\u6362\u3002";
@@ -19422,7 +19581,7 @@ async function handleCommand(adapter, msg, text2) {
19422
19581
  }
19423
19582
  if (!threadPick.match) {
19424
19583
  if (validateSessionId(args)) {
19425
- const desktop = getDesktopSessionByThreadId(args);
19584
+ const desktop = getDesktopSessionByThreadIdSafe(args, "thread switch");
19426
19585
  let binding2;
19427
19586
  try {
19428
19587
  binding2 = bindToSdkSession(msg.address, args, desktop ? {
@@ -19481,6 +19640,10 @@ async function handleCommand(adapter, msg, text2) {
19481
19640
  }
19482
19641
  const { showAll, limit } = listArgs;
19483
19642
  const desktopSessions = getDisplayedDesktopThreads(limit);
19643
+ if (!desktopSessions) {
19644
+ response = "\u8BFB\u53D6\u684C\u9762\u4F1A\u8BDD\u5217\u8868\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
19645
+ break;
19646
+ }
19484
19647
  if (desktopSessions.length === 0) {
19485
19648
  response = showAll ? "\u6CA1\u6709\u627E\u5230\u684C\u9762\u4F1A\u8BDD\u3002\u5148\u5728 Codex Desktop App \u4E2D\u6253\u5F00\u4E00\u4E2A\u4F1A\u8BDD\uFF0C\u518D\u56DE\u6765\u8BD5\u4E00\u6B21\u3002" : "\u6CA1\u6709\u627E\u5230\u6700\u8FD1\u684C\u9762\u4F1A\u8BDD\u3002\u5148\u5728 Codex Desktop App \u4E2D\u6253\u5F00\u4E00\u4E2A\u4F1A\u8BDD\uFF0C\u518D\u56DE\u6765\u8BD5\u4E00\u6B21\u3002";
19486
19649
  break;
@@ -20887,6 +21050,7 @@ var PendingPermissions = class {
20887
21050
  // src/logger.ts
20888
21051
  import fs11 from "node:fs";
20889
21052
  import path13 from "node:path";
21053
+ import { inspect as inspect2 } from "node:util";
20890
21054
  var MASK_PATTERNS = [
20891
21055
  /(?:token|secret|password|api_key)["']?\s*[:=]\s*["']?([^\s"',]+)/gi,
20892
21056
  /bot\d+:[A-Za-z0-9_-]{35}/g,
@@ -20908,6 +21072,22 @@ var LOG_PATH = path13.join(LOG_DIR, "bridge.log");
20908
21072
  var MAX_LOG_SIZE = 10 * 1024 * 1024;
20909
21073
  var MAX_ROTATED = 3;
20910
21074
  var logStream = null;
21075
+ function formatLogArg(value) {
21076
+ if (typeof value === "string") return value;
21077
+ if (value instanceof Error) {
21078
+ return value.stack || `${value.name}: ${value.message}`;
21079
+ }
21080
+ if (value === null) return "null";
21081
+ if (typeof value === "undefined") return "undefined";
21082
+ if (typeof value === "object") {
21083
+ return inspect2(value, {
21084
+ depth: 4,
21085
+ breakLength: Infinity,
21086
+ compact: true
21087
+ });
21088
+ }
21089
+ return String(value);
21090
+ }
20911
21091
  function openLogStream() {
20912
21092
  return fs11.createWriteStream(LOG_PATH, { flags: "a" });
20913
21093
  }
@@ -20937,7 +21117,7 @@ function setupLogger() {
20937
21117
  logStream = openLogStream();
20938
21118
  const write = (level, args) => {
20939
21119
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
20940
- const message = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
21120
+ const message = args.map((a) => formatLogArg(a)).join(" ");
20941
21121
  const formatted = `[${timestamp}] [${level}] ${message}`;
20942
21122
  const masked = maskSecrets(formatted);
20943
21123
  rotateIfNeeded();
@@ -5646,6 +5646,30 @@ function isProcessAlive(pid) {
5646
5646
  return false;
5647
5647
  }
5648
5648
  }
5649
+ function collectTrackedBridgePids(bridgePid, statusPid) {
5650
+ const unique = /* @__PURE__ */ new Set();
5651
+ for (const pid of [bridgePid, statusPid]) {
5652
+ if (Number.isFinite(pid) && pid > 0) {
5653
+ unique.add(pid);
5654
+ }
5655
+ }
5656
+ return [...unique];
5657
+ }
5658
+ function resolveTrackedBridgePid(bridgePid, statusPid, isAlive = isProcessAlive) {
5659
+ if (isAlive(bridgePid)) return bridgePid;
5660
+ if (isAlive(statusPid)) return statusPid;
5661
+ return bridgePid ?? statusPid;
5662
+ }
5663
+ function getTrackedBridgePids(status) {
5664
+ const resolvedStatus = status ?? readJsonFile(bridgeStatusFile, { running: false });
5665
+ return collectTrackedBridgePids(readPid(bridgePidFile), resolvedStatus.pid);
5666
+ }
5667
+ function clearBridgePidFile() {
5668
+ try {
5669
+ fs3.unlinkSync(bridgePidFile);
5670
+ } catch {
5671
+ }
5672
+ }
5649
5673
  function sleep(ms) {
5650
5674
  return new Promise((resolve) => setTimeout(resolve, ms));
5651
5675
  }
@@ -5700,7 +5724,7 @@ function getUiServerUrl(port2 = uiPort) {
5700
5724
  }
5701
5725
  function getBridgeStatus() {
5702
5726
  const status = readJsonFile(bridgeStatusFile, { running: false });
5703
- const pid = readPid(bridgePidFile) ?? status.pid;
5727
+ const pid = resolveTrackedBridgePid(readPid(bridgePidFile), status.pid);
5704
5728
  if (!isProcessAlive(pid)) {
5705
5729
  return {
5706
5730
  ...status,
@@ -5750,7 +5774,11 @@ async function waitForBridgeRunning(timeoutMs = 2e4) {
5750
5774
  async function startBridge() {
5751
5775
  ensureDirs();
5752
5776
  const current = getBridgeStatus();
5753
- if (current.running) return current;
5777
+ const extraAlivePids = getTrackedBridgePids(current).filter((pid) => pid !== current.pid && isProcessAlive(pid));
5778
+ if (current.running && extraAlivePids.length === 0) return current;
5779
+ if (current.running && extraAlivePids.length > 0) {
5780
+ await stopBridge();
5781
+ }
5754
5782
  const daemonEntry = path4.join(packageRoot, "dist", "daemon.mjs");
5755
5783
  if (!fs3.existsSync(daemonEntry)) {
5756
5784
  throw new Error(`Daemon bundle not found at ${daemonEntry}. Run npm run build first.`);
@@ -5772,31 +5800,38 @@ async function startBridge() {
5772
5800
  return status;
5773
5801
  }
5774
5802
  async function stopBridge() {
5775
- const status = getBridgeStatus();
5776
- if (!status.pid || !isProcessAlive(status.pid)) {
5777
- return { ...status, running: false };
5778
- }
5779
- if (process.platform === "win32") {
5780
- await new Promise((resolve) => {
5781
- const killer = spawn("cmd", ["/c", "taskkill", "/PID", String(status.pid), "/T", "/F"], {
5782
- stdio: "ignore",
5783
- ...WINDOWS_HIDE
5803
+ const status = readJsonFile(bridgeStatusFile, { running: false });
5804
+ const pids = getTrackedBridgePids(status).filter((pid) => isProcessAlive(pid));
5805
+ if (pids.length === 0) {
5806
+ clearBridgePidFile();
5807
+ return { ...getBridgeStatus(), running: false };
5808
+ }
5809
+ for (const pid of pids) {
5810
+ if (process.platform === "win32") {
5811
+ await new Promise((resolve) => {
5812
+ const killer = spawn("cmd", ["/c", "taskkill", "/PID", String(pid), "/T", "/F"], {
5813
+ stdio: "ignore",
5814
+ ...WINDOWS_HIDE
5815
+ });
5816
+ killer.on("exit", () => resolve());
5817
+ killer.on("error", () => resolve());
5784
5818
  });
5785
- killer.on("exit", () => resolve());
5786
- killer.on("error", () => resolve());
5787
- });
5788
- } else {
5789
- try {
5790
- process.kill(status.pid, "SIGTERM");
5791
- } catch {
5819
+ } else {
5820
+ try {
5821
+ process.kill(pid, "SIGTERM");
5822
+ } catch {
5823
+ }
5792
5824
  }
5793
5825
  }
5794
5826
  const startedAt = Date.now();
5795
5827
  while (Date.now() - startedAt < 1e4) {
5796
- const next = getBridgeStatus();
5797
- if (!next.running) return next;
5828
+ if (pids.every((pid) => !isProcessAlive(pid))) {
5829
+ clearBridgePidFile();
5830
+ return getBridgeStatus();
5831
+ }
5798
5832
  await sleep(300);
5799
5833
  }
5834
+ clearBridgePidFile();
5800
5835
  return getBridgeStatus();
5801
5836
  }
5802
5837
  async function restartBridge() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-to-im",
3
- "version": "1.0.28",
3
+ "version": "1.0.29",
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",