codex-to-im 1.0.18 → 1.0.20

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;
@@ -5638,7 +5649,16 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5638
5649
  status: statusLabels[status] || status,
5639
5650
  elapsed: formatElapsed(elapsedMs)
5640
5651
  };
5641
- const finalCardJson = buildFinalCardJson(responseText, state.toolCalls, footer);
5652
+ const existingText = state.pendingText || "";
5653
+ const trimmedExisting = existingText.trim();
5654
+ const trimmedResponse = responseText.trim();
5655
+ let finalText = trimmedResponse || trimmedExisting;
5656
+ if (status === "interrupted" && trimmedExisting && trimmedResponse && trimmedResponse !== trimmedExisting && !trimmedExisting.includes(trimmedResponse)) {
5657
+ finalText = `${trimmedExisting}
5658
+
5659
+ ${trimmedResponse}`;
5660
+ }
5661
+ const finalCardJson = buildFinalCardJson(finalText, state.toolCalls, footer);
5642
5662
  state.sequence++;
5643
5663
  await cardkit.card.update({
5644
5664
  path: { card_id: state.cardId },
@@ -5647,63 +5667,66 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5647
5667
  sequence: state.sequence
5648
5668
  }
5649
5669
  });
5650
- console.log(`[feishu-adapter] Card finalized: cardId=${state.cardId}, status=${status}, elapsed=${formatElapsed(elapsedMs)}`);
5670
+ console.log(`[feishu-adapter] Card finalized: streamKey=${cardKey}, cardId=${state.cardId}, status=${status}, elapsed=${formatElapsed(elapsedMs)}`);
5651
5671
  return true;
5652
5672
  } catch (err) {
5653
5673
  console.warn("[feishu-adapter] Card finalize failed:", err instanceof Error ? err.message : err);
5654
5674
  return false;
5655
5675
  } finally {
5656
- this.activeCards.delete(chatId);
5676
+ this.activeCards.delete(cardKey);
5657
5677
  }
5658
5678
  }
5659
5679
  /**
5660
5680
  * Clean up card state without finalizing (e.g. on unexpected errors).
5661
5681
  */
