codex-to-im 1.0.18 → 1.0.19

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
@@ -22,6 +22,29 @@ function resolveDefaultCtiHome() {
22
22
  }
23
23
  var CTI_HOME = process.env.CTI_HOME || resolveDefaultCtiHome();
24
24
  var CONFIG_PATH = path.join(CTI_HOME, "config.env");
25
+ function parseEnvFile(content) {
26
+ const entries = /* @__PURE__ */ new Map();
27
+ for (const line of content.split("\n")) {
28
+ const trimmed = line.trim();
29
+ if (!trimmed || trimmed.startsWith("#")) continue;
30
+ const eqIdx = trimmed.indexOf("=");
31
+ if (eqIdx === -1) continue;
32
+ const key = trimmed.slice(0, eqIdx).trim();
33
+ let value = trimmed.slice(eqIdx + 1).trim();
34
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
35
+ value = value.slice(1, -1);
36
+ }
37
+ entries.set(key, value);
38
+ }
39
+ return entries;
40
+ }
41
+ function loadRawConfigEnv() {
42
+ try {
43
+ return parseEnvFile(fs.readFileSync(CONFIG_PATH, "utf-8"));
44
+ } catch {
45
+ return /* @__PURE__ */ new Map();
46
+ }
47
+ }
25
48
 
26
49
  // src/service-manager.ts
27
50
  var moduleDir = path2.dirname(fileURLToPath(import.meta.url));
@@ -104,6 +127,24 @@ function getUiServerStatus() {
104
127
  port: status.port ?? uiPort
105
128
  };
106
129
  }
