codex-to-im 1.0.13 → 1.0.15

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/daemon.mjs CHANGED
@@ -14606,7 +14606,7 @@ function buildFallbackTitle(threadId, filePath, cwd) {
14606
14606
  continue;
14607
14607
  }
14608
14608
  if (!isSessionEventLine(parsed) || parsed.payload?.type !== "user_message") continue;
14609
- const firstUserMessage = trimTitle(normalizeFreeText(parsed.payload.message || ""));
14609
+ const firstUserMessage = trimTitle(extractNormalizedFreeText(parsed.payload.message));
14610
14610
  if (firstUserMessage) return firstUserMessage;
14611
14611
  }
14612
14612
  } catch {
@@ -14669,6 +14669,48 @@ function normalizeFreeText(text2) {
14669
14669
  function normalizeStructuredText(text2) {
14670
14670
  return text2.replace(/\r\n/g, "\n").trim();
14671
14671
  }
14672
+ function collectStructuredTextParts(value, parts, depth = 0) {
14673
+ if (value == null || depth > 6) return;
14674
+ if (typeof value === "string") {
14675
+ parts.push(value);
14676
+ return;
14677
+ }
14678
+ if (Array.isArray(value)) {
14679
+ for (const item of value) {
14680
+ collectStructuredTextParts(item, parts, depth + 1);
14681
+ }
14682
+ return;
14683
+ }
14684
+ if (typeof value !== "object") return;
14685
+ const record = value;
14686
+ if (typeof record.text === "string") {
14687
+ parts.push(record.text);
14688
+ }
14689
+ if (typeof record.message === "string") {
14690
+ parts.push(record.message);
14691
+ }
14692
+ if (typeof record.summary === "string") {
14693
+ parts.push(record.summary);
14694
+ }
14695
+ if ("content" in record) {
14696
+ collectStructuredTextParts(record.content, parts, depth + 1);
14697
+ }
14698
+ if ("items" in record) {
14699
+ collectStructuredTextParts(record.items, parts, depth + 1);
14700
+ }
14701
+ }
14702
+ function extractNormalizedFreeText(value) {
14703
+ if (typeof value === "string") return normalizeFreeText(value);
14704
+ const parts = [];
14705
+ collectStructuredTextParts(value, parts);
14706
+ return parts.length > 0 ? normalizeFreeText(parts.join("\n")) : "";
14707
+ }
14708
+ function extractNormalizedStructuredText(value) {
14709
+ if (typeof value === "string") return normalizeStructuredText(value);
14710
+ const parts = [];
14711
+ collectStructuredTextParts(value, parts);
14712
+ return parts.length > 0 ? normalizeStructuredText(parts.join("\n\n")) : "";
14713
+ }
14672
14714
  function createDesktopEventSignature(rawLine) {
14673
14715
  return crypto7.createHash("sha1").update(rawLine).digest("hex");
14674
14716
  }
@@ -14697,6 +14739,9 @@ function isSessionEventLine(line) {
14697
14739
  function isSessionMessageLine(line) {
14698
14740
  return line.type === "response_item";
14699
14741
  }
14742
+ function isTurnContextLine(line) {
14743
+ return line.type === "turn_context";
14744
+ }
14700
14745
  function listDesktopSessions(limit) {
14701
14746
  const root = getCodexSessionsRoot();
14702
14747
  if (!fs4.existsSync(root)) return [];
@@ -14753,7 +14798,7 @@ ${text2}`;
14753
14798
  }
14754
14799
  function pushDesktopSessionEvent(events, parsed, rawLine) {
14755
14800
  if (isSessionEventLine(parsed) && parsed.payload?.type === "user_message") {
14756
- const text2 = normalizeStructuredText(parsed.payload.message || "");
14801
+ const text2 = extractNormalizedStructuredText(parsed.payload.message);
14757
14802
  if (!text2) return;
14758
14803
  events.push({
14759
14804
  signature: createDesktopEventSignature(rawLine),
@@ -14764,7 +14809,7 @@ function pushDesktopSessionEvent(events, parsed, rawLine) {
14764
14809
  return;
14765
14810
  }
14766
14811
  if (isSessionEventLine(parsed) && parsed.payload?.type === "task_complete") {
14767
- const text2 = normalizeStructuredText(parsed.payload.last_agent_message || "");
14812
+ const text2 = extractNormalizedStructuredText(parsed.payload.last_agent_message);
14768
14813
  if (!text2) return;
14769
14814
  const lastEvent = events[events.length - 1];
14770
14815
  if (lastEvent?.role === "assistant" && lastEvent.content === text2) {
@@ -14789,7 +14834,7 @@ function pushDesktopSessionEvent(events, parsed, rawLine) {
14789
14834
  });
14790
14835
  }
14791
14836
  }
14792
- function pushDesktopMirrorRecord(records, parsed, rawLine) {
14837
+ function pushDesktopMirrorRecord(records, parsed, rawLine, activeTurnId) {
14793
14838
  if (isSessionEventLine(parsed) && parsed.payload?.type === "task_started") {
14794
14839
  records.push({
14795
14840
  signature: createDesktopEventSignature(rawLine),
@@ -14801,14 +14846,15 @@ function pushDesktopMirrorRecord(records, parsed, rawLine) {
14801
14846
  return;
14802
14847
  }
14803
14848
  if (isSessionEventLine(parsed) && parsed.payload?.type === "user_message") {
14804
- const text2 = normalizeStructuredText(parsed.payload.message || "");
14849
+ const text2 = extractNormalizedStructuredText(parsed.payload.message);
14805
14850
  if (!text2) return;
14806
14851
  records.push({
14807
14852
  signature: createDesktopEventSignature(rawLine),
14808
14853
  type: "message",
14809
14854
  role: "user",
14810
14855
  content: text2,
14811
- timestamp: parsed.timestamp || ""
14856
+ timestamp: parsed.timestamp || "",
14857
+ ...activeTurnId ? { turnId: activeTurnId } : {}
14812
14858
  });
14813
14859
  return;
14814
14860
  }
@@ -14817,7 +14863,7 @@ function pushDesktopMirrorRecord(records, parsed, rawLine) {
14817
14863
  signature: createDesktopEventSignature(rawLine),
14818
14864
  type: "task_complete",
14819
14865
  role: "assistant",
14820
- content: normalizeStructuredText(parsed.payload.last_agent_message || ""),
14866
+ content: extractNormalizedStructuredText(parsed.payload.last_agent_message),
14821
14867
  timestamp: parsed.timestamp || "",
14822
14868
  turnId: parsed.payload.turn_id || ""
14823
14869
  });
@@ -14831,31 +14877,34 @@ function pushDesktopMirrorRecord(records, parsed, rawLine) {
14831
14877
  type: "message",
14832
14878
  role: parsed.payload.phase === "commentary" ? "commentary" : "assistant",
14833
14879
  content: parsed.payload.phase === "commentary" ? text2.replace(/^\[commentary\]\n/, "") : text2,
14834
- timestamp: parsed.timestamp || ""
14880
+ timestamp: parsed.timestamp || "",
14881
+ ...activeTurnId ? { turnId: activeTurnId } : {}
14835
14882
  });
14836
14883
  return;
14837
14884
  }
14838
14885
  if (isSessionMessageLine(parsed) && parsed.payload?.type === "function_call") {
14839
- const toolName = normalizeFreeText(parsed.payload.name || "");
14840
- const toolId = normalizeFreeText(parsed.payload.call_id || "") || createDesktopEventSignature(rawLine);
14886
+ const toolName = extractNormalizedFreeText(parsed.payload.name);
14887
+ const toolId = extractNormalizedFreeText(parsed.payload.call_id) || createDesktopEventSignature(rawLine);
14841
14888
  if (!toolName) return;
14842
14889
  records.push({
14843
14890
  signature: createDesktopEventSignature(rawLine),
14844
14891
  type: "tool_started",
14845
14892
  content: "",
14846
14893
  timestamp: parsed.timestamp || "",
14894
+ ...activeTurnId ? { turnId: activeTurnId } : {},
14847
14895
  toolId,
14848
14896
  toolName
14849
14897
  });
14850
14898
  return;
14851
14899
  }
14852
14900
  if (isSessionMessageLine(parsed) && parsed.payload?.type === "function_call_output") {
14853
- const toolId = normalizeFreeText(parsed.payload.call_id || "") || createDesktopEventSignature(rawLine);
14901
+ const toolId = extractNormalizedFreeText(parsed.payload.call_id) || createDesktopEventSignature(rawLine);
14854
14902
  records.push({
14855
14903
  signature: createDesktopEventSignature(rawLine),
14856
14904
  type: "tool_finished",
14857
- content: normalizeFreeText(parsed.payload.output || ""),
14905
+ content: extractNormalizedFreeText(parsed.payload.output),
14858
14906
  timestamp: parsed.timestamp || "",
14907
+ ...activeTurnId ? { turnId: activeTurnId } : {},
14859
14908
  toolId,
14860
14909
  isError: parsed.payload.is_error === true
14861
14910
  });
@@ -14895,13 +14944,14 @@ function parseDesktopSessionEventText(content, leadingText = "", flushTrailingTe
14895
14944
  trailingText
14896
14945
  };
14897
14946
  }
14898
- function parseDesktopMirrorRecordText(content, leadingText = "", flushTrailingText = false) {
14947
+ function parseDesktopMirrorRecordText(content, leadingText = "", flushTrailingText = false, initialTurnId = null) {
14899
14948
  const combined = `${leadingText}${content}`;
14900
14949
  if (!combined) {
14901
14950
  return {
14902
14951
  records: [],
14903
14952
  nextOffset: 0,
14904
- trailingText: ""
14953
+ trailingText: "",
14954
+ nextTurnId: initialTurnId
14905
14955
  };
14906
14956
  }
14907
14957
  const hasTrailingNewline = combined.endsWith("\n") || combined.endsWith("\r");
@@ -14912,6 +14962,7 @@ function parseDesktopMirrorRecordText(content, leadingText = "", flushTrailingTe
14912
14962
  trailingText = "";
14913
14963
  }
14914
14964
  const records = [];
14965
+ let activeTurnId = initialTurnId;
14915
14966
  for (const line of rawLines) {
14916
14967
  const trimmed = line.trim();
14917
14968
  if (!trimmed) continue;
@@ -14921,12 +14972,28 @@ function parseDesktopMirrorRecordText(content, leadingText = "", flushTrailingTe
14921
14972
  } catch {
14922
14973
  continue;
14923
14974
  }
14924
- pushDesktopMirrorRecord(records, parsed, trimmed);
14975
+ if (isTurnContextLine(parsed)) {
14976
+ activeTurnId = parsed.payload?.turn_id || activeTurnId;
14977
+ continue;
14978
+ }
14979
+ if (isSessionEventLine(parsed) && parsed.payload?.type === "task_started") {
14980
+ const eventPayload = parsed.payload;
14981
+ activeTurnId = eventPayload?.turn_id || activeTurnId;
14982
+ }
14983
+ pushDesktopMirrorRecord(records, parsed, trimmed, activeTurnId);
14984
+ if (isSessionEventLine(parsed) && parsed.payload?.type === "task_complete") {
14985
+ const eventPayload = parsed.payload;
14986
+ const completedTurnId = eventPayload?.turn_id || activeTurnId;
14987
+ if (!completedTurnId || completedTurnId === activeTurnId) {
14988
+ activeTurnId = null;
14989
+ }
14990
+ }
14925
14991
  }
14926
14992
  return {
14927
14993
  records,
14928
14994
  nextOffset: 0,
14929
- trailingText
14995
+ trailingText,
14996
+ nextTurnId: activeTurnId
14930
14997
  };
14931
14998
  }
14932
14999
  function readDesktopSessionMessages(threadId, limit = 8) {
@@ -14947,16 +15014,7 @@ function readDesktopSessionEventStreamByFilePath(filePath) {
14947
15014
  }
14948
15015
  return parseDesktopSessionEventText(content, "", true).events;
14949
15016
  }
14950
- function readDesktopSessionMirrorRecordStreamByFilePath(filePath) {
14951
- let content = "";
14952
- try {
14953
- content = fs4.readFileSync(filePath, "utf-8");
14954
- } catch {
14955
- return [];
14956
- }
14957
- return parseDesktopMirrorRecordText(content, "", true).records;
14958
- }
14959
- function readDesktopSessionMirrorRecordDeltaByFilePath(filePath, startOffset, endOffset, trailingText = "") {
15017
+ function readDesktopSessionMirrorRecordDeltaByFilePath(filePath, startOffset, endOffset, trailingText = "", currentTurnId = null) {
14960
15018
  let content = "";
14961
15019
  try {
14962
15020
  content = readFileUtf8Range(filePath, startOffset, endOffset);
@@ -14964,14 +15022,16 @@ function readDesktopSessionMirrorRecordDeltaByFilePath(filePath, startOffset, en
14964
15022
  return {
14965
15023
  records: [],
14966
15024
  nextOffset: startOffset,
14967
- trailingText
15025
+ trailingText,
15026
+ nextTurnId: currentTurnId
14968
15027
  };
14969
15028
  }
14970
- const parsed = parseDesktopMirrorRecordText(content, trailingText);
15029
+ const parsed = parseDesktopMirrorRecordText(content, trailingText, false, currentTurnId);
14971
15030
  return {
14972
15031
  records: parsed.records,
14973
15032
  nextOffset: Math.max(startOffset, endOffset),
14974
- trailingText: parsed.trailingText
15033
+ trailingText: parsed.trailingText,
15034
+ nextTurnId: parsed.nextTurnId
14975
15035
  };
14976
15036
  }
14977
15037
  function readDesktopSessionEventStream(threadId) {
@@ -15342,7 +15402,14 @@ function buildLocalAttachmentPromptSupplement(files) {
15342
15402
  }
15343
15403
  return lines.join("\n");
15344
15404
  }
15345
- async function processMessage(binding, text2, onPermissionRequest, abortSignal, files, onPartialText, onToolEvent) {
15405
+ function buildConversationPromptText(text2, files = []) {
15406
+ const attachmentSupplement = buildLocalAttachmentPromptSupplement(files);
15407
+ if (!attachmentSupplement) return text2;
15408
+ return text2.trim() ? `${text2}
15409
+
15410
+ ${attachmentSupplement}` : attachmentSupplement;
15411
+ }
15412
+ async function processMessage(binding, text2, onPermissionRequest, abortSignal, files, onPartialText, onToolEvent, onPromptPrepared) {
15346
15413
  const { store, llm } = getBridgeContext();
15347
15414
  const sessionId = binding.codepilotSessionId;
15348
15415
  const lockId = crypto8.randomBytes(8).toString("hex");
@@ -15402,10 +15469,8 @@ async function processMessage(binding, text2, onPermissionRequest, abortSignal,
15402
15469
  }
15403
15470
  }
15404
15471
  store.addMessage(sessionId, "user", savedContent);
15405
- const attachmentSupplement = buildLocalAttachmentPromptSupplement(persistedFileMeta);
15406
- const promptText = attachmentSupplement ? text2.trim() ? `${text2}
15407
-
15408
- ${attachmentSupplement}` : attachmentSupplement : text2;
15472
+ const promptText = buildConversationPromptText(text2, persistedFileMeta);
15473
+ onPromptPrepared?.(promptText);
15409
15474
  let resolvedProvider;
15410
15475
  const providerId = session?.provider_id || "";
15411
15476
  if (providerId && providerId !== "env") {
@@ -16698,6 +16763,7 @@ var MIRROR_POLL_INTERVAL_MS = 2500;
16698
16763
  var MIRROR_WATCH_DEBOUNCE_MS = 350;
16699
16764
  var MIRROR_EVENT_BATCH_LIMIT = 8;
16700
16765
  var MIRROR_SUPPRESSION_WINDOW_MS = 4e3;
16766
+ var MIRROR_PROMPT_MATCH_GRACE_MS = 12e4;
16701
16767
  var MIRROR_IDLE_TIMEOUT_MS = 12e5;
16702
16768
  var AVAILABLE_CODEX_MODELS = listSelectableCodexModels();
16703
16769
  var AVAILABLE_CODEX_MODEL_MAP = new Map(AVAILABLE_CODEX_MODELS.map((model) => [model.slug, model]));
@@ -16731,7 +16797,7 @@ function resolveCommandAlias(rawCommand, args) {
16731
16797
  case "/h":
16732
16798
  return "/help";
16733
16799
  case "/t":
16734
- return args ? "/thread" : "/threads";
16800
+ return !args ? "/threads" : /^(all|n\b)/i.test(args.trim()) ? "/threads" : "/thread";
16735
16801
  case "/s":
16736
16802
  return args ? "/use" : "/sessions";
16737
16803
  case "/n":
@@ -16767,6 +16833,20 @@ function resolveByIndexOrPrefix(raw, items, getId) {
16767
16833
  function getDisplayedDesktopThreads(limit = 10) {
16768
16834
  return listDesktopSessions(limit);
16769
16835
  }
16836
+ function parseDesktopThreadListArgs(args) {
16837
+ const trimmed = args.trim().toLowerCase();
16838
+ if (!trimmed) {
16839
+ return { showAll: false, limit: 10 };
16840
+ }
16841
+ if (trimmed === "all") {
16842
+ return { showAll: true, limit: 0 };
16843
+ }
16844
+ const match2 = trimmed.match(/^n\s+(\d+)$/);
16845
+ if (!match2) return null;
16846
+ const limit = Number(match2[1]);
16847
+ if (!Number.isInteger(limit) || limit < 1) return null;
16848
+ return { showAll: false, limit };
16849
+ }
16770
16850
  function getDisplayedBridgeSessions(currentSessionId) {
16771
16851
  const { store } = getBridgeContext();
16772
16852
  const sessions = store.listSessions().filter((session) => session.hidden !== true).toReversed();
@@ -16875,6 +16955,27 @@ function buildIndexedCommandList(title, items, footer = [], markdown = false) {
16875
16955
  footer.filter(Boolean).forEach((line) => lines.push(line));
16876
16956
  return lines.join("\n").trim();
16877
16957
  }
16958
+ function buildDesktopThreadsCommandResponse(desktopSessions, markdown, showAll, limit = 10) {
16959
+ return buildIndexedCommandList(
16960
+ showAll ? "\u5168\u90E8\u684C\u9762\u4F1A\u8BDD" : `\u6700\u8FD1 ${limit} \u6761\u684C\u9762\u4F1A\u8BDD`,
16961
+ desktopSessions.map((session) => ({
16962
+ heading: session.title || "\u672A\u547D\u540D\u7EBF\u7A0B",
16963
+ details: [
16964
+ `\u76EE\u5F55\uFF1A${formatCommandPath(session.cwd)}`,
16965
+ `\u6765\u6E90\uFF1A${session.originator || "Codex Desktop"}`
16966
+ ]
16967
+ })),
16968
+ showAll ? [
16969
+ "\u53D1\u9001 `/t 1` \u53EF\u63A5\u7BA1\u7B2C 1 \u6761\u684C\u9762\u4F1A\u8BDD\u3002",
16970
+ "\u53D1\u9001 `/t` \u53EF\u53EA\u770B\u6700\u8FD1 10 \u6761\u3002"
16971
+ ] : [
16972
+ "\u53D1\u9001 `/t 1` \u53EF\u63A5\u7BA1\u7B2C 1 \u6761\u684C\u9762\u4F1A\u8BDD\u3002",
16973
+ "\u53D1\u9001 `/t all` \u53EF\u67E5\u770B\u5168\u90E8\u684C\u9762\u4F1A\u8BDD\u3002",
16974
+ "\u53D1\u9001 `/t n 100` \u53EF\u67E5\u770B\u6700\u8FD1 100 \u6761\u684C\u9762\u4F1A\u8BDD\u3002"
16975
+ ],
16976
+ markdown
16977
+ );
16978
+ }
16878
16979
  function isFeedbackMarkdownEnabled(channelType) {
16879
16980
  const { store } = getBridgeContext();
16880
16981
  if (channelType === "feishu") {
@@ -17316,6 +17417,7 @@ function getState() {
17316
17417
  mirrorSubscriptions: /* @__PURE__ */ new Map(),
17317
17418
  mirrorSyncInFlight: false,
17318
17419
  mirrorSuppressUntil: /* @__PURE__ */ new Map(),
17420
+ mirrorIgnoredTurnIds: /* @__PURE__ */ new Map(),
17319
17421
  queuedCounts: /* @__PURE__ */ new Map(),
17320
17422
  sessionLocks: /* @__PURE__ */ new Map(),
17321
17423
  autoStartChecked: false
@@ -17333,6 +17435,9 @@ function getState() {
17333
17435
  if (!g[GLOBAL_KEY].mirrorSuppressUntil) {
17334
17436
  g[GLOBAL_KEY].mirrorSuppressUntil = /* @__PURE__ */ new Map();
17335
17437
  }
17438
+ if (!g[GLOBAL_KEY].mirrorIgnoredTurnIds) {
17439
+ g[GLOBAL_KEY].mirrorIgnoredTurnIds = /* @__PURE__ */ new Map();
17440
+ }
17336
17441
  if (!Object.prototype.hasOwnProperty.call(g[GLOBAL_KEY], "mirrorSyncInFlight")) {
17337
17442
  g[GLOBAL_KEY].mirrorSyncInFlight = false;
17338
17443
  }
@@ -17394,87 +17499,203 @@ function formatMirrorStatus(session) {
17394
17499
  return "\u672A\u76D1\u542C";
17395
17500
  }
17396
17501
  function normalizeMirrorPromptText(text2) {
17397
- return text2.replace(/\r\n/g, "\n").trim();
17502
+ return text2.replace(/\r\n/g, "\n").normalize("NFKC").trim();
17398
17503
  }
17399
- function beginMirrorSuppression(sessionId, promptText) {
17400
- getState().mirrorSuppressUntil.set(sessionId, {
17401
- until: Number.POSITIVE_INFINITY,
17402
- promptText: normalizeMirrorPromptText(promptText) || null,
17403
- awaitingPromptMatch: true,
17404
- droppingTurn: false
17405
- });
17504
+ function getIgnoredMirrorTurns(sessionId) {
17505
+ const state = getState();
17506
+ const existing = state.mirrorIgnoredTurnIds.get(sessionId);
17507
+ if (existing) return existing;
17508
+ const created = /* @__PURE__ */ new Map();
17509
+ state.mirrorIgnoredTurnIds.set(sessionId, created);
17510
+ return created;
17511
+ }
17512
+ function cleanupIgnoredMirrorTurns(sessionId) {
17513
+ const turns = getIgnoredMirrorTurns(sessionId);
17514
+ const now2 = Date.now();
17515
+ for (const [turnId, until] of turns) {
17516
+ if (until <= now2) {
17517
+ turns.delete(turnId);
17518
+ }
17519
+ }
17520
+ if (turns.size === 0) {
17521
+ getState().mirrorIgnoredTurnIds.delete(sessionId);
17522
+ }
17523
+ return turns;
17406
17524
  }
17407
- function settleMirrorSuppression(sessionId, durationMs = MIRROR_SUPPRESSION_WINDOW_MS) {
17525
+ function markIgnoredMirrorTurn(sessionId, turnId, durationMs = MIRROR_PROMPT_MATCH_GRACE_MS) {
17526
+ const normalized = (turnId || "").trim();
17527
+ if (!normalized) return;
17528
+ const state = getState();
17529
+ const turns = cleanupIgnoredMirrorTurns(sessionId);
17530
+ turns.set(normalized, Date.now() + durationMs);
17531
+ state.mirrorIgnoredTurnIds.set(sessionId, turns);
17532
+ }
17533
+ function clearIgnoredMirrorTurn(sessionId, turnId) {
17534
+ const normalized = (turnId || "").trim();
17535
+ if (!normalized) return;
17536
+ const turns = cleanupIgnoredMirrorTurns(sessionId);
17537
+ turns.delete(normalized);
17538
+ if (turns.size === 0) {
17539
+ getState().mirrorIgnoredTurnIds.delete(sessionId);
17540
+ }
17541
+ }
17542
+ function getMirrorSuppressionStates(sessionId) {
17543
+ const state = getState();
17544
+ const existing = state.mirrorSuppressUntil.get(sessionId) || [];
17545
+ if (existing.length === 0) return [];
17546
+ const now2 = Date.now();
17547
+ const active = existing.filter((suppression) => suppression.until > now2);
17548
+ if (active.length === 0) {
17549
+ state.mirrorSuppressUntil.delete(sessionId);
17550
+ return [];
17551
+ }
17552
+ if (active.length !== existing.length) {
17553
+ state.mirrorSuppressUntil.set(sessionId, active);
17554
+ }
17555
+ return active;
17556
+ }
17557
+ function clearMirrorSuppression(sessionId, suppressionId) {
17408
17558
  const state = getState();
17409
17559
  const existing = state.mirrorSuppressUntil.get(sessionId);
17410
- if (existing) {
17411
- existing.until = Date.now() + durationMs;
17560
+ if (!existing || existing.length === 0) return;
17561
+ if (!suppressionId) {
17562
+ state.mirrorSuppressUntil.delete(sessionId);
17412
17563
  return;
17413
17564
  }
17414
- state.mirrorSuppressUntil.set(sessionId, {
17415
- until: Date.now() + durationMs,
17416
- promptText: null,
17417
- awaitingPromptMatch: false,
17418
- droppingTurn: false
17419
- });
17565
+ const next = existing.filter((suppression) => suppression.id !== suppressionId);
17566
+ if (next.length > 0) {
17567
+ state.mirrorSuppressUntil.set(sessionId, next);
17568
+ } else {
17569
+ state.mirrorSuppressUntil.delete(sessionId);
17570
+ }
17420
17571
  }
17421
- function getMirrorSuppressionState(sessionId) {
17572
+ function beginMirrorSuppression(sessionId, promptText) {
17573
+ const suppressionId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
17422
17574
  const state = getState();
17423
- const suppression = state.mirrorSuppressUntil.get(sessionId);
17424
- if (!suppression) return null;
17425
- if (suppression.until <= Date.now()) {
17426
- state.mirrorSuppressUntil.delete(sessionId);
17427
- return null;
17575
+ const suppressions = getMirrorSuppressionStates(sessionId);
17576
+ suppressions.push({
17577
+ id: suppressionId,
17578
+ until: Number.POSITIVE_INFINITY,
17579
+ promptText: normalizeMirrorPromptText(promptText) || null,
17580
+ awaitingPromptMatch: true,
17581
+ candidateTurnId: null,
17582
+ activeTurnId: null,
17583
+ droppingTurn: false
17584
+ });
17585
+ state.mirrorSuppressUntil.set(sessionId, suppressions);
17586
+ return suppressionId;
17587
+ }
17588
+ function settleMirrorSuppression(sessionId, suppressionId, durationMs = MIRROR_SUPPRESSION_WINDOW_MS) {
17589
+ const suppressions = getMirrorSuppressionStates(sessionId);
17590
+ if (suppressions.length === 0) return;
17591
+ const target = suppressionId ? suppressions.find((suppression) => suppression.id === suppressionId) : suppressions[suppressions.length - 1];
17592
+ if (!target) return;
17593
+ if (target.awaitingPromptMatch || target.droppingTurn) {
17594
+ target.until = Date.now() + MIRROR_PROMPT_MATCH_GRACE_MS;
17595
+ return;
17428
17596
  }
17429
- return suppression;
17597
+ target.until = Date.now() + durationMs;
17430
17598
  }
17431
17599
  function isMirrorSuppressed(sessionId) {
17432
- return getMirrorSuppressionState(sessionId) !== null;
17600
+ return getMirrorSuppressionStates(sessionId).length > 0;
17433
17601
  }
17434
17602
  function filterSuppressedMirrorRecords(sessionId, records) {
17435
- const suppression = getMirrorSuppressionState(sessionId);
17436
- if (!suppression || records.length === 0) return records;
17603
+ const suppressions = getMirrorSuppressionStates(sessionId);
17604
+ if (suppressions.length === 0 || records.length === 0) return records;
17437
17605
  const filtered = [];
17606
+ cleanupIgnoredMirrorTurns(sessionId);
17438
17607
  for (const record of records) {
17439
17608
  const normalizedContent = record.type === "message" ? normalizeMirrorPromptText(record.content || "") : "";
17440
- if (suppression.droppingTurn) {
17441
- if (record.type === "message" && record.role === "user") {
17442
- if (suppression.promptText && normalizedContent === suppression.promptText) {
17443
- continue;
17609
+ let handled = false;
17610
+ while (true) {
17611
+ const ignoredTurnIds = cleanupIgnoredMirrorTurns(sessionId);
17612
+ if (record.turnId && ignoredTurnIds.has(record.turnId)) {
17613
+ if (record.type === "task_complete") {
17614
+ clearIgnoredMirrorTurn(sessionId, record.turnId);
17444
17615
  }
17445
- filtered.push(record);
17446
- continue;
17616
+ handled = true;
17617
+ break;
17447
17618
  }
17448
- if (record.type === "task_complete") {
17449
- suppression.droppingTurn = false;
17450
- suppression.awaitingPromptMatch = false;
17451
- suppression.promptText = null;
17452
- continue;
17619
+ const suppression = getMirrorSuppressionStates(sessionId)[0];
17620
+ if (!suppression) break;
17621
+ if (suppression.awaitingPromptMatch) {
17622
+ if (record.type === "task_started") {
17623
+ suppression.candidateTurnId = record.turnId || suppression.candidateTurnId;
17624
+ handled = true;
17625
+ break;
17626
+ }
17627
+ if (record.turnId && suppression.candidateTurnId && record.turnId !== suppression.candidateTurnId) {
17628
+ break;
17629
+ }
17630
+ if (record.type === "message" && record.role === "user") {
17631
+ if (suppression.promptText && normalizedContent === suppression.promptText) {
17632
+ suppression.awaitingPromptMatch = false;
17633
+ suppression.droppingTurn = true;
17634
+ suppression.activeTurnId = record.turnId || suppression.candidateTurnId || null;
17635
+ handled = true;
17636
+ break;
17637
+ }
17638
+ clearMirrorSuppression(sessionId, suppression.id);
17639
+ continue;
17640
+ }
17641
+ if (record.type === "task_complete" && suppression.candidateTurnId && record.turnId && record.turnId === suppression.candidateTurnId) {
17642
+ clearMirrorSuppression(sessionId, suppression.id);
17643
+ handled = true;
17644
+ break;
17645
+ }
17646
+ break;
17453
17647
  }
17454
- continue;
17455
- }
17456
- if (suppression.awaitingPromptMatch) {
17457
- if (record.type === "message" && record.role === "user") {
17458
- if (suppression.promptText && normalizedContent === suppression.promptText) {
17459
- suppression.awaitingPromptMatch = false;
17460
- suppression.droppingTurn = true;
17648
+ if (suppression.droppingTurn) {
17649
+ if (record.turnId && suppression.activeTurnId && record.turnId !== suppression.activeTurnId) {
17650
+ if (record.type === "task_started") {
17651
+ markIgnoredMirrorTurn(sessionId, suppression.activeTurnId);
17652
+ clearMirrorSuppression(sessionId, suppression.id);
17653
+ continue;
17654
+ }
17655
+ break;
17656
+ }
17657
+ if (record.type === "task_started") {
17658
+ handled = true;
17659
+ break;
17660
+ }
17661
+ if (record.type === "task_complete") {
17662
+ clearMirrorSuppression(sessionId, suppression.id);
17663
+ handled = true;
17664
+ break;
17665
+ }
17666
+ if (record.type === "message" && record.role === "user" && suppression.promptText && normalizedContent !== suppression.promptText) {
17667
+ markIgnoredMirrorTurn(sessionId, suppression.activeTurnId);
17668
+ clearMirrorSuppression(sessionId, suppression.id);
17461
17669
  continue;
17462
17670
  }
17463
- filtered.push(record);
17464
- continue;
17671
+ handled = true;
17672
+ break;
17465
17673
  }
17466
- continue;
17674
+ break;
17675
+ }
17676
+ if (!handled) {
17677
+ filtered.push(record);
17467
17678
  }
17468
- filtered.push(record);
17469
17679
  }
17470
17680
  return filtered;
17471
17681
  }
17682
+ function resetMirrorSessionForInteractiveRun(sessionId) {
17683
+ const state = getState();
17684
+ for (const subscription of state.mirrorSubscriptions.values()) {
17685
+ if (subscription.sessionId !== sessionId) continue;
17686
+ stopMirrorStreaming(subscription, "interrupted");
17687
+ if (subscription.pendingTurn) {
17688
+ subscription.pendingTurn.streamStarted = false;
17689
+ }
17690
+ }
17691
+ }
17472
17692
  function resetMirrorReadState(subscription) {
17473
17693
  subscription.fileOffset = 0;
17474
17694
  subscription.fileSize = null;
17475
17695
  subscription.fileMtimeMs = null;
17476
17696
  subscription.fileIdentity = null;
17477
17697
  subscription.trailingText = "";
17698
+ subscription.activeMirrorTurnId = null;
17478
17699
  subscription.bufferedRecords = [];
17479
17700
  }
17480
17701
  function statMirrorFile(filePath) {
@@ -17549,20 +17770,43 @@ function getMirrorAssistantRuntimeLabel() {
17549
17770
  const runtime = (store.getSetting("bridge_runtime") || "codex").trim().toLowerCase();
17550
17771
  return runtime || "codex";
17551
17772
  }
17552
- function buildMirrorHeader(threadTitle, role = "assistant", markdown = false) {
17773
+ function buildMirrorTitle(threadTitle, markdown = false) {
17553
17774
  const title = threadTitle?.trim() || "\u684C\u9762\u7EBF\u7A0B";
17554
- const speaker = role === "user" ? "\u6211" : getMirrorAssistantRuntimeLabel();
17555
- const plainHeader = `<${title}> ${speaker}:`;
17556
- const markdownHeader = `&lt;${title}&gt; ${speaker}:`;
17557
- const header = markdown ? markdownHeader : plainHeader;
17558
- return markdown ? `**${header}**` : header;
17559
- }
17560
- function formatMirrorMessageForRole(threadTitle, text2, role = "assistant", markdown = false) {
17561
- const normalized = text2.trim();
17562
- if (!normalized) return "";
17563
- return `${buildMirrorHeader(threadTitle, role, markdown)}
17564
-
17565
- ${normalized}`;
17775
+ const rendered = markdown ? `&lt;${title}&gt;` : `<${title}>`;
17776
+ return markdown ? `**${rendered}**` : rendered;
17777
+ }
17778
+ function buildMirrorSpeakerLabel(label, markdown = false) {
17779
+ return markdown ? `**${label}:**` : `${label}:`;
17780
+ }
17781
+ function formatMirrorSpeakerBlock(label, text2, markdown = false, forceLabel = false) {
17782
+ const normalized = (text2 || "").trim();
17783
+ if (!normalized) {
17784
+ return forceLabel ? buildMirrorSpeakerLabel(label, markdown) : "";
17785
+ }
17786
+ const speaker = buildMirrorSpeakerLabel(label, markdown);
17787
+ return normalized.includes("\n") ? `${speaker}
17788
+ ${normalized}` : `${speaker} ${normalized}`;
17789
+ }
17790
+ function formatMirrorMessage(threadTitle, userText, assistantText, markdown = false, forceAssistantLabel = false) {
17791
+ const sections = [];
17792
+ const userBlock = formatMirrorSpeakerBlock("\u6211", userText, markdown);
17793
+ if (userBlock) {
17794
+ sections.push(userBlock);
17795
+ }
17796
+ const assistantBlock = formatMirrorSpeakerBlock(
17797
+ getMirrorAssistantRuntimeLabel(),
17798
+ assistantText,
17799
+ markdown,
17800
+ forceAssistantLabel
17801
+ );
17802
+ if (assistantBlock) {
17803
+ sections.push(assistantBlock);
17804
+ }
17805
+ if (sections.length === 0) {
17806
+ return "";
17807
+ }
17808
+ sections.unshift(buildMirrorTitle(threadTitle, markdown));
17809
+ return sections.join("\n\n").trim();
17566
17810
  }
17567
17811
  function getMirrorStreamingAdapter(subscription) {
17568
17812
  const state = getState();
@@ -17575,10 +17819,16 @@ function getMirrorStreamingAdapter(subscription) {
17575
17819
  return adapter;
17576
17820
  }
17577
17821
  function getMirrorStreamingText(subscription, turnState) {
17578
- const content = turnState.streamedText.trim();
17579
17822
  const title = getDesktopThreadTitle(subscription.threadId)?.trim() || "\u684C\u9762\u7EBF\u7A0B";
17580
17823
  const markdown = getFeedbackParseMode(subscription.channelType) === "Markdown";
17581
- return content ? formatMirrorMessageForRole(title, content, "assistant", markdown) : buildMirrorHeader(title, "assistant", markdown);
17824
+ const rendered = formatMirrorMessage(
17825
+ title,
17826
+ turnState.userText,
17827
+ turnState.streamedText,
17828
+ markdown,
17829
+ true
17830
+ );
17831
+ return rendered || buildMirrorTitle(title, markdown);
17582
17832
  }
17583
17833
  function startMirrorStreaming(subscription, turnState) {
17584
17834
  const adapter = getMirrorStreamingAdapter(subscription);
@@ -17633,11 +17883,13 @@ async function deliverMirrorTurn(subscription, turn) {
17633
17883
  const title = getDesktopThreadTitle(subscription.threadId)?.trim() || "\u684C\u9762\u7EBF\u7A0B";
17634
17884
  const responseParseMode = getFeedbackParseMode(subscription.channelType);
17635
17885
  const markdown = responseParseMode === "Markdown";
17636
- const text2 = turn.text ? renderFeedbackText(
17637
- formatMirrorMessageForRole(title, turn.text, turn.role || "assistant", markdown),
17886
+ const renderedText = formatMirrorMessage(title, turn.userText, turn.text, markdown);
17887
+ const renderedStreamText = formatMirrorMessage(title, turn.userText, turn.text, markdown, true);
17888
+ const text2 = renderedText ? renderFeedbackText(renderedText, responseParseMode) : "";
17889
+ const streamText = renderFeedbackText(
17890
+ renderedStreamText || buildMirrorTitle(title, markdown),
17638
17891
  responseParseMode
17639
- ) : "";
17640
- const streamText = text2 || buildMirrorHeader(title, "assistant", markdown);
17892
+ );
17641
17893
  if (subscription.channelType === "feishu" && typeof adapter.onStreamEnd === "function") {
17642
17894
  try {
17643
17895
  const finalized = await adapter.onStreamEnd(
@@ -17681,6 +17933,7 @@ function createMirrorTurnState(timestamp, turnId) {
17681
17933
  turnId: turnId || null,
17682
17934
  startedAt: safeTimestamp,
17683
17935
  lastActivityAt: safeTimestamp,
17936
+ userText: null,
17684
17937
  lastAssistantText: null,
17685
17938
  lastCommentaryText: null,
17686
17939
  streamedText: "",
@@ -17688,6 +17941,20 @@ function createMirrorTurnState(timestamp, turnId) {
17688
17941
  toolCalls: /* @__PURE__ */ new Map()
17689
17942
  };
17690
17943
  }
17944
+ function appendMirrorUserText(turnState, chunk) {
17945
+ const normalized = chunk.trim();
17946
+ if (!normalized) return;
17947
+ if (!turnState.userText) {
17948
+ turnState.userText = normalized;
17949
+ return;
17950
+ }
17951
+ if (turnState.userText === normalized) {
17952
+ return;
17953
+ }
17954
+ turnState.userText = `${turnState.userText}
17955
+
17956
+ ${normalized}`;
17957
+ }
17691
17958
  function appendMirrorStreamText(turnState, chunk) {
17692
17959
  const normalized = chunk.trim();
17693
17960
  if (!normalized) return;
@@ -17712,9 +17979,15 @@ function finalizeMirrorTurn(subscription, signature, timestamp, status, preferre
17712
17979
  const pendingTurn = subscription.pendingTurn;
17713
17980
  subscription.pendingTurn = null;
17714
17981
  if (!pendingTurn) return null;
17715
- const text2 = (preferredText || pendingTurn.lastAssistantText || pendingTurn.lastCommentaryText || "").trim();
17716
- if (!text2 && pendingTurn.toolCalls.size === 0) return null;
17982
+ const text2 = [
17983
+ preferredText,
17984
+ pendingTurn.lastAssistantText,
17985
+ pendingTurn.lastCommentaryText
17986
+ ].map((value) => (value || "").trim()).find(Boolean) || "";
17987
+ const userText = pendingTurn.userText?.trim() || null;
17988
+ if (!text2 && !userText && pendingTurn.toolCalls.size === 0) return null;
17717
17989
  return {
17990
+ userText,
17718
17991
  text: text2,
17719
17992
  signature,
17720
17993
  timestamp: timestamp || pendingTurn.lastActivityAt || nowIso(),
@@ -17725,9 +17998,22 @@ function consumeMirrorRecords(subscription, records) {
17725
17998
  const finalized = [];
17726
17999
  for (const record of records) {
17727
18000
  if (record.type === "task_started") {
17728
- const superseded = finalizeMirrorTurn(subscription, `superseded:${record.signature}`, record.timestamp, "interrupted");
17729
- if (superseded) finalized.push(superseded);
17730
- subscription.pendingTurn = createMirrorTurnState(record.timestamp, record.turnId);
18001
+ const pendingTurn = subscription.pendingTurn;
18002
+ const sameTurn = pendingTurn && (!pendingTurn.turnId || !record.turnId || pendingTurn.turnId === record.turnId);
18003
+ if (!sameTurn) {
18004
+ const superseded = finalizeMirrorTurn(subscription, `superseded:${record.signature}`, record.timestamp, "interrupted");
18005
+ if (superseded) finalized.push(superseded);
18006
+ }
18007
+ if (!subscription.pendingTurn) {
18008
+ subscription.pendingTurn = createMirrorTurnState(record.timestamp, record.turnId);
18009
+ } else {
18010
+ if (!subscription.pendingTurn.turnId && record.turnId) {
18011
+ subscription.pendingTurn.turnId = record.turnId;
18012
+ }
18013
+ if (record.timestamp) {
18014
+ subscription.pendingTurn.lastActivityAt = record.timestamp;
18015
+ }
18016
+ }
17731
18017
  startMirrorStreaming(subscription, subscription.pendingTurn);
17732
18018
  continue;
17733
18019
  }
@@ -17738,15 +18024,12 @@ function consumeMirrorRecords(subscription, records) {
17738
18024
  continue;
17739
18025
  }
17740
18026
  if (record.type === "message" && record.role === "user") {
18027
+ const pendingTurn = ensureMirrorTurnState(subscription, record);
17741
18028
  const text2 = record.content.trim();
17742
18029
  if (text2) {
17743
- finalized.push({
17744
- text: text2,
17745
- signature: record.signature,
17746
- timestamp: record.timestamp,
17747
- status: "completed",
17748
- role: "user"
17749
- });
18030
+ appendMirrorUserText(pendingTurn, text2);
18031
+ startMirrorStreaming(subscription, pendingTurn);
18032
+ updateMirrorStreaming(subscription, pendingTurn);
17750
18033
  }
17751
18034
  continue;
17752
18035
  }
@@ -17868,6 +18151,7 @@ function upsertMirrorSubscription(binding) {
17868
18151
  fileMtimeMs: null,
17869
18152
  fileIdentity: null,
17870
18153
  trailingText: "",
18154
+ activeMirrorTurnId: null,
17871
18155
  bufferedRecords: [],
17872
18156
  pendingTurn: null
17873
18157
  };
@@ -17962,24 +18246,33 @@ async function reconcileMirrorSubscription(subscription) {
17962
18246
  const requiresFullRecover = !subscription.cursor.initialized || subscription.fileOffset === 0 || subscription.fileIdentity !== null && subscription.fileIdentity !== snapshot.identity || subscription.fileSize !== null && snapshot.size < subscription.fileOffset || subscription.fileSize !== null && snapshot.size === subscription.fileOffset && subscription.fileMtimeMs !== null && snapshot.mtimeMs !== subscription.fileMtimeMs;
17963
18247
  if (requiresFullRecover) {
17964
18248
  const previousCursor = subscription.cursor;
17965
- const records = readDesktopSessionMirrorRecordStreamByFilePath(subscription.filePath);
17966
- const delta = reconcileDesktopMirrorCursor(subscription.cursor, records);
18249
+ const fullDelta = readDesktopSessionMirrorRecordDeltaByFilePath(
18250
+ subscription.filePath,
18251
+ 0,
18252
+ snapshot.size,
18253
+ "",
18254
+ null
18255
+ );
18256
+ const delta = reconcileDesktopMirrorCursor(subscription.cursor, fullDelta.records);
17967
18257
  subscription.cursor = delta.nextCursor;
17968
18258
  deliverableRecords = filterDuplicateAssistantEvents(previousCursor, delta.deliverableRecords);
17969
18259
  subscription.trailingText = "";
17970
18260
  subscription.fileOffset = snapshot.size;
18261
+ subscription.activeMirrorTurnId = fullDelta.nextTurnId;
17971
18262
  } else if (snapshot.size > subscription.fileOffset || subscription.trailingText) {
17972
18263
  const previousCursor = subscription.cursor;
17973
18264
  const delta = readDesktopSessionMirrorRecordDeltaByFilePath(
17974
18265
  subscription.filePath,
17975
18266
  subscription.fileOffset,
17976
18267
  snapshot.size,
17977
- subscription.trailingText
18268
+ subscription.trailingText,
18269
+ subscription.activeMirrorTurnId
17978
18270
  );
17979
18271
  deliverableRecords = filterDuplicateAssistantEvents(previousCursor, delta.records);
17980
18272
  subscription.cursor = advanceDesktopMirrorCursor(subscription.cursor, delta.records);
17981
18273
  subscription.trailingText = delta.trailingText;
17982
18274
  subscription.fileOffset = delta.nextOffset;
18275
+ subscription.activeMirrorTurnId = delta.nextTurnId;
17983
18276
  }
17984
18277
  subscription.fileSize = snapshot.size;
17985
18278
  subscription.fileMtimeMs = snapshot.mtimeMs;
@@ -18015,7 +18308,19 @@ async function reconcileMirrorSubscriptions() {
18015
18308
  try {
18016
18309
  syncMirrorSubscriptionSet();
18017
18310
  for (const subscription of state.mirrorSubscriptions.values()) {
18018
- await reconcileMirrorSubscription(subscription);
18311
+ try {
18312
+ await reconcileMirrorSubscription(subscription);
18313
+ } catch (error) {
18314
+ stopMirrorStreaming(subscription, "interrupted");
18315
+ resetMirrorReadState(subscription);
18316
+ subscription.status = "stale";
18317
+ subscription.dirty = false;
18318
+ console.error(
18319
+ `[bridge-manager] Mirror reconcile failed for thread ${subscription.threadId}:`,
18320
+ error instanceof Error ? error.stack || error.message : error
18321
+ );
18322
+ syncMirrorSessionState(subscription.sessionId);
18323
+ }
18019
18324
  }
18020
18325
  } finally {
18021
18326
  state.mirrorSyncInFlight = false;
@@ -18185,6 +18490,7 @@ async function stop() {
18185
18490
  }
18186
18491
  state.activeTasks.clear();
18187
18492
  state.mirrorSuppressUntil.clear();
18493
+ state.mirrorIgnoredTurnIds.clear();
18188
18494
  state.queuedCounts.clear();
18189
18495
  for (const sessionId of activeSessionIds) {
18190
18496
  syncSessionRuntimeState(sessionId);
@@ -18337,7 +18643,7 @@ async function handleMessage(adapter, msg) {
18337
18643
  if (pendingLinks.length > 1) {
18338
18644
  await deliver(adapter, {
18339
18645
  address: msg.address,
18340
- text: `Multiple pending permissions (${pendingLinks.length}). Please use the full command:
18646
+ text: `\u5F53\u524D\u6709 ${pendingLinks.length} \u6761\u5F85\u5904\u7406\u6743\u9650\uFF0C\u6570\u5B57\u5FEB\u6377\u56DE\u590D\u4F1A\u6709\u6B67\u4E49\u3002\u8BF7\u4F7F\u7528\u5B8C\u6574\u547D\u4EE4\uFF1A
18341
18647
  /perm allow|allow_session|deny <id>`,
18342
18648
  parseMode: getFeedbackParseMode(adapter.channelType),
18343
18649
  replyToMessageId: msg.messageId
@@ -18388,8 +18694,9 @@ async function handleMessage(adapter, msg) {
18388
18694
  adapter.onMessageStart?.(msg.address.chatId);
18389
18695
  const taskAbort = new AbortController();
18390
18696
  const state = getState();
18697
+ resetMirrorSessionForInteractiveRun(binding.codepilotSessionId);
18391
18698
  state.activeTasks.set(binding.codepilotSessionId, taskAbort);
18392
- beginMirrorSuppression(binding.codepilotSessionId, text2 || (hasAttachments ? "Describe this image." : ""));
18699
+ let mirrorSuppressionId = null;
18393
18700
  syncSessionRuntimeState(binding.codepilotSessionId);
18394
18701
  let previewState = null;
18395
18702
  const caps = adapter.getPreviewCapabilities?.(msg.address.chatId) ?? null;
@@ -18478,7 +18785,11 @@ async function handleMessage(adapter, msg) {
18478
18785
  perm.suggestions,
18479
18786
  msg.messageId
18480
18787
  );
18481
- }, taskAbort.signal, hasAttachments ? msg.attachments : void 0, onPartialText, onToolEvent);
18788
+ }, taskAbort.signal, hasAttachments ? msg.attachments : void 0, onPartialText, onToolEvent, (preparedPrompt) => {
18789
+ if (!mirrorSuppressionId) {
18790
+ mirrorSuppressionId = beginMirrorSuppression(binding.codepilotSessionId, preparedPrompt);
18791
+ }
18792
+ });
18482
18793
  let cardFinalized = false;
18483
18794
  if (hasStreamingCards && adapter.onStreamEnd) {
18484
18795
  try {
@@ -18535,7 +18846,9 @@ async function handleMessage(adapter, msg) {
18535
18846
  } catch {
18536
18847
  }
18537
18848
  }
18538
- settleMirrorSuppression(binding.codepilotSessionId);
18849
+ if (mirrorSuppressionId) {
18850
+ settleMirrorSuppression(binding.codepilotSessionId, mirrorSuppressionId);
18851
+ }
18539
18852
  state.activeTasks.delete(binding.codepilotSessionId);
18540
18853
  syncSessionRuntimeState(binding.codepilotSessionId);
18541
18854
  adapter.onMessageEnd?.(msg.address.chatId);
@@ -18560,7 +18873,7 @@ async function handleCommand(adapter, msg, text2) {
18560
18873
  console.warn(`[bridge-manager] Blocked dangerous command input from chat ${msg.address.chatId}: ${dangerCheck.reason}`);
18561
18874
  await deliver(adapter, {
18562
18875
  address: msg.address,
18563
- text: `Command rejected: invalid input detected.`,
18876
+ text: "\u547D\u4EE4\u88AB\u62D2\u7EDD\uFF1A\u68C0\u6D4B\u5230\u65E0\u6548\u8F93\u5165\u3002",
18564
18877
  parseMode: getFeedbackParseMode(adapter.channelType),
18565
18878
  replyToMessageId: msg.messageId
18566
18879
  });
@@ -18619,97 +18932,6 @@ async function handleCommand(adapter, msg, text2) {
18619
18932
  );
18620
18933
  break;
18621
18934
  }
18622
- case "/bind": {
18623
- if (!args) {
18624
- response = "\u7528\u6CD5\uFF1A/bind <\u5E8F\u53F7>";
18625
- break;
18626
- }
18627
- const prefersThreadList = parseListIndex(args) !== null;
18628
- const displayedThreads = getDisplayedDesktopThreads(10);
18629
- if (prefersThreadList) {
18630
- const threadPick2 = resolveByIndexOrPrefix(args, displayedThreads, (session) => session.threadId);
18631
- if (threadPick2.match) {
18632
- let importedBinding2;
18633
- try {
18634
- importedBinding2 = bindToSdkSession(msg.address, threadPick2.match.threadId, {
18635
- workingDirectory: threadPick2.match.cwd,
18636
- displayName: threadPick2.match.title
18637
- });
18638
- } catch (error) {
18639
- response = toUserVisibleBindingError(error, "\u7ED1\u5B9A\u684C\u9762\u4F1A\u8BDD\u5931\u8D25\u3002");
18640
- break;
18641
- }
18642
- const session = store.getSession(importedBinding2.codepilotSessionId);
18643
- response = buildCommandFields(
18644
- "\u5DF2\u7ED1\u5B9A\u684C\u9762\u4F1A\u8BDD",
18645
- [
18646
- ["\u6807\u9898", threadPick2.match.title || getSessionDisplayName(session, importedBinding2.workingDirectory)],
18647
- ["\u76EE\u5F55", formatCommandPath(importedBinding2.workingDirectory)]
18648
- ],
18649
- ["\u63A5\u4E0B\u6765\u76F4\u63A5\u53D1\u9001\u6587\u672C\u5373\u53EF\u7EE7\u7EED\u3002"],
18650
- responseParseMode === "Markdown"
18651
- );
18652
- break;
18653
- }
18654
- }
18655
- const displayedSessions = getDisplayedBridgeSessions(currentBinding?.codepilotSessionId);
18656
- const sessionPick = resolveByIndexOrPrefix(args, displayedSessions, (session) => session.id);
18657
- if (sessionPick.ambiguous) {
18658
- response = "\u5339\u914D\u5230\u591A\u4E2A\u517C\u5BB9\u4F1A\u8BDD\uFF0C\u8BF7\u4F7F\u7528\u66F4\u957F\u7684\u7F16\u53F7\uFF0C\u6216\u76F4\u63A5\u6539\u7528 `/t` \u5207\u6362\u684C\u9762\u4F1A\u8BDD\u3002";
18659
- break;
18660
- }
18661
- if (sessionPick.match) {
18662
- let binding;
18663
- try {
18664
- binding = bindToSession(msg.address, sessionPick.match.id);
18665
- } catch (error) {
18666
- response = toUserVisibleBindingError(error, "\u5207\u6362\u4F1A\u8BDD\u5931\u8D25\u3002");
18667
- break;
18668
- }
18669
- if (binding) {
18670
- response = buildCommandFields(
18671
- "\u5DF2\u5207\u6362\u4F1A\u8BDD\uFF08\u517C\u5BB9\u547D\u4EE4\uFF09",
18672
- [
18673
- ["\u6807\u9898", getSessionDisplayName(sessionPick.match, binding.workingDirectory)],
18674
- ["\u76EE\u5F55", formatCommandPath(binding.workingDirectory)]
18675
- ],
18676
- ["\u666E\u901A\u4F7F\u7528\u5EFA\u8BAE\u76F4\u63A5\u901A\u8FC7 `/t` \u5207\u6362\u684C\u9762\u4F1A\u8BDD\u3002"],
18677
- responseParseMode === "Markdown"
18678
- );
18679
- break;
18680
- }
18681
- }
18682
- const threadPick = resolveByIndexOrPrefix(args, displayedThreads, (session) => session.threadId);
18683
- if (threadPick.ambiguous) {
18684
- response = "\u5339\u914D\u5230\u591A\u4E2A\u684C\u9762\u4F1A\u8BDD\uFF0C\u8BF7\u5148\u53D1\u9001 `/t` \u67E5\u770B\u5217\u8868\uFF0C\u518D\u7528 `/t 1` \u8FD9\u79CD\u5E8F\u53F7\u5207\u6362\u3002";
18685
- break;
18686
- }
18687
- if (!threadPick.match) {
18688
- response = "\u6CA1\u6709\u627E\u5230\u5BF9\u5E94\u76EE\u6807\u3002\u5148\u53D1\u9001 `/t` \u67E5\u770B\u684C\u9762\u4F1A\u8BDD\uFF0C\u518D\u6309\u5E8F\u53F7\u5207\u6362\u3002";
18689
- break;
18690
- }
18691
- let importedBinding;
18692
- try {
18693
- importedBinding = bindToSdkSession(msg.address, threadPick.match.threadId, {
18694
- workingDirectory: threadPick.match.cwd,
18695
- displayName: threadPick.match.title
18696
- });
18697
- } catch (error) {
18698
- response = toUserVisibleBindingError(error, "\u7ED1\u5B9A\u684C\u9762\u4F1A\u8BDD\u5931\u8D25\u3002");
18699
- break;
18700
- }
18701
- const importedSession = store.getSession(importedBinding.codepilotSessionId);
18702
- response = buildCommandFields(
18703
- "\u5DF2\u7ED1\u5B9A\u684C\u9762\u4F1A\u8BDD",
18704
- [
18705
- ["\u6807\u9898", threadPick.match.title || getSessionDisplayName(importedSession, importedBinding.workingDirectory)],
18706
- ["\u76EE\u5F55", formatCommandPath(importedBinding.workingDirectory)]
18707
- ],
18708
- ["\u63A5\u4E0B\u6765\u76F4\u63A5\u53D1\u9001\u6587\u672C\u5373\u53EF\u7EE7\u7EED\u3002"],
18709
- responseParseMode === "Markdown"
18710
- );
18711
- break;
18712
- }
18713
18935
  case "/thread": {
18714
18936
  if (args === "0" || args === "0 reset") {
18715
18937
  const draftSession = args === "0 reset" ? resetDraftSession2(msg.address) : getOrCreateDraftSession(store, msg.address);
@@ -18737,7 +18959,20 @@ async function handleCommand(adapter, msg, text2) {
18737
18959
  break;
18738
18960
  }
18739
18961
  if (!args) {
18740
- response = "\u7528\u6CD5\uFF1A/thread <\u5E8F\u53F7>\uFF0C\u6216 /thread 0 \u8FDB\u5165\u4E34\u65F6\u8349\u7A3F\u7EBF\u7A0B";
18962
+ response = "\u7528\u6CD5\uFF1A/thread <\u5E8F\u53F7>\uFF0C\u6216 /thread 0 \u8FDB\u5165\u4E34\u65F6\u8349\u7A3F\u7EBF\u7A0B\uFF1B\u53D1\u9001 /t all \u67E5\u770B\u5168\u90E8\uFF0C\u6216 /t n 100 \u67E5\u770B\u6700\u8FD1 100 \u6761\u684C\u9762\u4F1A\u8BDD";
18963
+ break;
18964
+ }
18965
+ if (args === "all") {
18966
+ const desktopSessions = getDisplayedDesktopThreads(void 0);
18967
+ if (desktopSessions.length === 0) {
18968
+ response = "\u6CA1\u6709\u627E\u5230\u684C\u9762\u4F1A\u8BDD\u3002\u5148\u5728 Codex Desktop App \u4E2D\u6253\u5F00\u4E00\u4E2A\u4F1A\u8BDD\uFF0C\u518D\u56DE\u6765\u8BD5\u4E00\u6B21\u3002";
18969
+ break;
18970
+ }
18971
+ response = buildDesktopThreadsCommandResponse(
18972
+ desktopSessions,
18973
+ responseParseMode === "Markdown",
18974
+ true
18975
+ );
18741
18976
  break;
18742
18977
  }
18743
18978
  const displayedThreads = getDisplayedDesktopThreads(10);
@@ -18796,25 +19031,22 @@ async function handleCommand(adapter, msg, text2) {
18796
19031
  break;
18797
19032
  }
18798
19033
  case "/threads": {
18799
- const desktopSessions = getDisplayedDesktopThreads(10);
19034
+ const listArgs = parseDesktopThreadListArgs(args);
19035
+ if (!listArgs) {
19036
+ response = "\u7528\u6CD5\uFF1A/threads\u3001/threads all\u3001/threads n 100";
19037
+ break;
19038
+ }
19039
+ const { showAll, limit } = listArgs;
19040
+ const desktopSessions = getDisplayedDesktopThreads(showAll ? void 0 : limit);
18800
19041
  if (desktopSessions.length === 0) {
18801
- response = "\u6CA1\u6709\u627E\u5230\u6700\u8FD1\u684C\u9762\u4F1A\u8BDD\u3002\u5148\u5728 Codex Desktop App \u4E2D\u6253\u5F00\u4E00\u4E2A\u4F1A\u8BDD\uFF0C\u518D\u56DE\u6765\u8BD5\u4E00\u6B21\u3002";
19042
+ response = showAll ? "\u6CA1\u6709\u627E\u5230\u684C\u9762\u4F1A\u8BDD\u3002\u5148\u5728 Codex Desktop App \u4E2D\u6253\u5F00\u4E00\u4E2A\u4F1A\u8BDD\uFF0C\u518D\u56DE\u6765\u8BD5\u4E00\u6B21\u3002" : "\u6CA1\u6709\u627E\u5230\u6700\u8FD1\u684C\u9762\u4F1A\u8BDD\u3002\u5148\u5728 Codex Desktop App \u4E2D\u6253\u5F00\u4E00\u4E2A\u4F1A\u8BDD\uFF0C\u518D\u56DE\u6765\u8BD5\u4E00\u6B21\u3002";
18802
19043
  break;
18803
19044
  }
18804
- response = buildIndexedCommandList(
18805
- "\u6700\u8FD1\u684C\u9762\u4F1A\u8BDD",
18806
- desktopSessions.map((session) => ({
18807
- heading: session.title || "\u672A\u547D\u540D\u7EBF\u7A0B",
18808
- details: [
18809
- `\u76EE\u5F55\uFF1A${formatCommandPath(session.cwd)}`,
18810
- `\u6765\u6E90\uFF1A${session.originator || "Codex Desktop"}`
18811
- ]
18812
- })),
18813
- [
18814
- "\u53D1\u9001 `/t 1` \u53EF\u63A5\u7BA1\u7B2C 1 \u6761\u684C\u9762\u4F1A\u8BDD\u3002",
18815
- "\u5B8C\u6574\u547D\u4EE4\u4ECD\u517C\u5BB9\uFF0C\u4F8B\u5982 `/thread 1`\u3002"
18816
- ],
18817
- responseParseMode === "Markdown"
19045
+ response = buildDesktopThreadsCommandResponse(
19046
+ desktopSessions,
19047
+ responseParseMode === "Markdown",
19048
+ showAll,
19049
+ limit
18818
19050
  );
18819
19051
  break;
18820
19052
  }
@@ -18896,7 +19128,7 @@ async function handleCommand(adapter, msg, text2) {
18896
19128
  break;
18897
19129
  }
18898
19130
  case "/cwd": {
18899
- response = "\u5F53\u524D\u7248\u672C\u5DF2\u4E0D\u652F\u6301 /cwd\u3002\u8BF7\u4F7F\u7528 /new \u65B0\u5EFA\u4F1A\u8BDD\uFF0C\u6216\u4F7F\u7528 /thread /bind /use \u5207\u6362\u5230\u5DF2\u6709\u5DE5\u4F5C\u7A7A\u95F4\u3002";
19131
+ response = "\u5F53\u524D\u7248\u672C\u5DF2\u4E0D\u652F\u6301 /cwd\u3002\u8BF7\u4F7F\u7528 /new \u65B0\u5EFA\u4F1A\u8BDD\uFF0C\u6216\u4F7F\u7528 /thread /use \u5207\u6362\u5230\u5DF2\u6709\u5DE5\u4F5C\u7A7A\u95F4\u3002";
18900
19132
  break;
18901
19133
  }
18902
19134
  case "/mode": {
@@ -19063,7 +19295,7 @@ async function handleCommand(adapter, msg, text2) {
19063
19295
  "\u6700\u8FD1\u5BF9\u8BDD\uFF08raw\uFF09",
19064
19296
  [
19065
19297
  ["\u6807\u9898", threadTitle || getSessionDisplayName(session, currentBinding.workingDirectory)],
19066
- ["\u6765\u6E90", desktopMessages.length > 0 ? "desktop thread" : "bridge cache"],
19298
+ ["\u6765\u6E90", desktopMessages.length > 0 ? "\u684C\u9762\u7EBF\u7A0B" : "Bridge \u7F13\u5B58"],
19067
19299
  ["\u8FD4\u56DE\u6761\u6570", `${messages.length} / \u914D\u7F6E ${limit}`]
19068
19300
  ],
19069
19301
  [],
@@ -19126,9 +19358,9 @@ ${truncateHistoryContent(formatStoredMessageContent(message.content))}`;
19126
19358
  taskAbort.abort();
19127
19359
  st.activeTasks.delete(binding.codepilotSessionId);
19128
19360
  syncSessionRuntimeState(binding.codepilotSessionId);
19129
- response = "Stopping current task...";
19361
+ response = "\u6B63\u5728\u505C\u6B62\u5F53\u524D\u4EFB\u52A1...";
19130
19362
  } else {
19131
- response = "No task is currently running.";
19363
+ response = "\u5F53\u524D\u6CA1\u6709\u6B63\u5728\u8FD0\u884C\u7684\u4EFB\u52A1\u3002";
19132
19364
  }
19133
19365
  break;
19134
19366
  }
@@ -19185,7 +19417,9 @@ ${truncateHistoryContent(formatStoredMessageContent(message.content))}`;
19185
19417
  "**\u5E38\u7528**",
19186
19418
  "- `/` \u5F53\u524D\u4F1A\u8BDD",
19187
19419
  "- `/h` \u5E2E\u52A9",
19188
- "- `/t` \u6700\u8FD1\u684C\u9762\u4F1A\u8BDD",
19420
+ "- `/t` \u6700\u8FD1 10 \u6761\u684C\u9762\u4F1A\u8BDD",
19421
+ "- `/t all` \u5168\u90E8\u684C\u9762\u4F1A\u8BDD",
19422
+ "- `/t n 100` \u6700\u8FD1 100 \u6761\u684C\u9762\u4F1A\u8BDD",
19189
19423
  "- `/t 1` \u63A5\u7BA1\u7B2C 1 \u6761\u4F1A\u8BDD",
19190
19424
  "- `/n` \u5728\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u4E0B\u65B0\u5EFA\u7EBF\u7A0B\uFF08\u4EC5\u4FDD\u8BC1 IM \u53EF\u7EE7\u7EED\uFF0C\u4E0D\u4F1A\u81EA\u52A8\u51FA\u73B0\u5728\u684C\u9762\u4F1A\u8BDD\u5217\u8868\uFF09",
19191
19425
  "- `/n proj1` \u5728\u9ED8\u8BA4\u5DE5\u4F5C\u7A7A\u95F4\u4E0B\u65B0\u5EFA\u9879\u76EE\u4F1A\u8BDD",