chatccc 0.2.79 → 0.2.81

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatccc",
3
- "version": "0.2.79",
3
+ "version": "0.2.81",
4
4
  "description": "Feishu bot bridge for Claude Code",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -137,6 +137,8 @@ export interface DisplayCardState {
137
137
  lastSentAccLen?: number;
138
138
  /** WeChat delta: 上次发送时的 finalReply */
139
139
  lastSentFinalReply?: string;
140
+ /** 上次 loop 读取到的 turnCount,用于检测轮次切换 */
141
+ lastTurnCount?: number;
140
142
  }
141
143
 
142
144
  export const displayCards = new Map<string, DisplayCardState>();
package/src/session.ts CHANGED
@@ -861,6 +861,25 @@ export async function runAgentSession(
861
861
  const wasStopped = prompt?.stopped ?? false;
862
862
  activePrompts.delete(sessionId);
863
863
 
864
+ // 先写最终状态(done/stopped),确保 display loop 在下一轮消费前
865
+ // 读到新状态并终结旧卡片。否则 setImmediate 在 CHECK 阶段先于
866
+ // writeFile I/O(POLL 阶段)执行,display loop 会误以为旧轮仍在
867
+ // 运行中并更新旧卡片,而不是新建卡片。
868
+ const finalStatus = wasStopped ? "stopped" : "done";
869
+ const finalReply = pickFinalReply(state).trim();
870
+ await writeStreamState({
871
+ sessionId,
872
+ status: finalStatus,
873
+ accumulatedContent: state.accumulatedContent,
874
+ finalReply,
875
+ chunkCount: state.chunkCount,
876
+ turnCount: nextTurnCount,
877
+ contextTokens: existingInfo?.lastContextTokens ?? 0,
878
+ updatedAt: Date.now(),
879
+ cwd,
880
+ tool,
881
+ });
882
+
864
883
  // 消费队列中的缓存消息(异步,不阻塞后续清理)
865
884
  // 用户 /stop 后应丢弃队列消息,避免用户停止后又自动开始新轮
866
885
  if (wasStopped) {
@@ -879,28 +898,16 @@ export async function runAgentSession(
879
898
  setQueuePreservedChat(sessionId, preservedChat);
880
899
  }
881
900
  console.log(`[${ts()}] [QUEUE] Consuming queued message for session ${sessionId}: "${queued.text.slice(0, 50)}"`);
882
- setImmediate(() => {
901
+ // setTimeout 而非 setImmediate:给 display loop 的 setInterval
902
+ // 足够时间读到 "done" 状态并终结旧卡片,避免新轮更新旧卡片的 bug。
903
+ // setImmediate 在 check 阶段触发早于下一个 timers 阶段,
904
+ // display loop (setInterval) 还没机会读到 "done" 就被新 "running" 覆盖。
905
+ setTimeout(() => {
883
906
  consumeQueuedMessage(platform, queued);
884
- });
907
+ }, 200);
885
908
  }
886
909
  }
887
910
 
888
- // 写最终状态
889
- const finalStatus = wasStopped ? "stopped" : "done";
890
- const finalReply = pickFinalReply(state).trim();
891
- await writeStreamState({
892
- sessionId,
893
- status: finalStatus,
894
- accumulatedContent: state.accumulatedContent,
895
- finalReply,
896
- chunkCount: state.chunkCount,
897
- turnCount: nextTurnCount,
898
- contextTokens: existingInfo?.lastContextTokens ?? 0,
899
- updatedAt: Date.now(),
900
- cwd,
901
- tool,
902
- });
903
-
904
911
  // display loop 下一轮会读到最终状态并发送消息
905
912
 
906
913
  if (wasStopped) {
@@ -1100,11 +1107,44 @@ export function ensureDisplayLoop(sessionId: string): void {
1100
1107
  cardCreatedAt: Date.now(),
1101
1108
  lastSentContent: "",
1102
1109
  streamErrorNotified: false,
1110
+ lastTurnCount: state.turnCount,
1103
1111
  });
1104
1112
  }
1105
1113
  } else {
1106
1114
  if (display.cardBusy) return;
1107
1115
 
1116
+ // 检测轮次切换:turnCount 变化说明上一轮已完成、本 tick 是新轮,
1117
+ // 但上一轮的 display 卡片因 setImmediate/setTimeout 时序问题
1118
+ // 还没被终结分支清除。此处主动终结旧卡、创建新卡。
1119
+ if (display.lastTurnCount !== undefined && display.lastTurnCount !== state.turnCount) {
1120
+ display.cardBusy = true;
1121
+ try {
1122
+ const doneSeq = display.sequence + 1;
1123
+ const doneCard = buildProgressCard("", { showStop: false, headerTitle: "完成" });
1124
+ await p.cardUpdate(display.cardId, doneCard, doneSeq).catch(() => {});
1125
+ displayCards.delete(chatId);
1126
+ } catch (_) { /* ignore */ }
1127
+ display.cardBusy = false;
1128
+ // 新建卡片
1129
+ const cardId = await p.cardCreate(buildProgressCard("", { showStop: true, headerTitle: "生成中..." })).catch(() => null);
1130
+ if (cardId) {
1131
+ await p.cardSend(chatId, cardId).catch(() => null);
1132
+ displayCards.set(chatId, {
1133
+ cardId,
1134
+ sequence: 1,
1135
+ cardBusy: false,
1136
+ cardCreatedAt: Date.now(),
1137
+ lastSentContent: "",
1138
+ streamErrorNotified: false,
1139
+ lastTurnCount: state.turnCount,
1140
+ });
1141
+ }
1142
+ return;
1143
+ }
1144
+
1145
+ // 更新当前 turn 的 lastTurnCount
1146
+ display.lastTurnCount = state.turnCount;
1147
+
1108
1148
  // 卡片轮转
1109
1149
  if (Date.now() - display.cardCreatedAt > CARD_ROTATE_MS) {
1110
1150
  display.cardBusy = true;