130
+ function buildDaemonEnv() {
131
+ const env = { ...process.env };
132
+ env.CTI_HOME = CTI_HOME;
133
+ for (const [key, value] of loadRawConfigEnv()) {
134
+ env[key] = value;
135
+ }
136
+ delete env.CLAUDECODE;
137
+ return env;
138
+ }
139
+ async function waitForBridgeRunning(timeoutMs = 2e4) {
140
+ const startedAt = Date.now();
141
+ while (Date.now() - startedAt < timeoutMs) {
142
+ const status = getBridgeStatus();
143
+ if (status.running) return status;
144
+ await sleep(500);
145
+ }
146
+ return getBridgeStatus();
147
+ }
107
148
  async function waitForUiServer(timeoutMs = 15e3) {
108
149
  const startedAt = Date.now();
109
150
  while (Date.now() - startedAt < timeoutMs) {
@@ -119,6 +160,30 @@ async function waitForUiServer(timeoutMs = 15e3) {
119
160
  }
120
161
  return getUiServerStatus();
121
162
  }
163
+ async function startBridge() {
164
+ ensureDirs();
165
+ const current = getBridgeStatus();
166
+ if (current.running) return current;
167
+ const daemonEntry = path2.join(packageRoot, "dist", "daemon.mjs");
168
+ if (!fs2.existsSync(daemonEntry)) {
169
+ throw new Error(`Daemon bundle not found at ${daemonEntry}. Run npm run build first.`);
170
+ }
171
+ const stdoutFd = fs2.openSync(path2.join(logsDir, "bridge-launcher.out.log"), "a");
172
+ const stderrFd = fs2.openSync(path2.join(logsDir, "bridge-launcher.err.log"), "a");
173
+ const child = spawn(process.execPath, [daemonEntry], {
174
+ cwd: packageRoot,
175
+ detached: true,
176
+ env: buildDaemonEnv(),
177
+ stdio: ["ignore", stdoutFd, stderrFd],
178
+ ...WINDOWS_HIDE
179
+ });
180
+ child.unref();
181
+ const status = await waitForBridgeRunning();
182
+ if (!status.running) {
183
+ throw new Error(status.lastExitReason || "Bridge failed to report running=true.");
184
+ }
185
+ return status;
186
+ }
122
187
  async function stopBridge() {
123
188
  const status = getBridgeStatus();
124
189
  if (!status.pid || !isProcessAlive(status.pid)) {
@@ -238,8 +303,19 @@ async function main() {
238
303
  const status = await ensureUiServerRunning();
239
304
  const url = getUiServerUrl(status.port);
240
305
  openBrowser(url);
241
- process.stdout.write(`Codex to IM is available at ${url}
306
+ try {
307
+ await startBridge();
308
+ process.stdout.write(`Codex to IM is available at ${url}
242
309
  `);
310
+ } catch (error) {
311
+ process.stdout.write(`Codex to IM UI is available at ${url}
312
+ `);
313
+ process.stderr.write(
314
+ `Bridge failed to start. Open the UI and check logs/config first: ${error instanceof Error ? error.message : String(error)}
315
+ `
316
+ );
317
+ process.exitCode = 1;
318
+ }
243
319
  return;
244
320
  }
245
321
  case "url": {
@@ -260,14 +336,6 @@ async function main() {
260
336
  process.exitCode = 1;
261
337
  return;
262
338
  }
263
- case "share-feishu": {
264
- const status = await ensureUiServerRunning();
265
- const url = `${getUiServerUrl(status.port)}/#desktop`;
266
- openBrowser(url);
267
- process.stdout.write(`Opened Feishu handoff entry at ${url}
268
- `);
269
- return;
270
- }
271
339
  case "stop": {
272
340
  const bridge = await stopBridge();
273
341
  const ui = await stopUiServer();
@@ -290,7 +358,7 @@ async function main() {
290
358
  return;
291
359
  }
292
360
  default:
293
- process.stdout.write("Usage: codex-to-im [open|url|share-feishu|stop|status]\n");
361
+ process.stdout.write("Usage: codex-to-im [open|url|stop|status]\n");
294
362
  }
295
363
  }
296
364
  main().catch((error) => {
package/dist/daemon.mjs CHANGED
@@ -4047,7 +4047,9 @@ var init_codex_provider = __esm({
4047
4047
  }
4048
4048
  let sawAnyEvent = false;
4049
4049
  try {
4050
- const { events } = await thread.runStreamed(input);
4050
+ const { events } = await thread.runStreamed(input, {
4051
+ signal: params.abortController?.signal
4052
+ });
4051
4053
  for await (const event of events) {
4052
4054
  sawAnyEvent = true;
4053
4055
  if (params.abortController?.signal.aborted) {
@@ -5271,15 +5273,18 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5271
5273
  lastIncomingMessageId = /* @__PURE__ */ new Map();
5272
5274
  /** Track active typing reaction IDs per chat for cleanup. */
5273
5275
  typingReactions = /* @__PURE__ */ new Map();
5274
- /** Active streaming card state per chatId. */
5276
+ /** Active streaming card state per stream key. */
5275
5277
  activeCards = /* @__PURE__ */ new Map();
5276
- /** In-flight card creation promises per chatId — prevents duplicate creation. */
5278
+ /** In-flight card creation promises per stream key — prevents duplicate creation. */
5277
5279
  cardCreatePromises = /* @__PURE__ */ new Map();
5278
5280
  /** Cached tenant token for upload APIs. */
5279
5281
  tenantTokenCache = null;
5280
5282
  isStreamingEnabled() {
5281
5283
  return getBridgeContext().store.getSetting("bridge_feishu_streaming_enabled") !== "false";
5282
5284
  }
5285
+ resolveStreamKey(chatId, streamKey) {
5286
+ return streamKey?.trim() || chatId;
5287
+ }
5283
5288
  // ── Lifecycle ───────────────────────────────────────────────
5284
5289
  async start() {
5285
5290
  if (this.running) return;
@@ -5384,10 +5389,10 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5384
5389
  * Add a "Typing" emoji reaction to the user's message and create streaming card.
5385
5390
  * Called by bridge-manager via onMessageStart().
5386
5391
  */
5387
- onMessageStart(chatId) {
5392
+ onMessageStart(chatId, streamKey) {
5388
5393
  const messageId = this.lastIncomingMessageId.get(chatId);
5389
5394
  if (messageId && this.isStreamingEnabled()) {
5390
- this.createStreamingCard(chatId, messageId).catch(() => {
5395
+ this.createStreamingCard(chatId, messageId, streamKey).catch(() => {
5391
5396
  });
5392
5397
  }
5393
5398
  if (!messageId || !this.restClient) return;
@@ -5410,8 +5415,8 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5410
5415
  * Remove the "Typing" emoji reaction and clean up card state.
5411
5416
  * Called by bridge-manager via onMessageEnd().
5412
5417
  */
5413
- onMessageEnd(chatId) {
5414
- this.cleanupCard(chatId);
5418
+ onMessageEnd(chatId, streamKey) {
5419
+ this.cleanupCard(chatId, streamKey);
5415
5420
  const reactionId = this.typingReactions.get(chatId);
5416
5421
  const messageId = this.lastIncomingMessageId.get(chatId);
5417
5422
  if (!reactionId || !messageId || !this.restClient) return;
@@ -5462,17 +5467,19 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5462
5467
  * Create a new streaming card and send it as a message.
5463
5468
  * Returns true if card was created successfully.
5464
5469
  */
5465
- createStreamingCard(chatId, replyToMessageId) {
5466
- if (!this.restClient || this.activeCards.has(chatId)) return Promise.resolve(false);
5467
- const existing = this.cardCreatePromises.get(chatId);
5470
+ createStreamingCard(chatId, replyToMessageId, streamKey) {
5471
+ const cardKey = this.resolveStreamKey(chatId, streamKey);
5472
+ if (!this.restClient || this.activeCards.has(cardKey)) return Promise.resolve(false);
5473
+ const existing = this.cardCreatePromises.get(cardKey);
5468
5474
  if (existing) return existing;
5469
- const promise = this._doCreateStreamingCard(chatId, replyToMessageId);
5470
- this.cardCreatePromises.set(chatId, promise);
5471
- promise.finally(() => this.cardCreatePromises.delete(chatId));
5475
+ const promise = this._doCreateStreamingCard(chatId, replyToMessageId, cardKey);
5476
+ this.cardCreatePromises.set(cardKey, promise);
5477
+ promise.finally(() => this.cardCreatePromises.delete(cardKey));
5472
5478
  return promise;
5473
5479
  }
5474
- async _doCreateStreamingCard(chatId, replyToMessageId) {
5480
+ async _doCreateStreamingCard(chatId, replyToMessageId, streamKey) {
5475
5481
  if (!this.restClient) return false;
5482
+ const cardKey = this.resolveStreamKey(chatId, streamKey);
5476
5483
  const cardkit = this.restClient.cardkit?.v1;
5477
5484
  if (!cardkit?.card) {
5478
5485
  console.warn("[feishu-adapter] CardKit v1 API is unavailable in the current Feishu SDK client");
@@ -5526,7 +5533,8 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5526
5533
  console.warn("[feishu-adapter] Card message send returned no message_id");
5527
5534
  return false;
5528
5535
  }
5529
- this.activeCards.set(chatId, {
5536
+ this.activeCards.set(cardKey, {
5537
+ chatId,
5530
5538
  cardId,
5531
5539
  messageId,
5532
5540
  sequence: 0,
@@ -5537,7 +5545,7 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5537
5545
  lastUpdateAt: 0,
5538
5546
  throttleTimer: null
5539
5547
  });
5540
- console.log(`[feishu-adapter] Streaming card created: cardId=${cardId}, msgId=${messageId}`);
5548
+ console.log(`[feishu-adapter] Streaming card created: streamKey=${cardKey}, cardId=${cardId}, msgId=${messageId}`);
5541
5549
  return true;
5542
5550
  } catch (err) {
5543
5551
  console.warn("[feishu-adapter] Failed to create streaming card:", err instanceof Error ? err.message : err);
@@ -5547,8 +5555,9 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5547
5555
  /**
5548
5556
  * Update streaming card content with throttling.
5549
5557
  */
5550
- updateCardContent(chatId, text2) {
5551
- const state = this.activeCards.get(chatId);
5558
+ updateCardContent(chatId, text2, streamKey) {
5559
+ const cardKey = this.resolveStreamKey(chatId, streamKey);
5560
+ const state = this.activeCards.get(cardKey);
5552
5561
  if (!state || !this.restClient) return;
5553
5562
  if (state.thinking && text2.trim()) {
5554
5563
  state.thinking = false;
@@ -5559,7 +5568,7 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5559
5568
  if (!state.throttleTimer) {
5560
5569
  state.throttleTimer = setTimeout(() => {
5561
5570
  state.throttleTimer = null;
5562
- this.flushCardUpdate(chatId);
5571
+ this.flushCardUpdate(cardKey);
5563
5572
  }, CARD_THROTTLE_MS - elapsed);
5564
5573
  }
5565
5574
  return;
@@ -5568,13 +5577,13 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5568
5577
  clearTimeout(state.throttleTimer);
5569
5578
  state.throttleTimer = null;
5570
5579
  }
5571
- this.flushCardUpdate(chatId);
5580
+ this.flushCardUpdate(cardKey);
5572
5581
  }
5573
5582
  /**
5574
5583
  * Flush pending card update to Feishu API.
5575
5584
  */
5576
- flushCardUpdate(chatId) {
5577
- const state = this.activeCards.get(chatId);
5585
+ flushCardUpdate(streamKey) {
5586
+ const state = this.activeCards.get(streamKey);
5578
5587
  if (!state || !this.restClient) return;
5579
5588
  const cardkit = this.restClient.cardkit?.v1;
5580
5589
  if (!cardkit?.cardElement?.content) return;
@@ -5594,24 +5603,26 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5594
5603
  /**
5595
5604
  * Update tool progress in the streaming card.
5596
5605
  */
5597
- updateToolProgress(chatId, tools) {
5598
- const state = this.activeCards.get(chatId);
5606
+ updateToolProgress(chatId, tools, streamKey) {
5607
+ const cardKey = this.resolveStreamKey(chatId, streamKey);
5608
+ const state = this.activeCards.get(cardKey);
5599
5609
  if (!state) return;
5600
5610
  state.toolCalls = tools;
5601
- this.updateCardContent(chatId, state.pendingText || "");
5611
+ this.updateCardContent(chatId, state.pendingText || "", cardKey);
5602
5612
  }
5603
5613
  /**
5604
5614
  * Finalize the streaming card: close streaming mode, update with final content + footer.
5605
5615
  */
5606
- async finalizeCard(chatId, status, responseText) {
5607
- const pending = this.cardCreatePromises.get(chatId);
5616
+ async finalizeCard(chatId, status, responseText, streamKey) {
5617
+ const cardKey = this.resolveStreamKey(chatId, streamKey);
5618
+ const pending = this.cardCreatePromises.get(cardKey);
5608
5619
  if (pending) {
5609
5620
  try {
5610
5621
  await pending;
5611
5622
  } catch {
5612
5623
  }
5613
5624
  }
5614
- const state = this.activeCards.get(chatId);
5625
+ const state = this.activeCards.get(cardKey);
5615
5626
  if (!state || !this.restClient) return false;
5616
5627
  const cardkit = this.restClient.cardkit?.v1;
5617
5628
  if (!cardkit?.card?.settings || !cardkit?.card?.update) return false;
@@ -5647,63 +5658,66 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5647
5658
  sequence: state.sequence
5648
5659
  }
5649
5660
  });
5650
- console.log(`[feishu-adapter] Card finalized: cardId=${state.cardId}, status=${status}, elapsed=${formatElapsed(elapsedMs)}`);
5661
+ console.log(`[feishu-adapter] Card finalized: streamKey=${cardKey}, cardId=${state.cardId}, status=${status}, elapsed=${formatElapsed(elapsedMs)}`);
5651
5662
  return true;
5652
5663
  } catch (err) {
5653
5664
  console.warn("[feishu-adapter] Card finalize failed:", err instanceof Error ? err.message : err);
5654
5665
  return false;
5655
5666
  } finally {
5656
- this.activeCards.delete(chatId);
5667
+ this.activeCards.delete(cardKey);
5657
5668
  }
5658
5669
  }
5659
5670
  /**
5660
5671
  * Clean up card state without finalizing (e.g. on unexpected errors).
5661
5672
  */
5662
- cleanupCard(chatId) {
5663
- this.cardCreatePromises.delete(chatId);
5664
- const state = this.activeCards.get(chatId);
5673
+ cleanupCard(chatId, streamKey) {
5674
+ const cardKey = this.resolveStreamKey(chatId, streamKey);
5675
+ this.cardCreatePromises.delete(cardKey);
5676
+ const state = this.activeCards.get(cardKey);
5665
5677
  if (!state) return;
5666
5678
  if (state.throttleTimer) {
5667
5679
  clearTimeout(state.throttleTimer);
5668
5680
  }
5669
- this.activeCards.delete(chatId);
5681
+ this.activeCards.delete(cardKey);
5670
5682
  }
5671
5683
  /**
5672
5684
  * Check if there is an active streaming card for a given chat.
5673
5685
  */
5674
- hasActiveCard(chatId) {
5675
- return this.activeCards.has(chatId);
5686
+ hasActiveCard(chatId, streamKey) {
5687
+ return this.activeCards.has(this.resolveStreamKey(chatId, streamKey));
5676
5688
  }
5677
5689
  // ── Streaming adapter interface ────────────────────────────────
5678
5690
  /**
5679
5691
  * Called by bridge-manager on each text SSE event.
5680
5692
  * Creates streaming card on first call, then updates content.
5681
5693
  */
5682
- onStreamText(chatId, fullText) {
5694
+ onStreamText(chatId, fullText, streamKey) {
5683
5695
  if (!this.isStreamingEnabled()) return;
5684
- if (!this.activeCards.has(chatId)) {
5696
+ const cardKey = this.resolveStreamKey(chatId, streamKey);
5697
+ if (!this.activeCards.has(cardKey)) {
5685
5698
  const messageId = this.lastIncomingMessageId.get(chatId);
5686
- this.createStreamingCard(chatId, messageId).then((ok) => {
5687
- if (ok) this.updateCardContent(chatId, fullText);
5699
+ this.createStreamingCard(chatId, messageId, cardKey).then((ok) => {
5700
+ if (ok) this.updateCardContent(chatId, fullText, cardKey);
5688
5701
  }).catch(() => {
5689
5702
  });
5690
5703
  return;
5691
5704
  }
5692
- this.updateCardContent(chatId, fullText);
5705
+ this.updateCardContent(chatId, fullText, cardKey);
5693
5706
  }
5694
- onMirrorStreamStart(chatId) {
5707
+ onMirrorStreamStart(chatId, streamKey) {
5695
5708
  if (!this.isStreamingEnabled()) return;
5696
- if (this.activeCards.has(chatId)) return;
5697
- this.createStreamingCard(chatId, void 0).catch(() => {
5709
+ const cardKey = this.resolveStreamKey(chatId, streamKey);
5710
+ if (this.activeCards.has(cardKey)) return;
5711
+ this.createStreamingCard(chatId, void 0, cardKey).catch(() => {
5698
5712
  });
5699
5713
  }
5700
- onToolEvent(chatId, tools) {
5714
+ onToolEvent(chatId, tools, streamKey) {
5701
5715
  if (!this.isStreamingEnabled()) return;
5702
- this.updateToolProgress(chatId, tools);
5716
+ this.updateToolProgress(chatId, tools, streamKey);
5703
5717
  }
5704
- async onStreamEnd(chatId, status, responseText) {
5718
+ async onStreamEnd(chatId, status, responseText, streamKey) {
5705
5719
  if (!this.isStreamingEnabled()) return false;
5706
- return this.finalizeCard(chatId, status, responseText);
5720
+ return this.finalizeCard(chatId, status, responseText, streamKey);
5707
5721
  }
5708
5722
  // ── Send ────────────────────────────────────────────────────
5709
5723
  async send(message) {
@@ -16818,8 +16832,6 @@ function resolveCommandAlias(rawCommand, args) {
16818
16832
  return "/help";
16819
16833
  case "/t":
16820
16834
  return !args ? "/threads" : /^(all|n\b)/i.test(args.trim()) ? "/threads" : "/thread";
16821
- case "/s":
16822
- return args ? "/use" : "/sessions";
16823
16835
  case "/n":
16824
16836
  return "/new";
16825
16837
  case "/m":
@@ -16890,6 +16902,12 @@ function getSessionDisplayName(session, fallbackDirectory) {
16890
16902
  function nowIso() {
16891
16903
  return (/* @__PURE__ */ new Date()).toISOString();
16892
16904
  }
16905
+ function buildInteractiveStreamKey(sessionId, messageId) {
16906
+ return `im:${sessionId}:${messageId}`;
16907
+ }
16908
+ function buildMirrorStreamKey(sessionId, turnId, startedAt) {
16909
+ return `mirror:${sessionId}:${turnId || startedAt}`;
16910
+ }
16893
16911
  function getWorkspaceRoot() {
16894
16912
  const { store } = getBridgeContext();
16895
16913
  return store.getSetting("bridge_default_workspace_root") || DEFAULT_WORKSPACE_ROOT;
@@ -17829,6 +17847,16 @@ function formatMirrorMessage(threadTitle, userText, assistantText, markdown = fa
17829
17847
  sections.unshift(buildMirrorTitle(threadTitle, markdown));
17830
17848
  return sections.join("\n\n").trim();
17831
17849
  }
17850
+ function buildMirrorTimeoutNotice(markdown = false) {
17851
+ return markdown ? "> \u8D85\u65F6\u63D0\u9192\uFF1A\u957F\u65F6\u95F4\u6CA1\u6709\u6536\u5230\u65B0\u7684\u684C\u9762\u4F1A\u8BDD\u8F93\u51FA\uFF0C\u672C\u6B21\u6D41\u5F0F\u540C\u6B65\u5DF2\u5148\u7ED3\u675F\uFF1B\u5982\u679C\u684C\u9762\u540E\u7EED\u7EE7\u7EED\u4EA7\u51FA\u5185\u5BB9\uFF0C\u4F1A\u91CD\u65B0\u5F00\u59CB\u65B0\u4E00\u8F6E\u540C\u6B65\u3002" : "\u8D85\u65F6\u63D0\u9192\uFF1A\u957F\u65F6\u95F4\u6CA1\u6709\u6536\u5230\u65B0\u7684\u684C\u9762\u4F1A\u8BDD\u8F93\u51FA\uFF0C\u672C\u6B21\u6D41\u5F0F\u540C\u6B65\u5DF2\u5148\u7ED3\u675F\uFF1B\u5982\u679C\u684C\u9762\u540E\u7EED\u7EE7\u7EED\u4EA7\u51FA\u5185\u5BB9\uFF0C\u4F1A\u91CD\u65B0\u5F00\u59CB\u65B0\u4E00\u8F6E\u540C\u6B65\u3002";
17852
+ }
17853
+ function appendMirrorTimeoutNotice(text2, markdown = false) {
17854
+ const notice = buildMirrorTimeoutNotice(markdown);
17855
+ const normalized = text2.trim();
17856
+ return normalized ? `${normalized}
17857
+
17858
+ ${notice}` : notice;
17859
+ }
17832
17860
  function getMirrorStreamingAdapter(subscription) {
17833
17861
  const state = getState();
17834
17862
  const adapter = state.adapters.get(subscription.channelType);
@@ -17855,9 +17883,9 @@ function startMirrorStreaming(subscription, turnState) {
17855
17883
  const adapter = getMirrorStreamingAdapter(subscription);
17856
17884
  if (!adapter || turnState.streamStarted) return;
17857
17885
  try {
17858
- adapter.onMirrorStreamStart?.(subscription.chatId);
17886
+ adapter.onMirrorStreamStart?.(subscription.chatId, turnState.streamKey);
17859
17887
  if (!adapter.onMirrorStreamStart) {
17860
- adapter.onStreamText?.(subscription.chatId, "");
17888
+ adapter.onStreamText?.(subscription.chatId, "", turnState.streamKey);
17861
17889
  }
17862
17890
  turnState.streamStarted = true;
17863
17891
  } catch {
@@ -17873,7 +17901,7 @@ function updateMirrorStreaming(subscription, turnState) {
17873
17901
  );
17874
17902
  if (!text2) return;
17875
17903
  try {
17876
- adapter.onStreamText?.(subscription.chatId, text2);
17904
+ adapter.onStreamText?.(subscription.chatId, text2, turnState.streamKey);
17877
17905
  } catch {
17878
17906
  }
17879
17907
  }
@@ -17882,7 +17910,7 @@ function updateMirrorToolProgress(subscription, turnState) {
17882
17910
  if (!adapter || typeof adapter.onToolEvent !== "function") return;
17883
17911
  startMirrorStreaming(subscription, turnState);
17884
17912
  try {
17885
- adapter.onToolEvent(subscription.chatId, Array.from(turnState.toolCalls.values()));
17913
+ adapter.onToolEvent(subscription.chatId, Array.from(turnState.toolCalls.values()), turnState.streamKey);
17886
17914
  } catch {
17887
17915
  }
17888
17916
  }
@@ -17894,7 +17922,7 @@ function stopMirrorStreaming(subscription, status = "interrupted") {
17894
17922
  subscription.channelType,
17895
17923
  getMirrorStreamingText(subscription, pendingTurn)
17896
17924
  );
17897
- void adapter.onStreamEnd(subscription.chatId, status, text2).catch(() => {
17925
+ void adapter.onStreamEnd(subscription.chatId, status, text2, pendingTurn.streamKey).catch(() => {
17898
17926
  });
17899
17927
  }
17900
17928
  async function deliverMirrorTurn(subscription, turn) {
@@ -17904,8 +17932,10 @@ async function deliverMirrorTurn(subscription, turn) {
17904
17932
  const title = getDesktopThreadTitle(subscription.threadId)?.trim() || "\u684C\u9762\u7EBF\u7A0B";
17905
17933
  const responseParseMode = getFeedbackParseMode(subscription.channelType);
17906
17934
  const markdown = responseParseMode === "Markdown";
17907
- const renderedText = formatMirrorMessage(title, turn.userText, turn.text, markdown);
17908
- const renderedStreamText = formatMirrorMessage(title, turn.userText, turn.text, markdown, true);
17935
+ const renderedTextBase = formatMirrorMessage(title, turn.userText, turn.text, markdown);
17936
+ const renderedStreamTextBase = formatMirrorMessage(title, turn.userText, turn.text, markdown, true);
17937
+ const renderedText = turn.timedOut ? appendMirrorTimeoutNotice(renderedTextBase || buildMirrorTitle(title, markdown), markdown) : renderedTextBase;
17938
+ const renderedStreamText = turn.timedOut ? appendMirrorTimeoutNotice(renderedStreamTextBase || buildMirrorTitle(title, markdown), markdown) : renderedStreamTextBase;
17909
17939
  const text2 = renderedText ? renderFeedbackText(renderedText, responseParseMode) : "";
17910
17940
  const streamText = renderFeedbackText(
17911
17941
  renderedStreamText || buildMirrorTitle(title, markdown),
@@ -17916,7 +17946,8 @@ async function deliverMirrorTurn(subscription, turn) {
17916
17946
  const finalized = await adapter.onStreamEnd(
17917
17947
  subscription.chatId,
17918
17948
  turn.status,
17919
- streamText
17949
+ streamText,
17950
+ turn.streamKey
17920
17951
  );
17921
17952
  if (finalized) {
17922
17953
  subscription.lastDeliveredAt = turn.timestamp || nowIso();
@@ -17948,10 +17979,11 @@ async function deliverMirrorTurns(subscription, turns) {
17948
17979
  await deliverMirrorTurn(subscription, turn);
17949
17980
  }
17950
17981
  }
17951
- function createMirrorTurnState(timestamp, turnId) {
17982
+ function createMirrorTurnState(sessionId, timestamp, turnId) {
17952
17983
  const safeTimestamp = timestamp || nowIso();
17953
17984
  return {
17954
17985
  turnId: turnId || null,
17986
+ streamKey: buildMirrorStreamKey(sessionId, turnId || null, safeTimestamp),
17955
17987
  startedAt: safeTimestamp,
17956
17988
  lastActivityAt: safeTimestamp,
17957
17989
  userText: null,
@@ -17985,7 +18017,7 @@ ${normalized}` : normalized;
17985
18017
  }
17986
18018
  function ensureMirrorTurnState(subscription, record) {
17987
18019
  if (!subscription.pendingTurn) {
17988
- subscription.pendingTurn = createMirrorTurnState(record.timestamp, record.turnId);
18020
+ subscription.pendingTurn = createMirrorTurnState(subscription.sessionId, record.timestamp, record.turnId);
17989
18021
  return subscription.pendingTurn;
17990
18022
  }
17991
18023
  if (!subscription.pendingTurn.turnId && record.turnId) {
@@ -18008,11 +18040,13 @@ function finalizeMirrorTurn(subscription, signature, timestamp, status, preferre
18008
18040
  const userText = pendingTurn.userText?.trim() || null;
18009
18041
  if (!text2 && !userText && pendingTurn.toolCalls.size === 0) return null;
18010
18042
  return {
18043
+ streamKey: pendingTurn.streamKey,
18011
18044
  userText,
18012
18045
  text: text2,
18013
18046
  signature,
18014
18047
  timestamp: timestamp || pendingTurn.lastActivityAt || nowIso(),
18015
- status
18048
+ status,
18049
+ ...signature.startsWith("timeout:") ? { timedOut: true } : {}
18016
18050
  };
18017
18051
  }
18018
18052
  function consumeMirrorRecords(subscription, records) {
@@ -18026,7 +18060,7 @@ function consumeMirrorRecords(subscription, records) {
18026
18060
  if (superseded) finalized.push(superseded);
18027
18061
  }
18028
18062
  if (!subscription.pendingTurn) {
18029
- subscription.pendingTurn = createMirrorTurnState(record.timestamp, record.turnId);
18063
+ subscription.pendingTurn = createMirrorTurnState(subscription.sessionId, record.timestamp, record.turnId);
18030
18064
  } else {
18031
18065
  if (!subscription.pendingTurn.turnId && record.turnId) {
18032
18066
  subscription.pendingTurn.turnId = record.turnId;
@@ -18305,11 +18339,21 @@ async function reconcileMirrorSubscription(subscription) {
18305
18339
  subscription.bufferedRecords.push(...filteredRecords);
18306
18340
  }
18307
18341
  }
18342
+ const timedOutTurn = flushTimedOutMirrorTurn(subscription);
18308
18343
  if (getState().activeTasks.has(subscription.sessionId) || isMirrorSuppressed(subscription.sessionId)) {
18344
+ if (timedOutTurn) {
18345
+ try {
18346
+ await deliverMirrorTurns(subscription, [timedOutTurn]);
18347
+ } catch (error) {
18348
+ subscription.dirty = true;
18349
+ console.warn("[bridge-manager] Mirror delivery failed:", error instanceof Error ? error.message : error);
18350
+ }
18351
+ }
18309
18352
  syncMirrorSessionState(subscription.sessionId);
18310
18353
  return;
18311
18354
  }
18312
- const finalizedTurns = consumeBufferedMirrorTurns(subscription);
18355
+ const finalizedTurns = timedOutTurn ? [timedOutTurn] : [];
18356
+ finalizedTurns.push(...consumeBufferedMirrorTurns(subscription));
18313
18357
  if (finalizedTurns.length === 0) {
18314
18358
  syncMirrorSessionState(subscription.sessionId);
18315
18359
  return;
@@ -18712,7 +18756,8 @@ async function handleMessage(adapter, msg) {
18712
18756
  return;
18713
18757
  }
18714
18758
  const binding = resolve(msg.address);
18715
- adapter.onMessageStart?.(msg.address.chatId);
18759
+ const streamKey = buildInteractiveStreamKey(binding.codepilotSessionId, msg.messageId);
18760
+ adapter.onMessageStart?.(msg.address.chatId, streamKey);
18716
18761
  const taskAbort = new AbortController();
18717
18762
  const state = getState();
18718
18763
  resetMirrorSessionForInteractiveRun(binding.codepilotSessionId);
@@ -18773,7 +18818,7 @@ async function handleMessage(adapter, msg) {
18773
18818
  stripOutboundArtifactBlocksForStreaming(fullText)
18774
18819
  );
18775
18820
  try {
18776
- adapter.onStreamText(msg.address.chatId, rendered);
18821
+ adapter.onStreamText(msg.address.chatId, rendered, streamKey);
18777
18822
  } catch {
18778
18823
  }
18779
18824
  } : void 0;
@@ -18785,7 +18830,7 @@ async function handleMessage(adapter, msg) {
18785
18830
  if (existing) existing.status = status;
18786
18831
  }
18787
18832
  try {
18788
- adapter.onToolEvent(msg.address.chatId, Array.from(toolCallTracker.values()));
18833
+ adapter.onToolEvent(msg.address.chatId, Array.from(toolCallTracker.values()), streamKey);
18789
18834
  } catch {
18790
18835
  }
18791
18836
  } : void 0;
@@ -18818,7 +18863,8 @@ async function handleMessage(adapter, msg) {
18818
18863
  cardFinalized = await adapter.onStreamEnd(
18819
18864
  msg.address.chatId,
18820
18865
  status,
18821
- renderFeedbackTextForChannel(adapter.channelType, result.responseText)
18866
+ renderFeedbackTextForChannel(adapter.channelType, result.responseText),
18867
+ streamKey
18822
18868
  );
18823
18869
  } catch (err) {
18824
18870
  console.warn("[bridge-manager] Card finalize failed:", err instanceof Error ? err.message : err);
@@ -18863,7 +18909,7 @@ async function handleMessage(adapter, msg) {
18863
18909
  }
18864
18910
  if (hasStreamingCards && adapter.onStreamEnd && taskAbort.signal.aborted) {
18865
18911
  try {
18866
- await adapter.onStreamEnd(msg.address.chatId, "interrupted", "");
18912
+ await adapter.onStreamEnd(msg.address.chatId, "interrupted", "", streamKey);
18867
18913
  } catch {
18868
18914
  }
18869
18915
  }
@@ -18872,7 +18918,7 @@ async function handleMessage(adapter, msg) {
18872
18918
  }
18873
18919
  state.activeTasks.delete(binding.codepilotSessionId);
18874
18920
  syncSessionRuntimeState(binding.codepilotSessionId);
18875
- adapter.onMessageEnd?.(msg.address.chatId);
18921
+ adapter.onMessageEnd?.(msg.address.chatId, streamKey);
18876
18922
  ack();
18877
18923
  }
18878
18924
  }
@@ -18930,8 +18976,6 @@ async function handleCommand(adapter, msg, text2) {
18930
18976
  const oldTask = st.activeTasks.get(currentBinding.codepilotSessionId);
18931
18977
  if (oldTask) {
18932
18978
  oldTask.abort();
18933
- st.activeTasks.delete(currentBinding.codepilotSessionId);
18934
- syncSessionRuntimeState(currentBinding.codepilotSessionId);
18935
18979
  }
18936
18980
  }
18937
18981
  const workDir = resolved.workDir;
@@ -19377,8 +19421,6 @@ ${truncateHistoryContent(formatStoredMessageContent(message.content))}`;
19377
19421
  const taskAbort = st.activeTasks.get(binding.codepilotSessionId);
19378
19422
  if (taskAbort) {
19379
19423
  taskAbort.abort();
19380
- st.activeTasks.delete(binding.codepilotSessionId);
19381
- syncSessionRuntimeState(binding.codepilotSessionId);
19382
19424
  response = "\u6B63\u5728\u505C\u6B62\u5F53\u524D\u4EFB\u52A1...";
19383
19425
  } else {
19384
19426
  response = "\u5F53\u524D\u6CA1\u6709\u6B63\u5728\u8FD0\u884C\u7684\u4EFB\u52A1\u3002";
@@ -5045,7 +5045,9 @@ var CodexProvider = class {
5045
5045
  }
5046
5046
  let sawAnyEvent = false;
5047
5047
  try {
5048
- const { events } = await thread.runStreamed(input);
5048
+ const { events } = await thread.runStreamed(input, {
5049
+ signal: params.abortController?.signal
5050
+ });
5049
5051
  for await (const event of events) {
5050
5052
  sawAnyEvent = true;
5051
5053
  if (params.abortController?.signal.aborted) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-to-im",
3
- "version": "1.0.18",
3
+ "version": "1.0.19",
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",