codex-to-im 1.0.35 → 1.0.37

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
@@ -36,6 +36,8 @@ var DEFAULT_WORKSPACE_ROOT = path.join(os.homedir(), "cx2im");
36
36
  var CTI_HOME = process.env.CTI_HOME || DEFAULT_CTI_HOME;
37
37
  var CONFIG_PATH = path.join(CTI_HOME, "config.env");
38
38
  var CONFIG_V2_PATH = path.join(CTI_HOME, "config.v2.json");
39
+ var DEFAULT_STREAM_STATUS_IDLE_START_SECONDS = 180;
40
+ var DEFAULT_STREAM_STATUS_CHECK_INTERVAL_SECONDS = 10;
39
41
  function expandHomePath(value) {
40
42
  if (!value) return value;
41
43
  if (value === "~") return os.homedir();
@@ -168,6 +170,8 @@ function migrateLegacyEnvToV2(env) {
168
170
  defaultModel: env.get("CTI_DEFAULT_MODEL") || void 0,
169
171
  defaultMode: env.get("CTI_DEFAULT_MODE") || "code",
170
172
  historyMessageLimit: parsePositiveInt(env.get("CTI_HISTORY_MESSAGE_LIMIT")) ?? 8,
173
+ streamStatusIdleStartSeconds: parsePositiveInt(env.get("CTI_STREAM_STATUS_IDLE_START_SECONDS")) ?? DEFAULT_STREAM_STATUS_IDLE_START_SECONDS,
174
+ streamStatusCheckIntervalSeconds: parsePositiveInt(env.get("CTI_STREAM_STATUS_CHECK_INTERVAL_SECONDS")) ?? DEFAULT_STREAM_STATUS_CHECK_INTERVAL_SECONDS,
171
175
  codexSkipGitRepoCheck: env.has("CTI_CODEX_SKIP_GIT_REPO_CHECK") ? env.get("CTI_CODEX_SKIP_GIT_REPO_CHECK") === "true" : true,
172
176
  codexSandboxMode: parseSandboxMode(env.get("CTI_CODEX_SANDBOX_MODE")) ?? "workspace-write",
173
177
  codexReasoningEffort: parseReasoningEffort(env.get("CTI_CODEX_REASONING_EFFORT")) ?? "medium",
@@ -190,6 +194,8 @@ function expandConfig(v2) {
190
194
  defaultModel: v2.runtime.defaultModel,
191
195
  defaultMode: v2.runtime.defaultMode || "code",
192
196
  historyMessageLimit: v2.runtime.historyMessageLimit ?? 8,
197
+ streamStatusIdleStartSeconds: v2.runtime.streamStatusIdleStartSeconds ?? DEFAULT_STREAM_STATUS_IDLE_START_SECONDS,
198
+ streamStatusCheckIntervalSeconds: v2.runtime.streamStatusCheckIntervalSeconds ?? DEFAULT_STREAM_STATUS_CHECK_INTERVAL_SECONDS,
193
199
  codexSkipGitRepoCheck: v2.runtime.codexSkipGitRepoCheck ?? true,
194
200
  codexSandboxMode: v2.runtime.codexSandboxMode ?? "workspace-write",
195
201
  codexReasoningEffort: v2.runtime.codexReasoningEffort ?? "medium",
@@ -214,6 +220,8 @@ function loadConfig() {
214
220
  defaultWorkspaceRoot: DEFAULT_WORKSPACE_ROOT,
215
221
  defaultMode: "code",
216
222
  historyMessageLimit: 8,
223
+ streamStatusIdleStartSeconds: DEFAULT_STREAM_STATUS_IDLE_START_SECONDS,
224
+ streamStatusCheckIntervalSeconds: DEFAULT_STREAM_STATUS_CHECK_INTERVAL_SECONDS,
217
225
  codexSkipGitRepoCheck: true,
218
226
  codexSandboxMode: "workspace-write",
219
227
  codexReasoningEffort: "medium",
package/dist/daemon.mjs CHANGED
@@ -5123,6 +5123,8 @@ var DEFAULT_WORKSPACE_ROOT = path.join(os.homedir(), "cx2im");
5123
5123
  var CTI_HOME = process.env.CTI_HOME || DEFAULT_CTI_HOME;
5124
5124
  var CONFIG_PATH = path.join(CTI_HOME, "config.env");
5125
5125
  var CONFIG_V2_PATH = path.join(CTI_HOME, "config.v2.json");
5126
+ var DEFAULT_STREAM_STATUS_IDLE_START_SECONDS = 180;
5127
+ var DEFAULT_STREAM_STATUS_CHECK_INTERVAL_SECONDS = 10;
5126
5128
  function expandHomePath(value) {
5127
5129
  if (!value) return value;
5128
5130
  if (value === "~") return os.homedir();
@@ -5258,6 +5260,8 @@ function migrateLegacyEnvToV2(env) {
5258
5260
  defaultModel: env.get("CTI_DEFAULT_MODEL") || void 0,
5259
5261
  defaultMode: env.get("CTI_DEFAULT_MODE") || "code",
5260
5262
  historyMessageLimit: parsePositiveInt(env.get("CTI_HISTORY_MESSAGE_LIMIT")) ?? 8,
5263
+ streamStatusIdleStartSeconds: parsePositiveInt(env.get("CTI_STREAM_STATUS_IDLE_START_SECONDS")) ?? DEFAULT_STREAM_STATUS_IDLE_START_SECONDS,
5264
+ streamStatusCheckIntervalSeconds: parsePositiveInt(env.get("CTI_STREAM_STATUS_CHECK_INTERVAL_SECONDS")) ?? DEFAULT_STREAM_STATUS_CHECK_INTERVAL_SECONDS,
5261
5265
  codexSkipGitRepoCheck: env.has("CTI_CODEX_SKIP_GIT_REPO_CHECK") ? env.get("CTI_CODEX_SKIP_GIT_REPO_CHECK") === "true" : true,
5262
5266
  codexSandboxMode: parseSandboxMode(env.get("CTI_CODEX_SANDBOX_MODE")) ?? "workspace-write",
5263
5267
  codexReasoningEffort: parseReasoningEffort(env.get("CTI_CODEX_REASONING_EFFORT")) ?? "medium",
@@ -5284,6 +5288,8 @@ function expandConfig(v2) {
5284
5288
  defaultModel: v2.runtime.defaultModel,
5285
5289
  defaultMode: v2.runtime.defaultMode || "code",
5286
5290
  historyMessageLimit: v2.runtime.historyMessageLimit ?? 8,
5291
+ streamStatusIdleStartSeconds: v2.runtime.streamStatusIdleStartSeconds ?? DEFAULT_STREAM_STATUS_IDLE_START_SECONDS,
5292
+ streamStatusCheckIntervalSeconds: v2.runtime.streamStatusCheckIntervalSeconds ?? DEFAULT_STREAM_STATUS_CHECK_INTERVAL_SECONDS,
5287
5293
  codexSkipGitRepoCheck: v2.runtime.codexSkipGitRepoCheck ?? true,
5288
5294
  codexSandboxMode: v2.runtime.codexSandboxMode ?? "workspace-write",
5289
5295
  codexReasoningEffort: v2.runtime.codexReasoningEffort ?? "medium",
@@ -5308,6 +5314,8 @@ function loadConfig() {
5308
5314
  defaultWorkspaceRoot: DEFAULT_WORKSPACE_ROOT,
5309
5315
  defaultMode: "code",
5310
5316
  historyMessageLimit: 8,
5317
+ streamStatusIdleStartSeconds: DEFAULT_STREAM_STATUS_IDLE_START_SECONDS,
5318
+ streamStatusCheckIntervalSeconds: DEFAULT_STREAM_STATUS_CHECK_INTERVAL_SECONDS,
5311
5319
  codexSkipGitRepoCheck: true,
5312
5320
  codexSandboxMode: "workspace-write",
5313
5321
  codexReasoningEffort: "medium",
@@ -5351,6 +5359,18 @@ function configToSettings(config2) {
5351
5359
  "bridge_history_message_limit",
5352
5360
  String(config2.historyMessageLimit && config2.historyMessageLimit > 0 ? config2.historyMessageLimit : 8)
5353
5361
  );
5362
+ m.set(
5363
+ "bridge_stream_status_idle_start_seconds",
5364
+ String(
5365
+ config2.streamStatusIdleStartSeconds && config2.streamStatusIdleStartSeconds > 0 ? config2.streamStatusIdleStartSeconds : DEFAULT_STREAM_STATUS_IDLE_START_SECONDS
5366
+ )
5367
+ );
5368
+ m.set(
5369
+ "bridge_stream_status_check_interval_seconds",
5370
+ String(
5371
+ config2.streamStatusCheckIntervalSeconds && config2.streamStatusCheckIntervalSeconds > 0 ? config2.streamStatusCheckIntervalSeconds : DEFAULT_STREAM_STATUS_CHECK_INTERVAL_SECONDS
5372
+ )
5373
+ );
5354
5374
  m.set(
5355
5375
  "bridge_codex_skip_git_repo_check",
5356
5376
  config2.codexSkipGitRepoCheck === true ? "true" : "false"
@@ -5496,25 +5516,16 @@ function formatElapsed(ms) {
5496
5516
  const remSec = Math.floor(sec % 60);
5497
5517
  return `${min}m ${remSec}s`;
5498
5518
  }
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...";
5519
+ function buildStreamingTextContent(text2) {
5520
+ return text2 || "\u{1F4AD} Thinking...";
5521
+ }
5522
+ function buildStreamingToolsContent(tools) {
5523
+ return buildToolProgressMarkdown(tools);
5508
5524
  }
5509
5525
  function buildFinalCardJson(text2, tools, footer) {
5510
5526
  const elements = [];
5511
- let content = preprocessFeishuMarkdown(text2);
5527
+ const content = preprocessFeishuMarkdown(text2);
5512
5528
  const toolMd = buildToolProgressMarkdown(tools);
5513
- if (toolMd) {
5514
- content = content ? `${content}
5515
-
5516
- ${toolMd}` : toolMd;
5517
- }
5518
5529
  if (content) {
5519
5530
  elements.push({
5520
5531
  tag: "markdown",
@@ -5523,12 +5534,25 @@ ${toolMd}` : toolMd;
5523
5534
  text_size: "normal"
5524
5535
  });
5525
5536
  }
5537
+ if (toolMd) {
5538
+ if (elements.length > 0) {
5539
+ elements.push({ tag: "hr" });
5540
+ }
5541
+ elements.push({
5542
+ tag: "markdown",
5543
+ content: toolMd,
5544
+ text_align: "left",
5545
+ text_size: "normal"
5546
+ });
5547
+ }
5526
5548
  if (footer) {
5527
5549
  const parts = [];
5528
5550
  if (footer.status) parts.push(footer.status);
5529
5551
  if (footer.elapsed) parts.push(footer.elapsed);
5530
5552
  if (parts.length > 0) {
5531
- elements.push({ tag: "hr" });
5553
+ if (elements.length > 0) {
5554
+ elements.push({ tag: "hr" });
5555
+ }
5532
5556
  elements.push({
5533
5557
  tag: "markdown",
5534
5558
  content: parts.join(" \xB7 "),
@@ -5595,6 +5619,8 @@ var DEDUP_MAX = 1e3;
5595
5619
  var MAX_FILE_SIZE = 20 * 1024 * 1024;
5596
5620
  var TYPING_EMOJI = "Typing";
5597
5621
  var CARD_THROTTLE_MS = 200;
5622
+ var INITIAL_STREAMING_STATUS = "\u5DF2\u8FD0\u884C 0s";
5623
+ var EMPTY_STREAMING_TOOLS = "";
5598
5624
  var MIME_BY_TYPE = {
5599
5625
  image: "image/png",
5600
5626
  file: "application/octet-stream",
@@ -5642,6 +5668,9 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5642
5668
  isStreamingEnabled() {
5643
5669
  return this.channelConfig.streamingEnabled !== false;
5644
5670
  }
5671
+ supportsStructuredStreamingUi(_chatId) {
5672
+ return this.isStreamingEnabled();
5673
+ }
5645
5674
  resolveStreamKey(chatId, streamKey) {
5646
5675
  return streamKey?.trim() || chatId;
5647
5676
  }
@@ -5840,13 +5869,29 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5840
5869
  summary: { content: "\u601D\u8003\u4E2D..." }
5841
5870
  },
5842
5871
  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
- }]
5872
+ elements: [
5873
+ {
5874
+ tag: "markdown",
5875
+ content: "\u{1F4AD} Thinking...",
5876
+ text_align: "left",
5877
+ text_size: "normal",
5878
+ element_id: "streaming_content"
5879
+ },
5880
+ {
5881
+ tag: "markdown",
5882
+ content: EMPTY_STREAMING_TOOLS,
5883
+ text_align: "left",
5884
+ text_size: "normal",
5885
+ element_id: "streaming_tools"
5886
+ },
5887
+ {
5888
+ tag: "markdown",
5889
+ content: INITIAL_STREAMING_STATUS,
5890
+ text_align: "left",
5891
+ text_size: "notation",
5892
+ element_id: "streaming_status"
5893
+ }
5894
+ ]
5850
5895
  }
5851
5896
  };
5852
5897
  const createResp = await cardkit.card.create({
@@ -5888,8 +5933,14 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5888
5933
  toolCalls: [],
5889
5934
  thinking: true,
5890
5935
  pendingText: null,
5936
+ pendingStatusText: INITIAL_STREAMING_STATUS,
5937
+ renderedText: "\u{1F4AD} Thinking...",
5938
+ renderedToolsText: EMPTY_STREAMING_TOOLS,
5939
+ renderedStatusText: INITIAL_STREAMING_STATUS,
5891
5940
  lastUpdateAt: 0,
5892
- throttleTimer: null
5941
+ throttleTimer: null,
5942
+ flushInFlight: null,
5943
+ flushQueued: false
5893
5944
  });
5894
5945
  console.log(`[feishu-adapter] Streaming card created: streamKey=${cardKey}, cardId=${cardId}, msgId=${messageId}`);
5895
5946
  return true;
@@ -5909,12 +5960,43 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5909
5960
  state.thinking = false;
5910
5961
  }
5911
5962
  state.pendingText = text2;
5963
+ this.scheduleCardFlush(cardKey);
5964
+ }
5965
+ updateCardStatus(chatId, statusText, streamKey) {
5966
+ const cardKey = this.resolveStreamKey(chatId, streamKey);
5967
+ const state = this.activeCards.get(cardKey);
5968
+ if (!state || !this.restClient) return;
5969
+ state.pendingStatusText = statusText || INITIAL_STREAMING_STATUS;
5970
+ this.scheduleCardFlush(cardKey);
5971
+ }
5972
+ enqueueCardFlush(streamKey) {
5973
+ const state = this.activeCards.get(streamKey);
5974
+ if (!state) return;
5975
+ if (state.flushInFlight) {
5976
+ state.flushQueued = true;
5977
+ return;
5978
+ }
5979
+ state.flushInFlight = this.flushCardUpdate(streamKey).catch((err) => {
5980
+ console.warn("[feishu-adapter] cardElement.content failed:", err instanceof Error ? err.message : err);
5981
+ }).finally(() => {
5982
+ const current = this.activeCards.get(streamKey);
5983
+ if (!current) return;
5984
+ current.flushInFlight = null;
5985
+ if (current.flushQueued) {
5986
+ current.flushQueued = false;
5987
+ this.enqueueCardFlush(streamKey);
5988
+ }
5989
+ });
5990
+ }
5991
+ scheduleCardFlush(streamKey) {
5992
+ const state = this.activeCards.get(streamKey);
5993
+ if (!state) return;
5912
5994
  const elapsed = Date.now() - state.lastUpdateAt;
5913
5995
  if (elapsed < CARD_THROTTLE_MS && state.lastUpdateAt > 0) {
5914
5996
  if (!state.throttleTimer) {
5915
5997
  state.throttleTimer = setTimeout(() => {
5916
5998
  state.throttleTimer = null;
5917
- this.flushCardUpdate(cardKey);
5999
+ this.enqueueCardFlush(streamKey);
5918
6000
  }, CARD_THROTTLE_MS - elapsed);
5919
6001
  }
5920
6002
  return;
@@ -5923,28 +6005,65 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5923
6005
  clearTimeout(state.throttleTimer);
5924
6006
  state.throttleTimer = null;
5925
6007
  }
5926
- this.flushCardUpdate(cardKey);
6008
+ this.enqueueCardFlush(streamKey);
5927
6009
  }
5928
6010
  /**
5929
6011
  * Flush pending card update to Feishu API.
5930
6012
  */
5931
- flushCardUpdate(streamKey) {
6013
+ async flushCardUpdate(streamKey) {
5932
6014
  const state = this.activeCards.get(streamKey);
5933
6015
  if (!state || !this.restClient) return;
5934
6016
  const cardkit = this.restClient.cardkit?.v1;
5935
6017
  if (!cardkit?.cardElement?.content) return;
5936
- const content = buildStreamingContent(state.pendingText || "", state.toolCalls);
5937
- state.sequence++;
5938
- const seq = state.sequence;
6018
+ const content = buildStreamingTextContent(state.pendingText || "");
6019
+ const toolsText = buildStreamingToolsContent(state.toolCalls) || EMPTY_STREAMING_TOOLS;
6020
+ const statusText = state.pendingStatusText || INITIAL_STREAMING_STATUS;
6021
+ const updates = [];
6022
+ if (content !== state.renderedText) {
6023
+ updates.push({
6024
+ elementId: "streaming_content",
6025
+ content,
6026
+ onSuccess: () => {
6027
+ state.renderedText = content;
6028
+ }
6029
+ });
6030
+ }
6031
+ if (toolsText !== state.renderedToolsText) {
6032
+ updates.push({
6033
+ elementId: "streaming_tools",
6034
+ content: toolsText,
6035
+ onSuccess: () => {
6036
+ state.renderedToolsText = toolsText;
6037
+ }
6038
+ });
6039
+ }
6040
+ if (statusText !== state.renderedStatusText) {
6041
+ updates.push({
6042
+ elementId: "streaming_status",
6043
+ content: statusText,
6044
+ onSuccess: () => {
6045
+ state.renderedStatusText = statusText;
6046
+ }
6047
+ });
6048
+ }
6049
+ if (updates.length === 0) return;
5939
6050
  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
- });
6051
+ for (const update of updates) {
6052
+ state.sequence++;
6053
+ try {
6054
+ await cardkit.cardElement.content({
6055
+ path: { card_id: cardId, element_id: update.elementId },
6056
+ data: { content: update.content, sequence: state.sequence }
6057
+ });
6058
+ update.onSuccess();
6059
+ state.lastUpdateAt = Date.now();
6060
+ } catch (err) {
6061
+ console.warn(
6062
+ `[feishu-adapter] cardElement.content failed for ${update.elementId}:`,
6063
+ err instanceof Error ? err.message : err
6064
+ );
6065
+ }
6066
+ }
5948
6067
  }
5949
6068
  /**
5950
6069
  * Update tool progress in the streaming card.
@@ -5954,7 +6073,27 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5954
6073
  const state = this.activeCards.get(cardKey);
5955
6074
  if (!state) return;
5956
6075
  state.toolCalls = tools;
5957
- this.updateCardContent(chatId, state.pendingText || "", cardKey);
6076
+ this.scheduleCardFlush(cardKey);
6077
+ }
6078
+ async awaitCardFlushCompletion(streamKey) {
6079
+ while (true) {
6080
+ const state = this.activeCards.get(streamKey);
6081
+ if (!state) return;
6082
+ const inFlight = state.flushInFlight;
6083
+ if (inFlight) {
6084
+ try {
6085
+ await inFlight;
6086
+ } catch {
6087
+ }
6088
+ continue;
6089
+ }
6090
+ if (state.flushQueued) {
6091
+ state.flushQueued = false;
6092
+ this.enqueueCardFlush(streamKey);
6093
+ continue;
6094
+ }
6095
+ return;
6096
+ }
5958
6097
  }
5959
6098
  /**
5960
6099
  * Finalize the streaming card: close streaming mode, update with final content + footer.
@@ -5976,6 +6115,7 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5976
6115
  clearTimeout(state.throttleTimer);
5977
6116
  state.throttleTimer = null;
5978
6117
  }
6118
+ await this.awaitCardFlushCompletion(cardKey);
5979
6119
  try {
5980
6120
  state.sequence++;
5981
6121
  await cardkit.card.settings({
@@ -6041,6 +6181,9 @@ ${trimmedResponse}`;
6041
6181
  hasActiveCard(chatId, streamKey) {
6042
6182
  return this.activeCards.has(this.resolveStreamKey(chatId, streamKey));
6043
6183
  }
6184
+ hasActiveStreamingUi(chatId, streamKey) {
6185
+ return this.hasActiveCard(chatId, streamKey);
6186
+ }
6044
6187
  // ── Streaming adapter interface ────────────────────────────────
6045
6188
  /**
6046
6189
  * Called by bridge-manager on each text SSE event.
@@ -6070,6 +6213,19 @@ ${trimmedResponse}`;
6070
6213
  if (!this.isStreamingEnabled()) return;
6071
6214
  this.updateToolProgress(chatId, tools, streamKey);
6072
6215
  }
6216
+ onStreamStatus(chatId, statusText, streamKey) {
6217
+ if (!this.isStreamingEnabled()) return;
6218
+ const cardKey = this.resolveStreamKey(chatId, streamKey);
6219
+ if (!this.activeCards.has(cardKey)) {
6220
+ const messageId = this.lastIncomingMessageId.get(chatId);
6221
+ this.createStreamingCard(chatId, messageId, cardKey).then((ok) => {
6222
+ if (ok) this.updateCardStatus(chatId, statusText, cardKey);
6223
+ }).catch(() => {
6224
+ });
6225
+ return;
6226
+ }
6227
+ this.updateCardStatus(chatId, statusText, cardKey);
6228
+ }
6073
6229
  async onStreamEnd(chatId, status, responseText, streamKey) {
6074
6230
  if (!this.isStreamingEnabled()) return false;
6075
6231
  return this.finalizeCard(chatId, status, responseText, streamKey);
@@ -18789,6 +18945,16 @@ function pushStreamFeedbackTools(target, tools) {
18789
18945
  } catch {
18790
18946
  }
18791
18947
  }
18948
+ function pushStreamFeedbackStatus(target, text2) {
18949
+ if (typeof target.adapter.onStreamStatus !== "function") return;
18950
+ target.ensureStarted?.();
18951
+ const rendered = renderFeedbackTextForChannel(target.channelType, text2);
18952
+ if (!rendered) return;
18953
+ try {
18954
+ target.adapter.onStreamStatus(target.chatId, rendered, target.streamKey);
18955
+ } catch {
18956
+ }
18957
+ }
18792
18958
  async function finalizeStreamFeedback(target, status, text2) {
18793
18959
  if (typeof target.adapter.onStreamEnd !== "function") return false;
18794
18960
  const rendered = renderFeedbackTextForChannel(target.channelType, text2);
@@ -18807,6 +18973,8 @@ var STREAM_DEFAULTS = {
18807
18973
  telegram: { intervalMs: 700, minDeltaChars: 20, maxChars: 3900 },
18808
18974
  discord: { intervalMs: 1500, minDeltaChars: 40, maxChars: 1900 }
18809
18975
  };
18976
+ var STREAM_STATUS_IDLE_START_MS = 18e4;
18977
+ var STREAM_STATUS_HEARTBEAT_MS = 1e4;
18810
18978
  function getStreamConfig(channelType = "telegram") {
18811
18979
  const { store } = getBridgeContext();
18812
18980
  const defaults = STREAM_DEFAULTS[channelType] || STREAM_DEFAULTS.telegram;
@@ -18816,6 +18984,21 @@ function getStreamConfig(channelType = "telegram") {
18816
18984
  const maxChars = parseInt(store.getSetting(`${prefix}max_chars`) || "", 10) || defaults.maxChars;
18817
18985
  return { intervalMs, minDeltaChars, maxChars };
18818
18986
  }
18987
+ function getStructuredStreamStatusConfig() {
18988
+ const { store } = getBridgeContext();
18989
+ const idleStartSeconds = parseInt(store.getSetting("bridge_stream_status_idle_start_seconds") || "", 10);
18990
+ const heartbeatSeconds = parseInt(store.getSetting("bridge_stream_status_check_interval_seconds") || "", 10);
18991
+ return {
18992
+ idleStartMs: Math.max(
18993
+ 0,
18994
+ (Number.isFinite(idleStartSeconds) && idleStartSeconds > 0 ? idleStartSeconds : STREAM_STATUS_IDLE_START_MS / 1e3) * 1e3
18995
+ ),
18996
+ heartbeatMs: Math.max(
18997
+ 1e3,
18998
+ (Number.isFinite(heartbeatSeconds) && heartbeatSeconds > 0 ? heartbeatSeconds : STREAM_STATUS_HEARTBEAT_MS / 1e3) * 1e3
18999
+ )
19000
+ };
19001
+ }
18819
19002
  function flushPreview(adapter, state, config2) {
18820
19003
  if (state.degraded || !adapter.sendPreview) return;
18821
19004
  const text2 = state.pendingText.length > config2.maxChars ? state.pendingText.slice(0, config2.maxChars) + "..." : state.pendingText;
@@ -18826,12 +19009,48 @@ function flushPreview(adapter, state, config2) {
18826
19009
  }).catch(() => {
18827
19010
  });
18828
19011
  }
19012
+ function formatRuntimeDuration(ms) {
19013
+ const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
19014
+ if (totalSeconds < 60) return `${totalSeconds}s`;
19015
+ const totalMinutes = Math.floor(totalSeconds / 60);
19016
+ const seconds = totalSeconds % 60;
19017
+ if (totalMinutes < 60) {
19018
+ return seconds > 0 ? `${totalMinutes}m ${seconds}s` : `${totalMinutes}m`;
19019
+ }
19020
+ const hours = Math.floor(totalMinutes / 60);
19021
+ const minutes = totalMinutes % 60;
19022
+ if (minutes === 0 && seconds === 0) return `${hours}h`;
19023
+ if (seconds === 0) return `${hours}h ${minutes}m`;
19024
+ return `${hours}h ${minutes}m ${seconds}s`;
19025
+ }
19026
+ function formatInteractiveRuntimeStatus(elapsedMs, silentMs) {
19027
+ const parts = [`\u5DF2\u8FD0\u884C ${formatRuntimeDuration(elapsedMs)}`];
19028
+ if (typeof silentMs === "number" && silentMs >= 0) {
19029
+ parts.push(`\u6700\u8FD1 ${formatRuntimeDuration(silentMs)} \u65E0\u65B0\u8F93\u51FA`);
19030
+ }
19031
+ return parts.join("\uFF0C");
19032
+ }
18829
19033
  async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
18830
19034
  const binding = resolve(msg.address);
18831
19035
  const streamKey = buildInteractiveStreamKey(binding.codepilotSessionId, msg.messageId);
19036
+ const nowMs = deps.nowMs ?? (() => Date.now());
19037
+ const setIntervalFn = deps.setIntervalFn ?? ((callback, intervalMs) => setInterval(callback, intervalMs));
19038
+ const clearIntervalFn = deps.clearIntervalFn ?? ((handle) => clearInterval(handle));
19039
+ const processMessageImpl = deps.processMessageImpl ?? processMessage;
19040
+ const forwardPermissionRequestImpl = deps.forwardPermissionRequestImpl ?? forwardPermissionRequest;
19041
+ const structuredStreamStatusConfig = getStructuredStreamStatusConfig();
19042
+ const streamStatusIdleDetectionStartMs = Math.max(
19043
+ 0,
19044
+ deps.streamStatusIdleDetectionStartMs ?? structuredStreamStatusConfig.idleStartMs
19045
+ );
19046
+ const streamStatusHeartbeatMs = Math.max(
19047
+ 1e3,
19048
+ deps.streamStatusHeartbeatMs ?? structuredStreamStatusConfig.heartbeatMs
19049
+ );
18832
19050
  adapter.onMessageStart?.(msg.address.chatId, streamKey);
18833
19051
  const taskAbort = new AbortController();
18834
19052
  const taskId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
19053
+ const taskStartedAt = nowMs();
18835
19054
  deps.resetMirrorSessionForInteractiveRun(binding.codepilotSessionId);
18836
19055
  const taskState = {
18837
19056
  id: taskId,
@@ -18842,7 +19061,8 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
18842
19061
  streamKey,
18843
19062
  sessionId: binding.codepilotSessionId,
18844
19063
  hasStreamingCards: false,
18845
- lastActivityAt: Date.now(),
19064
+ structuredStreamUiActive: false,
19065
+ lastActivityAt: taskStartedAt,
18846
19066
  idleReminderSent: false,
18847
19067
  streamFinalized: false,
18848
19068
  uiEnded: false,
@@ -18905,6 +19125,33 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
18905
19125
  chatId: msg.address.chatId,
18906
19126
  streamKey
18907
19127
  };
19128
+ const supportsPersistentStreamStatus = hasStreamingCards && adapter.provider === "feishu" && typeof adapter.onStreamStatus === "function";
19129
+ const supportsStructuredStreamUi = supportsPersistentStreamStatus && (adapter.supportsStructuredStreamingUi?.(msg.address.chatId) ?? true);
19130
+ const syncStructuredStreamUiState = () => {
19131
+ if (!supportsStructuredStreamUi || taskState.structuredStreamUiActive) return;
19132
+ if (adapter.hasActiveStreamingUi?.(msg.address.chatId, streamKey)) {
19133
+ taskState.structuredStreamUiActive = true;
19134
+ }
19135
+ };
19136
+ const pushRunningStatus = (silentMs) => {
19137
+ if (!supportsStructuredStreamUi || streamStatusUpdatesClosed) return;
19138
+ pushStreamFeedbackStatus(
19139
+ streamFeedbackTarget,
19140
+ formatInteractiveRuntimeStatus(nowMs() - taskStartedAt, silentMs)
19141
+ );
19142
+ syncStructuredStreamUiState();
19143
+ };
19144
+ let streamStatusHeartbeat = null;
19145
+ let streamStatusUpdatesClosed = false;
19146
+ const clearStreamStatusHeartbeat = () => {
19147
+ if (streamStatusHeartbeat == null) return;
19148
+ clearIntervalFn(streamStatusHeartbeat);
19149
+ streamStatusHeartbeat = null;
19150
+ };
19151
+ const stopStructuredStreamStatusUpdates = () => {
19152
+ streamStatusUpdatesClosed = true;
19153
+ clearStreamStatusHeartbeat();
19154
+ };
18908
19155
  const onStreamCardText = hasStreamingCards ? (fullText) => {
18909
19156
  if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
18910
19157
  pushStreamFeedbackText(
@@ -18925,6 +19172,8 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
18925
19172
  if (hasStreamingCards) {
18926
19173
  pushStreamFeedbackTools(streamFeedbackTarget, Array.from(toolCallTracker.values()));
18927
19174
  }
19175
+ pushRunningStatus(null);
19176
+ syncStructuredStreamUiState();
18928
19177
  };
18929
19178
  const onPartialText = (fullText) => {
18930
19179
  if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
@@ -18932,17 +19181,38 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
18932
19181
  deps.recordInteractiveHealthProgress(binding.codepilotSessionId, "text");
18933
19182
  previewOnPartialText?.(fullText);
18934
19183
  onStreamCardText?.(fullText);
19184
+ pushRunningStatus(null);
19185
+ syncStructuredStreamUiState();
18935
19186
  };
19187
+ if (supportsStructuredStreamUi) {
19188
+ pushRunningStatus(null);
19189
+ streamStatusHeartbeat = setIntervalFn(() => {
19190
+ if (streamStatusUpdatesClosed) {
19191
+ clearStreamStatusHeartbeat();
19192
+ return;
19193
+ }
19194
+ if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId) || taskAbort.signal.aborted) {
19195
+ clearStreamStatusHeartbeat();
19196
+ return;
19197
+ }
19198
+ const elapsedMs = nowMs() - taskStartedAt;
19199
+ if (elapsedMs < streamStatusIdleDetectionStartMs) return;
19200
+ const silentMs = nowMs() - taskState.lastActivityAt;
19201
+ if (silentMs < streamStatusHeartbeatMs) return;
19202
+ pushRunningStatus(silentMs);
19203
+ syncStructuredStreamUiState();
19204
+ }, streamStatusHeartbeatMs);
19205
+ }
18936
19206
  let finalOutcome = "failed";
18937
19207
  let finalOutcomeDetail;
18938
19208
  let shouldRecordHealthEnd = true;
18939
19209
  try {
18940
19210
  const promptText = text2 || (attachments && attachments.length > 0 ? "Describe this image." : "");
18941
- const result = await processMessage(
19211
+ const result = await processMessageImpl(
18942
19212
  binding,
18943
19213
  promptText,
18944
19214
  async (perm) => {
18945
- await forwardPermissionRequest(
19215
+ await forwardPermissionRequestImpl(
18946
19216
  adapter,
18947
19217
  msg.address,
18948
19218
  perm.permissionRequestId,
@@ -18974,6 +19244,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
18974
19244
  }
18975
19245
  let cardFinalized = false;
18976
19246
  if (hasStreamingCards) {
19247
+ stopStructuredStreamStatusUpdates();
18977
19248
  cardFinalized = await finalizeStreamFeedback(
18978
19249
  streamFeedbackTarget,
18979
19250
  result.hasError ? "error" : "completed",
@@ -19008,6 +19279,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
19008
19279
  finalOutcome = result.hasError ? "failed" : "completed";
19009
19280
  finalOutcomeDetail = result.hasError ? result.errorMessage?.trim() || void 0 : void 0;
19010
19281
  } finally {
19282
+ stopStructuredStreamStatusUpdates();
19011
19283
  if (previewState) {
19012
19284
  if (previewState.throttleTimer) {
19013
19285
  clearTimeout(previewState.throttleTimer);
@@ -19052,6 +19324,9 @@ function buildInteractiveIdleReminderNotice() {
19052
19324
  "\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
19325
  ].join("\n");
19054
19326
  }
19327
+ function shouldSkipIdleReminder(task) {
19328
+ return task.adapter.provider === "feishu" && task.structuredStreamUiActive;
19329
+ }
19055
19330
  function createInteractiveRuntime(getState2, options, deps) {
19056
19331
  function getQueuedCount(sessionId) {
19057
19332
  return getState2().queuedCounts.get(sessionId) || 0;
@@ -19110,6 +19385,7 @@ function createInteractiveRuntime(getState2, options, deps) {
19110
19385
  const now2 = Date.now();
19111
19386
  const tasks = Array.from(getState2().activeTasks.values());
19112
19387
  for (const task of tasks) {
19388
+ if (shouldSkipIdleReminder(task)) continue;
19113
19389
  if (task.idleReminderSent) continue;
19114
19390
  if (now2 - task.lastActivityAt < options.idleReminderMs) continue;
19115
19391
  await remindIdleInteractiveTask(task);
@@ -4604,6 +4604,8 @@ var DEFAULT_WORKSPACE_ROOT = path.join(os.homedir(), "cx2im");
4604
4604
  var CTI_HOME = process.env.CTI_HOME || DEFAULT_CTI_HOME;
4605
4605
  var CONFIG_PATH = path.join(CTI_HOME, "config.env");
4606
4606
  var CONFIG_V2_PATH = path.join(CTI_HOME, "config.v2.json");
4607
+ var DEFAULT_STREAM_STATUS_IDLE_START_SECONDS = 180;
4608
+ var DEFAULT_STREAM_STATUS_CHECK_INTERVAL_SECONDS = 10;
4607
4609
  function expandHomePath(value) {
4608
4610
  if (!value) return value;
4609
4611
  if (value === "~") return os.homedir();
@@ -4739,6 +4741,8 @@ function migrateLegacyEnvToV2(env) {
4739
4741
  defaultModel: env.get("CTI_DEFAULT_MODEL") || void 0,
4740
4742
  defaultMode: env.get("CTI_DEFAULT_MODE") || "code",
4741
4743
  historyMessageLimit: parsePositiveInt(env.get("CTI_HISTORY_MESSAGE_LIMIT")) ?? 8,
4744
+ streamStatusIdleStartSeconds: parsePositiveInt(env.get("CTI_STREAM_STATUS_IDLE_START_SECONDS")) ?? DEFAULT_STREAM_STATUS_IDLE_START_SECONDS,
4745
+ streamStatusCheckIntervalSeconds: parsePositiveInt(env.get("CTI_STREAM_STATUS_CHECK_INTERVAL_SECONDS")) ?? DEFAULT_STREAM_STATUS_CHECK_INTERVAL_SECONDS,
4742
4746
  codexSkipGitRepoCheck: env.has("CTI_CODEX_SKIP_GIT_REPO_CHECK") ? env.get("CTI_CODEX_SKIP_GIT_REPO_CHECK") === "true" : true,
4743
4747
  codexSandboxMode: parseSandboxMode(env.get("CTI_CODEX_SANDBOX_MODE")) ?? "workspace-write",
4744
4748
  codexReasoningEffort: parseReasoningEffort(env.get("CTI_CODEX_REASONING_EFFORT")) ?? "medium",
@@ -4765,6 +4769,8 @@ function expandConfig(v2) {
4765
4769
  defaultModel: v2.runtime.defaultModel,
4766
4770
  defaultMode: v2.runtime.defaultMode || "code",
4767
4771
  historyMessageLimit: v2.runtime.historyMessageLimit ?? 8,
4772
+ streamStatusIdleStartSeconds: v2.runtime.streamStatusIdleStartSeconds ?? DEFAULT_STREAM_STATUS_IDLE_START_SECONDS,
4773
+ streamStatusCheckIntervalSeconds: v2.runtime.streamStatusCheckIntervalSeconds ?? DEFAULT_STREAM_STATUS_CHECK_INTERVAL_SECONDS,
4768
4774
  codexSkipGitRepoCheck: v2.runtime.codexSkipGitRepoCheck ?? true,
4769
4775
  codexSandboxMode: v2.runtime.codexSandboxMode ?? "workspace-write",
4770
4776
  codexReasoningEffort: v2.runtime.codexReasoningEffort ?? "medium",
@@ -4784,6 +4790,8 @@ function buildV2FileFromExpandedConfig(config, current) {
4784
4790
  defaultModel: config.defaultModel,
4785
4791
  defaultMode: config.defaultMode,
4786
4792
  historyMessageLimit: config.historyMessageLimit,
4793
+ streamStatusIdleStartSeconds: config.streamStatusIdleStartSeconds,
4794
+ streamStatusCheckIntervalSeconds: config.streamStatusCheckIntervalSeconds,
4787
4795
  codexSkipGitRepoCheck: config.codexSkipGitRepoCheck,
4788
4796
  codexSandboxMode: config.codexSandboxMode,
4789
4797
  codexReasoningEffort: config.codexReasoningEffort,
@@ -4814,6 +4822,8 @@ function loadConfig() {
4814
4822
  defaultWorkspaceRoot: DEFAULT_WORKSPACE_ROOT,
4815
4823
  defaultMode: "code",
4816
4824
  historyMessageLimit: 8,
4825
+ streamStatusIdleStartSeconds: DEFAULT_STREAM_STATUS_IDLE_START_SECONDS,
4826
+ streamStatusCheckIntervalSeconds: DEFAULT_STREAM_STATUS_CHECK_INTERVAL_SECONDS,
4817
4827
  codexSkipGitRepoCheck: true,
4818
4828
  codexSandboxMode: "workspace-write",
4819
4829
  codexReasoningEffort: "medium",
@@ -4845,6 +4855,12 @@ function saveConfig(config) {
4845
4855
  if (next.runtime.historyMessageLimit !== void 0) {
4846
4856
  out += formatEnvLine("CTI_HISTORY_MESSAGE_LIMIT", String(next.runtime.historyMessageLimit));
4847
4857
  }
4858
+ if (next.runtime.streamStatusIdleStartSeconds !== void 0) {
4859
+ out += formatEnvLine("CTI_STREAM_STATUS_IDLE_START_SECONDS", String(next.runtime.streamStatusIdleStartSeconds));
4860
+ }
4861
+ if (next.runtime.streamStatusCheckIntervalSeconds !== void 0) {
4862
+ out += formatEnvLine("CTI_STREAM_STATUS_CHECK_INTERVAL_SECONDS", String(next.runtime.streamStatusCheckIntervalSeconds));
4863
+ }
4848
4864
  if (next.runtime.codexSkipGitRepoCheck !== void 0) {
4849
4865
  out += formatEnvLine("CTI_CODEX_SKIP_GIT_REPO_CHECK", String(next.runtime.codexSkipGitRepoCheck));
4850
4866
  }
@@ -4917,6 +4933,18 @@ function configToSettings(config) {
4917
4933
  "bridge_history_message_limit",
4918
4934
  String(config.historyMessageLimit && config.historyMessageLimit > 0 ? config.historyMessageLimit : 8)
4919
4935
  );
4936
+ m.set(
4937
+ "bridge_stream_status_idle_start_seconds",
4938
+ String(
4939
+ config.streamStatusIdleStartSeconds && config.streamStatusIdleStartSeconds > 0 ? config.streamStatusIdleStartSeconds : DEFAULT_STREAM_STATUS_IDLE_START_SECONDS
4940
+ )
4941
+ );
4942
+ m.set(
4943
+ "bridge_stream_status_check_interval_seconds",
4944
+ String(
4945
+ config.streamStatusCheckIntervalSeconds && config.streamStatusCheckIntervalSeconds > 0 ? config.streamStatusCheckIntervalSeconds : DEFAULT_STREAM_STATUS_CHECK_INTERVAL_SECONDS
4946
+ )
4947
+ );
4920
4948
  m.set(
4921
4949
  "bridge_codex_skip_git_repo_check",
4922
4950
  config.codexSkipGitRepoCheck === true ? "true" : "false"
@@ -7626,6 +7654,8 @@ function configToPayload(config) {
7626
7654
  availableModels: availableCodexModels,
7627
7655
  defaultMode: config.defaultMode,
7628
7656
  historyMessageLimit: config.historyMessageLimit ?? 8,
7657
+ streamStatusIdleStartSeconds: config.streamStatusIdleStartSeconds ?? 180,
7658
+ streamStatusCheckIntervalSeconds: config.streamStatusCheckIntervalSeconds ?? 10,
7629
7659
  codexSkipGitRepoCheck: config.codexSkipGitRepoCheck === true,
7630
7660
  codexSandboxMode: config.codexSandboxMode || "workspace-write",
7631
7661
  codexReasoningEffort: config.codexReasoningEffort || "medium",
@@ -7649,6 +7679,8 @@ function mergeConfig(payload) {
7649
7679
  defaultModel: rawDefaultModel === void 0 ? current.defaultModel : rawDefaultModel === "" ? void 0 : availableCodexModelSlugs.has(rawDefaultModel) ? rawDefaultModel : current.defaultModel,
7650
7680
  defaultMode: payload.defaultMode === "plan" || payload.defaultMode === "ask" ? payload.defaultMode : "code",
7651
7681
  historyMessageLimit: asPositiveInt(payload.historyMessageLimit) || current.historyMessageLimit || 8,
7682
+ streamStatusIdleStartSeconds: asPositiveInt(payload.streamStatusIdleStartSeconds) || current.streamStatusIdleStartSeconds || 180,
7683
+ streamStatusCheckIntervalSeconds: asPositiveInt(payload.streamStatusCheckIntervalSeconds) || current.streamStatusCheckIntervalSeconds || 10,
7652
7684
  codexSkipGitRepoCheck: payload.codexSkipGitRepoCheck === true,
7653
7685
  codexSandboxMode: payload.codexSandboxMode === "read-only" || payload.codexSandboxMode === "danger-full-access" ? payload.codexSandboxMode : "workspace-write",
7654
7686
  codexReasoningEffort: payload.codexReasoningEffort === "minimal" || payload.codexReasoningEffort === "low" || payload.codexReasoningEffort === "high" || payload.codexReasoningEffort === "xhigh" ? payload.codexReasoningEffort : "medium",
@@ -9486,6 +9518,14 @@ function renderHtml() {
9486
9518
  /history \u8FD4\u56DE\u6761\u6570
9487
9519
  <input id="historyMessageLimit" type="number" min="1" max="20" value="8" />
9488
9520
  </label>
9521
+ <label>
9522
+ \u9759\u9ED8\u68C0\u6D4B\u542F\u52A8\u65F6\u957F\uFF08\u79D2\uFF09
9523
+ <input id="streamStatusIdleStartSeconds" type="number" min="1" value="180" />
9524
+ </label>
9525
+ <label>
9526
+ \u9759\u9ED8\u68C0\u6D4B\u95F4\u9694\uFF08\u79D2\uFF09
9527
+ <input id="streamStatusCheckIntervalSeconds" type="number" min="1" value="10" />
9528
+ </label>
9489
9529
  </div>
9490
9530
  <label>
9491
9531
  \u9ED8\u8BA4\u5DE5\u4F5C\u7A7A\u95F4
@@ -9515,7 +9555,7 @@ function renderHtml() {
9515
9555
  </select>
9516
9556
  </label>
9517
9557
  </div>
9518
- <div class="small">\u672A\u7ED1\u5B9A\u7684 IM \u804A\u5929\u4F1A\u5148\u8FDB\u5165\u4E34\u65F6\u8349\u7A3F\u7EBF\u7A0B\uFF08\u7B49\u540C <code>/t 0</code>\uFF09\uFF1B\u201C\u9ED8\u8BA4\u5DE5\u4F5C\u7A7A\u95F4\u201D\u53EA\u7528\u4E8E <code>/new proj1</code> \u8FD9\u7C7B\u76F8\u5BF9\u9879\u76EE\u540D\u3002\u7559\u7A7A\u65F6\u4F1A\u6309\u5F53\u524D\u7CFB\u7EDF\u81EA\u52A8\u56DE\u9000\u5230 <code>~/cx2im</code>\u3002\u9ED8\u8BA4\u6A21\u578B\u5019\u9009\u9879\u6765\u81EA\u542F\u52A8\u65F6\u8BFB\u53D6\u7684 Codex \u6A21\u578B\u7F13\u5B58\uFF1A\u9690\u85CF\u6A21\u578B\u4E0D\u4F1A\u5C55\u793A\uFF0CCLI only \u6A21\u578B\u4F1A\u6807\u6210\u201C\u4EC5 IM / CLI\u201D\u3002\u7559\u7A7A\u5219\u7EE7\u7EED\u8DDF\u968F Codex \u5F53\u524D\u9ED8\u8BA4\u6A21\u578B\u3002\u6587\u4EF6\u7CFB\u7EDF\u6743\u9650\u662F\u5168\u5C40\u9ED8\u8BA4\u503C\uFF0C\u601D\u8003\u7EA7\u522B\u53EF\u5728 IM \u4F1A\u8BDD\u91CC\u518D\u5355\u72EC\u8986\u76D6\u3002</div>
9558
+ <div class="small">\u672A\u7ED1\u5B9A\u7684 IM \u804A\u5929\u4F1A\u5148\u8FDB\u5165\u4E34\u65F6\u8349\u7A3F\u7EBF\u7A0B\uFF08\u7B49\u540C <code>/t 0</code>\uFF09\uFF1B\u201C\u9ED8\u8BA4\u5DE5\u4F5C\u7A7A\u95F4\u201D\u53EA\u7528\u4E8E <code>/new proj1</code> \u8FD9\u7C7B\u76F8\u5BF9\u9879\u76EE\u540D\u3002\u7559\u7A7A\u65F6\u4F1A\u6309\u5F53\u524D\u7CFB\u7EDF\u81EA\u52A8\u56DE\u9000\u5230 <code>~/cx2im</code>\u3002\u9ED8\u8BA4\u6A21\u578B\u5019\u9009\u9879\u6765\u81EA\u542F\u52A8\u65F6\u8BFB\u53D6\u7684 Codex \u6A21\u578B\u7F13\u5B58\uFF1A\u9690\u85CF\u6A21\u578B\u4E0D\u4F1A\u5C55\u793A\uFF0CCLI only \u6A21\u578B\u4F1A\u6807\u6210\u201C\u4EC5 IM / CLI\u201D\u3002\u7559\u7A7A\u5219\u7EE7\u7EED\u8DDF\u968F Codex \u5F53\u524D\u9ED8\u8BA4\u6A21\u578B\u3002\u6587\u4EF6\u7CFB\u7EDF\u6743\u9650\u662F\u5168\u5C40\u9ED8\u8BA4\u503C\uFF0C\u601D\u8003\u7EA7\u522B\u53EF\u5728 IM \u4F1A\u8BDD\u91CC\u518D\u5355\u72EC\u8986\u76D6\u3002\u9759\u9ED8\u68C0\u6D4B\u914D\u7F6E\u53EA\u5F71\u54CD\u98DE\u4E66\u957F\u4EFB\u52A1\u5E95\u90E8\u201C\u6700\u8FD1 X \u65E0\u65B0\u8F93\u51FA\u201D\u7684\u51FA\u73B0\u65F6\u673A\u3002</div>
9519
9559
  <div class="small">\u5F53\u524D\u9700\u8981\u91CD\u542F Bridge \u7684\u914D\u7F6E\uFF1A<code>Runtime</code>\u3001<code>\u81EA\u52A8\u6279\u51C6\u5DE5\u5177\u6743\u9650</code>\u3001<code>\u5141\u8BB8\u5728\u672A\u4FE1\u4EFB Git \u76EE\u5F55\u8FD0\u884C Codex</code>\u3002\u901A\u9053\u5B9E\u4F8B\u7684\u63A5\u5165\u914D\u7F6E\u8BF7\u5728\u201C\u901A\u9053\u201D\u9875\u7EF4\u62A4\u3002</div>
9520
9560
  <div class="checkbox-row">
9521
9561
  <label class="checkbox"><input id="autoApprove" type="checkbox" /> \u81EA\u52A8\u6279\u51C6\u5DE5\u5177\u6743\u9650</label>
@@ -9835,6 +9875,8 @@ function renderHtml() {
9835
9875
  runtime: document.getElementById('runtime').value,
9836
9876
  defaultMode: document.getElementById('defaultMode').value,
9837
9877
  historyMessageLimit: document.getElementById('historyMessageLimit').value,
9878
+ streamStatusIdleStartSeconds: document.getElementById('streamStatusIdleStartSeconds').value,
9879
+ streamStatusCheckIntervalSeconds: document.getElementById('streamStatusCheckIntervalSeconds').value,
9838
9880
  defaultWorkspaceRoot: document.getElementById('defaultWorkspaceRoot').value,
9839
9881
  defaultModel: document.getElementById('defaultModel').value,
9840
9882
  codexSkipGitRepoCheck: document.getElementById('codexSkipGitRepoCheck').checked,
@@ -10012,6 +10054,8 @@ function renderHtml() {
10012
10054
  defaultModel: '\u9ED8\u8BA4\u6A21\u578B',
10013
10055
  defaultMode: '\u9ED8\u8BA4\u6A21\u5F0F',
10014
10056
  historyMessageLimit: '/history \u8FD4\u56DE\u6761\u6570',
10057
+ streamStatusIdleStartSeconds: '\u9759\u9ED8\u68C0\u6D4B\u542F\u52A8\u65F6\u957F',
10058
+ streamStatusCheckIntervalSeconds: '\u9759\u9ED8\u68C0\u6D4B\u95F4\u9694',
10015
10059
  codexSkipGitRepoCheck: '\u5141\u8BB8\u5728\u672A\u4FE1\u4EFB Git \u76EE\u5F55\u8FD0\u884C Codex',
10016
10060
  codexSandboxMode: 'Codex \u6587\u4EF6\u7CFB\u7EDF\u6743\u9650',
10017
10061
  codexReasoningEffort: 'Codex \u601D\u8003\u7EA7\u522B',
@@ -10033,6 +10077,8 @@ function renderHtml() {
10033
10077
  'defaultModel',
10034
10078
  'defaultMode',
10035
10079
  'historyMessageLimit',
10080
+ 'streamStatusIdleStartSeconds',
10081
+ 'streamStatusCheckIntervalSeconds',
10036
10082
  'codexSandboxMode',
10037
10083
  'codexReasoningEffort',
10038
10084
  'uiAllowLan',
@@ -10604,6 +10650,8 @@ function renderHtml() {
10604
10650
  document.getElementById('runtime').value = config.runtime || 'codex';
10605
10651
  document.getElementById('defaultMode').value = config.defaultMode || 'code';
10606
10652
  document.getElementById('historyMessageLimit').value = String(config.historyMessageLimit || 8);
10653
+ document.getElementById('streamStatusIdleStartSeconds').value = String(config.streamStatusIdleStartSeconds || 180);
10654
+ document.getElementById('streamStatusCheckIntervalSeconds').value = String(config.streamStatusCheckIntervalSeconds || 10);
10607
10655
  document.getElementById('defaultWorkspaceRoot').value = config.defaultWorkspaceRoot || '';
10608
10656
  renderDefaultModelOptions(config);
10609
10657
  document.getElementById('codexSkipGitRepoCheck').checked = config.codexSkipGitRepoCheck === true;
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.37",
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",