5662
- cleanupCard(chatId) {
5663
- this.cardCreatePromises.delete(chatId);
5664
- const state = this.activeCards.get(chatId);
5682
+ cleanupCard(chatId, streamKey) {
5683
+ const cardKey = this.resolveStreamKey(chatId, streamKey);
5684
+ this.cardCreatePromises.delete(cardKey);
5685
+ const state = this.activeCards.get(cardKey);
5665
5686
  if (!state) return;
5666
5687
  if (state.throttleTimer) {
5667
5688
  clearTimeout(state.throttleTimer);
5668
5689
  }
5669
- this.activeCards.delete(chatId);
5690
+ this.activeCards.delete(cardKey);
5670
5691
  }
5671
5692
  /**
5672
5693
  * Check if there is an active streaming card for a given chat.
5673
5694
  */
5674
- hasActiveCard(chatId) {
5675
- return this.activeCards.has(chatId);
5695
+ hasActiveCard(chatId, streamKey) {
5696
+ return this.activeCards.has(this.resolveStreamKey(chatId, streamKey));
5676
5697
  }
5677
5698
  // ── Streaming adapter interface ────────────────────────────────
5678
5699
  /**
5679
5700
  * Called by bridge-manager on each text SSE event.
5680
5701
  * Creates streaming card on first call, then updates content.
5681
5702
  */
5682
- onStreamText(chatId, fullText) {
5703
+ onStreamText(chatId, fullText, streamKey) {
5683
5704
  if (!this.isStreamingEnabled()) return;
5684
- if (!this.activeCards.has(chatId)) {
5705
+ const cardKey = this.resolveStreamKey(chatId, streamKey);
5706
+ if (!this.activeCards.has(cardKey)) {
5685
5707
  const messageId = this.lastIncomingMessageId.get(chatId);
5686
- this.createStreamingCard(chatId, messageId).then((ok) => {
5687
- if (ok) this.updateCardContent(chatId, fullText);
5708
+ this.createStreamingCard(chatId, messageId, cardKey).then((ok) => {
5709
+ if (ok) this.updateCardContent(chatId, fullText, cardKey);
5688
5710
  }).catch(() => {
5689
5711
  });
5690
5712
  return;
5691
5713
  }
5692
- this.updateCardContent(chatId, fullText);
5714
+ this.updateCardContent(chatId, fullText, cardKey);
5693
5715
  }
5694
- onMirrorStreamStart(chatId) {
5716
+ onMirrorStreamStart(chatId, streamKey) {
5695
5717
  if (!this.isStreamingEnabled()) return;
5696
- if (this.activeCards.has(chatId)) return;
5697
- this.createStreamingCard(chatId, void 0).catch(() => {
5718
+ const cardKey = this.resolveStreamKey(chatId, streamKey);
5719
+ if (this.activeCards.has(cardKey)) return;
5720
+ this.createStreamingCard(chatId, void 0, cardKey).catch(() => {
5698
5721
  });
5699
5722
  }
5700
- onToolEvent(chatId, tools) {
5723
+ onToolEvent(chatId, tools, streamKey) {
5701
5724
  if (!this.isStreamingEnabled()) return;
5702
- this.updateToolProgress(chatId, tools);
5725
+ this.updateToolProgress(chatId, tools, streamKey);
5703
5726
  }
5704
- async onStreamEnd(chatId, status, responseText) {
5727
+ async onStreamEnd(chatId, status, responseText, streamKey) {
5705
5728
  if (!this.isStreamingEnabled()) return false;
5706
- return this.finalizeCard(chatId, status, responseText);
5729
+ return this.finalizeCard(chatId, status, responseText, streamKey);
5707
5730
  }
5708
5731
  // ── Send ────────────────────────────────────────────────────
5709
5732
  async send(message) {
@@ -16784,7 +16807,8 @@ var MIRROR_WATCH_DEBOUNCE_MS = 350;
16784
16807
  var MIRROR_EVENT_BATCH_LIMIT = 8;
16785
16808
  var MIRROR_SUPPRESSION_WINDOW_MS = 4e3;
16786
16809
  var MIRROR_PROMPT_MATCH_GRACE_MS = 12e4;
16787
- var MIRROR_IDLE_TIMEOUT_MS = 12e5;
16810
+ var INTERACTIVE_IDLE_REMINDER_MS = 6e5;
16811
+ var MIRROR_IDLE_TIMEOUT_MS = 6e5;
16788
16812
  var AVAILABLE_CODEX_MODELS = listSelectableCodexModels();
16789
16813
  var AVAILABLE_CODEX_MODEL_MAP = new Map(AVAILABLE_CODEX_MODELS.map((model) => [model.slug, model]));
16790
16814
  function generateDraftId() {
@@ -16818,8 +16842,6 @@ function resolveCommandAlias(rawCommand, args) {
16818
16842
  return "/help";
16819
16843
  case "/t":
16820
16844
  return !args ? "/threads" : /^(all|n\b)/i.test(args.trim()) ? "/threads" : "/thread";
16821
- case "/s":
16822
- return args ? "/use" : "/sessions";
16823
16845
  case "/n":
16824
16846
  return "/new";
16825
16847
  case "/m":
@@ -16890,6 +16912,12 @@ function getSessionDisplayName(session, fallbackDirectory) {
16890
16912
  function nowIso() {
16891
16913
  return (/* @__PURE__ */ new Date()).toISOString();
16892
16914
  }
16915
+ function buildInteractiveStreamKey(sessionId, messageId) {
16916
+ return `im:${sessionId}:${messageId}`;
16917
+ }
16918
+ function buildMirrorStreamKey(sessionId, turnId, startedAt) {
16919
+ return `mirror:${sessionId}:${turnId || startedAt}`;
16920
+ }
16893
16921
  function getWorkspaceRoot() {
16894
16922
  const { store } = getBridgeContext();
16895
16923
  return store.getSetting("bridge_default_workspace_root") || DEFAULT_WORKSPACE_ROOT;
@@ -17468,6 +17496,53 @@ function getQueuedCount(sessionId) {
17468
17496
  const state = getState();
17469
17497
  return state.queuedCounts.get(sessionId) || 0;
17470
17498
  }
17499
+ function buildInteractiveIdleReminderNotice() {
17500
+ return [
17501
+ "\u63D0\u9192\uFF1A\u8FD9\u8F6E\u4EFB\u52A1\u4ECD\u5728\u8FD0\u884C\uFF0C\u4F46\u5DF2\u7ECF\u8D85\u8FC7 10 \u5206\u949F\u6CA1\u6709\u65B0\u7684\u6267\u884C\u8F93\u51FA\u3002",
17502
+ "\u7CFB\u7EDF\u4E0D\u4F1A\u81EA\u52A8\u7EC8\u6B62\u5B83\uFF1B\u5982\u679C\u4F60\u4ECD\u5728\u5BF9\u5E94\u7EBF\u7A0B\uFF0C\u53EF\u53D1\u9001 `/stop` \u4E3B\u52A8\u505C\u6B62\uFF1B\u5982\u679C\u5DF2\u7ECF\u5207\u5230\u522B\u7684\u7EBF\u7A0B\uFF0C\u9700\u8981\u5148\u5207\u56DE\u5BF9\u5E94\u7EBF\u7A0B\u3002"
17503
+ ].join("\n");
17504
+ }
17505
+ function isCurrentInteractiveTask(sessionId, taskId) {
17506
+ return getState().activeTasks.get(sessionId)?.id === taskId;
17507
+ }
17508
+ function touchInteractiveTask(sessionId, taskId) {
17509
+ const task = getState().activeTasks.get(sessionId);
17510
+ if (task?.id !== taskId) return;
17511
+ task.lastActivityAt = Date.now();
17512
+ task.idleReminderSent = false;
17513
+ }
17514
+ function releaseInteractiveTask(sessionId, taskId) {
17515
+ const state = getState();
17516
+ const current = state.activeTasks.get(sessionId);
17517
+ if (current?.id !== taskId) return;
17518
+ state.activeTasks.delete(sessionId);
17519
+ syncSessionRuntimeState(sessionId);
17520
+ }
17521
+ async function remindIdleInteractiveTask(task) {
17522
+ if (!isCurrentInteractiveTask(task.sessionId, task.id) || task.idleReminderSent) return;
17523
+ task.idleReminderSent = true;
17524
+ try {
17525
+ await deliver(task.adapter, {
17526
+ address: task.address,
17527
+ text: renderFeedbackTextForChannel(
17528
+ task.adapter.channelType,
17529
+ buildInteractiveIdleReminderNotice()
17530
+ ),
17531
+ parseMode: getFeedbackParseMode(task.adapter.channelType),
17532
+ replyToMessageId: task.requestMessageId
17533
+ });
17534
+ } catch {
17535
+ }
17536
+ }
17537
+ async function reconcileIdleInteractiveTasks() {
17538
+ const now2 = Date.now();
17539
+ const tasks = Array.from(getState().activeTasks.values());
17540
+ for (const task of tasks) {
17541
+ if (task.idleReminderSent) continue;
17542
+ if (now2 - task.lastActivityAt < INTERACTIVE_IDLE_REMINDER_MS) continue;
17543
+ await remindIdleInteractiveTask(task);
17544
+ }
17545
+ }
17471
17546
  function syncSessionRuntimeState(sessionId) {
17472
17547
  const { store } = getBridgeContext();
17473
17548
  const session = store.getSession(sessionId);
@@ -17829,6 +17904,16 @@ function formatMirrorMessage(threadTitle, userText, assistantText, markdown = fa
17829
17904
  sections.unshift(buildMirrorTitle(threadTitle, markdown));
17830
17905
  return sections.join("\n\n").trim();
17831
17906
  }
17907
+ function buildMirrorTimeoutNotice(markdown = false) {
17908
+ 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";
17909
+ }
17910
+ function appendMirrorTimeoutNotice(text2, markdown = false) {
17911
+ const notice = buildMirrorTimeoutNotice(markdown);
17912
+ const normalized = text2.trim();
17913
+ return normalized ? `${normalized}
17914
+
17915
+ ${notice}` : notice;
17916
+ }
17832
17917
  function getMirrorStreamingAdapter(subscription) {
17833
17918
  const state = getState();
17834
17919
  const adapter = state.adapters.get(subscription.channelType);
@@ -17855,9 +17940,9 @@ function startMirrorStreaming(subscription, turnState) {
17855
17940
  const adapter = getMirrorStreamingAdapter(subscription);
17856
17941
  if (!adapter || turnState.streamStarted) return;
17857
17942
  try {
17858
- adapter.onMirrorStreamStart?.(subscription.chatId);
17943
+ adapter.onMirrorStreamStart?.(subscription.chatId, turnState.streamKey);
17859
17944
  if (!adapter.onMirrorStreamStart) {
17860
- adapter.onStreamText?.(subscription.chatId, "");
17945
+ adapter.onStreamText?.(subscription.chatId, "", turnState.streamKey);
17861
17946
  }
17862
17947
  turnState.streamStarted = true;
17863
17948
  } catch {
@@ -17873,7 +17958,7 @@ function updateMirrorStreaming(subscription, turnState) {
17873
17958
  );
17874
17959
  if (!text2) return;
17875
17960
  try {
17876
- adapter.onStreamText?.(subscription.chatId, text2);
17961
+ adapter.onStreamText?.(subscription.chatId, text2, turnState.streamKey);
17877
17962
  } catch {
17878
17963
  }
17879
17964
  }
@@ -17882,7 +17967,7 @@ function updateMirrorToolProgress(subscription, turnState) {
17882
17967
  if (!adapter || typeof adapter.onToolEvent !== "function") return;
17883
17968
  startMirrorStreaming(subscription, turnState);
17884
17969
  try {
17885
- adapter.onToolEvent(subscription.chatId, Array.from(turnState.toolCalls.values()));
17970
+ adapter.onToolEvent(subscription.chatId, Array.from(turnState.toolCalls.values()), turnState.streamKey);
17886
17971
  } catch {
17887
17972
  }
17888
17973
  }
@@ -17894,7 +17979,7 @@ function stopMirrorStreaming(subscription, status = "interrupted") {
17894
17979
  subscription.channelType,
17895
17980
  getMirrorStreamingText(subscription, pendingTurn)
17896
17981
  );
17897
- void adapter.onStreamEnd(subscription.chatId, status, text2).catch(() => {
17982
+ void adapter.onStreamEnd(subscription.chatId, status, text2, pendingTurn.streamKey).catch(() => {
17898
17983
  });
17899
17984
  }
17900
17985
  async function deliverMirrorTurn(subscription, turn) {
@@ -17904,8 +17989,10 @@ async function deliverMirrorTurn(subscription, turn) {
17904
17989
  const title = getDesktopThreadTitle(subscription.threadId)?.trim() || "\u684C\u9762\u7EBF\u7A0B";
17905
17990
  const responseParseMode = getFeedbackParseMode(subscription.channelType);
17906
17991
  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);
17992
+ const renderedTextBase = formatMirrorMessage(title, turn.userText, turn.text, markdown);
17993
+ const renderedStreamTextBase = formatMirrorMessage(title, turn.userText, turn.text, markdown, true);
17994
+ const renderedText = turn.timedOut ? appendMirrorTimeoutNotice(renderedTextBase || buildMirrorTitle(title, markdown), markdown) : renderedTextBase;
17995
+ const renderedStreamText = turn.timedOut ? appendMirrorTimeoutNotice(renderedStreamTextBase || buildMirrorTitle(title, markdown), markdown) : renderedStreamTextBase;
17909
17996
  const text2 = renderedText ? renderFeedbackText(renderedText, responseParseMode) : "";
17910
17997
  const streamText = renderFeedbackText(
17911
17998
  renderedStreamText || buildMirrorTitle(title, markdown),
@@ -17916,7 +18003,8 @@ async function deliverMirrorTurn(subscription, turn) {
17916
18003
  const finalized = await adapter.onStreamEnd(
17917
18004
  subscription.chatId,
17918
18005
  turn.status,
17919
- streamText
18006
+ streamText,
18007
+ turn.streamKey
17920
18008
  );
17921
18009
  if (finalized) {
17922
18010
  subscription.lastDeliveredAt = turn.timestamp || nowIso();
@@ -17948,10 +18036,11 @@ async function deliverMirrorTurns(subscription, turns) {
17948
18036
  await deliverMirrorTurn(subscription, turn);
17949
18037
  }
17950
18038
  }
17951
- function createMirrorTurnState(timestamp, turnId) {
18039
+ function createMirrorTurnState(sessionId, timestamp, turnId) {
17952
18040
  const safeTimestamp = timestamp || nowIso();
17953
18041
  return {
17954
18042
  turnId: turnId || null,
18043
+ streamKey: buildMirrorStreamKey(sessionId, turnId || null, safeTimestamp),
17955
18044
  startedAt: safeTimestamp,
17956
18045
  lastActivityAt: safeTimestamp,
17957
18046
  userText: null,
@@ -17985,7 +18074,7 @@ ${normalized}` : normalized;
17985
18074
  }
17986
18075
  function ensureMirrorTurnState(subscription, record) {
17987
18076
  if (!subscription.pendingTurn) {
17988
- subscription.pendingTurn = createMirrorTurnState(record.timestamp, record.turnId);
18077
+ subscription.pendingTurn = createMirrorTurnState(subscription.sessionId, record.timestamp, record.turnId);
17989
18078
  return subscription.pendingTurn;
17990
18079
  }
17991
18080
  if (!subscription.pendingTurn.turnId && record.turnId) {
@@ -18008,11 +18097,13 @@ function finalizeMirrorTurn(subscription, signature, timestamp, status, preferre
18008
18097
  const userText = pendingTurn.userText?.trim() || null;
18009
18098
  if (!text2 && !userText && pendingTurn.toolCalls.size === 0) return null;
18010
18099
  return {
18100
+ streamKey: pendingTurn.streamKey,
18011
18101
  userText,
18012
18102
  text: text2,
18013
18103
  signature,
18014
18104
  timestamp: timestamp || pendingTurn.lastActivityAt || nowIso(),
18015
- status
18105
+ status,
18106
+ ...signature.startsWith("timeout:") ? { timedOut: true } : {}
18016
18107
  };
18017
18108
  }
18018
18109
  function consumeMirrorRecords(subscription, records) {
@@ -18026,7 +18117,7 @@ function consumeMirrorRecords(subscription, records) {
18026
18117
  if (superseded) finalized.push(superseded);
18027
18118
  }
18028
18119
  if (!subscription.pendingTurn) {
18029
- subscription.pendingTurn = createMirrorTurnState(record.timestamp, record.turnId);
18120
+ subscription.pendingTurn = createMirrorTurnState(subscription.sessionId, record.timestamp, record.turnId);
18030
18121
  } else {
18031
18122
  if (!subscription.pendingTurn.turnId && record.turnId) {
18032
18123
  subscription.pendingTurn.turnId = record.turnId;
@@ -18305,11 +18396,21 @@ async function reconcileMirrorSubscription(subscription) {
18305
18396
  subscription.bufferedRecords.push(...filteredRecords);
18306
18397
  }
18307
18398
  }
18399
+ const timedOutTurn = flushTimedOutMirrorTurn(subscription);
18308
18400
  if (getState().activeTasks.has(subscription.sessionId) || isMirrorSuppressed(subscription.sessionId)) {
18401
+ if (timedOutTurn) {
18402
+ try {
18403
+ await deliverMirrorTurns(subscription, [timedOutTurn]);
18404
+ } catch (error) {
18405
+ subscription.dirty = true;
18406
+ console.warn("[bridge-manager] Mirror delivery failed:", error instanceof Error ? error.message : error);
18407
+ }
18408
+ }
18309
18409
  syncMirrorSessionState(subscription.sessionId);
18310
18410
  return;
18311
18411
  }
18312
- const finalizedTurns = consumeBufferedMirrorTurns(subscription);
18412
+ const finalizedTurns = timedOutTurn ? [timedOutTurn] : [];
18413
+ finalizedTurns.push(...consumeBufferedMirrorTurns(subscription));
18313
18414
  if (finalizedTurns.length === 0) {
18314
18415
  syncMirrorSessionState(subscription.sessionId);
18315
18416
  return;
@@ -18473,6 +18574,9 @@ async function start() {
18473
18574
  void syncConfiguredAdapters({ startLoops: true }).catch((err) => {
18474
18575
  console.error("[bridge-manager] Adapter reconcile failed:", err);
18475
18576
  });
18577
+ void reconcileIdleInteractiveTasks().catch((err) => {
18578
+ console.error("[bridge-manager] Interactive idle reminder reconcile failed:", err);
18579
+ });
18476
18580
  }, 5e3);
18477
18581
  state.mirrorPollTimer = setInterval(() => {
18478
18582
  void reconcileMirrorSubscriptions().catch((err) => {
@@ -18506,8 +18610,8 @@ async function stop() {
18506
18610
  }
18507
18611
  state.loopAborts.clear();
18508
18612
  const activeSessionIds = Array.from(state.activeTasks.keys());
18509
- for (const abort of state.activeTasks.values()) {
18510
- abort.abort();
18613
+ for (const task of state.activeTasks.values()) {
18614
+ task.abortController.abort();
18511
18615
  }
18512
18616
  state.activeTasks.clear();
18513
18617
  state.mirrorSuppressUntil.clear();
@@ -18712,12 +18816,28 @@ async function handleMessage(adapter, msg) {
18712
18816
  return;
18713
18817
  }
18714
18818
  const binding = resolve(msg.address);
18715
- adapter.onMessageStart?.(msg.address.chatId);
18819
+ const streamKey = buildInteractiveStreamKey(binding.codepilotSessionId, msg.messageId);
18820
+ adapter.onMessageStart?.(msg.address.chatId, streamKey);
18716
18821
  const taskAbort = new AbortController();
18822
+ const taskId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
18717
18823
  const state = getState();
18718
18824
  resetMirrorSessionForInteractiveRun(binding.codepilotSessionId);
18719
- state.activeTasks.set(binding.codepilotSessionId, taskAbort);
18720
- let mirrorSuppressionId = null;
18825
+ const taskState = {
18826
+ id: taskId,
18827
+ abortController: taskAbort,
18828
+ adapter,
18829
+ address: msg.address,
18830
+ requestMessageId: msg.messageId,
18831
+ streamKey,
18832
+ sessionId: binding.codepilotSessionId,
18833
+ hasStreamingCards: false,
18834
+ lastActivityAt: Date.now(),
18835
+ idleReminderSent: false,
18836
+ streamFinalized: false,
18837
+ uiEnded: false,
18838
+ mirrorSuppressionId: null
18839
+ };
18840
+ state.activeTasks.set(binding.codepilotSessionId, taskState);
18721
18841
  syncSessionRuntimeState(binding.codepilotSessionId);
18722
18842
  let previewState = null;
18723
18843
  const caps = adapter.getPreviewCapabilities?.(msg.address.chatId) ?? null;
@@ -18766,18 +18886,22 @@ async function handleMessage(adapter, msg) {
18766
18886
  flushPreview(adapter, ps, cfg);
18767
18887
  } : void 0;
18768
18888
  const hasStreamingCards = typeof adapter.onStreamText === "function";
18889
+ taskState.hasStreamingCards = hasStreamingCards;
18769
18890
  const toolCallTracker = /* @__PURE__ */ new Map();
18770
18891
  const onStreamCardText = hasStreamingCards ? (fullText) => {
18892
+ if (!isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
18771
18893
  const rendered = renderFeedbackTextForChannel(
18772
18894
  adapter.channelType,
18773
18895
  stripOutboundArtifactBlocksForStreaming(fullText)
18774
18896
  );
18775
18897
  try {
18776
- adapter.onStreamText(msg.address.chatId, rendered);
18898
+ adapter.onStreamText(msg.address.chatId, rendered, streamKey);
18777
18899
  } catch {
18778
18900
  }
18779
18901
  } : void 0;
18780
18902
  const onToolEvent = hasStreamingCards ? (toolId, toolName, status) => {
18903
+ if (!isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
18904
+ touchInteractiveTask(binding.codepilotSessionId, taskId);
18781
18905
  if (toolName) {
18782
18906
  toolCallTracker.set(toolId, { id: toolId, name: toolName, status });
18783
18907
  } else {
@@ -18785,11 +18909,13 @@ async function handleMessage(adapter, msg) {
18785
18909
  if (existing) existing.status = status;
18786
18910
  }
18787
18911
  try {
18788
- adapter.onToolEvent(msg.address.chatId, Array.from(toolCallTracker.values()));
18912
+ adapter.onToolEvent(msg.address.chatId, Array.from(toolCallTracker.values()), streamKey);
18789
18913
  } catch {
18790
18914
  }
18791
18915
  } : void 0;
18792
18916
  const onPartialText = previewOnPartialText || onStreamCardText ? (fullText) => {
18917
+ if (!isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
18918
+ touchInteractiveTask(binding.codepilotSessionId, taskId);
18793
18919
  if (previewOnPartialText) previewOnPartialText(fullText);
18794
18920
  if (onStreamCardText) onStreamCardText(fullText);
18795
18921
  } : void 0;
@@ -18807,10 +18933,13 @@ async function handleMessage(adapter, msg) {
18807
18933
  msg.messageId
18808
18934
  );
18809
18935
  }, taskAbort.signal, hasAttachments ? msg.attachments : void 0, onPartialText, onToolEvent, (preparedPrompt) => {
18810
- if (!mirrorSuppressionId) {
18811
- mirrorSuppressionId = beginMirrorSuppression(binding.codepilotSessionId, preparedPrompt);
18936
+ if (!taskState.mirrorSuppressionId) {
18937
+ taskState.mirrorSuppressionId = beginMirrorSuppression(binding.codepilotSessionId, preparedPrompt);
18812
18938
  }
18813
18939
  });
18940
+ if (!isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) {
18941
+ return;
18942
+ }
18814
18943
  let cardFinalized = false;
18815
18944
  if (hasStreamingCards && adapter.onStreamEnd) {
18816
18945
  try {
@@ -18818,8 +18947,10 @@ async function handleMessage(adapter, msg) {
18818
18947
  cardFinalized = await adapter.onStreamEnd(
18819
18948
  msg.address.chatId,
18820
18949
  status,
18821
- renderFeedbackTextForChannel(adapter.channelType, result.responseText)
18950
+ renderFeedbackTextForChannel(adapter.channelType, result.responseText),
18951
+ streamKey
18822
18952
  );
18953
+ taskState.streamFinalized = cardFinalized;
18823
18954
  } catch (err) {
18824
18955
  console.warn("[bridge-manager] Card finalize failed:", err instanceof Error ? err.message : err);
18825
18956
  }
@@ -18844,14 +18975,9 @@ async function handleMessage(adapter, msg) {
18844
18975
  []
18845
18976
  );
18846
18977
  }
18847
- if (binding.id) {
18848
- try {
18849
- const update = computeSdkSessionUpdate(result.sdkSessionId, result.hasError);
18850
- if (update !== null) {
18851
- store.updateChannelBinding(binding.id, { sdkSessionId: update });
18852
- }
18853
- } catch {
18854
- }
18978
+ try {
18979
+ persistSdkSessionUpdate(binding.codepilotSessionId, result.sdkSessionId, result.hasError);
18980
+ } catch {
18855
18981
  }
18856
18982
  } finally {
18857
18983
  if (previewState) {
@@ -18861,18 +18987,22 @@ async function handleMessage(adapter, msg) {
18861
18987
  }
18862
18988
  adapter.endPreview?.(msg.address.chatId, previewState.draftId);
18863
18989
  }
18864
- if (hasStreamingCards && adapter.onStreamEnd && taskAbort.signal.aborted) {
18990
+ if (hasStreamingCards && adapter.onStreamEnd && taskAbort.signal.aborted && !taskState.streamFinalized) {
18865
18991
  try {
18866
- await adapter.onStreamEnd(msg.address.chatId, "interrupted", "");
18992
+ await adapter.onStreamEnd(msg.address.chatId, "interrupted", "", streamKey);
18993
+ taskState.streamFinalized = true;
18867
18994
  } catch {
18868
18995
  }
18869
18996
  }
18870
- if (mirrorSuppressionId) {
18871
- settleMirrorSuppression(binding.codepilotSessionId, mirrorSuppressionId);
18997
+ if (taskState.mirrorSuppressionId) {
18998
+ settleMirrorSuppression(binding.codepilotSessionId, taskState.mirrorSuppressionId);
18999
+ taskState.mirrorSuppressionId = null;
19000
+ }
19001
+ releaseInteractiveTask(binding.codepilotSessionId, taskId);
19002
+ if (!taskState.uiEnded) {
19003
+ adapter.onMessageEnd?.(msg.address.chatId, streamKey);
19004
+ taskState.uiEnded = true;
18872
19005
  }
18873
- state.activeTasks.delete(binding.codepilotSessionId);
18874
- syncSessionRuntimeState(binding.codepilotSessionId);
18875
- adapter.onMessageEnd?.(msg.address.chatId);
18876
19006
  ack();
18877
19007
  }
18878
19008
  }
@@ -18925,15 +19055,6 @@ async function handleCommand(adapter, msg, text2) {
18925
19055
  response = resolved.message;
18926
19056
  break;
18927
19057
  }
18928
- if (currentBinding) {
18929
- const st = getState();
18930
- const oldTask = st.activeTasks.get(currentBinding.codepilotSessionId);
18931
- if (oldTask) {
18932
- oldTask.abort();
18933
- st.activeTasks.delete(currentBinding.codepilotSessionId);
18934
- syncSessionRuntimeState(currentBinding.codepilotSessionId);
18935
- }
18936
- }
18937
19058
  const workDir = resolved.workDir;
18938
19059
  ensureWorkingDirectoryExists(workDir);
18939
19060
  const binding = createBinding(msg.address, workDir);
@@ -18947,6 +19068,7 @@ async function handleCommand(adapter, msg, text2) {
18947
19068
  ],
18948
19069
  [
18949
19070
  args.trim() ? "\u63A5\u4E0B\u6765\u76F4\u63A5\u53D1\u9001\u6587\u672C\u5373\u53EF\u7EE7\u7EED\u3002" : "\u5DF2\u5728\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u4E0B\u65B0\u5EFA\u4E00\u4E2A\u7EBF\u7A0B\u3002\u63A5\u4E0B\u6765\u76F4\u63A5\u53D1\u9001\u6587\u672C\u5373\u53EF\u7EE7\u7EED\u3002",
19071
+ "\u5982\u679C\u5F53\u524D\u804A\u5929\u91CC\u5DF2\u6709\u65E7\u4EFB\u52A1\u5728\u8FD0\u884C\uFF0C\u5B83\u4E0D\u4F1A\u88AB\u7EC8\u6B62\uFF0C\u4ECD\u4F1A\u5728\u540E\u53F0\u7EE7\u7EED\u6267\u884C\u5E76\u53EF\u80FD\u7A0D\u540E\u56DE\u6D88\u606F\u3002",
18950
19072
  "\u8FD9\u662F IM \u4FA7\u7EBF\u7A0B\uFF0C\u5F53\u524D\u53EA\u4FDD\u8BC1\u5728 IM \u4E2D\u53EF\u7EE7\u7EED\uFF1B\u4E0D\u4F1A\u81EA\u52A8\u51FA\u73B0\u5728 Codex Desktop \u4F1A\u8BDD\u5217\u8868\u4E2D\u3002"
18951
19073
  ],
18952
19074
  responseParseMode === "Markdown"
@@ -19358,6 +19480,7 @@ ${truncateHistoryContent(formatStoredMessageContent(message.content))}`;
19358
19480
  return {
19359
19481
  heading: `${getSessionDisplayName(session, session.working_directory)}${session.id === currentBinding?.codepilotSessionId ? " [\u5F53\u524D]" : ""}`,
19360
19482
  details: [
19483
+ `\u72B6\u6001\uFF1A${formatRuntimeStatus(session)}`,
19361
19484
  `\u76EE\u5F55\uFF1A${formatCommandPath(session.working_directory)}`
19362
19485
  ]
19363
19486
  };
@@ -19376,9 +19499,7 @@ ${truncateHistoryContent(formatStoredMessageContent(message.content))}`;
19376
19499
  const st = getState();
19377
19500
  const taskAbort = st.activeTasks.get(binding.codepilotSessionId);
19378
19501
  if (taskAbort) {
19379
- taskAbort.abort();
19380
- st.activeTasks.delete(binding.codepilotSessionId);
19381
- syncSessionRuntimeState(binding.codepilotSessionId);
19502
+ taskAbort.abortController.abort();
19382
19503
  response = "\u6B63\u5728\u505C\u6B62\u5F53\u524D\u4EFB\u52A1...";
19383
19504
  } else {
19384
19505
  response = "\u5F53\u524D\u6CA1\u6709\u6B63\u5728\u8FD0\u884C\u7684\u4EFB\u52A1\u3002";
@@ -19483,6 +19604,13 @@ function computeSdkSessionUpdate(sdkSessionId, hasError) {
19483
19604
  }
19484
19605
  return null;
19485
19606
  }
19607
+ function persistSdkSessionUpdate(sessionId, sdkSessionId, hasError) {
19608
+ const update = computeSdkSessionUpdate(sdkSessionId, hasError);
19609
+ if (update === null) {
19610
+ return;
19611
+ }
19612
+ getBridgeContext().store.updateSdkSessionId(sessionId, update);
19613
+ }
19486
19614
 
19487
19615
  // src/store.ts
19488
19616
  import fs9 from "node:fs";
@@ -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.20",
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",