chatccc 0.2.157 → 0.2.158

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.157",
3
+ "version": "0.2.158",
4
4
  "description": "Feishu bot bridge for Claude Code",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,7 +1,7 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
2
 
3
3
  import { readUtf8JsonBody } from "./agent-rpc-body.ts";
4
- import { activePrompts, getChatsForSession, cancelQueuedMessage } from "./session-chat-binding.ts";
4
+ import { activePrompts, getChatsForSession } from "./session-chat-binding.ts";
5
5
  import { getPlatformForChat } from "./session.ts";
6
6
  import { readStreamState, writeStreamState } from "./stream-state.ts";
7
7
  import { ts } from "./config.ts";
@@ -78,8 +78,8 @@ export async function handleAgentStopStuckRequest(
78
78
  }
79
79
  }
80
80
 
81
- // 丢弃缓存队列中的消息
82
- cancelQueuedMessage(sessionId);
81
+ // 不丢弃缓存队列中的消息,让 runAgentSession 的 finally 块正常消费,
82
+ // 确保 stop-stuck-loop 结束后排队的消息仍能被正常处理。
83
83
 
84
84
  const finalReply = typeof body?.final_reply === "string" ? body.final_reply.trim() : "";
85
85
 
package/src/session.ts CHANGED
@@ -167,15 +167,15 @@ function formatTerminalHeader(status: "running" | "done" | "stopped" | "error"):
167
167
  return { title: "完成" };
168
168
  }
169
169
 
