codex-to-im 1.0.40 → 1.0.41

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
@@ -3959,6 +3959,55 @@ function shouldRetryFreshThread(message) {
3959
3959
  const lower = message.toLowerCase();
3960
3960
  return lower.includes("resuming session with different model") || lower.includes("no such session") || lower.includes("resume") && lower.includes("session");
3961
3961
  }
3962
+ function normalizeCodexErrorMessage(message) {
3963
+ const trimmed = (message || "").trim();
3964
+ if (!trimmed) return "Codex \u6267\u884C\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
3965
+ const lower = trimmed.toLowerCase();
3966
+ if (lower.includes("timeout waiting for child process to exit") || lower.includes("reconnecting...")) {
3967
+ return "Codex \u4F1A\u8BDD\u6062\u590D\u5931\u8D25\uFF0C\u4E0A\u4E00\u8F6E\u6267\u884C\u8FDB\u7A0B\u672A\u6B63\u5E38\u9000\u51FA\u3002\u8BF7\u7A0D\u540E\u91CD\u8BD5\uFF1B\u5982\u679C\u8FDE\u7EED\u5931\u8D25\uFF0C\u8BF7\u65B0\u5F00\u7EBF\u7A0B\u6216\u5207\u6362\u5230 `/t 0`\u3002";
3968
+ }
3969
+ return trimmed;
3970
+ }
3971
+ function normalizeTaskText(value) {
3972
+ return typeof value === "string" ? value.trim() : "";
3973
+ }
3974
+ function mapTodoListItems(items) {
3975
+ if (!Array.isArray(items)) return [];
3976
+ const normalized = items.map((item) => ({
3977
+ text: normalizeTaskText(item?.text),
3978
+ completed: item?.completed === true
3979
+ })).filter((item) => item.text);
3980
+ let firstIncompleteSeen = false;
3981
+ return normalized.map((item) => {
3982
+ if (item.completed) {
3983
+ return { text: item.text, status: "completed" };
3984
+ }
3985
+ if (!firstIncompleteSeen) {
3986
+ firstIncompleteSeen = true;
3987
+ return { text: item.text, status: "in_progress" };
3988
+ }
3989
+ return { text: item.text, status: "pending" };
3990
+ });
3991
+ }
3992
+ function extractMcpContentText(value) {
3993
+ if (!Array.isArray(value)) return "";
3994
+ return value.map((block2) => {
3995
+ if (!block2 || typeof block2 !== "object") return "";
3996
+ const record = block2;
3997
+ if (typeof record.text === "string") return record.text.trim();
3998
+ if (typeof record.content === "string") return record.content.trim();
3999
+ return "";
4000
+ }).filter(Boolean).join("\n\n").trim();
4001
+ }
4002
+ function stringifyUnknown(value) {
4003
+ if (typeof value === "string") return value;
4004
+ if (value == null) return "";
4005
+ try {
4006
+ return JSON.stringify(value);
4007
+ } catch {
4008
+ return String(value);
4009
+ }
4010
+ }
3962
4011
  var MIME_EXT, CodexProvider;
