codex-to-im 1.0.35 → 1.0.36

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 +272 -42
  2. package/package.json +1 -1
package/dist/daemon.mjs CHANGED
@@ -5496,25 +5496,16 @@ function formatElapsed(ms) {
5496
5496
  const remSec = Math.floor(sec % 60);
5497
5497
  return `${min}m ${remSec}s`;
5498
5498
  }
5499
- function buildStreamingContent(text2, tools) {
5500
- let content = text2 || "";
5501
- const toolMd = buildToolProgressMarkdown(tools);
5502
- if (toolMd) {
5503
- content = content ? `${content}
5504
-
5505
- ${toolMd}` : toolMd;
5506
- }
5507
- return content || "\u{1F4AD} Thinking...";
5499
+ function buildStreamingTextContent(text2) {
5500
+ return text2 || "\u{1F4AD} Thinking...";
5501
+ }
5502
+ function buildStreamingToolsContent(tools) {
5503
+ return buildToolProgressMarkdown(tools);
5508
5504
  }
5509
5505
  function buildFinalCardJson(text2, tools, footer) {
5510
5506
  const elements = [];
5511
- let content = preprocessFeishuMarkdown(text2);
5507
+ const content = preprocessFeishuMarkdown(text2);
5512
5508
  const toolMd = buildToolProgressMarkdown(tools);
5513
- if (toolMd) {
5514
- content = content ? `${content}
5515
-
5516
- ${toolMd}` : toolMd;
5517
- }
5518
5509
  if (content) {
5519
5510
  elements.push({
5520
5511
  tag: "markdown",
@@ -5523,12 +5514,25 @@ ${toolMd}` : toolMd;
5523
5514
  text_size: "normal"
5524
5515
  });
5525
5516
  }
5517
+ if (toolMd) {
5518
+ if (elements.length > 0) {
5519
+ elements.push({ tag: "hr" });
5520
+ }
5521
+ elements.push({
5522
+ tag: "markdown",
5523
+ content: toolMd,
5524
+ text_align: "left",
5525
+ text_size: "normal"
5526
+ });
5527
+ }
5526
5528
  if (footer) {
5527
5529
  const parts = [];
5528
5530
  if (footer.status) parts.push(footer.status);
5529
5531
  if (footer.elapsed) parts.push(footer.elapsed);
5530
5532
  if (parts.length > 0) {
5531
- elements.push({ tag: "hr" });
5533
+ if (elements.length > 0) {
5534
+ elements.push({ tag: "hr" });
5535
+ }
5532
5536
  elements.push({
5533
5537
  tag: "markdown",
5534
5538
  content: parts.join(" \xB7 "),
@@ -5595,6 +5599,8 @@ var DEDUP_MAX = 1e3;
5595
5599
  var MAX_FILE_SIZE = 20 * 1024 * 1024;
5596
5600
  var TYPING_EMOJI = "Typing";
5597
5601
  var CARD_THROTTLE_MS = 200;
5602
+ var INITIAL_STREAMING_STATUS = "\u5DF2\u8FD0\u884C 0s";
5603
+ var EMPTY_STREAMING_TOOLS = "";
5598
5604
  var MIME_BY_TYPE = {
5599
5605
  image: "image/png",
5600
5606
  file: "application/octet-stream",
@@ -5642,6 +5648,9 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5642
5648
  isStreamingEnabled() {
5643
5649
  return this.channelConfig.streamingEnabled !== false;
5644
5650
  }
5651
+ supportsStructuredStreamingUi(_chatId) {
5652
+ return this.isStreamingEnabled();
5653
+ }
5645
5654
  resolveStreamKey(chatId, streamKey) {
5646
5655
  return streamKey?.trim() || chatId;
5647
5656
  }
@@ -5840,13 +5849,29 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5840
5849
  summary: { content: "\u601D\u8003\u4E2D..." }
5841
5850
  },
5842
5851
  body: {
5843
- elements: [{
5844
- tag: "markdown",
5845
- content: "\u{1F4AD} Thinking...",
5846
- text_align: "left",
5847
- text_size: "normal",
5848
- element_id: "streaming_content"
5849
- }]
5852
+ elements: [
5853
+ {
5854
+ tag: "markdown",
5855
+ content: "\u{1F4AD} Thinking...",
5856
+ text_align: "left",
5857
+ text_size: "normal",
5858
+ element_id: "streaming_content"
5859
+ },
5860
+ {
5861
+ tag: "markdown",
5862
+ content: EMPTY_STREAMING_TOOLS,
5863
+ text_align: "left",
5864
+ text_size: "normal",
5865
+ element_id: "streaming_tools"
5866
+ },
5867
+ {
5868
+ tag: "markdown",
5869
+ content: INITIAL_STREAMING_STATUS,
5870
+ text_align: "left",
5871
+ text_size: "notation",
5872
+ element_id: "streaming_status"
5873
+ }
5874
+ ]
5850
5875
  }
5851
5876
  };
5852
5877
  const createResp = await cardkit.card.create({
@@ -5888,8 +5913,14 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5888
5913
  toolCalls: [],
5889
5914
  thinking: true,
5890
5915
  pendingText: null,
5916
+ pendingStatusText: INITIAL_STREAMING_STATUS,
5917
+ renderedText: "\u{1F4AD} Thinking...",
5918
+ renderedToolsText: EMPTY_STREAMING_TOOLS,
5919
+ renderedStatusText: INITIAL_STREAMING_STATUS,
5891
5920
  lastUpdateAt: 0,
5892
- throttleTimer: null
5921
+ throttleTimer: null,
5922
+ flushInFlight: null,
5923
+ flushQueued: false
5893
5924
  });
5894
5925
  console.log(`[feishu-adapter] Streaming card created: streamKey=${cardKey}, cardId=${cardId}, msgId=${messageId}`);
5895
5926
  return true;
@@ -5909,12 +5940,43 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5909
5940
  state.thinking = false;
5910
5941
  }
5911
5942
  state.pendingText = text2;
5943
+ this.scheduleCardFlush(cardKey);
5944
+ }
5945
+ updateCardStatus(chatId, statusText, streamKey) {
5946
+ const cardKey = this.resolveStreamKey(chatId, streamKey);
5947
+ const state = this.activeCards.get(cardKey);
5948
+ if (!state || !this.restClient) return;
5949
+ state.pendingStatusText = statusText || INITIAL_STREAMING_STATUS;
5950
+ this.scheduleCardFlush(cardKey);
5951
+ }
5952
+ enqueueCardFlush(streamKey) {
5953
+ const state = this.activeCards.get(streamKey);
5954
+ if (!state) return;
5955
+ if (state.flushInFlight) {
5956
+ state.flushQueued = true;
5957
+ return;
5958
+ }
5959
+ state.flushInFlight = this.flushCardUpdate(streamKey).catch((err) => {
5960
+ console.warn("[feishu-adapter] cardElement.content failed:", err instanceof Error ? err.message : err);
5961
+ }).finally(() => {
5962
+ const current = this.activeCards.get(streamKey);
5963
+ if (!current) return;
5964
+ current.flushInFlight = null;
5965
+ if (current.flushQueued) {
5966
+ current.flushQueued = false;
5967
+ this.enqueueCardFlush(streamKey);
5968
+ }
5969
+ });
5970
+ }
5971
+ scheduleCardFlush(streamKey) {
5972
+ const state = this.activeCards.get(streamKey);
5973
+ if (!state) return;
5912
5974
  const elapsed = Date.now() - state.lastUpdateAt;
5913
5975
  if (elapsed < CARD_THROTTLE_MS && state.lastUpdateAt > 0) {
5914
5976
  if (!state.throttleTimer) {
5915
5977
  state.throttleTimer = setTimeout(() => {
5916
5978
  state.throttleTimer = null;
5917
- this.flushCardUpdate(cardKey);
5979
+ this.enqueueCardFlush(streamKey);
5918
5980
  }, CARD_THROTTLE_MS - elapsed);
5919
5981
  }
5920
5982
  return;
@@ -5923,28 +5985,65 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5923
5985
  clearTimeout(state.throttleTimer);
5924
5986
  state.throttleTimer = null;
5925
5987
  }
5926
- this.flushCardUpdate(cardKey);
5988
+ this.enqueueCardFlush(streamKey);
5927
5989
  }
5928
5990
  /**
5929
5991
  * Flush pending card update to Feishu API.
5930
5992
  */
5931
- flushCardUpdate(streamKey) {
5993
+ async flushCardUpdate(streamKey) {
5932
5994
  const state = this.activeCards.get(streamKey);
5933
5995
  if (!state || !this.restClient) return;
5934
5996
  const cardkit = this.restClient.cardkit?.v1;
5935
5997
  if (!cardkit?.cardElement?.content) return;
5936
- const content = buildStreamingContent(state.pendingText || "", state.toolCalls);
5937
- state.sequence++;
5938
- const seq = state.sequence;
5998
+ const content = buildStreamingTextContent(state.pendingText || "");
5999
+ const toolsText = buildStreamingToolsContent(state.toolCalls) || EMPTY_STREAMING_TOOLS;
6000
+ const statusText = state.pendingStatusText || INITIAL_STREAMING_STATUS;
6001
+ const updates = [];
6002
+ if (content !== state.renderedText) {
6003
+ updates.push({
6004
+ elementId: "streaming_content",
6005
+ content,
6006
+ onSuccess: () => {
6007
+ state.renderedText = content;
6008
+ }
6009
+ });
6010
+ }
6011
+ if (toolsText !== state.renderedToolsText) {
6012
+ updates.push({
6013
+ elementId: "streaming_tools",
6014
+ content: toolsText,
6015
+ onSuccess: () => {
6016
+ state.renderedToolsText = toolsText;
6017
+ }
6018
+ });
6019
+ }
6020
+ if (statusText !== state.renderedStatusText) {
6021
+ updates.push({
6022
+ elementId: "streaming_status",
6023
+ content: statusText,
6024
+ onSuccess: () => {
6025
+ state.renderedStatusText = statusText;
6026
+ }
6027
+ });
6028
+ }
6029
+ if (updates.length === 0) return;
5939
6030
  const cardId = state.cardId;
5940
- cardkit.cardElement.content({
5941
- path: { card_id: cardId, element_id: "streaming_content" },
5942
- data: { content, sequence: seq }
5943
- }).then(() => {
5944
- state.lastUpdateAt = Date.now();
5945
- }).catch((err) => {
5946
- console.warn("[feishu-adapter] cardElement.content failed:", err instanceof Error ? err.message : err);
5947
- });
6031
+ for (const update of updates) {
6032
+ state.sequence++;
6033
+ try {
6034
+ await cardkit.cardElement.content({
6035
+ path: { card_id: cardId, element_id: update.elementId },
6036
+ data: { content: update.content, sequence: state.sequence }
6037
+ });
6038
+ update.onSuccess();
6039
+ state.lastUpdateAt = Date.now();
6040
+ } catch (err) {
6041
+ console.warn(
6042
+ `[feishu-adapter] cardElement.content failed for ${update.elementId}:`,
6043
+ err instanceof Error ? err.message : err
6044
+ );
6045
+ }
6046
+ }
5948
6047
  }
5949
6048
  /**
5950
6049
  * Update tool progress in the streaming card.
@@ -5954,7 +6053,27 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5954
6053
  const state = this.activeCards.get(cardKey);
5955
6054
  if (!state) return;
5956
6055
  state.toolCalls = tools;
5957
- this.updateCardContent(chatId, state.pendingText || "", cardKey);
6056
+ this.scheduleCardFlush(cardKey);
6057
+ }
6058
+ async awaitCardFlushCompletion(streamKey) {
6059
+ while (true) {
6060
+ const state = this.activeCards.get(streamKey);
6061
+ if (!state) return;
6062
+ const inFlight = state.flushInFlight;
6063
+ if (inFlight) {
6064
+ try {
6065
+ await inFlight;
6066
+ } catch {
6067
+ }
6068
+ continue;
6069
+ }
6070
+ if (state.flushQueued) {
6071
+ state.flushQueued = false;
6072
+ this.enqueueCardFlush(streamKey);
6073
+ continue;
6074
+ }
6075
+ return;
6076
+ }
5958
6077
  }
5959
6078
  /**
5960
6079
  * Finalize the streaming card: close streaming mode, update with final content + footer.
@@ -5976,6 +6095,7 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5976
6095
  clearTimeout(state.throttleTimer);
5977
6096
  state.throttleTimer = null;
5978
6097
  }
6098
+ await this.awaitCardFlushCompletion(cardKey);
5979
6099
  try {
5980
6100
  state.sequence++;
5981
6101
  await cardkit.card.settings({
@@ -6041,6 +6161,9 @@ ${trimmedResponse}`;
6041
6161
  hasActiveCard(chatId, streamKey) {
6042
6162
  return this.activeCards.has(this.resolveStreamKey(chatId, streamKey));
6043
6163
  }
6164
+ hasActiveStreamingUi(chatId, streamKey) {
6165
+ return this.hasActiveCard(chatId, streamKey);
6166
+ }
6044
6167
  // ── Streaming adapter interface ────────────────────────────────
6045
6168
  /**
6046
6169
  * Called by bridge-manager on each text SSE event.
@@ -6070,6 +6193,19 @@ ${trimmedResponse}`;
6070
6193
  if (!this.isStreamingEnabled()) return;
6071
6194
  this.updateToolProgress(chatId, tools, streamKey);
6072
6195
  }
6196
+ onStreamStatus(chatId, statusText, streamKey) {
6197
+ if (!this.isStreamingEnabled()) return;
6198
+ const cardKey = this.resolveStreamKey(chatId, streamKey);
6199
+ if (!this.activeCards.has(cardKey)) {
6200
+ const messageId = this.lastIncomingMessageId.get(chatId);
6201
+ this.createStreamingCard(chatId, messageId, cardKey).then((ok) => {
6202
+ if (ok) this.updateCardStatus(chatId, statusText, cardKey);
6203
+ }).catch(() => {
6204
+ });
6205
+ return;
6206
+ }
6207
+ this.updateCardStatus(chatId, statusText, cardKey);
6208
+ }
6073
6209
  async onStreamEnd(chatId, status, responseText, streamKey) {
6074
6210
  if (!this.isStreamingEnabled()) return false;
6075
6211
  return this.finalizeCard(chatId, status, responseText, streamKey);
@@ -18789,6 +18925,16 @@ function pushStreamFeedbackTools(target, tools) {
18789
18925
  } catch {
18790
18926
  }
18791
18927
  }
18928
+ function pushStreamFeedbackStatus(target, text2) {
18929
+ if (typeof target.adapter.onStreamStatus !== "function") return;
18930
+ target.ensureStarted?.();
18931
+ const rendered = renderFeedbackTextForChannel(target.channelType, text2);
18932
+ if (!rendered) return;
18933
+ try {
18934
+ target.adapter.onStreamStatus(target.chatId, rendered, target.streamKey);
18935
+ } catch {
18936
+ }
18937
+ }
18792
18938
  async function finalizeStreamFeedback(target, status, text2) {
18793
18939
  if (typeof target.adapter.onStreamEnd !== "function") return false;
18794
18940
  const rendered = renderFeedbackTextForChannel(target.channelType, text2);
@@ -18807,6 +18953,7 @@ var STREAM_DEFAULTS = {
18807
18953
  telegram: { intervalMs: 700, minDeltaChars: 20, maxChars: 3900 },
18808
18954
  discord: { intervalMs: 1500, minDeltaChars: 40, maxChars: 1900 }
18809
18955
  };
18956
+ var STREAM_STATUS_HEARTBEAT_MS = 1e4;
18810
18957
  function getStreamConfig(channelType = "telegram") {
18811
18958
  const { store } = getBridgeContext();
18812
18959
  const defaults = STREAM_DEFAULTS[channelType] || STREAM_DEFAULTS.telegram;
@@ -18826,12 +18973,40 @@ function flushPreview(adapter, state, config2) {
18826
18973
  }).catch(() => {
18827
18974
  });
18828
18975
  }
18976
+ function formatRuntimeDuration(ms) {
18977
+ const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
18978
+ if (totalSeconds < 60) return `${totalSeconds}s`;
18979
+ const totalMinutes = Math.floor(totalSeconds / 60);
18980
+ const seconds = totalSeconds % 60;
18981
+ if (totalMinutes < 60) {
18982
+ return seconds > 0 ? `${totalMinutes}m ${seconds}s` : `${totalMinutes}m`;
18983
+ }
18984
+ const hours = Math.floor(totalMinutes / 60);
18985
+ const minutes = totalMinutes % 60;
18986
+ if (minutes === 0 && seconds === 0) return `${hours}h`;
18987
+ if (seconds === 0) return `${hours}h ${minutes}m`;
18988
+ return `${hours}h ${minutes}m ${seconds}s`;
18989
+ }
18990
+ function formatInteractiveRuntimeStatus(elapsedMs, silentMs) {
18991
+ const parts = [`\u5DF2\u8FD0\u884C ${formatRuntimeDuration(elapsedMs)}`];
18992
+ if (typeof silentMs === "number" && silentMs >= 0) {
18993
+ parts.push(`\u6700\u8FD1 ${formatRuntimeDuration(silentMs)} \u65E0\u65B0\u8F93\u51FA`);
18994
+ }
18995
+ return parts.join("\uFF0C");
18996
+ }
18829
18997
  async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
18830
18998
  const binding = resolve(msg.address);
18831
18999
  const streamKey = buildInteractiveStreamKey(binding.codepilotSessionId, msg.messageId);
19000
+ const nowMs = deps.nowMs ?? (() => Date.now());
19001
+ const setIntervalFn = deps.setIntervalFn ?? ((callback, intervalMs) => setInterval(callback, intervalMs));
19002
+ const clearIntervalFn = deps.clearIntervalFn ?? ((handle) => clearInterval(handle));
19003
+ const processMessageImpl = deps.processMessageImpl ?? processMessage;
19004
+ const forwardPermissionRequestImpl = deps.forwardPermissionRequestImpl ?? forwardPermissionRequest;
19005
+ const streamStatusHeartbeatMs = Math.max(1e3, deps.streamStatusHeartbeatMs ?? STREAM_STATUS_HEARTBEAT_MS);
18832
19006
  adapter.onMessageStart?.(msg.address.chatId, streamKey);
18833
19007
  const taskAbort = new AbortController();
18834
19008
  const taskId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
19009
+ const taskStartedAt = nowMs();
18835
19010
  deps.resetMirrorSessionForInteractiveRun(binding.codepilotSessionId);
18836
19011
  const taskState = {
18837
19012
  id: taskId,
@@ -18842,7 +19017,8 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
18842
19017
  streamKey,
18843
19018
  sessionId: binding.codepilotSessionId,
18844
19019
  hasStreamingCards: false,
18845
- lastActivityAt: Date.now(),
19020
+ structuredStreamUiActive: false,
19021
+ lastActivityAt: taskStartedAt,
18846
19022
  idleReminderSent: false,
18847
19023
  streamFinalized: false,
18848
19024
  uiEnded: false,
@@ -18905,6 +19081,33 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
18905
19081
  chatId: msg.address.chatId,
18906
19082
  streamKey
18907
19083
  };
19084
+ const supportsPersistentStreamStatus = hasStreamingCards && adapter.provider === "feishu" && typeof adapter.onStreamStatus === "function";
19085
+ const supportsStructuredStreamUi = supportsPersistentStreamStatus && (adapter.supportsStructuredStreamingUi?.(msg.address.chatId) ?? true);
19086
+ const syncStructuredStreamUiState = () => {
19087
+ if (!supportsStructuredStreamUi || taskState.structuredStreamUiActive) return;
19088
+ if (adapter.hasActiveStreamingUi?.(msg.address.chatId, streamKey)) {
19089
+ taskState.structuredStreamUiActive = true;
19090
+ }
19091
+ };
19092
+ const pushRunningStatus = (silentMs) => {
19093
+ if (!supportsStructuredStreamUi || streamStatusUpdatesClosed) return;
19094
+ pushStreamFeedbackStatus(
19095
+ streamFeedbackTarget,
19096
+ formatInteractiveRuntimeStatus(nowMs() - taskStartedAt, silentMs)
19097
+ );
19098
+ syncStructuredStreamUiState();
19099
+ };
19100
+ let streamStatusHeartbeat = null;
19101
+ let streamStatusUpdatesClosed = false;
19102
+ const clearStreamStatusHeartbeat = () => {
19103
+ if (streamStatusHeartbeat == null) return;
19104
+ clearIntervalFn(streamStatusHeartbeat);
19105
+ streamStatusHeartbeat = null;
19106
+ };
19107
+ const stopStructuredStreamStatusUpdates = () => {
19108
+ streamStatusUpdatesClosed = true;
19109
+ clearStreamStatusHeartbeat();
19110
+ };
18908
19111
  const onStreamCardText = hasStreamingCards ? (fullText) => {
18909
19112
  if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
18910
19113
  pushStreamFeedbackText(
@@ -18925,6 +19128,8 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
18925
19128
  if (hasStreamingCards) {
18926
19129
  pushStreamFeedbackTools(streamFeedbackTarget, Array.from(toolCallTracker.values()));
18927
19130
  }
19131
+ pushRunningStatus(null);
19132
+ syncStructuredStreamUiState();
18928
19133
  };
18929
19134
  const onPartialText = (fullText) => {
18930
19135
  if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
@@ -18932,17 +19137,36 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
18932
19137
  deps.recordInteractiveHealthProgress(binding.codepilotSessionId, "text");
18933
19138
  previewOnPartialText?.(fullText);
18934
19139
  onStreamCardText?.(fullText);
19140
+ pushRunningStatus(null);
19141
+ syncStructuredStreamUiState();
18935
19142
  };
19143
+ if (supportsStructuredStreamUi) {
19144
+ pushRunningStatus(null);
19145
+ streamStatusHeartbeat = setIntervalFn(() => {
19146
+ if (streamStatusUpdatesClosed) {
19147
+ clearStreamStatusHeartbeat();
19148
+ return;
19149
+ }
19150
+ if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId) || taskAbort.signal.aborted) {
19151
+ clearStreamStatusHeartbeat();
19152
+ return;
19153
+ }
19154
+ const silentMs = nowMs() - taskState.lastActivityAt;
19155
+ if (silentMs < streamStatusHeartbeatMs) return;
19156
+ pushRunningStatus(silentMs);
19157
+ syncStructuredStreamUiState();
19158
+ }, streamStatusHeartbeatMs);
19159
+ }
18936
19160
  let finalOutcome = "failed";
18937
19161
  let finalOutcomeDetail;
18938
19162
  let shouldRecordHealthEnd = true;
18939
19163
  try {
18940
19164
  const promptText = text2 || (attachments && attachments.length > 0 ? "Describe this image." : "");
18941
- const result = await processMessage(
19165
+ const result = await processMessageImpl(
18942
19166
  binding,
18943
19167
  promptText,
18944
19168
  async (perm) => {
18945
- await forwardPermissionRequest(
19169
+ await forwardPermissionRequestImpl(
18946
19170
  adapter,
18947
19171
  msg.address,
18948
19172
  perm.permissionRequestId,
@@ -18974,6 +19198,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
18974
19198
  }
18975
19199
  let cardFinalized = false;
18976
19200
  if (hasStreamingCards) {
19201
+ stopStructuredStreamStatusUpdates();
18977
19202
  cardFinalized = await finalizeStreamFeedback(
18978
19203
  streamFeedbackTarget,
18979
19204
  result.hasError ? "error" : "completed",
@@ -19008,6 +19233,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
19008
19233
  finalOutcome = result.hasError ? "failed" : "completed";
19009
19234
  finalOutcomeDetail = result.hasError ? result.errorMessage?.trim() || void 0 : void 0;
19010
19235
  } finally {
19236
+ stopStructuredStreamStatusUpdates();
19011
19237
  if (previewState) {
19012
19238
  if (previewState.throttleTimer) {
19013
19239
  clearTimeout(previewState.throttleTimer);
@@ -19052,6 +19278,9 @@ function buildInteractiveIdleReminderNotice() {
19052
19278
  "\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"
19053
19279
  ].join("\n");
19054
19280
  }
19281
+ function shouldSkipIdleReminder(task) {
19282
+ return task.adapter.provider === "feishu" && task.structuredStreamUiActive;
19283
+ }
19055
19284
  function createInteractiveRuntime(getState2, options, deps) {
19056
19285
  function getQueuedCount(sessionId) {
19057
19286
  return getState2().queuedCounts.get(sessionId) || 0;
@@ -19110,6 +19339,7 @@ function createInteractiveRuntime(getState2, options, deps) {
19110
19339
  const now2 = Date.now();
19111
19340
  const tasks = Array.from(getState2().activeTasks.values());
19112
19341
  for (const task of tasks) {
19342
+ if (shouldSkipIdleReminder(task)) continue;
19113
19343
  if (task.idleReminderSent) continue;
19114
19344
  if (now2 - task.lastActivityAt < options.idleReminderMs) continue;
19115
19345
  await remindIdleInteractiveTask(task);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-to-im",
3
- "version": "1.0.35",
3
+ "version": "1.0.36",
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",