170
- function turnFinalStatus(status: "running" | "done" | "stopped" | "error"): "done" | "stopped" {
171
- return status === "stopped" || status === "error" ? "stopped" : "done";
172
- }
173
-
174
- function isCardKitSequenceConflict(err: unknown): boolean {
175
- return err instanceof Error && err.message.includes("300317");
176
- }
177
-
178
- function startPromptProcessMonitor(sessionId: string, info: ToolProcessInfo): void {
170
+ function turnFinalStatus(status: "running" | "done" | "stopped" | "error"): "done" | "stopped" {
171
+ return status === "stopped" || status === "error" ? "stopped" : "done";
172
+ }
173
+
174
+ function isCardKitSequenceConflict(err: unknown): boolean {
175
+ return err instanceof Error && err.message.includes("300317");
176
+ }
177
+
178
+ function startPromptProcessMonitor(sessionId: string, info: ToolProcessInfo): void {
179
179
  const prompt = activePrompts.get(sessionId);
180
180
  if (!prompt) return;
181
181
  prompt.processPid = info.pid;
@@ -1161,11 +1161,11 @@ export async function runAgentSession(
1161
1161
  // 标记 prompt 结束
1162
1162
  resourceMonitor.off("stuck", onResourceStuck);
1163
1163
  const prompt = activePrompts.get(sessionId);
1164
- const wasStopped = prompt?.stopped ?? false;
1165
- const wasAbnormalExit = prompt?.abnormalExit ?? false;
1166
- const wasResourceStuck = prompt?.resourceStuck ?? false;
1167
- clearPromptProcessMonitor(sessionId);
1168
- activePrompts.delete(sessionId);
1164
+ const wasStopped = prompt?.stopped ?? false;
1165
+ const wasAbnormalExit = prompt?.abnormalExit ?? false;
1166
+ const wasResourceStuck = prompt?.resourceStuck ?? false;
1167
+ clearPromptProcessMonitor(sessionId);
1168
+ activePrompts.delete(sessionId);
1169
1169
 
1170
1170
  // 先写最终状态(done/stopped),确保 display loop 在下一轮消费前
1171
1171
  // 读到新状态并终结旧卡片。否则 setImmediate 在 CHECK 阶段先于
@@ -1175,14 +1175,19 @@ export async function runAgentSession(
1175
1175
  const finalReply = pickFinalReply(state).trim();
1176
1176
 
1177
1177
  // stop-stuck-loop 接口可能在 fire-and-forget 中已写入带 final_reply 的
1178
- // stream state,finally 不应覆盖它。
1179
- let finalReplyToWrite = finalReply;
1180
- try {
1181
- const existing = await readStreamState(sessionId);
1182
- if (existing && existing.finalReply.length > finalReply.length) {
1183
- finalReplyToWrite = existing.finalReply;
1184
- }
1185
- } catch {}
1178
+ // stream state,finally 不应覆盖它。同时保留 stuckAt 标记,防止
1179
+ // stop-stuck-loop 结束后 session 被错误恢复。
1180
+ let finalReplyToWrite = finalReply;
1181
+ let preserveStuckAt: number | undefined;
1182
+ try {
1183
+ const existing = await readStreamState(sessionId);
1184
+ if (existing) {
1185
+ if (existing.finalReply.length > finalReply.length) {
1186
+ finalReplyToWrite = existing.finalReply;
1187
+ }
1188
+ preserveStuckAt = existing.stuckAt;
1189
+ }
1190
+ } catch {}
1186
1191
 
1187
1192
  await writeStreamState({
1188
1193
  sessionId,
@@ -1191,11 +1196,12 @@ export async function runAgentSession(
1191
1196
  finalReply: finalReplyToWrite,
1192
1197
  chunkCount: state.chunkCount,
1193
1198
  turnCount: nextTurnCount,
1194
- contextTokens: existingInfo?.lastContextTokens ?? 0,
1195
- updatedAt: Date.now(),
1196
- cwd,
1197
- tool,
1198
- });
1199
+ contextTokens: existingInfo?.lastContextTokens ?? 0,
1200
+ updatedAt: Date.now(),
1201
+ cwd,
1202
+ tool,
1203
+ ...(preserveStuckAt ? { stuckAt: preserveStuckAt } : {}),
1204
+ });
1199
1205
 
1200
1206
  // 消费队列中的缓存消息(异步,不阻塞后续清理)
1201
1207
  // 用户 /stop 后应丢弃队列消息,避免用户停止后又自动开始新轮
@@ -1360,42 +1366,42 @@ export function startUnifiedDisplayLoop(): void {
1360
1366
  displayCards.delete(chatId);
1361
1367
  } else {
1362
1368
  // 发送最终结果(卡片平台)
1363
- while (display.cardBusy) await new Promise(r => setTimeout(r, 20));
1364
- const promptStillActive = activePrompts.has(sessionId);
1365
- if (
1366
- promptStillActive &&
1367
- display.lastSentAccLen === state.accumulatedContent.length &&
1368
- display.lastSentFinalReply === state.finalReply
1369
- ) {
1370
- continue;
1371
- }
1372
- const nextSeq = display.sequence + 1;
1369
+ while (display.cardBusy) await new Promise(r => setTimeout(r, 20));
1370
+ const promptStillActive = activePrompts.has(sessionId);
1371
+ if (
1372
+ promptStillActive &&
1373
+ display.lastSentAccLen === state.accumulatedContent.length &&
1374
+ display.lastSentFinalReply === state.finalReply
1375
+ ) {
1376
+ continue;
1377
+ }
1378
+ const nextSeq = display.sequence + 1;
1373
1379
  const { title: headerTitle, template: headerTemplate } = formatTerminalHeader(state.status);
1374
1380
  const cardContent = truncateContent(state.accumulatedContent + state.finalReply) || " ";
1375
1381
  const doneCard = buildProgressCard(cardContent, { showStop: false, headerTitle, headerTemplate });
1376
- let terminalCardUpdateAccepted = false;
1377
- await p.cardUpdate(display.cardId, doneCard, nextSeq).then(() => {
1378
- display.sequence = nextSeq;
1379
- terminalCardUpdateAccepted = true;
1380
- }).catch(err => {
1381
- console.error(`[${ts()}] [DISPLAY] terminal cardUpdate failed: ${(err as Error).message}`);
1382
- if (isCardKitSequenceConflict(err)) {
1383
- display.sequence = nextSeq;
1384
- terminalCardUpdateAccepted = true;
1385
- }
1386
- });
1382
+ let terminalCardUpdateAccepted = false;
1383
+ await p.cardUpdate(display.cardId, doneCard, nextSeq).then(() => {
1384
+ display.sequence = nextSeq;
1385
+ terminalCardUpdateAccepted = true;
1386
+ }).catch(err => {
1387
+ console.error(`[${ts()}] [DISPLAY] terminal cardUpdate failed: ${(err as Error).message}`);
1388
+ if (isCardKitSequenceConflict(err)) {
1389
+ display.sequence = nextSeq;
1390
+ terminalCardUpdateAccepted = true;
1391
+ }
1392
+ });
1387
1393
 
1388
1394
  // 若 session 仍在 activePrompts 中,说明 runAgentSession 的 finally
1389
1395
  // 还没执行,当前 stream state 可能是 stopSession fire-and-forget
1390
1396
  // 写入的,finalReply 滞后于内存态。卡片已更新为终态外观,但不发送
1391
1397
  // 文本、不删除 display 条目,留给 finally 落盘后的下一次 tick 处理。
1392
- if (promptStillActive) {
1393
- if (terminalCardUpdateAccepted) {
1394
- display.lastSentAccLen = state.accumulatedContent.length;
1395
- display.lastSentFinalReply = state.finalReply;
1396
- }
1397
- continue;
1398
- }
1398
+ if (promptStillActive) {
1399
+ if (terminalCardUpdateAccepted) {
1400
+ display.lastSentAccLen = state.accumulatedContent.length;
1401
+ display.lastSentFinalReply = state.finalReply;
1402
+ }
1403
+ continue;
1404
+ }
1399
1405
 
1400
1406
  const finalSt = turnFinalStatus(state.status);
1401
1407
  finalizeTurnCards(sessionId, state.turnCount, finalSt).catch(() => {});