3963
4012
  var init_codex_provider = __esm({
3964
4013
  "src/codex-provider.ts"() {
@@ -4047,6 +4096,7 @@ var init_codex_provider = __esm({
4047
4096
  input = params.prompt;
4048
4097
  }
4049
4098
  let retryFresh = false;
4099
+ const emittedToolStarts = /* @__PURE__ */ new Set();
4050
4100
  while (true) {
4051
4101
  let thread;
4052
4102
  if (savedThreadId) {
@@ -4078,9 +4128,19 @@ var init_codex_provider = __esm({
4078
4128
  }));
4079
4129
  break;
4080
4130
  }
4131
+ case "turn.started":
4132
+ break;
4133
+ case "item.started":
4134
+ case "item.updated":
4081
4135
  case "item.completed": {
4082
4136
  const item = event.item;
4083
- self.handleCompletedItem(controller, item);
4137
+ self.handleItemEvent(
4138
+ controller,
4139
+ item,
4140
+ event.type === "item.started" ? "started" : event.type === "item.updated" ? "updated" : "completed",
4141
+ params.sessionId,
4142
+ emittedToolStarts
4143
+ );
4084
4144
  break;
4085
4145
  }
4086
4146
  case "turn.completed": {
@@ -4098,17 +4158,25 @@ var init_codex_provider = __esm({
4098
4158
  break;
4099
4159
  }
4100
4160
  case "turn.failed": {
4101
- const error = event.message;
4102
- controller.enqueue(sseEvent("error", error || "Turn failed"));
4161
+ const error = event.error?.message;
4162
+ controller.enqueue(sseEvent("error", normalizeCodexErrorMessage(error || "Turn failed")));
4103
4163
  sawTerminalEvent = true;
4104
4164
  break;
4105
4165
  }
4106
4166
  case "error": {
4107
4167
  const error = event.message;
4108
- controller.enqueue(sseEvent("error", error || "Thread error"));
4168
+ controller.enqueue(sseEvent("error", normalizeCodexErrorMessage(error || "Thread error")));
4109
4169
  sawTerminalEvent = true;
4110
4170
  break;
4111
4171
  }
4172
+ default: {
4173
+ const exhaustiveEvent = event;
4174
+ console.warn(
4175
+ "[codex-provider] Unhandled thread event:",
4176
+ stringifyUnknown(exhaustiveEvent)
4177
+ );
4178
+ break;
4179
+ }
4112
4180
  }
4113
4181
  if (sawTerminalEvent) {
4114
4182
  break;
@@ -4131,7 +4199,7 @@ var init_codex_provider = __esm({
4131
4199
  const message = err instanceof Error ? err.message : String(err);
4132
4200
  console.error("[codex-provider] Error:", err instanceof Error ? err.stack || err.message : err);
4133
4201
  try {
4134
- controller.enqueue(sseEvent("error", message));
4202
+ controller.enqueue(sseEvent("error", normalizeCodexErrorMessage(message)));
4135
4203
  controller.close();
4136
4204
  } catch {
4137
4205
  }
@@ -4148,12 +4216,22 @@ var init_codex_provider = __esm({
4148
4216
  });
4149
4217
  }
4150
4218
  /**
4151
- * Map a completed Codex item to SSE events.
4219
+ * Map a Codex item event to SSE events.
4152
4220
  */
4153
- handleCompletedItem(controller, item) {
4221
+ handleItemEvent(controller, item, phase, sessionId, emittedToolStarts) {
4154
4222
  const itemType = item.type;
4223
+ const ensureToolUse = (toolId, name, input) => {
4224
+ if (emittedToolStarts.has(toolId)) return;
4225
+ emittedToolStarts.add(toolId);
4226
+ controller.enqueue(sseEvent("tool_use", {
4227
+ id: toolId,
4228
+ name,
4229
+ input
4230
+ }));
4231
+ };
4155
4232
  switch (itemType) {
4156
4233
  case "agent_message": {
4234
+ if (phase !== "completed") break;
4157
4235
  const text2 = item.text || "";
4158
4236
  if (text2) {
4159
4237
  controller.enqueue(sseEvent("text", text2));
@@ -4165,12 +4243,11 @@ var init_codex_provider = __esm({
4165
4243
  const command = item.command || "";
4166
4244
  const output = item.aggregated_output || "";
4167
4245
  const exitCode = item.exit_code;
4246
+ const status = item.status;
4168
4247
  const isError = exitCode != null && exitCode !== 0;
4169
- controller.enqueue(sseEvent("tool_use", {
4170
- id: toolId,
4171
- name: "Bash",
4172
- input: { command }
4173
- }));
4248
+ const terminal = phase === "completed" || status === "completed" || status === "failed";
4249
+ ensureToolUse(toolId, "Bash", { command });
4250
+ if (!terminal) break;
4174
4251
  const resultContent = output || (isError ? `Exit code: ${exitCode}` : "Done");
4175
4252
  controller.enqueue(sseEvent("tool_result", {
4176
4253
  tool_use_id: toolId,
@@ -4180,14 +4257,11 @@ var init_codex_provider = __esm({
4180
4257
  break;
4181
4258
  }
4182
4259
  case "file_change": {
4260
+ if (phase !== "completed") break;
4183
4261
  const toolId = item.id || `tool-${Date.now()}`;
4184
4262
  const changes = item.changes || [];
4185
4263
  const summary = changes.map((c) => `${c.kind}: ${c.path}`).join("\n");
4186
- controller.enqueue(sseEvent("tool_use", {
4187
- id: toolId,
4188
- name: "Edit",
4189
- input: { files: changes }
4190
- }));
4264
+ ensureToolUse(toolId, "Edit", { files: changes });
4191
4265
  controller.enqueue(sseEvent("tool_result", {
4192
4266
  tool_use_id: toolId,
4193
4267
  content: summary || "File changes applied",
@@ -4202,13 +4276,11 @@ var init_codex_provider = __esm({
4202
4276
  const args = item.arguments;
4203
4277
  const result = item.result;
4204
4278
  const error = item.error;
4205
- const resultContent = result?.content ?? result?.structured_content;
4206
- const resultText = typeof resultContent === "string" ? resultContent : resultContent ? JSON.stringify(resultContent) : void 0;
4207
- controller.enqueue(sseEvent("tool_use", {
4208
- id: toolId,
4209
- name: `mcp__${server}__${tool}`,
4210
- input: args
4211
- }));
4279
+ const status = item.status;
4280
+ const terminal = phase === "completed" || status === "completed" || status === "failed";
4281
+ const resultText = extractMcpContentText(result?.content) || stringifyUnknown(result?.structured_content) || stringifyUnknown(result?.content);
4282
+ ensureToolUse(toolId, `mcp__${server}__${tool}`, args);
4283
+ if (!terminal) break;
4212
4284
  controller.enqueue(sseEvent("tool_result", {
4213
4285
  tool_use_id: toolId,
4214
4286
  content: error?.message || resultText || "Done",
@@ -4216,6 +4288,18 @@ var init_codex_provider = __esm({
4216
4288
  }));
4217
4289
  break;
4218
4290
  }
4291
+ case "web_search": {
4292
+ const toolId = item.id || `tool-${Date.now()}`;
4293
+ const query2 = item.query || "";
4294
+ ensureToolUse(toolId, "Web Search", { query: query2 });
4295
+ if (phase !== "completed") break;
4296
+ controller.enqueue(sseEvent("tool_result", {
4297
+ tool_use_id: toolId,
4298
+ content: query2 || "Search completed",
4299
+ is_error: false
4300
+ }));
4301
+ break;
4302
+ }
4219
4303
  case "reasoning": {
4220
4304
  const text2 = item.text || "";
4221
4305
  if (text2) {
@@ -4223,8 +4307,33 @@ var init_codex_provider = __esm({
4223
4307
  }
4224
4308
  break;
4225
4309
  }
4310
+ case "todo_list": {
4311
+ const tasks = mapTodoListItems(item.items);
4312
+ controller.enqueue(sseEvent("task_update", {
4313
+ session_id: sessionId,
4314
+ sdk_session_id: this.threadIds.get(sessionId) || void 0,
4315
+ tasks,
4316
+ todos: tasks
4317
+ }));
4318
+ break;
4319
+ }
4320
+ case "error": {
4321
+ controller.enqueue(sseEvent("error", normalizeCodexErrorMessage(item.message || "Codex error")));
4322
+ break;
4323
+ }
4324
+ default: {
4325
+ const exhaustiveItem = item;
4326
+ console.warn(
4327
+ "[codex-provider] Unhandled thread item:",
4328
+ stringifyUnknown(exhaustiveItem)
4329
+ );
4330
+ break;
4331
+ }
4226
4332
  }
4227
4333
  }
4334
+ handleCompletedItem(controller, item) {
4335
+ this.handleItemEvent(controller, item, "completed", "test-session", /* @__PURE__ */ new Set());
4336
+ }
4228
4337
  };
4229
4338
  }
4230
4339
  });
@@ -5515,6 +5624,14 @@ function buildToolProgressMarkdown(tools) {
5515
5624
  });
5516
5625
  return lines.join("\n");
5517
5626
  }
5627
+ function buildTaskProgressMarkdown(tasks) {
5628
+ if (tasks.length === 0) return "";
5629
+ return tasks.map((task) => {
5630
+ const icon = task.status === "completed" ? "\u2705" : task.status === "in_progress" ? "\u{1F504}" : "\u23F3";
5631
+ const label = task.status === "completed" ? "\u5DF2\u5B8C\u6210" : task.status === "in_progress" ? "\u6267\u884C\u4E2D" : "\u7B49\u5F85\u4E2D";
5632
+ return `${icon} ${task.text}\uFF08${label}\uFF09`;
5633
+ }).join("\n");
5634
+ }
5518
5635
  function formatElapsed(ms) {
5519
5636
  if (ms < 1e3) return `${ms}ms`;
5520
5637
  const sec = ms / 1e3;
@@ -5529,9 +5646,13 @@ function buildStreamingTextContent(text2) {
5529
5646
  function buildStreamingToolsContent(tools) {
5530
5647
  return buildToolProgressMarkdown(tools);
5531
5648
  }
5532
- function buildFinalCardJson(text2, tools, footer) {
5649
+ function buildStreamingTaskContent(tasks) {
5650
+ return buildTaskProgressMarkdown(tasks);
5651
+ }
5652
+ function buildFinalCardJson(text2, tasks, tools, footer) {
5533
5653
  const elements = [];
5534
5654
  const content = preprocessFeishuMarkdown(text2);
5655
+ const taskMd = buildTaskProgressMarkdown(tasks);
5535
5656
  const toolMd = buildToolProgressMarkdown(tools);
5536
5657
  if (content) {
5537
5658
  elements.push({
@@ -5541,6 +5662,17 @@ function buildFinalCardJson(text2, tools, footer) {
5541
5662
  text_size: "normal"
5542
5663
  });
5543
5664
  }
5665
+ if (taskMd) {
5666
+ if (elements.length > 0) {
5667
+ elements.push({ tag: "hr" });
5668
+ }
5669
+ elements.push({
5670
+ tag: "markdown",
5671
+ content: taskMd,
5672
+ text_align: "left",
5673
+ text_size: "normal"
5674
+ });
5675
+ }
5544
5676
  if (toolMd) {
5545
5677
  if (elements.length > 0) {
5546
5678
  elements.push({ tag: "hr" });
@@ -5626,7 +5758,9 @@ var DEDUP_MAX = 1e3;
5626
5758
  var MAX_FILE_SIZE = 20 * 1024 * 1024;
5627
5759
  var TYPING_EMOJI = "Typing";
5628
5760
  var CARD_THROTTLE_MS = 200;
5761
+ var CARD_REQUEST_TIMEOUT_MS = 15e3;
5629
5762
  var INITIAL_STREAMING_STATUS = "\u5904\u7406\u4E2D";
5763
+ var EMPTY_STREAMING_TASKS = "";
5630
5764
  var EMPTY_STREAMING_TOOLS = "";
5631
5765
  var MIME_BY_TYPE = {
5632
5766
  image: "image/png",
@@ -5657,6 +5791,7 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5657
5791
  cardCreatePromises = /* @__PURE__ */ new Map();
5658
5792
  /** Cached tenant token for upload APIs. */
5659
5793
  tenantTokenCache = null;
5794
+ cardRequestTimeoutMs = CARD_REQUEST_TIMEOUT_MS;
5660
5795
  constructor(instance) {
5661
5796
  super();
5662
5797
  this.channelType = instance?.id || "feishu";
@@ -5884,6 +6019,13 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5884
6019
  text_size: "normal",
5885
6020
  element_id: "streaming_content"
5886
6021
  },
6022
+ {
6023
+ tag: "markdown",
6024
+ content: EMPTY_STREAMING_TASKS,
6025
+ text_align: "left",
6026
+ text_size: "normal",
6027
+ element_id: "streaming_tasks"
6028
+ },
5887
6029
  {
5888
6030
  tag: "markdown",
5889
6031
  content: EMPTY_STREAMING_TOOLS,
@@ -5901,9 +6043,9 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5901
6043
  ]
5902
6044
  }
5903
6045
  };
5904
- const createResp = await cardkit.card.create({
6046
+ const createResp = await this.withFeishuRequestTimeout(cardKey, "card.create", () => cardkit.card.create({
5905
6047
  data: { type: "card_json", data: JSON.stringify(cardBody) }
5906
- });
6048
+ }));
5907
6049
  const cardId = createResp?.data?.card_id;
5908
6050
  if (!cardId) {
5909
6051
  console.warn("[feishu-adapter] Card create returned no card_id");
@@ -5912,19 +6054,19 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5912
6054
  const cardContent = JSON.stringify({ type: "card", data: { card_id: cardId } });
5913
6055
  let msgResp;
5914
6056
  if (replyToMessageId) {
5915
- msgResp = await this.restClient.im.message.reply({
6057
+ msgResp = await this.withFeishuRequestTimeout(cardKey, "im.message.reply:interactive", () => this.restClient.im.message.reply({
5916
6058
  path: { message_id: replyToMessageId },
5917
6059
  data: { content: cardContent, msg_type: "interactive" }
5918
- });
6060
+ }));
5919
6061
  } else {
5920
- msgResp = await this.restClient.im.message.create({
6062
+ msgResp = await this.withFeishuRequestTimeout(cardKey, "im.message.create:interactive", () => this.restClient.im.message.create({
5921
6063
  params: { receive_id_type: "chat_id" },
5922
6064
  data: {
5923
6065
  receive_id: chatId,
5924
6066
  msg_type: "interactive",
5925
6067
  content: cardContent
5926
6068
  }
5927
- });
6069
+ }));
5928
6070
  }
5929
6071
  const messageId = msgResp?.data?.message_id;
5930
6072
  if (!messageId) {
@@ -5937,17 +6079,25 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5937
6079
  messageId,
5938
6080
  sequence: 0,
5939
6081
  startTime: Date.now(),
6082
+ taskItems: [],
5940
6083
  toolCalls: [],
5941
6084
  thinking: true,
5942
6085
  pendingText: null,
6086
+ pendingTasksText: EMPTY_STREAMING_TASKS,
5943
6087
  pendingStatusText: INITIAL_STREAMING_STATUS,
5944
6088
  renderedText: "\u{1F4AD} Thinking...",
6089
+ renderedTasksText: EMPTY_STREAMING_TASKS,
5945
6090
  renderedToolsText: EMPTY_STREAMING_TOOLS,
5946
6091
  renderedStatusText: INITIAL_STREAMING_STATUS,
5947
6092
  lastUpdateAt: 0,
5948
6093
  throttleTimer: null,
5949
6094
  flushInFlight: null,
5950
- flushQueued: false
6095
+ flushQueued: false,
6096
+ lastFlushStartedAt: null,
6097
+ lastSuccessfulFlushAt: null,
6098
+ lastFlushErrorAt: null,
6099
+ lastFlushError: null,
6100
+ consecutiveFlushFailures: 0
5951
6101
  });
5952
6102
  console.log(`[feishu-adapter] Streaming card created: streamKey=${cardKey}, cardId=${cardId}, msgId=${messageId}`);
5953
6103
  return true;
@@ -5976,6 +6126,14 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5976
6126
  state.pendingStatusText = statusText || INITIAL_STREAMING_STATUS;
5977
6127
  this.scheduleCardFlush(cardKey);
5978
6128
  }
6129
+ updateTaskProgress(chatId, tasks, streamKey) {
6130
+ const cardKey = this.resolveStreamKey(chatId, streamKey);
6131
+ const state = this.activeCards.get(cardKey);
6132
+ if (!state) return;
6133
+ state.taskItems = tasks;
6134
+ state.pendingTasksText = buildStreamingTaskContent(tasks) || EMPTY_STREAMING_TASKS;
6135
+ this.scheduleCardFlush(cardKey);
6136
+ }
5979
6137
  enqueueCardFlush(streamKey) {
5980
6138
  const state = this.activeCards.get(streamKey);
5981
6139
  if (!state) return;
@@ -5983,6 +6141,7 @@ var FeishuAdapter = class extends BaseChannelAdapter {
5983
6141
  state.flushQueued = true;
5984
6142
  return;
5985
6143
  }
6144
+ state.lastFlushStartedAt = Date.now();
5986
6145
  state.flushInFlight = this.flushCardUpdate(streamKey).catch((err) => {
5987
6146
  console.warn("[feishu-adapter] cardElement.content failed:", err instanceof Error ? err.message : err);
5988
6147
  }).finally(() => {
@@ -6023,6 +6182,7 @@ var FeishuAdapter = class extends BaseChannelAdapter {
6023
6182
  const cardkit = this.restClient.cardkit?.v1;
6024
6183
  if (!cardkit?.cardElement?.content) return;
6025
6184
  const content = buildStreamingTextContent(state.pendingText || "");
6185
+ const tasksText = state.pendingTasksText || EMPTY_STREAMING_TASKS;
6026
6186
  const toolsText = buildStreamingToolsContent(state.toolCalls) || EMPTY_STREAMING_TOOLS;
6027
6187
  const statusText = state.pendingStatusText || INITIAL_STREAMING_STATUS;
6028
6188
  const updates = [];
@@ -6035,6 +6195,15 @@ var FeishuAdapter = class extends BaseChannelAdapter {
6035
6195
  }
6036
6196
  });
6037
6197
  }
6198
+ if (tasksText !== state.renderedTasksText) {
6199
+ updates.push({
6200
+ elementId: "streaming_tasks",
6201
+ content: tasksText,
6202
+ onSuccess: () => {
6203
+ state.renderedTasksText = tasksText;
6204
+ }
6205
+ });
6206
+ }
6038
6207
  if (toolsText !== state.renderedToolsText) {
6039
6208
  updates.push({
6040
6209
  elementId: "streaming_tools",
@@ -6058,13 +6227,14 @@ var FeishuAdapter = class extends BaseChannelAdapter {
6058
6227
  for (const update of updates) {
6059
6228
  state.sequence++;
6060
6229
  try {
6061
- await cardkit.cardElement.content({
6230
+ await this.withFeishuRequestTimeout(streamKey, `cardElement.content:${update.elementId}`, () => cardkit.cardElement.content({
6062
6231
  path: { card_id: cardId, element_id: update.elementId },
6063
6232
  data: { content: update.content, sequence: state.sequence }
6064
- });
6233
+ }));
6065
6234
  update.onSuccess();
6066
- state.lastUpdateAt = Date.now();
6235
+ this.markCardFlushSuccess(state);
6067
6236
  } catch (err) {
6237
+ this.markCardFlushFailure(state, err);
6068
6238
  console.warn(
6069
6239
  `[feishu-adapter] cardElement.content failed for ${update.elementId}:`,
6070
6240
  err instanceof Error ? err.message : err
@@ -6125,13 +6295,13 @@ var FeishuAdapter = class extends BaseChannelAdapter {
6125
6295
  await this.awaitCardFlushCompletion(cardKey);
6126
6296
  try {
6127
6297
  state.sequence++;
6128
- await cardkit.card.settings({
6298
+ await this.withFeishuRequestTimeout(cardKey, "card.settings", () => cardkit.card.settings({
6129
6299
  path: { card_id: state.cardId },
6130
6300
  data: {
6131
6301
  settings: JSON.stringify({ streaming_mode: false }),
6132
6302
  sequence: state.sequence
6133
6303
  }
6134
- });
6304
+ }));
6135
6305
  const statusLabels = {
6136
6306
  completed: "\u2705 Completed",
6137
6307
  interrupted: "\u26A0\uFE0F Interrupted",
@@ -6151,15 +6321,15 @@ var FeishuAdapter = class extends BaseChannelAdapter {
6151
6321
 
6152
6322
  ${trimmedResponse}`;
6153
6323
  }
6154
- const finalCardJson = buildFinalCardJson(finalText, state.toolCalls, footer);
6324
+ const finalCardJson = buildFinalCardJson(finalText, state.taskItems, state.toolCalls, footer);
6155
6325
  state.sequence++;
6156
- await cardkit.card.update({
6326
+ await this.withFeishuRequestTimeout(cardKey, "card.update", () => cardkit.card.update({
6157
6327
  path: { card_id: state.cardId },
6158
6328
  data: {
6159
6329
  card: { type: "card_json", data: finalCardJson },
6160
6330
  sequence: state.sequence
6161
6331
  }
6162
- });
6332
+ }));
6163
6333
  console.log(`[feishu-adapter] Card finalized: streamKey=${cardKey}, cardId=${state.cardId}, status=${status}, elapsed=${formatElapsed(elapsedMs)}`);
6164
6334
  return true;
6165
6335
  } catch (err) {
@@ -6191,6 +6361,76 @@ ${trimmedResponse}`;
6191
6361
  hasActiveStreamingUi(chatId, streamKey) {
6192
6362
  return this.hasActiveCard(chatId, streamKey);
6193
6363
  }
6364
+ getStructuredStreamingUiSnapshot(chatId, streamKey) {
6365
+ const state = this.activeCards.get(this.resolveStreamKey(chatId, streamKey));
6366
+ if (!state) return null;
6367
+ return {
6368
+ active: true,
6369
+ lastAttemptAt: state.lastFlushStartedAt,
6370
+ lastUpdateAt: state.lastSuccessfulFlushAt ?? (state.lastUpdateAt > 0 ? state.lastUpdateAt : null),
6371
+ lastErrorAt: state.lastFlushErrorAt,
6372
+ lastError: state.lastFlushError,
6373
+ flushInFlight: Boolean(state.flushInFlight),
6374
+ flushInFlightSince: state.flushInFlight ? state.lastFlushStartedAt : null,
6375
+ consecutiveFailures: state.consecutiveFlushFailures
6376
+ };
6377
+ }
6378
+ getCardRequestTimeoutMs() {
6379
+ return Math.max(1, this.cardRequestTimeoutMs);
6380
+ }
6381
+ logRequestOperation(phase, scope, target, startedAt, detail) {
6382
+ const durationMs = Math.max(0, Date.now() - startedAt);
6383
+ const suffix = detail ? `, detail=${detail}` : "";
6384
+ const line = `[feishu-adapter] Request ${phase}: scope=${scope}, target=${target}, duration=${durationMs}ms${suffix}`;
6385
+ if (phase === "start" || phase === "success") {
6386
+ console.log(line);
6387
+ return;
6388
+ }
6389
+ console.warn(line);
6390
+ }
6391
+ async withFeishuRequestTimeout(scope, target, operation) {
6392
+ const startedAt = Date.now();
6393
+ const timeoutMs = this.getCardRequestTimeoutMs();
6394
+ this.logRequestOperation("start", scope, target, startedAt);
6395
+ let timeoutHandle = null;
6396
+ const timeoutPromise = new Promise((_, reject) => {
6397
+ timeoutHandle = setTimeout(() => {
6398
+ reject(new Error(`timeout after ${timeoutMs}ms`));
6399
+ }, timeoutMs);
6400
+ });
6401
+ const operationPromise = operation();
6402
+ operationPromise.catch(() => {
6403
+ });
6404
+ try {
6405
+ const result = await Promise.race([operationPromise, timeoutPromise]);
6406
+ this.logRequestOperation("success", scope, target, startedAt);
6407
+ return result;
6408
+ } catch (error) {
6409
+ const detail = error instanceof Error ? error.message : String(error);
6410
+ this.logRequestOperation(
6411
+ detail.startsWith("timeout after ") ? "timeout" : "error",
6412
+ scope,
6413
+ target,
6414
+ startedAt,
6415
+ detail
6416
+ );
6417
+ throw error;
6418
+ } finally {
6419
+ if (timeoutHandle) clearTimeout(timeoutHandle);
6420
+ }
6421
+ }
6422
+ markCardFlushFailure(state, error) {
6423
+ state.lastFlushErrorAt = Date.now();
6424
+ state.lastFlushError = error instanceof Error ? error.message : String(error);
6425
+ state.consecutiveFlushFailures += 1;
6426
+ }
6427
+ markCardFlushSuccess(state) {
6428
+ const now2 = Date.now();
6429
+ state.lastUpdateAt = now2;
6430
+ state.lastSuccessfulFlushAt = now2;
6431
+ state.lastFlushError = null;
6432
+ state.consecutiveFlushFailures = 0;
6433
+ }
6194
6434
  // ── Streaming adapter interface ────────────────────────────────
6195
6435
  /**
6196
6436
  * Called by bridge-manager on each text SSE event.
@@ -6218,8 +6458,30 @@ ${trimmedResponse}`;
6218
6458
  }
6219
6459
  onToolEvent(chatId, tools, streamKey) {
6220
6460
  if (!this.isStreamingEnabled()) return;
6461
+ const cardKey = this.resolveStreamKey(chatId, streamKey);
6462
+ if (!this.activeCards.has(cardKey)) {
6463
+ const messageId = this.lastIncomingMessageId.get(chatId);
6464
+ this.createStreamingCard(chatId, messageId, cardKey).then((ok) => {
6465
+ if (ok) this.updateToolProgress(chatId, tools, cardKey);
6466
+ }).catch(() => {
6467
+ });
6468
+ return;
6469
+ }
6221
6470
  this.updateToolProgress(chatId, tools, streamKey);
6222
6471
  }
6472
+ onTaskEvent(chatId, tasks, streamKey) {
6473
+ if (!this.isStreamingEnabled()) return;
6474
+ const cardKey = this.resolveStreamKey(chatId, streamKey);
6475
+ if (!this.activeCards.has(cardKey)) {
6476
+ const messageId = this.lastIncomingMessageId.get(chatId);
6477
+ this.createStreamingCard(chatId, messageId, cardKey).then((ok) => {
6478
+ if (ok) this.updateTaskProgress(chatId, tasks, cardKey);
6479
+ }).catch(() => {
6480
+ });
6481
+ return;
6482
+ }
6483
+ this.updateTaskProgress(chatId, tasks, streamKey);
6484
+ }
6223
6485
  onStreamStatus(chatId, statusText, streamKey) {
6224
6486
  if (!this.isStreamingEnabled()) return;
6225
6487
  const cardKey = this.resolveStreamKey(chatId, streamKey);
@@ -6369,17 +6631,17 @@ ${trimmedResponse}`;
6369
6631
  }
6370
6632
  async sendStructuredMessage(chatId, msgType, content, replyToMessageId) {
6371
6633
  try {
6372
- const res = replyToMessageId ? await this.restClient.im.message.reply({
6634
+ const res = replyToMessageId ? await this.withFeishuRequestTimeout(chatId, `im.message.reply:${msgType}`, () => this.restClient.im.message.reply({
6373
6635
  path: { message_id: replyToMessageId },
6374
6636
  data: { msg_type: msgType, content }
6375
- }) : await this.restClient.im.message.create({
6637
+ })) : await this.withFeishuRequestTimeout(chatId, `im.message.create:${msgType}`, () => this.restClient.im.message.create({
6376
6638
  params: { receive_id_type: "chat_id" },
6377
6639
  data: {
6378
6640
  receive_id: chatId,
6379
6641
  msg_type: msgType,
6380
6642
  content
6381
6643
  }
6382
- });
6644
+ }));
6383
6645
  if (res?.data?.message_id) {
6384
6646
  return { ok: true, messageId: res.data.message_id };
6385
6647
  }
@@ -6395,14 +6657,14 @@ ${trimmedResponse}`;
6395
6657
  async sendAsCard(chatId, text2) {
6396
6658
  const cardContent = buildCardContent(text2);
6397
6659
  try {
6398
- const res = await this.restClient.im.message.create({
6660
+ const res = await this.withFeishuRequestTimeout(chatId, "im.message.create:interactive-card", () => this.restClient.im.message.create({
6399
6661
  params: { receive_id_type: "chat_id" },
6400
6662
  data: {
6401
6663
  receive_id: chatId,
6402
6664
  msg_type: "interactive",
6403
6665
  content: cardContent
6404
6666
  }
6405
- });
6667
+ }));
6406
6668
  if (res?.data?.message_id) {
6407
6669
  return { ok: true, messageId: res.data.message_id };
6408
6670
  }
@@ -6419,14 +6681,14 @@ ${trimmedResponse}`;
6419
6681
  async sendAsPost(chatId, text2) {
6420
6682
  const postContent = buildPostContent(text2);
6421
6683
  try {
6422
- const res = await this.restClient.im.message.create({
6684
+ const res = await this.withFeishuRequestTimeout(chatId, "im.message.create:post", () => this.restClient.im.message.create({
6423
6685
  params: { receive_id_type: "chat_id" },
6424
6686
  data: {
6425
6687
  receive_id: chatId,
6426
6688
  msg_type: "post",
6427
6689
  content: postContent
6428
6690
  }
6429
- });
6691
+ }));
6430
6692
  if (res?.data?.message_id) {
6431
6693
  return { ok: true, messageId: res.data.message_id };
6432
6694
  }
@@ -6438,14 +6700,14 @@ ${trimmedResponse}`;
6438
6700
  }
6439
6701
  async sendAsPlainText(chatId, text2) {
6440
6702
  try {
6441
- const res = await this.restClient.im.message.create({
6703
+ const res = await this.withFeishuRequestTimeout(chatId, "im.message.create:text", () => this.restClient.im.message.create({
6442
6704
  params: { receive_id_type: "chat_id" },
6443
6705
  data: {
6444
6706
  receive_id: chatId,
6445
6707
  msg_type: "text",
6446
6708
  content: JSON.stringify({ text: text2 })
6447
6709
  }
6448
- });
6710
+ }));
6449
6711
  if (res?.data?.message_id) {
6450
6712
  return { ok: true, messageId: res.data.message_id };
6451
6713
  }
@@ -6470,14 +6732,14 @@ ${trimmedResponse}`;
6470
6732
  if (permId) {
6471
6733
  const cardJson2 = buildPermissionButtonCard(mdText, permId, chatId);
6472
6734
  try {
6473
- const res = await this.restClient.im.message.create({
6735
+ const res = await this.withFeishuRequestTimeout(chatId, "im.message.create:permission-button-card", () => this.restClient.im.message.create({
6474
6736
  params: { receive_id_type: "chat_id" },
6475
6737
  data: {
6476
6738
  receive_id: chatId,
6477
6739
  msg_type: "interactive",
6478
6740
  content: cardJson2
6479
6741
  }
6480
- });
6742
+ }));
6481
6743
  if (res?.data?.message_id) {
6482
6744
  return { ok: true, messageId: res.data.message_id };
6483
6745
  }
@@ -6521,14 +6783,14 @@ ${trimmedResponse}`;
6521
6783
  }
6522
6784
  });
6523
6785
  try {
6524
- const res = await this.restClient.im.message.create({
6786
+ const res = await this.withFeishuRequestTimeout(chatId, "im.message.create:permission-fallback-card", () => this.restClient.im.message.create({
6525
6787
  params: { receive_id_type: "chat_id" },
6526
6788
  data: {
6527
6789
  receive_id: chatId,
6528
6790
  msg_type: "interactive",
6529
6791
  content: cardJson
6530
6792
  }
6531
- });
6793
+ }));
6532
6794
  if (res?.data?.message_id) {
6533
6795
  return { ok: true, messageId: res.data.message_id };
6534
6796
  }
@@ -6545,14 +6807,14 @@ ${trimmedResponse}`;
6545
6807
  ...permCommands
6546
6808
  ].join("\n");
6547
6809
  try {
6548
- const res = await this.restClient.im.message.create({
6810
+ const res = await this.withFeishuRequestTimeout(chatId, "im.message.create:permission-fallback-text", () => this.restClient.im.message.create({
6549
6811
  params: { receive_id_type: "chat_id" },
6550
6812
  data: {
6551
6813
  receive_id: chatId,
6552
6814
  msg_type: "text",
6553
6815
  content: JSON.stringify({ text: plainText })
6554
6816
  }
6555
- });
6817
+ }));
6556
6818
  if (res?.data?.message_id) {
6557
6819
  return { ok: true, messageId: res.data.message_id };
6558
6820
  }
@@ -15028,6 +15290,54 @@ function extractNormalizedStructuredText(value) {
15028
15290
  collectStructuredTextParts(value, parts);
15029
15291
  return parts.length > 0 ? normalizeStructuredText(parts.join("\n\n")) : "";
15030
15292
  }
15293
+ function parseJsonSafely(value) {
15294
+ if (!value) return null;
15295
+ try {
15296
+ return JSON.parse(value);
15297
+ } catch {
15298
+ return null;
15299
+ }
15300
+ }
15301
+ function normalizeTaskStatus(value) {
15302
+ const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
15303
+ if (normalized === "in_progress" || normalized === "running" || normalized === "active") {
15304
+ return "in_progress";
15305
+ }
15306
+ if (normalized === "completed" || normalized === "complete" || normalized === "done") {
15307
+ return "completed";
15308
+ }
15309
+ return "pending";
15310
+ }
15311
+ function parseTaskProgressItems(value) {
15312
+ if (!Array.isArray(value)) return [];
15313
+ return value.map((item) => {
15314
+ const record = item;
15315
+ const text2 = extractNormalizedStructuredText(record.text ?? record.step);
15316
+ if (!text2) return null;
15317
+ return {
15318
+ text: text2,
15319
+ status: normalizeTaskStatus(record.status)
15320
+ };
15321
+ }).filter((item) => Boolean(item));
15322
+ }
15323
+ function parseUpdatePlanTasks(argumentsJson) {
15324
+ const parsed = parseJsonSafely(argumentsJson);
15325
+ if (!parsed || typeof parsed !== "object") return [];
15326
+ return parseTaskProgressItems(parsed.plan ?? parsed.tasks);
15327
+ }
15328
+ function extractToolOutputText(value) {
15329
+ if (typeof value !== "string") return extractNormalizedFreeText(value);
15330
+ const trimmed = value.trim();
15331
+ if (!trimmed) return "";
15332
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
15333
+ const parsed = parseJsonSafely(trimmed);
15334
+ if (parsed && typeof parsed === "object") {
15335
+ const extracted = extractNormalizedFreeText(parsed.output ?? parsed);
15336
+ if (extracted) return extracted;
15337
+ }
15338
+ }
15339
+ return extractNormalizedFreeText(value);
15340
+ }
15031
15341
  function createDesktopEventSignature(rawLine) {
15032
15342
  return crypto7.createHash("sha1").update(rawLine).digest("hex");
15033
15343
  }
@@ -15059,6 +15369,17 @@ function isSessionMessageLine(line) {
15059
15369
  function isTurnContextLine(line) {
15060
15370
  return line.type === "turn_context";
15061
15371
  }
15372
+ function describeUnhandledMirrorLineKind(line) {
15373
+ if (isSessionEventLine(line)) {
15374
+ const payloadType = typeof line.payload?.type === "string" ? line.payload.type.trim() : "";
15375
+ return `event_msg:${payloadType || "<unknown>"}`;
15376
+ }
15377
+ if (isSessionMessageLine(line)) {
15378
+ const payloadType = typeof line.payload?.type === "string" ? line.payload.type.trim() : "";
15379
+ return `response_item:${payloadType || "<unknown>"}`;
15380
+ }
15381
+ return null;
15382
+ }
15062
15383
  function listDesktopSessions(limit) {
15063
15384
  const root = getCodexSessionsRoot();
15064
15385
  if (!fs4.existsSync(root)) return [];
@@ -15151,81 +15472,213 @@ function pushDesktopSessionEvent(events, parsed, rawLine) {
15151
15472
  });
15152
15473
  }
15153
15474
  }
15154
- function pushDesktopMirrorRecord(records, parsed, rawLine, activeTurnId) {
15155
- if (isSessionEventLine(parsed) && parsed.payload?.type === "task_started") {
15475
+ function pushDesktopMirrorRecord(records, parsed, rawLine, activeTurnId, activeSpecialCallIds) {
15476
+ if (isSessionEventLine(parsed)) {
15477
+ return pushDesktopMirrorEventRecord(records, parsed, rawLine, activeTurnId);
15478
+ }
15479
+ if (isSessionMessageLine(parsed)) {
15480
+ return pushDesktopMirrorResponseRecord(records, parsed, rawLine, activeTurnId, activeSpecialCallIds);
15481
+ }
15482
+ return false;
15483
+ }
15484
+ function pushDesktopMirrorEventRecord(records, parsed, rawLine, activeTurnId) {
15485
+ const signature = createDesktopEventSignature(rawLine);
15486
+ const timestamp = parsed.timestamp || "";
15487
+ if (parsed.payload?.type === "task_started") {
15156
15488
  records.push({
15157
- signature: createDesktopEventSignature(rawLine),
15489
+ signature,
15158
15490
  type: "task_started",
15159
15491
  content: "",
15160
- timestamp: parsed.timestamp || "",
15492
+ timestamp,
15161
15493
  turnId: parsed.payload.turn_id || ""
15162
15494
  });
15163
- return;
15495
+ return true;
15164
15496
  }
15165
- if (isSessionEventLine(parsed) && parsed.payload?.type === "user_message") {
15497
+ if (parsed.payload?.type === "turn_aborted") {
15498
+ records.push({
15499
+ signature,
15500
+ type: "task_aborted",
15501
+ content: extractNormalizedStructuredText(parsed.payload.reason),
15502
+ timestamp,
15503
+ ...activeTurnId ? { turnId: activeTurnId } : {}
15504
+ });
15505
+ return true;
15506
+ }
15507
+ if (parsed.payload?.type === "agent_reasoning") {
15508
+ const text2 = extractNormalizedStructuredText(parsed.payload.text);
15509
+ if (!text2) return true;
15510
+ records.push({
15511
+ signature,
15512
+ type: "reasoning",
15513
+ content: text2,
15514
+ timestamp,
15515
+ ...activeTurnId ? { turnId: activeTurnId } : {}
15516
+ });
15517
+ return true;
15518
+ }
15519
+ if (parsed.payload?.type === "web_search_end") {
15520
+ const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
15521
+ records.push({
15522
+ signature,
15523
+ type: "tool_finished",
15524
+ content: extractNormalizedStructuredText(parsed.payload.query),
15525
+ timestamp,
15526
+ ...activeTurnId ? { turnId: activeTurnId } : {},
15527
+ toolId,
15528
+ toolName: "Web Search"
15529
+ });
15530
+ return true;
15531
+ }
15532
+ if (parsed.payload?.type === "mcp_tool_call_end") {
15533
+ const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
15534
+ const server = extractNormalizedFreeText(parsed.payload.invocation?.server);
15535
+ const tool = extractNormalizedFreeText(parsed.payload.invocation?.tool);
15536
+ const toolName = server && tool ? `mcp__${server}__${tool}` : "mcp_tool_call";
15537
+ records.push({
15538
+ signature,
15539
+ type: "tool_finished",
15540
+ content: "",
15541
+ timestamp,
15542
+ ...activeTurnId ? { turnId: activeTurnId } : {},
15543
+ toolId,
15544
+ toolName,
15545
+ isError: false
15546
+ });
15547
+ return true;
15548
+ }
15549
+ if (parsed.payload?.type === "user_message") {
15166
15550
  const text2 = extractNormalizedStructuredText(parsed.payload.message);
15167
- if (!text2) return;
15551
+ if (!text2) return true;
15168
15552
  records.push({
15169
- signature: createDesktopEventSignature(rawLine),
15553
+ signature,
15170
15554
  type: "message",
15171
15555
  role: "user",
15172
15556
  content: text2,
15173
- timestamp: parsed.timestamp || "",
15557
+ timestamp,
15174
15558
  ...activeTurnId ? { turnId: activeTurnId } : {}
15175
15559
  });
15176
- return;
15560
+ return true;
15177
15561
  }
15178
- if (isSessionEventLine(parsed) && parsed.payload?.type === "task_complete") {
15562
+ if (parsed.payload?.type === "task_complete") {
15179
15563
  records.push({
15180
- signature: createDesktopEventSignature(rawLine),
15564
+ signature,
15181
15565
  type: "task_complete",
15182
15566
  role: "assistant",
15183
15567
  content: extractNormalizedStructuredText(parsed.payload.last_agent_message),
15184
- timestamp: parsed.timestamp || "",
15568
+ timestamp,
15185
15569
  turnId: parsed.payload.turn_id || ""
15186
15570
  });
15187
- return;
15571
+ return true;
15188
15572
  }
15189
- if (isSessionMessageLine(parsed) && parsed.payload?.type === "message" && parsed.payload.role === "assistant") {
15573
+ return false;
15574
+ }
15575
+ function pushDesktopMirrorResponseRecord(records, parsed, rawLine, activeTurnId, activeSpecialCallIds) {
15576
+ const signature = createDesktopEventSignature(rawLine);
15577
+ const timestamp = parsed.timestamp || "";
15578
+ if (parsed.payload?.type === "message" && parsed.payload.role === "assistant") {
15190
15579
  const text2 = extractDesktopMessageText(parsed);
15191
- if (!text2) return;
15580
+ if (!text2) return true;
15192
15581
  records.push({
15193
- signature: createDesktopEventSignature(rawLine),
15582
+ signature,
15194
15583
  type: "message",
15195
15584
  role: parsed.payload.phase === "commentary" ? "commentary" : "assistant",
15196
15585
  content: parsed.payload.phase === "commentary" ? text2.replace(/^\[commentary\]\n/, "") : text2,
15197
- timestamp: parsed.timestamp || "",
15586
+ timestamp,
15198
15587
  ...activeTurnId ? { turnId: activeTurnId } : {}
15199
15588
  });
15200
- return;
15589
+ return true;
15201
15590
  }
15202
- if (isSessionMessageLine(parsed) && parsed.payload?.type === "function_call") {
15591
+ if (parsed.payload?.type === "function_call") {
15203
15592
  const toolName = extractNormalizedFreeText(parsed.payload.name);
15204
- const toolId = extractNormalizedFreeText(parsed.payload.call_id) || createDesktopEventSignature(rawLine);
15205
- if (!toolName) return;
15593
+ const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
15594
+ if (!toolName) return true;
15595
+ if (toolName === "update_plan") {
15596
+ const tasks = parseUpdatePlanTasks(parsed.payload.arguments);
15597
+ activeSpecialCallIds.add(toolId);
15598
+ records.push({
15599
+ signature,
15600
+ type: "plan_update",
15601
+ content: "",
15602
+ timestamp,
15603
+ ...activeTurnId ? { turnId: activeTurnId } : {},
15604
+ tasks
15605
+ });
15606
+ return true;
15607
+ }
15206
15608
  records.push({
15207
- signature: createDesktopEventSignature(rawLine),
15609
+ signature,
15208
15610
  type: "tool_started",
15209
15611
  content: "",
15210
- timestamp: parsed.timestamp || "",
15612
+ timestamp,
15211
15613
  ...activeTurnId ? { turnId: activeTurnId } : {},
15212
15614
  toolId,
15213
15615
  toolName
15214
15616
  });
15215
- return;
15617
+ return true;
15216
15618
  }
15217
- if (isSessionMessageLine(parsed) && parsed.payload?.type === "function_call_output") {
15218
- const toolId = extractNormalizedFreeText(parsed.payload.call_id) || createDesktopEventSignature(rawLine);
15619
+ if (parsed.payload?.type === "custom_tool_call") {
15620
+ const toolName = extractNormalizedFreeText(parsed.payload.name);
15621
+ const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
15622
+ if (!toolName) return true;
15623
+ if (toolName === "update_plan") {
15624
+ const tasks = parseUpdatePlanTasks(typeof parsed.payload.input === "string" ? parsed.payload.input : void 0);
15625
+ activeSpecialCallIds.add(toolId);
15626
+ records.push({
15627
+ signature,
15628
+ type: "plan_update",
15629
+ content: "",
15630
+ timestamp,
15631
+ ...activeTurnId ? { turnId: activeTurnId } : {},
15632
+ tasks
15633
+ });
15634
+ return true;
15635
+ }
15219
15636
  records.push({
15220
- signature: createDesktopEventSignature(rawLine),
15637
+ signature,
15638
+ type: "tool_started",
15639
+ content: "",
15640
+ timestamp,
15641
+ ...activeTurnId ? { turnId: activeTurnId } : {},
15642
+ toolId,
15643
+ toolName
15644
+ });
15645
+ return true;
15646
+ }
15647
+ if (parsed.payload?.type === "function_call_output") {
15648
+ const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
15649
+ if (activeSpecialCallIds.has(toolId)) {
15650
+ activeSpecialCallIds.delete(toolId);
15651
+ return true;
15652
+ }
15653
+ records.push({
15654
+ signature,
15655
+ type: "tool_finished",
15656
+ content: extractToolOutputText(parsed.payload.output),
15657
+ timestamp,
15658
+ ...activeTurnId ? { turnId: activeTurnId } : {},
15659
+ toolId,
15660
+ isError: parsed.payload.is_error === true
15661
+ });
15662
+ return true;
15663
+ }
15664
+ if (parsed.payload?.type === "custom_tool_call_output") {
15665
+ const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
15666
+ if (activeSpecialCallIds.has(toolId)) {
15667
+ activeSpecialCallIds.delete(toolId);
15668
+ return true;
15669
+ }
15670
+ records.push({
15671
+ signature,
15221
15672
  type: "tool_finished",
15222
- content: extractNormalizedFreeText(parsed.payload.output),
15223
- timestamp: parsed.timestamp || "",
15673
+ content: extractToolOutputText(parsed.payload.output),
15674
+ timestamp,
15224
15675
  ...activeTurnId ? { turnId: activeTurnId } : {},
15225
15676
  toolId,
15226
15677
  isError: parsed.payload.is_error === true
15227
15678
  });
15679
+ return true;
15228
15680
  }
15681
+ return false;
15229
15682
  }
15230
15683
  function parseDesktopSessionEventText(content, leadingText = "", flushTrailingText = false) {
15231
15684
  const combined = `${leadingText}${content}`;
@@ -15261,14 +15714,16 @@ function parseDesktopSessionEventText(content, leadingText = "", flushTrailingTe
15261
15714
  trailingText
15262
15715
  };
15263
15716
  }
15264
- function parseDesktopMirrorRecordText(content, leadingText = "", flushTrailingText = false, initialTurnId = null) {
15717
+ function parseDesktopMirrorRecordText(content, leadingText = "", flushTrailingText = false, initialTurnId = null, initialSpecialCallIds = []) {
15265
15718
  const combined = `${leadingText}${content}`;
15266
15719
  if (!combined) {
15267
15720
  return {
15268
15721
  records: [],
15269
15722
  nextOffset: 0,
15270
15723
  trailingText: "",
15271
- nextTurnId: initialTurnId
15724
+ nextTurnId: initialTurnId,
15725
+ nextSpecialCallIds: [],
15726
+ unknownKinds: []
15272
15727
  };
15273
15728
  }
15274
15729
  const hasTrailingNewline = combined.endsWith("\n") || combined.endsWith("\r");
@@ -15280,6 +15735,8 @@ function parseDesktopMirrorRecordText(content, leadingText = "", flushTrailingTe
15280
15735
  }
15281
15736
  const records = [];
15282
15737
  let activeTurnId = initialTurnId;
15738
+ const activeSpecialCallIds = new Set(initialSpecialCallIds);
15739
+ const unknownKinds = /* @__PURE__ */ new Set();
15283
15740
  for (const line of rawLines) {
15284
15741
  const trimmed = line.trim();
15285
15742
  if (!trimmed) continue;
@@ -15297,20 +15754,27 @@ function parseDesktopMirrorRecordText(content, leadingText = "", flushTrailingTe
15297
15754
  const eventPayload = parsed.payload;
15298
15755
  activeTurnId = eventPayload?.turn_id || activeTurnId;
15299
15756
  }
15300
- pushDesktopMirrorRecord(records, parsed, trimmed, activeTurnId);
15301
- if (isSessionEventLine(parsed) && parsed.payload?.type === "task_complete") {
15757
+ const handled = pushDesktopMirrorRecord(records, parsed, trimmed, activeTurnId, activeSpecialCallIds);
15758
+ if (!handled) {
15759
+ const unknownKind = describeUnhandledMirrorLineKind(parsed);
15760
+ if (unknownKind) unknownKinds.add(unknownKind);
15761
+ }
15762
+ if (isSessionEventLine(parsed) && (parsed.payload?.type === "task_complete" || parsed.payload?.type === "turn_aborted")) {
15302
15763
  const eventPayload = parsed.payload;
15303
15764
  const completedTurnId = eventPayload?.turn_id || activeTurnId;
15304
15765
  if (!completedTurnId || completedTurnId === activeTurnId) {
15305
15766
  activeTurnId = null;
15306
15767
  }
15768
+ activeSpecialCallIds.clear();
15307
15769
  }
15308
15770
  }
15309
15771
  return {
15310
15772
  records,
15311
15773
  nextOffset: 0,
15312
15774
  trailingText,
15313
- nextTurnId: activeTurnId
15775
+ nextTurnId: activeTurnId,
15776
+ nextSpecialCallIds: Array.from(activeSpecialCallIds),
15777
+ unknownKinds: Array.from(unknownKinds)
15314
15778
  };
15315
15779
  }
15316
15780
  function readDesktopSessionMessages(threadId, limit = 8) {
@@ -15331,7 +15795,7 @@ function readDesktopSessionEventStreamByFilePath(filePath) {
15331
15795
  }
15332
15796
  return parseDesktopSessionEventText(content, "", true).events;
15333
15797
  }
15334
- function readDesktopSessionMirrorRecordDeltaByFilePath(filePath, startOffset, endOffset, trailingText = "", currentTurnId = null) {
15798
+ function readDesktopSessionMirrorRecordDeltaByFilePath(filePath, startOffset, endOffset, trailingText = "", currentTurnId = null, currentSpecialCallIds = []) {
15335
15799
  let content = "";
15336
15800
  try {
15337
15801
  content = readFileUtf8Range(filePath, startOffset, endOffset);
@@ -15340,15 +15804,19 @@ function readDesktopSessionMirrorRecordDeltaByFilePath(filePath, startOffset, en
15340
15804
  records: [],
15341
15805
  nextOffset: startOffset,
15342
15806
  trailingText,
15343
- nextTurnId: currentTurnId
15807
+ nextTurnId: currentTurnId,
15808
+ nextSpecialCallIds: Array.from(currentSpecialCallIds),
15809
+ unknownKinds: []
15344
15810
  };
15345
15811
  }
15346
- const parsed = parseDesktopMirrorRecordText(content, trailingText, false, currentTurnId);
15812
+ const parsed = parseDesktopMirrorRecordText(content, trailingText, false, currentTurnId, currentSpecialCallIds);
15347
15813
  return {
15348
15814
  records: parsed.records,
15349
15815
  nextOffset: Math.max(startOffset, endOffset),
15350
15816
  trailingText: parsed.trailingText,
15351
- nextTurnId: parsed.nextTurnId
15817
+ nextTurnId: parsed.nextTurnId,
15818
+ nextSpecialCallIds: parsed.nextSpecialCallIds,
15819
+ unknownKinds: parsed.unknownKinds
15352
15820
  };
15353
15821
  }
15354
15822
  function readDesktopSessionEventStream(threadId) {
@@ -16453,6 +16921,8 @@ function formatHealthStatusLabel(healthStatus) {
16453
16921
  return "\u957F\u65F6\u8FD0\u884C\uFF0C\u5F85\u89C2\u5BDF";
16454
16922
  case "suspected_stall":
16455
16923
  return "\u7591\u4F3C\u5361\u4F4F";
16924
+ case "suspected_stream_ui_stall":
16925
+ return "\u6D41\u5F0F UI \u7591\u4F3C\u5361\u4F4F";
16456
16926
  case "suspected_detached":
16457
16927
  return "\u7591\u4F3C\u8131\u6302";
16458
16928
  case "completed":
@@ -16507,8 +16977,10 @@ function buildHealthCommandResponse(title, diagnosis, markdown = false) {
16507
16977
  ["\u5065\u5EB7\u72B6\u6001", formatHealthStatusLabel(diagnosis.healthStatus)],
16508
16978
  ["\u5F53\u524D\u9636\u6BB5", currentStage],
16509
16979
  ["\u6700\u540E\u8FDB\u5C55", formatCommandTimestamp(diagnosis.lastProgressAt)],
16980
+ ["\u6D41\u5F0F UI \u5237\u65B0", formatCommandTimestamp(diagnosis.lastStreamUiUpdateAt)],
16510
16981
  ["\u5DE5\u5177\u5F00\u59CB", formatCommandTimestamp(diagnosis.activeToolStartedAt)],
16511
16982
  ["\u6700\u8FD1\u5DE5\u5177\u5B8C\u6210", formatCommandTimestamp(diagnosis.lastToolFinishedAt)],
16983
+ ["\u6D41\u5F0F UI \u9519\u8BEF", diagnosis.lastStreamUiErrorAt ? `${formatCommandTimestamp(diagnosis.lastStreamUiErrorAt)}${diagnosis.lastStreamUiError ? ` \xB7 ${diagnosis.lastStreamUiError}` : ""}` : "-"],
16512
16984
  ["\u672C\u5730\u8FDB\u7A0B", formatHealthProcessProbe(diagnosis)]
16513
16985
  ],
16514
16986
  [diagnosis.healthReason],
@@ -16630,11 +17102,13 @@ function createMirrorTurnState(sessionId, timestamp, turnId) {
16630
17102
  lastActivityAt: safeTimestamp,
16631
17103
  lastStatusText: null,
16632
17104
  lastStatusAt: 0,
17105
+ statusNote: null,
16633
17106
  userText: null,
16634
17107
  lastAssistantText: null,
16635
17108
  lastCommentaryText: null,
16636
17109
  streamedText: "",
16637
17110
  streamStarted: false,
17111
+ taskItems: [],
16638
17112
  toolCalls: /* @__PURE__ */ new Map()
16639
17113
  };
16640
17114
  }
@@ -16682,7 +17156,7 @@ function finalizeMirrorTurn(subscription, signature, timestamp, status, preferre
16682
17156
  pendingTurn.lastCommentaryText
16683
17157
  ].map((value) => (value || "").trim()).find(Boolean) || "";
16684
17158
  const userText = pendingTurn.userText?.trim() || null;
16685
- if (!text2 && !userText && pendingTurn.toolCalls.size === 0) return null;
17159
+ if (!text2 && !userText && pendingTurn.toolCalls.size === 0 && pendingTurn.taskItems.length === 0) return null;
16686
17160
  return {
16687
17161
  streamKey: pendingTurn.streamKey,
16688
17162
  userText,
@@ -16721,6 +17195,12 @@ function consumeMirrorRecords(subscription, records, hooks = {}) {
16721
17195
  if (completed) finalized.push(completed);
16722
17196
  continue;
16723
17197
  }
17198
+ if (record.type === "task_aborted") {
17199
+ ensureMirrorTurnState(subscription, record);
17200
+ const interrupted = finalizeMirrorTurn(subscription, record.signature, record.timestamp, "interrupted");
17201
+ if (interrupted) finalized.push(interrupted);
17202
+ continue;
17203
+ }
16724
17204
  if (record.type === "message" && record.role === "user") {
16725
17205
  const pendingTurn = ensureMirrorTurnState(subscription, record);
16726
17206
  const text2 = record.content.trim();
@@ -16749,6 +17229,20 @@ function consumeMirrorRecords(subscription, records, hooks = {}) {
16749
17229
  }
16750
17230
  continue;
16751
17231
  }
17232
+ if (record.type === "reasoning") {
17233
+ const pendingTurn = ensureMirrorTurnState(subscription, record);
17234
+ const text2 = record.content.trim();
17235
+ if (!text2) continue;
17236
+ pendingTurn.statusNote = text2;
17237
+ hooks.onStatusProgress?.(subscription, pendingTurn);
17238
+ continue;
17239
+ }
17240
+ if (record.type === "plan_update") {
17241
+ const pendingTurn = ensureMirrorTurnState(subscription, record);
17242
+ pendingTurn.taskItems = record.tasks || [];
17243
+ hooks.onTaskProgress?.(subscription, pendingTurn);
17244
+ continue;
17245
+ }
16752
17246
  if (record.type === "tool_started") {
16753
17247
  const pendingTurn = ensureMirrorTurnState(subscription, record);
16754
17248
  const toolId = record.toolId || record.signature;
@@ -16791,8 +17285,30 @@ function flushTimedOutMirrorTurn(subscription, idleTimeoutMs, nowMs = Date.now()
16791
17285
  "interrupted"
16792
17286
  );
16793
17287
  }
17288
+ function enqueuePendingMirrorDeliveries(subscription, turns) {
17289
+ if (turns.length === 0) return;
17290
+ const existingSignatures = new Set(subscription.pendingDeliveries.map((turn) => turn.signature));
17291
+ for (const turn of turns) {
17292
+ if (existingSignatures.has(turn.signature)) continue;
17293
+ subscription.pendingDeliveries.push(turn);
17294
+ existingSignatures.add(turn.signature);
17295
+ }
17296
+ }
17297
+ function removePendingMirrorDeliveries(subscription, turns) {
17298
+ if (turns.length === 0 || subscription.pendingDeliveries.length === 0) return;
17299
+ const deliveredSignatures = new Set(turns.map((turn) => turn.signature));
17300
+ subscription.pendingDeliveries = subscription.pendingDeliveries.filter(
17301
+ (turn) => !deliveredSignatures.has(turn.signature)
17302
+ );
17303
+ }
17304
+ function selectPendingMirrorDeliveries(subscription, blocked) {
17305
+ if (!blocked) {
17306
+ return subscription.pendingDeliveries.slice();
17307
+ }
17308
+ return subscription.pendingDeliveries.filter((turn) => turn.timedOut);
17309
+ }
16794
17310
  function hasPendingMirrorWork(subscription) {
16795
- return subscription.bufferedRecords.length > 0 || subscription.pendingTurn !== null;
17311
+ return subscription.bufferedRecords.length > 0 || subscription.pendingTurn !== null || subscription.pendingDeliveries.length > 0;
16796
17312
  }
16797
17313
  function consumeBufferedMirrorTurns(subscription, idleTimeoutMs, nowMs = Date.now(), hooks = {}) {
16798
17314
  const bufferedRecords = subscription.bufferedRecords;
@@ -18613,7 +19129,7 @@ function buildConversationPromptText(text2, files = []) {
18613
19129
 
18614
19130
  ${attachmentSupplement}` : attachmentSupplement;
18615
19131
  }
18616
- async function processMessage(binding, text2, onPermissionRequest, abortSignal, files, onPartialText, onToolEvent, onPromptPrepared) {
19132
+ async function processMessage(binding, text2, onPermissionRequest, abortSignal, files, onPartialText, onToolEvent, onTaskEvent, onStatusNote, onPromptPrepared) {
18617
19133
  const { store, llm } = getBridgeContext();
18618
19134
  const sessionId = binding.codepilotSessionId;
18619
19135
  const lockId = crypto8.randomBytes(8).toString("hex");
@@ -18732,14 +19248,22 @@ async function processMessage(binding, text2, onPermissionRequest, abortSignal,
18732
19248
  }
18733
19249
  }
18734
19250
  });
18735
- return await consumeStream(stream, sessionId, onPermissionRequest, onPartialText, onToolEvent);
19251
+ return await consumeStream(
19252
+ stream,
19253
+ sessionId,
19254
+ onPermissionRequest,
19255
+ onPartialText,
19256
+ onToolEvent,
19257
+ onTaskEvent,
19258
+ onStatusNote
19259
+ );
18736
19260
  } finally {
18737
19261
  clearInterval(renewalInterval);
18738
19262
  store.releaseSessionLock(sessionId, lockId);
18739
19263
  store.setSessionRuntimeStatus(sessionId, "idle");
18740
19264
  }
18741
19265
  }
18742
- async function consumeStream(stream, sessionId, onPermissionRequest, onPartialText, onToolEvent) {
19266
+ async function consumeStream(stream, sessionId, onPermissionRequest, onPartialText, onToolEvent, onTaskEvent, onStatusNote) {
18743
19267
  const { store } = getBridgeContext();
18744
19268
  const contentBlocks = [];
18745
19269
  let currentText = "";
@@ -18848,6 +19372,12 @@ async function consumeStream(stream, sessionId, onPermissionRequest, onPartialTe
18848
19372
  if (statusData.model) {
18849
19373
  store.updateSessionModel(sessionId, statusData.model);
18850
19374
  }
19375
+ if (typeof statusData.reasoning === "string" && onStatusNote) {
19376
+ try {
19377
+ onStatusNote(statusData.reasoning);
19378
+ } catch {
19379
+ }
19380
+ }
18851
19381
  } catch {
18852
19382
  }
18853
19383
  break;
@@ -18855,8 +19385,15 @@ async function consumeStream(stream, sessionId, onPermissionRequest, onPartialTe
18855
19385
  case "task_update": {
18856
19386
  try {
18857
19387
  const taskData = JSON.parse(event.data);
18858
- if (taskData.session_id && taskData.todos) {
18859
- store.syncSdkTasks(taskData.session_id, taskData.todos);
19388
+ const tasks = Array.isArray(taskData.tasks) ? taskData.tasks : Array.isArray(taskData.todos) ? taskData.todos : null;
19389
+ if (tasks) {
19390
+ store.syncSdkTasks(sessionId, tasks);
19391
+ if (onTaskEvent) {
19392
+ try {
19393
+ onTaskEvent(tasks);
19394
+ } catch {
19395
+ }
19396
+ }
18860
19397
  }
18861
19398
  } catch {
18862
19399
  }
@@ -18954,6 +19491,14 @@ function pushStreamFeedbackTools(target, tools) {
18954
19491
  } catch {
18955
19492
  }
18956
19493
  }
19494
+ function pushStreamFeedbackTasks(target, tasks) {
19495
+ if (typeof target.adapter.onTaskEvent !== "function") return;
19496
+ target.ensureStarted?.();
19497
+ try {
19498
+ target.adapter.onTaskEvent(target.chatId, tasks, target.streamKey);
19499
+ } catch {
19500
+ }
19501
+ }
18957
19502
  function pushStreamFeedbackStatus(target, text2) {
18958
19503
  if (typeof target.adapter.onStreamStatus !== "function") return;
18959
19504
  target.ensureStarted?.();
@@ -19032,12 +19577,15 @@ function formatRuntimeDuration(ms) {
19032
19577
  if (seconds === 0) return `${hours}h ${minutes}m`;
19033
19578
  return `${hours}h ${minutes}m ${seconds}s`;
19034
19579
  }
19035
- function formatInteractiveRuntimeStatus(elapsedMs, silentMs) {
19580
+ function formatInteractiveRuntimeStatus(elapsedMs, silentMs, statusNote) {
19036
19581
  const parts = [elapsedMs < 1e3 ? "\u5904\u7406\u4E2D" : `\u5DF2\u8FD0\u884C ${formatRuntimeDuration(elapsedMs)}`];
19037
19582
  if (typeof silentMs === "number" && silentMs >= 0) {
19038
19583
  parts.push(`\u6700\u8FD1 ${formatRuntimeDuration(silentMs)} \u65E0\u65B0\u8F93\u51FA`);
19039
19584
  }
19040
- return parts.join("\uFF0C");
19585
+ const runtimeText = parts.join("\uFF0C");
19586
+ const note = (statusNote || "").trim();
19587
+ return note ? `\u5F53\u524D\u6B65\u9AA4\uFF1A${note}
19588
+ ${runtimeText}` : runtimeText;
19041
19589
  }
19042
19590
  async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
19043
19591
  const binding = resolve(msg.address);
@@ -19132,23 +19680,35 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
19132
19680
  adapter,
19133
19681
  channelType: adapter.channelType,
19134
19682
  chatId: msg.address.chatId,
19135
- streamKey
19683
+ streamKey,
19684
+ ensureStarted: () => {
19685
+ adapter.onMessageStart?.(msg.address.chatId, streamKey);
19686
+ }
19136
19687
  };
19137
19688
  const supportsPersistentStreamStatus = hasStreamingCards && adapter.provider === "feishu" && typeof adapter.onStreamStatus === "function";
19138
19689
  const supportsStructuredStreamUi = supportsPersistentStreamStatus && (adapter.supportsStructuredStreamingUi?.(msg.address.chatId) ?? true);
19690
+ let latestStatusNote = null;
19691
+ let latestTasks = [];
19139
19692
  const syncStructuredStreamUiState = () => {
19140
19693
  if (!supportsStructuredStreamUi || taskState.structuredStreamUiActive) return;
19141
19694
  if (adapter.hasActiveStreamingUi?.(msg.address.chatId, streamKey)) {
19142
19695
  taskState.structuredStreamUiActive = true;
19143
19696
  }
19144
19697
  };
19698
+ const syncStructuredStreamUiSnapshot = () => {
19699
+ if (!supportsStructuredStreamUi) return;
19700
+ syncStructuredStreamUiState();
19701
+ const snapshot = adapter.getStructuredStreamingUiSnapshot?.(msg.address.chatId, streamKey);
19702
+ if (!snapshot) return;
19703
+ deps.recordInteractiveStreamUiSnapshot?.(binding.codepilotSessionId, snapshot);
19704
+ };
19145
19705
  const pushRunningStatus = (silentMs) => {
19146
19706
  if (!supportsStructuredStreamUi || streamStatusUpdatesClosed) return;
19147
19707
  pushStreamFeedbackStatus(
19148
19708
  streamFeedbackTarget,
19149
- formatInteractiveRuntimeStatus(nowMs() - taskStartedAt, silentMs)
19709
+ formatInteractiveRuntimeStatus(nowMs() - taskStartedAt, silentMs, latestStatusNote)
19150
19710
  );
19151
- syncStructuredStreamUiState();
19711
+ syncStructuredStreamUiSnapshot();
19152
19712
  };
19153
19713
  let streamStatusHeartbeat = null;
19154
19714
  let streamStatusUpdatesClosed = false;
@@ -19182,7 +19742,24 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
19182
19742
  pushStreamFeedbackTools(streamFeedbackTarget, Array.from(toolCallTracker.values()));
19183
19743
  }
19184
19744
  pushRunningStatus(null);
19185
- syncStructuredStreamUiState();
19745
+ syncStructuredStreamUiSnapshot();
19746
+ };
19747
+ const onTaskEvent = (tasks) => {
19748
+ if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
19749
+ deps.touchInteractiveTask(binding.codepilotSessionId, taskId);
19750
+ latestTasks = tasks;
19751
+ if (hasStreamingCards) {
19752
+ pushStreamFeedbackTasks(streamFeedbackTarget, latestTasks);
19753
+ }
19754
+ pushRunningStatus(null);
19755
+ syncStructuredStreamUiSnapshot();
19756
+ };
19757
+ const onStatusNote = (note) => {
19758
+ if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
19759
+ deps.touchInteractiveTask(binding.codepilotSessionId, taskId);
19760
+ latestStatusNote = (note || "").trim() || null;
19761
+ pushRunningStatus(null);
19762
+ syncStructuredStreamUiSnapshot();
19186
19763
  };
19187
19764
  const onPartialText = (fullText) => {
19188
19765
  if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
@@ -19191,7 +19768,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
19191
19768
  previewOnPartialText?.(fullText);
19192
19769
  onStreamCardText?.(fullText);
19193
19770
  pushRunningStatus(null);
19194
- syncStructuredStreamUiState();
19771
+ syncStructuredStreamUiSnapshot();
19195
19772
  };
19196
19773
  if (supportsStructuredStreamUi) {
19197
19774
  pushRunningStatus(null);
@@ -19205,11 +19782,10 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
19205
19782
  return;
19206
19783
  }
19207
19784
  const elapsedMs = nowMs() - taskStartedAt;
19208
- if (elapsedMs < streamStatusIdleDetectionStartMs) return;
19209
19785
  const silentMs = nowMs() - taskState.lastActivityAt;
19210
- if (silentMs < streamStatusHeartbeatMs) return;
19211
- pushRunningStatus(silentMs);
19212
- syncStructuredStreamUiState();
19786
+ const showSilentDuration = elapsedMs >= streamStatusIdleDetectionStartMs && silentMs >= streamStatusHeartbeatMs ? silentMs : null;
19787
+ pushRunningStatus(showSilentDuration);
19788
+ syncStructuredStreamUiSnapshot();
19213
19789
  }, streamStatusHeartbeatMs);
19214
19790
  }
19215
19791
  let finalOutcome = "failed";
@@ -19236,11 +19812,16 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
19236
19812
  "permission_wait",
19237
19813
  `\u5F53\u524D\u6B63\u5728\u7B49\u5F85\u5DE5\u5177 ${perm.toolName} \u7684\u6743\u9650\u786E\u8BA4\u3002`
19238
19814
  );
19815
+ deps.touchInteractiveTask(binding.codepilotSessionId, taskId);
19816
+ pushRunningStatus(null);
19817
+ syncStructuredStreamUiSnapshot();
19239
19818
  },
19240
19819
  taskAbort.signal,
19241
19820
  attachments && attachments.length > 0 ? attachments : void 0,
19242
19821
  onPartialText,
19243
19822
  onToolEvent,
19823
+ onTaskEvent,
19824
+ onStatusNote,
19244
19825
  (preparedPrompt) => {
19245
19826
  if (!taskState.mirrorSuppressionId) {
19246
19827
  taskState.mirrorSuppressionId = deps.beginMirrorSuppression(binding.codepilotSessionId, preparedPrompt);
@@ -19263,14 +19844,16 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
19263
19844
  }
19264
19845
  if (result.responseText || result.outboundAttachments.length > 0) {
19265
19846
  const textToDeliver = cardFinalized ? "" : result.responseText;
19266
- await deps.deliverResponse(
19267
- adapter,
19268
- msg.address,
19269
- textToDeliver,
19270
- binding.codepilotSessionId,
19271
- msg.messageId,
19272
- result.outboundAttachments
19273
- );
19847
+ if (!cardFinalized || result.outboundAttachments.length > 0) {
19848
+ await deps.deliverResponse(
19849
+ adapter,
19850
+ msg.address,
19851
+ textToDeliver,
19852
+ binding.codepilotSessionId,
19853
+ msg.messageId,
19854
+ result.outboundAttachments
19855
+ );
19856
+ }
19274
19857
  } else if (result.hasError) {
19275
19858
  await deps.deliverResponse(
19276
19859
  adapter,
@@ -19289,6 +19872,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
19289
19872
  finalOutcomeDetail = result.hasError ? result.errorMessage?.trim() || void 0 : void 0;
19290
19873
  } finally {
19291
19874
  stopStructuredStreamStatusUpdates();
19875
+ deps.recordInteractiveStreamUiSnapshot?.(binding.codepilotSessionId, { active: false });
19292
19876
  if (previewState) {
19293
19877
  if (previewState.throttleTimer) {
19294
19878
  clearTimeout(previewState.throttleTimer);
@@ -19504,6 +20088,7 @@ function resetMirrorReadState(subscription) {
19504
20088
  subscription.fileIdentity = null;
19505
20089
  subscription.trailingText = "";
19506
20090
  subscription.activeMirrorTurnId = null;
20091
+ subscription.activeSpecialCallIds.clear();
19507
20092
  subscription.bufferedRecords = [];
19508
20093
  }
19509
20094
  function createMirrorSubscription(input) {
@@ -19527,8 +20112,11 @@ function createMirrorSubscription(input) {
19527
20112
  fileIdentity: null,
19528
20113
  trailingText: "",
19529
20114
  activeMirrorTurnId: null,
20115
+ activeSpecialCallIds: /* @__PURE__ */ new Set(),
19530
20116
  bufferedRecords: [],
19531
20117
  pendingTurn: null,
20118
+ pendingDeliveries: [],
20119
+ unknownMirrorKindsSeen: /* @__PURE__ */ new Set(),
19532
20120
  missingThreadPolls: 0,
19533
20121
  consecutiveFailures: 0,
19534
20122
  suspendedUntil: null
@@ -19539,6 +20127,8 @@ function resetMirrorSubscriptionForThreadChange(subscription, lastDeliveredAt) {
19539
20127
  subscription.lastDeliveredAt = lastDeliveredAt;
19540
20128
  subscription.dirty = true;
19541
20129
  subscription.pendingTurn = null;
20130
+ subscription.pendingDeliveries = [];
20131
+ subscription.unknownMirrorKindsSeen.clear();
19542
20132
  subscription.missingThreadPolls = 0;
19543
20133
  subscription.consecutiveFailures = 0;
19544
20134
  subscription.suspendedUntil = null;
@@ -19735,6 +20325,7 @@ function isMirrorSnapshotUnchanged(subscription, snapshot) {
19735
20325
  }
19736
20326
  function readMirrorDeliverableRecords(subscription, snapshot) {
19737
20327
  let deliverableRecords = [];
20328
+ let unknownKinds = [];
19738
20329
  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;
19739
20330
  if (requiresFullRecover) {
19740
20331
  const previousCursor = subscription.cursor;
@@ -19743,7 +20334,8 @@ function readMirrorDeliverableRecords(subscription, snapshot) {
19743
20334
  0,
19744
20335
  snapshot.size,
19745
20336
  "",
19746
- null
20337
+ null,
20338
+ []
19747
20339
  );
19748
20340
  const delta = reconcileDesktopMirrorCursor(subscription.cursor, fullDelta.records);
19749
20341
  subscription.cursor = delta.nextCursor;
@@ -19751,6 +20343,8 @@ function readMirrorDeliverableRecords(subscription, snapshot) {
19751
20343
  subscription.trailingText = "";
19752
20344
  subscription.fileOffset = snapshot.size;
19753
20345
  subscription.activeMirrorTurnId = fullDelta.nextTurnId;
20346
+ subscription.activeSpecialCallIds = new Set(fullDelta.nextSpecialCallIds);
20347
+ unknownKinds = fullDelta.unknownKinds;
19754
20348
  } else if (snapshot.size > subscription.fileOffset || subscription.trailingText) {
19755
20349
  const previousCursor = subscription.cursor;
19756
20350
  const delta = readDesktopSessionMirrorRecordDeltaByFilePath(
@@ -19758,19 +20352,25 @@ function readMirrorDeliverableRecords(subscription, snapshot) {
19758
20352
  subscription.fileOffset,
19759
20353
  snapshot.size,
19760
20354
  subscription.trailingText,
19761
- subscription.activeMirrorTurnId
20355
+ subscription.activeMirrorTurnId,
20356
+ subscription.activeSpecialCallIds
19762
20357
  );
19763
20358
  deliverableRecords = filterDuplicateAssistantEvents(previousCursor, delta.records);
19764
20359
  subscription.cursor = advanceDesktopMirrorCursor(subscription.cursor, delta.records);
19765
20360
  subscription.trailingText = delta.trailingText;
19766
20361
  subscription.fileOffset = delta.nextOffset;
19767
20362
  subscription.activeMirrorTurnId = delta.nextTurnId;
20363
+ subscription.activeSpecialCallIds = new Set(delta.nextSpecialCallIds);
20364
+ unknownKinds = delta.unknownKinds;
19768
20365
  }
19769
20366
  subscription.fileSize = snapshot.size;
19770
20367
  subscription.fileMtimeMs = snapshot.mtimeMs;
19771
20368
  subscription.fileIdentity = snapshot.identity;
19772
20369
  subscription.dirty = false;
19773
- return deliverableRecords;
20370
+ return {
20371
+ records: deliverableRecords,
20372
+ unknownKinds
20373
+ };
19774
20374
  }
19775
20375
 
19776
20376
  // src/lib/bridge/mirror-delivery-plan.ts
@@ -19785,7 +20385,7 @@ function buildMirrorDeliveryPlan(subscription, deliverableRecords, options) {
19785
20385
  if (options.blocked) {
19786
20386
  return {
19787
20387
  syncReason: "mirror reconcile active task",
19788
- turnsToDeliver: timedOutTurn ? [timedOutTurn] : []
20388
+ finalizedTurns: timedOutTurn ? [timedOutTurn] : []
19789
20389
  };
19790
20390
  }
19791
20391
  const finalizedTurns = timedOutTurn ? [timedOutTurn] : [];
@@ -19793,12 +20393,12 @@ function buildMirrorDeliveryPlan(subscription, deliverableRecords, options) {
19793
20393
  if (finalizedTurns.length === 0) {
19794
20394
  return {
19795
20395
  syncReason: "mirror reconcile no finalized turns",
19796
- turnsToDeliver: []
20396
+ finalizedTurns: []
19797
20397
  };
19798
20398
  }
19799
20399
  return {
19800
20400
  syncReason: "mirror reconcile delivered turns",
19801
- turnsToDeliver: finalizedTurns
20401
+ finalizedTurns
19802
20402
  };
19803
20403
  }
19804
20404
 
@@ -20022,21 +20622,36 @@ function createMirrorRuntime(getState2, options, deps) {
20022
20622
  deps.syncMirrorSessionStateSafe(subscription.sessionId, "mirror reconcile unchanged snapshot");
20023
20623
  return "processed";
20024
20624
  }
20025
- const deliverableRecords = readMirrorDeliverableRecords(subscription, snapshot);
20625
+ const readResult = readMirrorDeliverableRecords(subscription, snapshot);
20626
+ const deliverableRecords = readResult.records;
20627
+ for (const kind of readResult.unknownKinds) {
20628
+ if (subscription.unknownMirrorKindsSeen.has(kind)) continue;
20629
+ subscription.unknownMirrorKindsSeen.add(kind);
20630
+ console.warn(
20631
+ `[bridge-manager] Unhandled desktop mirror event for thread ${subscription.threadId}: ${kind}`
20632
+ );
20633
+ }
20026
20634
  if (deliverableRecords.length > 0) {
20027
20635
  deps.observeSessionHealthRecords(subscription.sessionId, subscription.threadId, deliverableRecords);
20028
20636
  }
20637
+ const blocked = getState2().activeTasks.has(subscription.sessionId) || deps.isMirrorSuppressed(subscription.sessionId);
20029
20638
  const deliveryPlan = buildMirrorDeliveryPlan(subscription, deliverableRecords, {
20030
- blocked: getState2().activeTasks.has(subscription.sessionId) || deps.isMirrorSuppressed(subscription.sessionId),
20639
+ blocked,
20031
20640
  filterSuppressedRecords: deps.filterSuppressedMirrorRecords,
20032
20641
  flushTimedOutTurn: (currentSubscription) => deps.flushTimedOutMirrorTurn(currentSubscription),
20033
20642
  consumeBufferedTurns: (currentSubscription) => deps.consumeBufferedMirrorTurns(currentSubscription)
20034
20643
  });
20035
- if (deliveryPlan.turnsToDeliver.length > 0) {
20036
- try {
20037
- await deps.deliverMirrorTurns(subscription, deliveryPlan.turnsToDeliver);
20038
- } catch (error) {
20039
- subscription.dirty = true;
20644
+ if (deliveryPlan.finalizedTurns.length > 0) {
20645
+ enqueuePendingMirrorDeliveries(subscription, deliveryPlan.finalizedTurns);
20646
+ }
20647
+ const turnsToAttempt = selectPendingMirrorDeliveries(subscription, blocked);
20648
+ if (turnsToAttempt.length > 0) {
20649
+ const deliveryResult = await deps.deliverMirrorTurns(subscription, turnsToAttempt);
20650
+ if (deliveryResult.deliveredCount > 0) {
20651
+ removePendingMirrorDeliveries(subscription, turnsToAttempt.slice(0, deliveryResult.deliveredCount));
20652
+ }
20653
+ if (deliveryResult.error) {
20654
+ const error = deliveryResult.error;
20040
20655
  console.warn("[bridge-manager] Mirror delivery failed:", error instanceof Error ? error.message : error);
20041
20656
  }
20042
20657
  }
@@ -20210,11 +20825,13 @@ var HEALTH_RECENT_PROGRESS_MS = 10 * 60 * 1e3;
20210
20825
  var HEALTH_SLOW_OBSERVED_MS = 30 * 60 * 1e3;
20211
20826
  var HEALTH_PROGRESS_PERSIST_THROTTLE_MS = 15 * 1e3;
20212
20827
  var HEALTH_PROCESS_PROBE_CACHE_MS = 30 * 1e3;
20828
+ var HEALTH_STREAM_UI_STALL_MS = 60 * 1e3;
20213
20829
  var RUNNING_HEALTH_STATUSES = /* @__PURE__ */ new Set([
20214
20830
  "running_active",
20215
20831
  "waiting_tool",
20216
20832
  "slow_observed",
20217
20833
  "suspected_stall",
20834
+ "suspected_stream_ui_stall",
20218
20835
  "suspected_detached"
20219
20836
  ]);
20220
20837
  function parseIsoMs(value) {
@@ -20282,6 +20899,10 @@ function buildProgressReason(type, detail) {
20282
20899
  return "\u6700\u8FD1\u6536\u5230\u4E86\u65B0\u7684\u684C\u9762\u4F1A\u8BDD\u6D88\u606F\u3002";
20283
20900
  case "commentary":
20284
20901
  return "\u6700\u8FD1\u6536\u5230\u4E86\u65B0\u7684\u6267\u884C\u8FDB\u5C55\u8BF4\u660E\u3002";
20902
+ case "reasoning":
20903
+ return "\u6700\u8FD1\u6536\u5230\u4E86\u65B0\u7684\u601D\u8003/\u72B6\u6001\u8BF4\u660E\u3002";
20904
+ case "plan_update":
20905
+ return "\u6700\u8FD1\u66F4\u65B0\u4E86\u4EFB\u52A1\u8BA1\u5212\u3002";
20285
20906
  case "text":
20286
20907
  return "\u6700\u8FD1\u6536\u5230\u4E86\u65B0\u7684\u6B63\u6587\u8F93\u51FA\u3002";
20287
20908
  case "permission_wait":
@@ -20329,6 +20950,12 @@ function computeBaseDiagnosis(session, nowMs) {
20329
20950
  const activeToolName = summarizeActiveTools(activeTools) || trimOrNull(session.active_tool_name);
20330
20951
  const activeToolStartedAt = getActiveToolStartedAt(activeTools) || trimOrNull(session.active_tool_started_at);
20331
20952
  const lastToolFinishedAt = trimOrNull(session.last_tool_finished_at);
20953
+ const lastStreamUiAttemptAt = trimOrNull(session.last_stream_ui_attempt_at);
20954
+ const lastStreamUiUpdateAt = trimOrNull(session.last_stream_ui_update_at);
20955
+ const streamUiFlushStartedAt = trimOrNull(session.stream_ui_flush_started_at);
20956
+ const lastStreamUiErrorAt = trimOrNull(session.last_stream_ui_error_at);
20957
+ const lastStreamUiError = trimOrNull(session.last_stream_ui_error);
20958
+ const streamUiConsecutiveFailures = typeof session.stream_ui_consecutive_failures === "number" && Number.isFinite(session.stream_ui_consecutive_failures) && session.stream_ui_consecutive_failures > 0 ? session.stream_ui_consecutive_failures : 0;
20332
20959
  const sdkSessionId = trimOrNull(session.sdk_session_id);
20333
20960
  const lastProgressMs = parseIsoMs(lastProgressAt || void 0);
20334
20961
  const previousStatus = session.health_status || "idle";
@@ -20344,6 +20971,12 @@ function computeBaseDiagnosis(session, nowMs) {
20344
20971
  activeToolName,
20345
20972
  activeToolStartedAt,
20346
20973
  lastToolFinishedAt,
20974
+ lastStreamUiAttemptAt,
20975
+ lastStreamUiUpdateAt,
20976
+ streamUiFlushStartedAt,
20977
+ lastStreamUiErrorAt,
20978
+ lastStreamUiError,
20979
+ streamUiConsecutiveFailures,
20347
20980
  sdkSessionId
20348
20981
  };
20349
20982
  }
@@ -20375,6 +21008,12 @@ function computeBaseDiagnosis(session, nowMs) {
20375
21008
  activeToolName,
20376
21009
  activeToolStartedAt,
20377
21010
  lastToolFinishedAt,
21011
+ lastStreamUiAttemptAt,
21012
+ lastStreamUiUpdateAt,
21013
+ streamUiFlushStartedAt,
21014
+ lastStreamUiErrorAt,
21015
+ lastStreamUiError,
21016
+ streamUiConsecutiveFailures,
20378
21017
  sdkSessionId
20379
21018
  };
20380
21019
  }
@@ -20415,11 +21054,75 @@ function applyProcessProbeDiagnosis(diagnosis, processProbe) {
20415
21054
  processProbe
20416
21055
  };
20417
21056
  }
21057
+ function applyStreamUiDiagnosis(diagnosis, nowMs) {
21058
+ if (!isRunningRuntimeStatus(diagnosis.runtimeStatus)) {
21059
+ return diagnosis;
21060
+ }
21061
+ const lastProgressMs = parseIsoMs(diagnosis.lastProgressAt || void 0);
21062
+ if (!lastProgressMs || nowMs - lastProgressMs > HEALTH_RECENT_PROGRESS_MS) {
21063
+ return diagnosis;
21064
+ }
21065
+ const lastStreamUiUpdateMs = parseIsoMs(diagnosis.lastStreamUiUpdateAt || void 0);
21066
+ const lastStreamUiAttemptMs = parseIsoMs(diagnosis.lastStreamUiAttemptAt || void 0);
21067
+ const streamUiFlushStartedMs = parseIsoMs(diagnosis.streamUiFlushStartedAt || void 0);
21068
+ const lastStreamUiErrorText = diagnosis.lastStreamUiError?.trim();
21069
+ if (streamUiFlushStartedMs && nowMs - streamUiFlushStartedMs >= HEALTH_STREAM_UI_STALL_MS) {
21070
+ const details = ["\u4EFB\u52A1\u4ECD\u5728\u7EE7\u7EED\uFF0C\u4F46\u6D41\u5F0F UI \u5237\u65B0\u8BF7\u6C42\u5DF2\u957F\u65F6\u95F4\u672A\u5B8C\u6210\uFF0C\u7591\u4F3C\u5361\u4F4F\u3002"];
21071
+ if (diagnosis.streamUiConsecutiveFailures > 0) {
21072
+ details.push(`\u6700\u8FD1\u8FDE\u7EED\u5931\u8D25 ${diagnosis.streamUiConsecutiveFailures} \u6B21\u3002`);
21073
+ }
21074
+ if (lastStreamUiErrorText) {
21075
+ details.push(`\u6700\u8FD1\u9519\u8BEF\uFF1A${lastStreamUiErrorText}`);
21076
+ }
21077
+ return {
21078
+ ...diagnosis,
21079
+ healthStatus: "suspected_stream_ui_stall",
21080
+ healthReason: details.join(" ")
21081
+ };
21082
+ }
21083
+ if (lastStreamUiUpdateMs && lastProgressMs - lastStreamUiUpdateMs >= HEALTH_STREAM_UI_STALL_MS) {
21084
+ const details = ["\u4EFB\u52A1\u4ECD\u5728\u7EE7\u7EED\uFF0C\u4F46\u6D41\u5F0F UI \u5DF2\u957F\u65F6\u95F4\u6CA1\u6709\u8DDF\u4E0A\u6700\u65B0\u6267\u884C\u8FDB\u5C55\uFF0C\u7591\u4F3C\u505C\u66F4\u3002"];
21085
+ if (lastStreamUiErrorText) {
21086
+ details.push(`\u6700\u8FD1\u9519\u8BEF\uFF1A${lastStreamUiErrorText}`);
21087
+ }
21088
+ return {
21089
+ ...diagnosis,
21090
+ healthStatus: "suspected_stream_ui_stall",
21091
+ healthReason: details.join(" ")
21092
+ };
21093
+ }
21094
+ if (!lastStreamUiUpdateMs && lastStreamUiAttemptMs && lastProgressMs - lastStreamUiAttemptMs >= HEALTH_STREAM_UI_STALL_MS) {
21095
+ const details = ["\u4EFB\u52A1\u4ECD\u5728\u7EE7\u7EED\uFF0C\u4F46\u6D41\u5F0F UI \u53EA\u6709\u53D1\u9001\u5C1D\u8BD5\u3001\u6CA1\u6709\u6210\u529F\u5237\u65B0\u8BB0\u5F55\uFF0C\u7591\u4F3C\u505C\u66F4\u3002"];
21096
+ if (lastStreamUiErrorText) {
21097
+ details.push(`\u6700\u8FD1\u9519\u8BEF\uFF1A${lastStreamUiErrorText}`);
21098
+ }
21099
+ return {
21100
+ ...diagnosis,
21101
+ healthStatus: "suspected_stream_ui_stall",
21102
+ healthReason: details.join(" ")
21103
+ };
21104
+ }
21105
+ return diagnosis;
21106
+ }
20418
21107
 
20419
21108
  // src/lib/bridge/session-health-runtime.ts
20420
21109
  function createSessionHealthRuntime(deps) {
20421
21110
  const lastProgressPersistAt = /* @__PURE__ */ new Map();
20422
21111
  const processProbeCache = /* @__PURE__ */ new Map();
21112
+ function summarizePlanUpdate(tasks) {
21113
+ if (!Array.isArray(tasks) || tasks.length === 0) {
21114
+ return "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u66F4\u65B0\u4E86\u4EFB\u52A1\u8BA1\u5212\u3002";
21115
+ }
21116
+ let inProgress = 0;
21117
+ let pending = 0;
21118
+ let completed = 0;
21119
+ for (const task of tasks) {
21120
+ if (task?.status === "completed") completed += 1;
21121
+ else if (task?.status === "in_progress") inProgress += 1;
21122
+ else pending += 1;
21123
+ }
21124
+ return `\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u66F4\u65B0\u4E86\u4EFB\u52A1\u8BA1\u5212\uFF08\u6267\u884C\u4E2D ${inProgress} \u9879\uFF0C\u7B49\u5F85\u4E2D ${pending} \u9879\uFF0C\u5DF2\u5B8C\u6210 ${completed} \u9879\uFF09\u3002`;
21125
+ }
20423
21126
  function updateSessionHealth(sessionId, updates, options) {
20424
21127
  const store = deps.getStore();
20425
21128
  const session = store.getSession(sessionId);
@@ -20462,7 +21165,13 @@ function createSessionHealthRuntime(deps) {
20462
21165
  active_tools_json: void 0,
20463
21166
  active_tool_name: void 0,
20464
21167
  active_tool_started_at: void 0,
20465
- last_tool_finished_at: void 0
21168
+ last_tool_finished_at: void 0,
21169
+ last_stream_ui_attempt_at: void 0,
21170
+ last_stream_ui_update_at: void 0,
21171
+ stream_ui_flush_started_at: void 0,
21172
+ last_stream_ui_error_at: void 0,
21173
+ last_stream_ui_error: void 0,
21174
+ stream_ui_consecutive_failures: void 0
20466
21175
  });
20467
21176
  }
20468
21177
  function recordInteractiveProgress(sessionId, type, detail) {
@@ -20530,11 +21239,29 @@ function createSessionHealthRuntime(deps) {
20530
21239
  active_tools_json: void 0,
20531
21240
  active_tool_name: void 0,
20532
21241
  active_tool_started_at: void 0,
21242
+ stream_ui_flush_started_at: void 0,
20533
21243
  last_health_check_at: nowIso4
20534
21244
  }, { force: true });
20535
21245
  lastProgressPersistAt.set(sessionId, Date.now());
20536
21246
  processProbeCache.delete(sessionId);
20537
21247
  }
21248
+ function toIso(value) {
21249
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return void 0;
21250
+ return new Date(value).toISOString();
21251
+ }
21252
+ function recordStructuredStreamUi(sessionId, snapshot) {
21253
+ const updates = {
21254
+ stream_ui_flush_started_at: snapshot.active && snapshot.flushInFlight ? toIso(snapshot.flushInFlightSince ?? snapshot.lastAttemptAt) : void 0
21255
+ };
21256
+ if (snapshot.active) {
21257
+ updates.last_stream_ui_attempt_at = toIso(snapshot.lastAttemptAt);
21258
+ updates.last_stream_ui_update_at = toIso(snapshot.lastUpdateAt);
21259
+ updates.last_stream_ui_error_at = toIso(snapshot.lastErrorAt);
21260
+ updates.last_stream_ui_error = snapshot.lastError?.trim() || void 0;
21261
+ updates.stream_ui_consecutive_failures = snapshot.consecutiveFailures && snapshot.consecutiveFailures > 0 ? snapshot.consecutiveFailures : void 0;
21262
+ }
21263
+ updateSessionHealth(sessionId, updates);
21264
+ }
20538
21265
  function observeDesktopMirrorRecords(sessionId, _threadId, records) {
20539
21266
  for (const record of records) {
20540
21267
  if (record.type === "task_started") {
@@ -20545,6 +21272,10 @@ function createSessionHealthRuntime(deps) {
20545
21272
  recordInteractiveEnd(sessionId, "completed", "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u5DF2\u5B8C\u6210\u5F53\u524D\u4EFB\u52A1\u3002");
20546
21273
  continue;
20547
21274
  }
21275
+ if (record.type === "task_aborted") {
21276
+ recordInteractiveEnd(sessionId, "aborted", "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u5DF2\u505C\u6B62\u5F53\u524D\u4EFB\u52A1\u3002");
21277
+ continue;
21278
+ }
20548
21279
  if (record.type === "tool_started") {
20549
21280
  recordToolState(sessionId, record.toolId || record.signature, record.toolName || "tool", "running");
20550
21281
  continue;
@@ -20558,6 +21289,22 @@ function createSessionHealthRuntime(deps) {
20558
21289
  );
20559
21290
  continue;
20560
21291
  }
21292
+ if (record.type === "reasoning") {
21293
+ recordInteractiveProgress(
21294
+ sessionId,
21295
+ "reasoning",
21296
+ "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u65B0\u7684\u601D\u8003/\u72B6\u6001\u8BF4\u660E\u3002"
21297
+ );
21298
+ continue;
21299
+ }
21300
+ if (record.type === "plan_update") {
21301
+ recordInteractiveProgress(
21302
+ sessionId,
21303
+ "plan_update",
21304
+ summarizePlanUpdate(record.tasks)
21305
+ );
21306
+ continue;
21307
+ }
20561
21308
  if (record.type === "message") {
20562
21309
  recordInteractiveProgress(
20563
21310
  sessionId,
@@ -20572,7 +21319,10 @@ function createSessionHealthRuntime(deps) {
20572
21319
  const store = deps.getStore();
20573
21320
  for (const session of store.listSessions()) {
20574
21321
  if (!shouldTrackSession(session)) continue;
20575
- const diagnosis = computeBaseDiagnosis(session, nowMs);
21322
+ const diagnosis = applyStreamUiDiagnosis(
21323
+ { ...computeBaseDiagnosis(session, nowMs), processProbe: null },
21324
+ nowMs
21325
+ );
20576
21326
  updateSessionHealth(session.id, {
20577
21327
  health_status: diagnosis.healthStatus,
20578
21328
  health_reason: diagnosis.healthReason
@@ -20600,7 +21350,10 @@ function createSessionHealthRuntime(deps) {
20600
21350
  if (!session) return null;
20601
21351
  const base = computeBaseDiagnosis(session, Date.now());
20602
21352
  const processProbe = await loadProcessProbe(session);
20603
- const diagnosis = applyProcessProbeDiagnosis(base, processProbe);
21353
+ const diagnosis = applyStreamUiDiagnosis(
21354
+ applyProcessProbeDiagnosis(base, processProbe),
21355
+ Date.now()
21356
+ );
20604
21357
  updateSessionHealth(sessionId, {
20605
21358
  health_status: diagnosis.healthStatus,
20606
21359
  health_reason: diagnosis.healthReason,
@@ -20618,6 +21371,7 @@ function createSessionHealthRuntime(deps) {
20618
21371
  recordInteractiveStart,
20619
21372
  recordInteractiveProgress,
20620
21373
  recordToolState,
21374
+ recordStructuredStreamUi,
20621
21375
  recordInteractiveEnd,
20622
21376
  observeDesktopMirrorRecords,
20623
21377
  reconcileSessionHealth,
@@ -20871,7 +21625,8 @@ function pushMirrorStreamingStatus(subscription, turnState, options = {}) {
20871
21625
  }
20872
21626
  const statusText = formatInteractiveRuntimeStatus(
20873
21627
  Math.max(0, nowMs - startedAtMs),
20874
- options.silentMs
21628
+ options.silentMs,
21629
+ turnState.statusNote
20875
21630
  );
20876
21631
  if (turnState.lastStatusText === statusText) return;
20877
21632
  pushStreamFeedbackStatus(
@@ -20920,6 +21675,20 @@ function updateMirrorToolProgress(subscription, turnState) {
20920
21675
  );
20921
21676
  pushMirrorStreamingStatus(subscription, turnState);
20922
21677
  }
21678
+ function updateMirrorTaskProgress(subscription, turnState) {
21679
+ const adapter = getMirrorStreamingAdapter(subscription);
21680
+ if (!adapter) return;
21681
+ pushStreamFeedbackTasks(
21682
+ createMirrorStreamFeedbackTarget(subscription, turnState, adapter),
21683
+ turnState.taskItems
21684
+ );
21685
+ pushMirrorStreamingStatus(subscription, turnState);
21686
+ }
21687
+ function updateMirrorStatusProgress(subscription, turnState) {
21688
+ const adapter = getMirrorStreamingAdapter(subscription);
21689
+ if (!adapter) return;
21690
+ pushMirrorStreamingStatus(subscription, turnState);
21691
+ }
20923
21692
  function stopMirrorStreaming(subscription, status = "interrupted") {
20924
21693
  const adapter = getMirrorStreamingAdapter(subscription);
20925
21694
  const pendingTurn = subscription.pendingTurn;
@@ -20980,12 +21749,21 @@ async function deliverMirrorTurn(subscription, turn) {
20980
21749
  subscription.lastDeliveredAt = turn.timestamp || nowIso3();
20981
21750
  }
20982
21751
  async function deliverMirrorTurns(subscription, turns) {
20983
- for (const turn of turns.slice(-MIRROR_EVENT_BATCH_LIMIT)) {
20984
- await deliverMirrorTurn(subscription, turn);
21752
+ let deliveredCount = 0;
21753
+ for (const turn of turns.slice(0, MIRROR_EVENT_BATCH_LIMIT)) {
21754
+ try {
21755
+ await deliverMirrorTurn(subscription, turn);
21756
+ deliveredCount += 1;
21757
+ } catch (error) {
21758
+ return { deliveredCount, error };
21759
+ }
20985
21760
  }
21761
+ return { deliveredCount };
20986
21762
  }
20987
21763
  var MIRROR_TURN_HOOKS = {
20988
21764
  onStreamText: updateMirrorStreaming,
21765
+ onStatusProgress: updateMirrorStatusProgress,
21766
+ onTaskProgress: updateMirrorTaskProgress,
20989
21767
  onToolProgress: updateMirrorToolProgress
20990
21768
  };
20991
21769
  function consumeMirrorRecords2(subscription, records) {
@@ -21272,6 +22050,9 @@ async function handleMessage(adapter, msg) {
21272
22050
  recordInteractiveHealthTool: (sessionId, toolId, toolName, status) => {
21273
22051
  SESSION_HEALTH_RUNTIME.recordToolState(sessionId, toolId, toolName, status);
21274
22052
  },
22053
+ recordInteractiveStreamUiSnapshot: (sessionId, snapshot) => {
22054
+ SESSION_HEALTH_RUNTIME.recordStructuredStreamUi(sessionId, snapshot);
22055
+ },
21275
22056
  recordInteractiveHealthEnd: (sessionId, outcome, detail) => SESSION_HEALTH_RUNTIME.recordInteractiveEnd(sessionId, outcome, detail),
21276
22057
  beginMirrorSuppression: beginMirrorSuppression2,
21277
22058
  abortMirrorSuppression: abortMirrorSuppression2,