codex-to-im 1.0.19 → 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.
Files changed (2) hide show
  1. package/dist/daemon.mjs +116 -30
  2. package/package.json +1 -1
package/dist/daemon.mjs CHANGED
@@ -5649,7 +5649,16 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5649
5649
  status: statusLabels[status] || status,
5650
5650
  elapsed: formatElapsed(elapsedMs)
5651
5651
  };
5652
- 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);
5653
5662
  state.sequence++;
5654
5663
  await cardkit.card.update({
5655
5664
  path: { card_id: state.cardId },
@@ -16798,7 +16807,8 @@ var MIRROR_WATCH_DEBOUNCE_MS = 350;
16798
16807
  var MIRROR_EVENT_BATCH_LIMIT = 8;
16799
16808
  var MIRROR_SUPPRESSION_WINDOW_MS = 4e3;
16800
16809
  var MIRROR_PROMPT_MATCH_GRACE_MS = 12e4;
16801
- var MIRROR_IDLE_TIMEOUT_MS = 12e5;
16810
+ var INTERACTIVE_IDLE_REMINDER_MS = 6e5;
16811
+ var MIRROR_IDLE_TIMEOUT_MS = 6e5;
16802
16812
  var AVAILABLE_CODEX_MODELS = listSelectableCodexModels();
16803
16813
  var AVAILABLE_CODEX_MODEL_MAP = new Map(AVAILABLE_CODEX_MODELS.map((model) => [model.slug, model]));
16804
16814
  function generateDraftId() {
@@ -17486,6 +17496,53 @@ function getQueuedCount(sessionId) {
17486
17496
  const state = getState();
17487
17497
  return state.queuedCounts.get(sessionId) || 0;
17488
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
+ }
17489
17546
  function syncSessionRuntimeState(sessionId) {
17490
17547
  const { store } = getBridgeContext();
17491
17548
  const session = store.getSession(sessionId);
@@ -18517,6 +18574,9 @@ async function start() {
18517
18574
  void syncConfiguredAdapters({ startLoops: true }).catch((err) => {
18518
18575
  console.error("[bridge-manager] Adapter reconcile failed:", err);
18519
18576
  });
18577
+ void reconcileIdleInteractiveTasks().catch((err) => {
18578
+ console.error("[bridge-manager] Interactive idle reminder reconcile failed:", err);
18579
+ });
18520
18580
  }, 5e3);
18521
18581
  state.mirrorPollTimer = setInterval(() => {
18522
18582
  void reconcileMirrorSubscriptions().catch((err) => {
@@ -18550,8 +18610,8 @@ async function stop() {
18550
18610
  }
18551
18611
  state.loopAborts.clear();
18552
18612
  const activeSessionIds = Array.from(state.activeTasks.keys());
18553
- for (const abort of state.activeTasks.values()) {
18554
- abort.abort();
18613
+ for (const task of state.activeTasks.values()) {
18614
+ task.abortController.abort();
18555
18615
  }
18556
18616
  state.activeTasks.clear();
18557
18617
  state.mirrorSuppressUntil.clear();
@@ -18759,10 +18819,25 @@ async function handleMessage(adapter, msg) {
18759
18819
  const streamKey = buildInteractiveStreamKey(binding.codepilotSessionId, msg.messageId);
18760
18820
  adapter.onMessageStart?.(msg.address.chatId, streamKey);
18761
18821
  const taskAbort = new AbortController();
18822
+ const taskId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
18762
18823
  const state = getState();
18763
18824
  resetMirrorSessionForInteractiveRun(binding.codepilotSessionId);
18764
- state.activeTasks.set(binding.codepilotSessionId, taskAbort);
18765
- 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);
18766
18841
  syncSessionRuntimeState(binding.codepilotSessionId);
18767
18842
  let previewState = null;
18768
18843
  const caps = adapter.getPreviewCapabilities?.(msg.address.chatId) ?? null;
@@ -18811,8 +18886,10 @@ async function handleMessage(adapter, msg) {
18811
18886
  flushPreview(adapter, ps, cfg);
18812
18887
  } : void 0;
18813
18888
  const hasStreamingCards = typeof adapter.onStreamText === "function";
18889
+ taskState.hasStreamingCards = hasStreamingCards;
18814
18890
  const toolCallTracker = /* @__PURE__ */ new Map();
18815
18891
  const onStreamCardText = hasStreamingCards ? (fullText) => {
18892
+ if (!isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
18816
18893
  const rendered = renderFeedbackTextForChannel(
18817
18894
  adapter.channelType,
18818
18895
  stripOutboundArtifactBlocksForStreaming(fullText)
@@ -18823,6 +18900,8 @@ async function handleMessage(adapter, msg) {
18823
18900
  }
18824
18901
  } : void 0;
18825
18902
  const onToolEvent = hasStreamingCards ? (toolId, toolName, status) => {
18903
+ if (!isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
18904
+ touchInteractiveTask(binding.codepilotSessionId, taskId);
18826
18905
  if (toolName) {
18827
18906
  toolCallTracker.set(toolId, { id: toolId, name: toolName, status });
18828
18907
  } else {
@@ -18835,6 +18914,8 @@ async function handleMessage(adapter, msg) {
18835
18914
  }
18836
18915
  } : void 0;
18837
18916
  const onPartialText = previewOnPartialText || onStreamCardText ? (fullText) => {
18917
+ if (!isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
18918
+ touchInteractiveTask(binding.codepilotSessionId, taskId);
18838
18919
  if (previewOnPartialText) previewOnPartialText(fullText);
18839
18920
  if (onStreamCardText) onStreamCardText(fullText);
18840
18921
  } : void 0;
@@ -18852,10 +18933,13 @@ async function handleMessage(adapter, msg) {
18852
18933
  msg.messageId
18853
18934
  );
18854
18935
  }, taskAbort.signal, hasAttachments ? msg.attachments : void 0, onPartialText, onToolEvent, (preparedPrompt) => {
18855
- if (!mirrorSuppressionId) {
18856
- mirrorSuppressionId = beginMirrorSuppression(binding.codepilotSessionId, preparedPrompt);
18936
+ if (!taskState.mirrorSuppressionId) {
18937
+ taskState.mirrorSuppressionId = beginMirrorSuppression(binding.codepilotSessionId, preparedPrompt);
18857
18938
  }
18858
18939
  });
18940
+ if (!isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) {
18941
+ return;
18942
+ }
18859
18943
  let cardFinalized = false;
18860
18944
  if (hasStreamingCards && adapter.onStreamEnd) {
18861
18945
  try {
@@ -18866,6 +18950,7 @@ async function handleMessage(adapter, msg) {
18866
18950
  renderFeedbackTextForChannel(adapter.channelType, result.responseText),
18867
18951
  streamKey
18868
18952
  );
18953
+ taskState.streamFinalized = cardFinalized;
18869
18954
  } catch (err) {
18870
18955
  console.warn("[bridge-manager] Card finalize failed:", err instanceof Error ? err.message : err);
18871
18956
  }
@@ -18890,14 +18975,9 @@ async function handleMessage(adapter, msg) {
18890
18975
  []
18891
18976
  );
18892
18977
  }
18893
- if (binding.id) {
18894
- try {
18895
- const update = computeSdkSessionUpdate(result.sdkSessionId, result.hasError);
18896
- if (update !== null) {
18897
- store.updateChannelBinding(binding.id, { sdkSessionId: update });
18898
- }
18899
- } catch {
18900
- }
18978
+ try {
18979
+ persistSdkSessionUpdate(binding.codepilotSessionId, result.sdkSessionId, result.hasError);
18980
+ } catch {
18901
18981
  }
18902
18982
  } finally {
18903
18983
  if (previewState) {
@@ -18907,18 +18987,22 @@ async function handleMessage(adapter, msg) {
18907
18987
  }
18908
18988
  adapter.endPreview?.(msg.address.chatId, previewState.draftId);
18909
18989
  }
18910
- if (hasStreamingCards && adapter.onStreamEnd && taskAbort.signal.aborted) {
18990
+ if (hasStreamingCards && adapter.onStreamEnd && taskAbort.signal.aborted && !taskState.streamFinalized) {
18911
18991
  try {
18912
18992
  await adapter.onStreamEnd(msg.address.chatId, "interrupted", "", streamKey);
18993
+ taskState.streamFinalized = true;
18913
18994
  } catch {
18914
18995
  }
18915
18996
  }
18916
- if (mirrorSuppressionId) {
18917
- 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;
18918
19005
  }
18919
- state.activeTasks.delete(binding.codepilotSessionId);
18920
- syncSessionRuntimeState(binding.codepilotSessionId);
18921
- adapter.onMessageEnd?.(msg.address.chatId, streamKey);
18922
19006
  ack();
18923
19007
  }
18924
19008
  }
@@ -18971,13 +19055,6 @@ async function handleCommand(adapter, msg, text2) {
18971
19055
  response = resolved.message;
18972
19056
  break;
18973
19057
  }
18974
- if (currentBinding) {
18975
- const st = getState();
18976
- const oldTask = st.activeTasks.get(currentBinding.codepilotSessionId);
18977
- if (oldTask) {
18978
- oldTask.abort();
18979
- }
18980
- }
18981
19058
  const workDir = resolved.workDir;
18982
19059
  ensureWorkingDirectoryExists(workDir);
18983
19060
  const binding = createBinding(msg.address, workDir);
@@ -18991,6 +19068,7 @@ async function handleCommand(adapter, msg, text2) {
18991
19068
  ],
18992
19069
  [
18993
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",
18994
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"
18995
19073
  ],
18996
19074
  responseParseMode === "Markdown"
@@ -19402,6 +19480,7 @@ ${truncateHistoryContent(formatStoredMessageContent(message.content))}`;
19402
19480
  return {
19403
19481
  heading: `${getSessionDisplayName(session, session.working_directory)}${session.id === currentBinding?.codepilotSessionId ? " [\u5F53\u524D]" : ""}`,
19404
19482
  details: [
19483
+ `\u72B6\u6001\uFF1A${formatRuntimeStatus(session)}`,
19405
19484
  `\u76EE\u5F55\uFF1A${formatCommandPath(session.working_directory)}`
19406
19485
  ]
19407
19486
  };
@@ -19420,7 +19499,7 @@ ${truncateHistoryContent(formatStoredMessageContent(message.content))}`;
19420
19499
  const st = getState();
19421
19500
  const taskAbort = st.activeTasks.get(binding.codepilotSessionId);
19422
19501
  if (taskAbort) {
19423
- taskAbort.abort();
19502
+ taskAbort.abortController.abort();
19424
19503
  response = "\u6B63\u5728\u505C\u6B62\u5F53\u524D\u4EFB\u52A1...";
19425
19504
  } else {
19426
19505
  response = "\u5F53\u524D\u6CA1\u6709\u6B63\u5728\u8FD0\u884C\u7684\u4EFB\u52A1\u3002";
@@ -19525,6 +19604,13 @@ function computeSdkSessionUpdate(sdkSessionId, hasError) {
19525
19604
  }
19526
19605
  return null;
19527
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
+ }
19528
19614
 
19529
19615
  // src/store.ts
19530
19616
  import fs9 from "node:fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-to-im",
3
- "version": "1.0.19",
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",