chatccc 0.2.243 → 0.2.244

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.
Files changed (59) hide show
  1. package/README.md +5 -5
  2. package/bin/cccagent.mjs +17 -17
  3. package/deepccc-agent/bin/deepccc.mjs +26 -26
  4. package/deepccc-agent/os-prompts/darwin.md +8 -0
  5. package/deepccc-agent/os-prompts/linux.md +8 -0
  6. package/deepccc-agent/os-prompts/win32.md +9 -0
  7. package/deepccc-agent/package.json +2 -1
  8. package/deepccc-agent/src/__tests__/chat-session.test.ts +39 -0
  9. package/deepccc-agent/src/__tests__/cli-json.test.ts +49 -49
  10. package/deepccc-agent/src/__tests__/config.test.ts +26 -26
  11. package/deepccc-agent/src/__tests__/context.test.ts +319 -319
  12. package/deepccc-agent/src/__tests__/file-tools.test.ts +240 -240
  13. package/deepccc-agent/src/__tests__/permissions.test.ts +195 -195
  14. package/deepccc-agent/src/__tests__/progress-reducer.test.ts +121 -121
  15. package/deepccc-agent/src/__tests__/session-search.test.ts +262 -262
  16. package/deepccc-agent/src/__tests__/session-select.test.ts +116 -116
  17. package/deepccc-agent/src/__tests__/sigint.test.ts +56 -56
  18. package/deepccc-agent/src/__tests__/skills.test.ts +284 -284
  19. package/deepccc-agent/src/__tests__/terminal-renderer.test.ts +247 -247
  20. package/deepccc-agent/src/__tests__/web-tools.test.ts +220 -220
  21. package/deepccc-agent/src/config.ts +84 -84
  22. package/deepccc-agent/src/context.ts +465 -465
  23. package/deepccc-agent/src/file-log.ts +38 -38
  24. package/deepccc-agent/src/index.ts +46 -16
  25. package/deepccc-agent/src/proc-tree-kill.ts +61 -61
  26. package/deepccc-agent/src/progress/cards-helpers.ts +76 -76
  27. package/deepccc-agent/src/progress/reducer.ts +113 -113
  28. package/deepccc-agent/src/progress/terminal-renderer.ts +294 -294
  29. package/deepccc-agent/src/progress/view.ts +77 -77
  30. package/deepccc-agent/src/raw-stream-log.ts +124 -124
  31. package/deepccc-agent/src/session-search.ts +370 -370
  32. package/deepccc-agent/src/session-select.ts +48 -48
  33. package/deepccc-agent/src/sigint.ts +50 -50
  34. package/deepccc-agent/src/skills.ts +205 -205
  35. package/deepccc-agent/src/web-tools.ts +313 -313
  36. package/deepccc-agent/tsconfig.build.json +13 -13
  37. package/deepccc-agent/tsconfig.json +13 -13
  38. package/deepccc-agent/vitest.config.ts +7 -7
  39. package/package.json +73 -73
  40. package/src/__tests__/builtin-chat-session.test.ts +522 -522
  41. package/src/__tests__/builtin-config.test.ts +26 -26
  42. package/src/__tests__/builtin-context.test.ts +319 -319
  43. package/src/__tests__/builtin-file-tools.test.ts +240 -240
  44. package/src/__tests__/builtin-permissions.test.ts +211 -211
  45. package/src/__tests__/builtin-session-search.test.ts +262 -262
  46. package/src/__tests__/builtin-session-select.test.ts +116 -116
  47. package/src/__tests__/builtin-sigint.test.ts +56 -56
  48. package/src/__tests__/builtin-skills.test.ts +284 -284
  49. package/src/__tests__/builtin-web-tools.test.ts +220 -220
  50. package/src/__tests__/config.test.ts +17 -17
  51. package/src/__tests__/progress-reducer.test.ts +121 -121
  52. package/src/__tests__/session-ccc-config.test.ts +45 -45
  53. package/src/__tests__/session.test.ts +298 -298
  54. package/src/adapters/ccc-adapter.ts +145 -145
  55. package/src/config-utils.ts +13 -13
  56. package/src/config.ts +13 -13
  57. package/src/progress/reducer.ts +113 -113
  58. package/src/session-chat-binding.ts +83 -83
  59. package/src/session.ts +311 -311
package/src/session.ts CHANGED
@@ -41,14 +41,14 @@ import { createCccAdapter } from "./adapters/ccc-adapter.ts";
41
41
  import { killProcessTree } from "./adapters/proc-tree-kill.ts";
42
42
  import { resourceMonitor, registerProcess, unregisterProcess } from "./adapters/resource-monitor.ts";
43
43
  import { buildImSkillsPromptCached, exportSkillSubDocs, clearImSkillsPromptCache } from "./im-skills.ts";
44
- import type { PlatformAdapter } from "./platform-adapter.ts";
45
- import { hasResponseStalled, observeResponseProgress } from "./response-stall.ts";
46
- import {
47
- classifyTerminalError,
48
- formatTerminalErrorNotice,
49
- formatTerminalErrorReason,
50
- type TerminalErrorInfo,
51
- } from "./terminal-error.ts";
44
+ import type { PlatformAdapter } from "./platform-adapter.ts";
45
+ import { hasResponseStalled, observeResponseProgress } from "./response-stall.ts";
46
+ import {
47
+ classifyTerminalError,
48
+ formatTerminalErrorNotice,
49
+ formatTerminalErrorReason,
50
+ type TerminalErrorInfo,
51
+ } from "./terminal-error.ts";
52
52
  import {
53
53
  MAX_PROCESSED,
54
54
  clearFeishuMessageLedgerMemory,
@@ -174,20 +174,20 @@ function platformForChat(chatId: string): PlatformAdapter | null {
174
174
  return chatPlatformMap.get(chatId) ?? platformRef;
175
175
  }
176
176
 
177
- const DEFAULT_PROCESS_MONITOR_INTERVAL_MS = 5000;
178
- const DEFAULT_AVATAR_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
179
- const DEFAULT_RESPONSE_STALL_TIMEOUT_MS = 3 * 60 * 1000;
177
+ const DEFAULT_PROCESS_MONITOR_INTERVAL_MS = 5000;
178
+ const DEFAULT_AVATAR_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
179
+ const DEFAULT_RESPONSE_STALL_TIMEOUT_MS = 3 * 60 * 1000;
180
180
  const DEFAULT_RESPONSE_STALL_CHECK_INTERVAL_MS = 5000;
181
181
  const DEFAULT_FINAL_RESPONSE_CLOSE_TIMEOUT_MS = 10_000;
182
182
  export const RESPONSE_STALL_RECOVERY_PROMPT = "完成了吗?如果没完成继续";
183
183
  export const RESPONSE_STALL_RECOVERY_NOTICE =
184
184
  `检测到会话停滞,正在自动确认并继续。\n\n${RESPONSE_STALL_RECOVERY_PROMPT}`;
185
- export const RESPONSE_STALL_RECOVERY_EXHAUSTED_NOTICE =
186
- "⚠️ 自动续跑仍连续 3 分钟没有生成新回复,本次不再自动继续。";
185
+ export const RESPONSE_STALL_RECOVERY_EXHAUSTED_NOTICE =
186
+ "⚠️ 自动续跑仍连续 3 分钟没有生成新回复,本次不再自动继续。";
187
187
  const RESPONSE_STALL_RECOVERY_DELAY_MS = 200;
188
- let processMonitorIntervalMs = DEFAULT_PROCESS_MONITOR_INTERVAL_MS;
189
- let avatarRefreshIntervalMs = DEFAULT_AVATAR_REFRESH_INTERVAL_MS;
190
- let responseStallTimeoutMs = DEFAULT_RESPONSE_STALL_TIMEOUT_MS;
188
+ let processMonitorIntervalMs = DEFAULT_PROCESS_MONITOR_INTERVAL_MS;
189
+ let avatarRefreshIntervalMs = DEFAULT_AVATAR_REFRESH_INTERVAL_MS;
190
+ let responseStallTimeoutMs = DEFAULT_RESPONSE_STALL_TIMEOUT_MS;
191
191
  let responseStallCheckIntervalMs = DEFAULT_RESPONSE_STALL_CHECK_INTERVAL_MS;
192
192
  let finalResponseCloseTimeoutMs = DEFAULT_FINAL_RESPONSE_CLOSE_TIMEOUT_MS;
193
193
  let isProcessAliveImpl = (pid: number): boolean => {
@@ -218,17 +218,17 @@ export function _setProcessMonitorIntervalForTest(ms: number): void {
218
218
  processMonitorIntervalMs = ms;
219
219
  }
220
220
 
221
- export function _resetProcessMonitorIntervalForTest(): void {
222
- processMonitorIntervalMs = DEFAULT_PROCESS_MONITOR_INTERVAL_MS;
223
- }
224
-
225
- export function _setAvatarRefreshIntervalForTest(ms: number): void {
226
- avatarRefreshIntervalMs = ms;
227
- }
228
-
229
- export function _resetAvatarRefreshIntervalForTest(): void {
230
- avatarRefreshIntervalMs = DEFAULT_AVATAR_REFRESH_INTERVAL_MS;
231
- }
221
+ export function _resetProcessMonitorIntervalForTest(): void {
222
+ processMonitorIntervalMs = DEFAULT_PROCESS_MONITOR_INTERVAL_MS;
223
+ }
224
+
225
+ export function _setAvatarRefreshIntervalForTest(ms: number): void {
226
+ avatarRefreshIntervalMs = ms;
227
+ }
228
+
229
+ export function _resetAvatarRefreshIntervalForTest(): void {
230
+ avatarRefreshIntervalMs = DEFAULT_AVATAR_REFRESH_INTERVAL_MS;
231
+ }
232
232
 
233
233
  export function _setResponseStallTimeoutForTest(ms: number): void {
234
234
  responseStallTimeoutMs = ms;
@@ -261,21 +261,21 @@ function clearPromptProcessMonitor(sessionId: string): void {
261
261
  prompt.processMonitor = undefined;
262
262
  }
263
263
 
264
- function clearPromptResponseStallMonitor(sessionId: string): void {
265
- const prompt = activePrompts.get(sessionId);
266
- if (!prompt?.responseStallMonitor) return;
267
- clearInterval(prompt.responseStallMonitor);
268
- prompt.responseStallMonitor = undefined;
269
- }
270
-
271
- function clearPromptAvatarRefreshTimer(sessionId: string): void {
272
- const prompt = activePrompts.get(sessionId);
273
- if (!prompt?.avatarRefreshTimer) return;
274
- clearInterval(prompt.avatarRefreshTimer);
275
- prompt.avatarRefreshTimer = undefined;
276
- }
277
-
278
- function clearPromptFinalResponseCloseTimer(sessionId: string): void {
264
+ function clearPromptResponseStallMonitor(sessionId: string): void {
265
+ const prompt = activePrompts.get(sessionId);
266
+ if (!prompt?.responseStallMonitor) return;
267
+ clearInterval(prompt.responseStallMonitor);
268
+ prompt.responseStallMonitor = undefined;
269
+ }
270
+
271
+ function clearPromptAvatarRefreshTimer(sessionId: string): void {
272
+ const prompt = activePrompts.get(sessionId);
273
+ if (!prompt?.avatarRefreshTimer) return;
274
+ clearInterval(prompt.avatarRefreshTimer);
275
+ prompt.avatarRefreshTimer = undefined;
276
+ }
277
+
278
+ function clearPromptFinalResponseCloseTimer(sessionId: string): void {
279
279
  const prompt = activePrompts.get(sessionId);
280
280
  if (!prompt?.finalResponseCloseTimer) return;
281
281
  clearTimeout(prompt.finalResponseCloseTimer);
@@ -328,18 +328,18 @@ function scheduleFinalResponseCloseGuard(
328
328
  runningPrompt.finalResponseCloseTimer = handle;
329
329
  }
330
330
 
331
- function formatTerminalHeader(
332
- status: "running" | "done" | "stopped" | "error" | "auto_ended",
333
- terminalError?: TerminalErrorInfo,
334
- ): {
331
+ function formatTerminalHeader(
332
+ status: "running" | "done" | "stopped" | "error" | "auto_ended",
333
+ terminalError?: TerminalErrorInfo,
334
+ ): {
335
335
  title: string;
336
336
  template?: string;
337
337
  } {
338
338
  if (status === "auto_ended") return { title: "已自动结束 · 3分钟无新内容", template: "orange" };
339
339
  if (status === "stopped") return { title: "已停止", template: "red" };
340
- if (status === "error") {
341
- return { title: terminalError ? `异常结束 · ${terminalError.title}` : "异常结束", template: "red" };
342
- }
340
+ if (status === "error") {
341
+ return { title: terminalError ? `异常结束 · ${terminalError.title}` : "异常结束", template: "red" };
342
+ }
343
343
  return { title: "完成" };
344
344
  }
345
345
 
@@ -347,59 +347,59 @@ function turnFinalStatus(status: "running" | "done" | "stopped" | "error" | "aut
347
347
  return status === "stopped" || status === "error" || status === "auto_ended" ? "stopped" : "done";
348
348
  }
349
349
 
350
- function formatAutoEndedReply(finalReply: string): string {
351
- const reason = "⚠️ 已自动结束:生成回复阶段连续 3 分钟没有字符变化。";
350
+ function formatAutoEndedReply(finalReply: string): string {
351
+ const reason = "⚠️ 已自动结束:生成回复阶段连续 3 分钟没有字符变化。";
352
352
  return finalReply
353
353
  ? `${reason}以下回复可能不完整。\n\n${finalReply}`
354
354
  : `${reason}本轮没有可发送的回复内容。`;
355
355
  }
356
356
 
357
357
  /**
358
- * 只监控明确的回复生成阶段。启动、压缩、思考、工具调用和搜索都有各自的
359
- * 状态或资源保护,不能把它们消耗的时间算入回复停滞窗口。
358
+ * 只监控明确的回复生成阶段。启动、压缩、思考、工具调用和搜索都有各自的
359
+ * 状态或资源保护,不能把它们消耗的时间算入回复停滞窗口。
360
360
  */
361
- function monitorsOutputProgress(kind: AgentActivityKind): boolean {
362
- return kind === "responding";
363
- }
364
-
365
- function formatTerminalReply(
366
- status: "running" | "done" | "stopped" | "error" | "auto_ended",
367
- finalReply: string,
368
- terminalError?: TerminalErrorInfo,
369
- ): string | null {
370
- if (status === "auto_ended") return formatAutoEndedReply(finalReply);
371
- if (status === "error") {
372
- const error = terminalError ?? {
373
- kind: "unknown" as const,
374
- title: "原因未记录",
375
- message: "当前状态中没有可用的错误详情,请查看运行日志。",
376
- occurredAt: Date.now(),
377
- };
378
- return formatTerminalErrorNotice(error, finalReply);
379
- }
380
- return finalReply || null;
381
- }
382
-
383
- function formatTerminalCardContent(state: {
384
- status: "running" | "done" | "stopped" | "error" | "auto_ended";
385
- accumulatedContent: string;
386
- finalReply: string;
387
- terminalError?: TerminalErrorInfo;
388
- }): string {
389
- const content = state.accumulatedContent + state.finalReply;
390
- if (state.status !== "error") return content;
391
-
392
- const error = state.terminalError ?? {
393
- kind: "unknown" as const,
394
- title: "原因未记录",
395
- message: "当前状态中没有可用的错误详情,请查看运行日志。",
396
- occurredAt: Date.now(),
397
- };
398
- const reason = formatTerminalErrorReason(error);
399
- return content.trim()
400
- ? `${content}\n\n${reason}\n以上内容可能不完整。`
401
- : reason;
402
- }
361
+ function monitorsOutputProgress(kind: AgentActivityKind): boolean {
362
+ return kind === "responding";
363
+ }
364
+
365
+ function formatTerminalReply(
366
+ status: "running" | "done" | "stopped" | "error" | "auto_ended",
367
+ finalReply: string,
368
+ terminalError?: TerminalErrorInfo,
369
+ ): string | null {
370
+ if (status === "auto_ended") return formatAutoEndedReply(finalReply);
371
+ if (status === "error") {
372
+ const error = terminalError ?? {
373
+ kind: "unknown" as const,
374
+ title: "原因未记录",
375
+ message: "当前状态中没有可用的错误详情,请查看运行日志。",
376
+ occurredAt: Date.now(),
377
+ };
378
+ return formatTerminalErrorNotice(error, finalReply);
379
+ }
380
+ return finalReply || null;
381
+ }
382
+
383
+ function formatTerminalCardContent(state: {
384
+ status: "running" | "done" | "stopped" | "error" | "auto_ended";
385
+ accumulatedContent: string;
386
+ finalReply: string;
387
+ terminalError?: TerminalErrorInfo;
388
+ }): string {
389
+ const content = state.accumulatedContent + state.finalReply;
390
+ if (state.status !== "error") return content;
391
+
392
+ const error = state.terminalError ?? {
393
+ kind: "unknown" as const,
394
+ title: "原因未记录",
395
+ message: "当前状态中没有可用的错误详情,请查看运行日志。",
396
+ occurredAt: Date.now(),
397
+ };
398
+ const reason = formatTerminalErrorReason(error);
399
+ return content.trim()
400
+ ? `${content}\n\n${reason}\n以上内容可能不完整。`
401
+ : reason;
402
+ }
403
403
 
404
404
  function isCardKitSequenceConflict(err: unknown): boolean {
405
405
  return err instanceof Error && err.message.includes("300317");
@@ -530,11 +530,11 @@ export function resetState(): void {
530
530
  clearFeishuMessageLedgerMemory();
531
531
  lastMsgTimestamps.clear();
532
532
  chatPlatformMap.clear();
533
- for (const prompt of activePrompts.values()) {
534
- if (prompt.processMonitor) clearInterval(prompt.processMonitor);
535
- if (prompt.responseStallMonitor) clearInterval(prompt.responseStallMonitor);
536
- if (prompt.avatarRefreshTimer) clearInterval(prompt.avatarRefreshTimer);
537
- if (prompt.finalResponseCloseTimer) clearTimeout(prompt.finalResponseCloseTimer);
533
+ for (const prompt of activePrompts.values()) {
534
+ if (prompt.processMonitor) clearInterval(prompt.processMonitor);
535
+ if (prompt.responseStallMonitor) clearInterval(prompt.responseStallMonitor);
536
+ if (prompt.avatarRefreshTimer) clearInterval(prompt.avatarRefreshTimer);
537
+ if (prompt.finalResponseCloseTimer) clearTimeout(prompt.finalResponseCloseTimer);
538
538
  }
539
539
  activePrompts.clear();
540
540
  displayCards.clear();
@@ -600,7 +600,7 @@ export function getEffectiveFastModeForTool(tool: string, sessionId?: string): b
600
600
  return config.codex.fastMode;
601
601
  }
602
602
 
603
- function setSessionChatAvatar(
603
+ function setSessionChatAvatar(
604
604
  platform: PlatformAdapter,
605
605
  chatId: string,
606
606
  tool: string,
@@ -609,59 +609,59 @@ function setSessionChatAvatar(
609
609
  ): Promise<void> {
610
610
  return getEffectiveFastModeForTool(tool, sessionId)
611
611
  ? platform.setChatAvatar(chatId, tool, status, { fastMode: true })
612
- : platform.setChatAvatar(chatId, tool, status);
613
- }
614
-
615
- async function refreshBusySessionAvatar(
616
- sessionId: string,
617
- tool: string,
618
- fallbackPlatform: PlatformAdapter,
619
- ): Promise<void> {
620
- const chatId = pickDisplayChat(sessionId) ?? getLastActiveChat(sessionId) ?? getChatsForSession(sessionId)[0];
621
- if (!chatId) return;
622
- const platform = platformForChat(chatId) ?? fallbackPlatform;
623
- await setSessionChatAvatar(platform, chatId, tool, "busy", sessionId);
624
- }
625
-
626
- function startPromptAvatarRefresh(
627
- sessionId: string,
628
- tool: string,
629
- fallbackPlatform: PlatformAdapter,
630
- runningPrompt: NonNullable<ReturnType<typeof activePrompts.get>>,
631
- ): void {
632
- let refreshInFlight = false;
633
- const timer = setInterval(() => {
634
- const current = activePrompts.get(sessionId);
635
- if (!current || current !== runningPrompt) {
636
- clearInterval(timer);
637
- if (runningPrompt.avatarRefreshTimer === timer) {
638
- runningPrompt.avatarRefreshTimer = undefined;
639
- }
640
- return;
641
- }
642
- if (
643
- refreshInFlight
644
- || current.stopped
645
- || current.abnormalExit
646
- || current.resourceStuck
647
- || current.autoEnded
648
- || current.finalResponseObserved
649
- ) {
650
- return;
651
- }
652
-
653
- refreshInFlight = true;
654
- void refreshBusySessionAvatar(sessionId, tool, fallbackPlatform)
655
- .catch((err) => {
656
- console.warn(`[${ts()}] [AVATAR] Periodic refresh failed for ${sessionId}: ${(err as Error).message}`);
657
- })
658
- .finally(() => {
659
- refreshInFlight = false;
660
- });
661
- }, avatarRefreshIntervalMs);
662
- timer.unref?.();
663
- runningPrompt.avatarRefreshTimer = timer;
664
- }
612
+ : platform.setChatAvatar(chatId, tool, status);
613
+ }
614
+
615
+ async function refreshBusySessionAvatar(
616
+ sessionId: string,
617
+ tool: string,
618
+ fallbackPlatform: PlatformAdapter,
619
+ ): Promise<void> {
620
+ const chatId = pickDisplayChat(sessionId) ?? getLastActiveChat(sessionId) ?? getChatsForSession(sessionId)[0];
621
+ if (!chatId) return;
622
+ const platform = platformForChat(chatId) ?? fallbackPlatform;
623
+ await setSessionChatAvatar(platform, chatId, tool, "busy", sessionId);
624
+ }
625
+
626
+ function startPromptAvatarRefresh(
627
+ sessionId: string,
628
+ tool: string,
629
+ fallbackPlatform: PlatformAdapter,
630
+ runningPrompt: NonNullable<ReturnType<typeof activePrompts.get>>,
631
+ ): void {
632
+ let refreshInFlight = false;
633
+ const timer = setInterval(() => {
634
+ const current = activePrompts.get(sessionId);
635
+ if (!current || current !== runningPrompt) {
636
+ clearInterval(timer);
637
+ if (runningPrompt.avatarRefreshTimer === timer) {
638
+ runningPrompt.avatarRefreshTimer = undefined;
639
+ }
640
+ return;
641
+ }
642
+ if (
643
+ refreshInFlight
644
+ || current.stopped
645
+ || current.abnormalExit
646
+ || current.resourceStuck
647
+ || current.autoEnded
648
+ || current.finalResponseObserved
649
+ ) {
650
+ return;
651
+ }
652
+
653
+ refreshInFlight = true;
654
+ void refreshBusySessionAvatar(sessionId, tool, fallbackPlatform)
655
+ .catch((err) => {
656
+ console.warn(`[${ts()}] [AVATAR] Periodic refresh failed for ${sessionId}: ${(err as Error).message}`);
657
+ })
658
+ .finally(() => {
659
+ refreshInFlight = false;
660
+ });
661
+ }, avatarRefreshIntervalMs);
662
+ timer.unref?.();
663
+ runningPrompt.avatarRefreshTimer = timer;
664
+ }
665
665
 
666
666
  /** 为指定 session 设置模型覆盖(/model <name>) */
667
667
  export function setSessionModelOverride(sessionId: string, model: string): void {
@@ -707,13 +707,13 @@ export function getAdapterForTool(tool: string, sessionId?: string): ToolAdapter
707
707
  effort: effectiveEffort || undefined,
708
708
  fastMode: effectiveFastMode,
709
709
  });
710
- } else if (tool === "ccc") {
711
- adapter = createCccAdapter({
712
- apiKey: config.ccc.DEEPSEEK_API_KEY,
713
- baseURL: config.ccc.DEEPSEEK_BASE_URL,
714
- model: effectiveModel || undefined,
715
- effort: effectiveEffort || undefined,
716
- });
710
+ } else if (tool === "ccc") {
711
+ adapter = createCccAdapter({
712
+ apiKey: config.ccc.DEEPSEEK_API_KEY,
713
+ baseURL: config.ccc.DEEPSEEK_BASE_URL,
714
+ model: effectiveModel || undefined,
715
+ effort: effectiveEffort || undefined,
716
+ });
717
717
  } else {
718
718
  adapter = createClaudeAdapter({
719
719
  model: effectiveModel,
@@ -1019,16 +1019,16 @@ export function accumulateBlockContent(
1019
1019
  // 覆盖而非追加:适配器已保证这是一段完整最终文本(如 Cursor 流末快照)
1020
1020
  state.finalCompleteText = block.text;
1021
1021
  break;
1022
- case "compact_boundary": {
1022
+ case "compact_boundary": {
1023
1023
  const triggerLabel = block.trigger === "manual" ? "手动" : "自动"; // 手动 / 自动
1024
1024
  state.accumulatedContent +=
1025
1025
  `\n\n🔄 上下文压缩(${triggerLabel}): **${block.pre_tokens}** → **${block.post_tokens}** tokens\n`; // 🔄 / →
1026
- break;
1027
- }
1028
- case "agent_status":
1029
- break;
1030
- }
1031
- }
1026
+ break;
1027
+ }
1028
+ case "agent_status":
1029
+ break;
1030
+ }
1031
+ }
1032
1032
 
1033
1033
  // ---------------------------------------------------------------------------
1034
1034
  // switchChatBinding — /newh、/session N 共用的事务式"切换 chat 绑定"
@@ -1217,7 +1217,7 @@ export async function initClaudeSession(tool: string, overrideCwd?: string, chat
1217
1217
  return { sessionId, cwd };
1218
1218
  }
1219
1219
 
1220
- export async function resumeAndPrompt(
1220
+ export async function resumeAndPrompt(
1221
1221
  sessionId: string,
1222
1222
  userText: string,
1223
1223
  platform: PlatformAdapter,
@@ -1225,25 +1225,25 @@ export async function resumeAndPrompt(
1225
1225
  msgTimestamp: number,
1226
1226
  tool: string,
1227
1227
  traceId?: string,
1228
- ): Promise<SessionRunOutcome> {
1229
- return runAgentSession(sessionId, userText, platform, chatId, msgTimestamp, tool, traceId);
1230
- }
1228
+ ): Promise<SessionRunOutcome> {
1229
+ return runAgentSession(sessionId, userText, platform, chatId, msgTimestamp, tool, traceId);
1230
+ }
1231
1231
 
1232
1232
  // ---------------------------------------------------------------------------
1233
1233
  // runAgentSession — session 中心的 agent prompt(文件持久化 + display 解耦)
1234
1234
  // ---------------------------------------------------------------------------
1235
1235
 
1236
- interface RunAgentSessionOptions {
1236
+ interface RunAgentSessionOptions {
1237
1237
  /**
1238
1238
  * 标记本轮是 response-stall 后的唯一一次内部续跑。若本轮再次因相同原因
1239
1239
  * 停滞,不再递归创建第三轮,避免服务异常期间无限消耗 token。
1240
1240
  */
1241
1241
  autoRecovery?: boolean;
1242
- }
1242
+ }
1243
+
1244
+ export type SessionRunOutcome = "busy" | "done" | "stopped" | "error" | "auto_ended";
1243
1245
 
1244
- export type SessionRunOutcome = "busy" | "done" | "stopped" | "error" | "auto_ended";
1245
-
1246
- export async function runAgentSession(
1246
+ export async function runAgentSession(
1247
1247
  sessionId: string,
1248
1248
  userText: string,
1249
1249
  platform: PlatformAdapter,
@@ -1252,7 +1252,7 @@ export async function runAgentSession(
1252
1252
  tool: string,
1253
1253
  traceId?: string,
1254
1254
  options: RunAgentSessionOptions = {},
1255
- ): Promise<SessionRunOutcome> {
1255
+ ): Promise<SessionRunOutcome> {
1256
1256
  const tid = traceId ?? "";
1257
1257
 
1258
1258
  // runAgentSession 是飞书用户消息、队列消息与内部自动恢复共同使用的唯一
@@ -1279,7 +1279,7 @@ export async function runAgentSession(
1279
1279
  ? "当前正在生成回复中,请等待完成后再发送消息。也可以发送 /stop 结束,已完成的步骤不会丢失。"
1280
1280
  : "该会话正在生成回复中,请等待完成后再发送消息。也可以发送 /stop 结束,已完成的步骤不会丢失。";
1281
1281
  await platform.sendText(_chatId, busyMsg).catch(() => {});
1282
- return "busy";
1282
+ return "busy";
1283
1283
  }
1284
1284
 
1285
1285
  // 立即标记活跃,确保 /sessions、isSessionRunning 等查询在异步准备阶段就能看到运行状态。
@@ -1407,7 +1407,7 @@ export async function runAgentSession(
1407
1407
  // 再开始缓存问题对应的任务"。
1408
1408
  const prevState = await readStreamState(sessionId);
1409
1409
  if (prevState && prevState.status !== "running") {
1410
- const prevTerminalReply = formatTerminalReply(prevState.status, prevState.finalReply, prevState.terminalError);
1410
+ const prevTerminalReply = formatTerminalReply(prevState.status, prevState.finalReply, prevState.terminalError);
1411
1411
  const displayChatId = pickDisplayChat(sessionId);
1412
1412
  if (displayChatId) {
1413
1413
  const pp = platformForChat(displayChatId);
@@ -1426,8 +1426,8 @@ export async function runAgentSession(
1426
1426
  setSessionChatAvatar(pp, displayChatId, prevState.tool, "idle", sessionId).catch(() => {});
1427
1427
  } else {
1428
1428
  const nextSeq = display.sequence + 1;
1429
- const { title: headerTitle, template: headerTemplate } = formatTerminalHeader(prevState.status, prevState.terminalError);
1430
- const cardContent = truncateContent(formatTerminalCardContent(prevState)) || " ";
1429
+ const { title: headerTitle, template: headerTemplate } = formatTerminalHeader(prevState.status, prevState.terminalError);
1430
+ const cardContent = truncateContent(formatTerminalCardContent(prevState)) || " ";
1431
1431
  const doneCard = buildProgressCard(progressView({ text: cardContent, status: "done", showStop: false, headerTitle, headerTemplate }));
1432
1432
  await pp.cardUpdate(display.cardId, doneCard, nextSeq).catch(err => {
1433
1433
  console.error(`[${ts()}] [DISPLAY] prevState final cardUpdate failed: ${(err as Error).message}`);
@@ -1506,7 +1506,7 @@ export async function runAgentSession(
1506
1506
  }
1507
1507
 
1508
1508
  // 设置最后活跃群头像为 busy
1509
- refreshBusySessionAvatar(sessionId, tool, platform).catch(() => {});
1509
+ refreshBusySessionAvatar(sessionId, tool, platform).catch(() => {});
1510
1510
 
1511
1511
  const state: AccumulatorState = {
1512
1512
  accumulatedContent: "",
@@ -1517,23 +1517,23 @@ export async function runAgentSession(
1517
1517
 
1518
1518
  let lastFileWrite = Date.now();
1519
1519
  const FILE_WRITE_INTERVAL_MS = 2000;
1520
- const toolCallMap = new Map<string, { name: string; input: unknown }>();
1521
- let streamErrored = false;
1522
- let streamTerminalError: TerminalErrorInfo | undefined;
1523
- let runOutcome: SessionRunOutcome = "error";
1524
-
1525
- const runningPrompt = activePrompts.get(sessionId);
1526
- if (runningPrompt) {
1527
- startPromptAvatarRefresh(sessionId, tool, platform, runningPrompt);
1528
-
1529
- // 在消费第一个事件前建立阶段感知的零字符基线;启动阶段不计时,只有后续
1530
- // 收到明确的 responding 状态后才会启动三分钟回复停滞保护。
1531
- runningPrompt.responseProgress = observeResponseProgress(
1532
- undefined,
1533
- monitorsOutputProgress(activityTracker.activity.kind),
1534
- 0,
1535
- activityTracker.activity.startedAt,
1536
- );
1520
+ const toolCallMap = new Map<string, { name: string; input: unknown }>();
1521
+ let streamErrored = false;
1522
+ let streamTerminalError: TerminalErrorInfo | undefined;
1523
+ let runOutcome: SessionRunOutcome = "error";
1524
+
1525
+ const runningPrompt = activePrompts.get(sessionId);
1526
+ if (runningPrompt) {
1527
+ startPromptAvatarRefresh(sessionId, tool, platform, runningPrompt);
1528
+
1529
+ // 在消费第一个事件前建立阶段感知的零字符基线;启动阶段不计时,只有后续
1530
+ // 收到明确的 responding 状态后才会启动三分钟回复停滞保护。
1531
+ runningPrompt.responseProgress = observeResponseProgress(
1532
+ undefined,
1533
+ monitorsOutputProgress(activityTracker.activity.kind),
1534
+ 0,
1535
+ activityTracker.activity.startedAt,
1536
+ );
1537
1537
 
1538
1538
  const checkResponseStall = async () => {
1539
1539
  const current = activePrompts.get(sessionId);
@@ -1603,7 +1603,7 @@ export async function runAgentSession(
1603
1603
  current.controller.abort();
1604
1604
  await killProcessTree(current.processPid);
1605
1605
  console.warn(
1606
- `[${ts()}] [RESPONSE-STALL] Session ${sessionId} auto-ended after 3 minutes without reply progress`,
1606
+ `[${ts()}] [RESPONSE-STALL] Session ${sessionId} auto-ended after 3 minutes without reply progress`,
1607
1607
  );
1608
1608
  };
1609
1609
 
@@ -1695,10 +1695,10 @@ export async function runAgentSession(
1695
1695
  });
1696
1696
  }
1697
1697
  }
1698
- } catch (streamErr) {
1699
- streamErrored = true;
1700
- streamTerminalError = classifyTerminalError(streamErr);
1701
- console.error(`[${ts()}] [STREAM] Error in stream loop for ${sessionId}: ${(streamErr as Error).message}`);
1698
+ } catch (streamErr) {
1699
+ streamErrored = true;
1700
+ streamTerminalError = classifyTerminalError(streamErr);
1701
+ console.error(`[${ts()}] [STREAM] Error in stream loop for ${sessionId}: ${(streamErr as Error).message}`);
1702
1702
  } finally {
1703
1703
  // 标记 prompt 结束
1704
1704
  resourceMonitor.off("stuck", onResourceStuck);
@@ -1712,10 +1712,10 @@ export async function runAgentSession(
1712
1712
  const wasAutoEnded = timeoutTriggered && !completedAtTimeoutBoundary;
1713
1713
  const wasAutoRecovery = prompt?.autoRecovery ?? false;
1714
1714
  const autoEndedAt = wasAutoEnded ? prompt?.autoEndedAt : undefined;
1715
- clearPromptResponseStallMonitor(sessionId);
1716
- clearPromptProcessMonitor(sessionId);
1717
- clearPromptAvatarRefreshTimer(sessionId);
1718
- clearPromptFinalResponseCloseTimer(sessionId);
1715
+ clearPromptResponseStallMonitor(sessionId);
1716
+ clearPromptProcessMonitor(sessionId);
1717
+ clearPromptAvatarRefreshTimer(sessionId);
1718
+ clearPromptFinalResponseCloseTimer(sessionId);
1719
1719
  markSessionFinalizing(sessionId);
1720
1720
  activePrompts.delete(sessionId);
1721
1721
 
@@ -1747,24 +1747,24 @@ export async function runAgentSession(
1747
1747
  : wasStopped
1748
1748
  ? "stopped"
1749
1749
  : "done";
1750
- const finalReply = pickFinalReply(state).trim();
1751
- const terminalError = streamTerminalError
1752
- ?? (wasAbnormalExit
1753
- ? {
1754
- kind: "process" as const,
1755
- title: "Agent 进程意外退出",
1756
- message: "Agent CLI 进程已退出。若回复不完整,请重新发送上一条指令。",
1757
- occurredAt: Date.now(),
1758
- }
1759
- : wasResourceStuck
1760
- ? {
1761
- kind: "resource" as const,
1762
- title: "Agent 进程失去响应",
1763
- message: "Agent 进程长时间没有 CPU 或内存变化,已被强制停止。若回复不完整,请重新发送上一条指令。",
1764
- occurredAt: Date.now(),
1765
- }
1766
- : undefined);
1767
- runOutcome = finalStatus;
1750
+ const finalReply = pickFinalReply(state).trim();
1751
+ const terminalError = streamTerminalError
1752
+ ?? (wasAbnormalExit
1753
+ ? {
1754
+ kind: "process" as const,
1755
+ title: "Agent 进程意外退出",
1756
+ message: "Agent CLI 进程已退出。若回复不完整,请重新发送上一条指令。",
1757
+ occurredAt: Date.now(),
1758
+ }
1759
+ : wasResourceStuck
1760
+ ? {
1761
+ kind: "resource" as const,
1762
+ title: "Agent 进程失去响应",
1763
+ message: "Agent 进程长时间没有 CPU 或内存变化,已被强制停止。若回复不完整,请重新发送上一条指令。",
1764
+ occurredAt: Date.now(),
1765
+ }
1766
+ : undefined);
1767
+ runOutcome = finalStatus;
1768
1768
 
1769
1769
  // stop-stuck-loop 接口可能在 fire-and-forget 中已写入带 final_reply 的
1770
1770
  // stream state,finally 不应覆盖它。同时保留 stuckAt 标记,防止
@@ -1794,9 +1794,9 @@ export async function runAgentSession(
1794
1794
  cwd,
1795
1795
  tool,
1796
1796
  ...(preserveStuckAt ? { stuckAt: preserveStuckAt } : {}),
1797
- ...(autoEndedAt !== undefined ? { autoEndedAt } : {}),
1798
- ...(terminalError ? { terminalError } : {}),
1799
- });
1797
+ ...(autoEndedAt !== undefined ? { autoEndedAt } : {}),
1798
+ ...(terminalError ? { terminalError } : {}),
1799
+ });
1800
1800
 
1801
1801
  // display loop 下一轮会读到最终状态并发送消息
1802
1802
 
@@ -1867,9 +1867,9 @@ export async function runAgentSession(
1867
1867
  autoRecoveryTarget = { chatId: activeAutoEnded, platform: pp };
1868
1868
  }
1869
1869
  }
1870
- console.warn(`[${ts()}] Session ${sessionId} auto-ended after stalled response output (content chunks: ${state.chunkCount})`);
1870
+ console.warn(`[${ts()}] Session ${sessionId} auto-ended after stalled response output (content chunks: ${state.chunkCount})`);
1871
1871
  if (tid) logTrace(tid, "SESSION_END", { sessionId, outcome: "response_stall", chunks: state.chunkCount });
1872
- } else if (wasAbnormalExit) {
1872
+ } else if (wasAbnormalExit) {
1873
1873
  for (const cid of finalizationChatIds) {
1874
1874
  const finfo = sessionInfoMap.get(cid);
1875
1875
  await recordSessionRegistry({
@@ -1884,55 +1884,55 @@ export async function runAgentSession(
1884
1884
  }
1885
1885
  const activeErr = getLastActiveChat(sessionId) ?? finalizationChatIds[0];
1886
1886
  if (activeErr) setSessionChatAvatar(platform, activeErr, tool, "idle", sessionId).catch(() => {});
1887
- console.log(`[${ts()}] Session ${sessionId} process exited unexpectedly (content chunks: ${state.chunkCount})`);
1888
- if (tid) logTrace(tid, "SESSION_END", { sessionId, outcome: "process_missing", chunks: state.chunkCount });
1889
- } else if (streamErrored || wasResourceStuck) {
1890
- for (const cid of finalizationChatIds) {
1891
- const finfo = sessionInfoMap.get(cid);
1892
- await recordSessionRegistry({
1893
- chatId: cid,
1894
- sessionId,
1895
- tool,
1896
- turnCount: finfo?.turnCount ?? nextTurnCount,
1897
- lastContextTokens: finfo?.lastContextTokens ?? nextContextTokens,
1898
- startTime: finfo?.startTime ?? now,
1899
- running: false,
1900
- });
1901
- }
1902
- const activeErr = getLastActiveChat(sessionId) ?? finalizationChatIds[0];
1903
- if (activeErr) {
1904
- const pp = platformForChat(activeErr) ?? platform;
1905
- const terminalState = await readStreamState(sessionId);
1906
- if (
1907
- terminalError
1908
- && !displayCards.has(activeErr)
1909
- && (!terminalState || !isFinalReplySentForTurn(terminalState))
1910
- ) {
1911
- await sendFinalReplyTextOnce(
1912
- pp,
1913
- activeErr,
1914
- sessionId,
1915
- nextTurnCount,
1916
- formatTerminalErrorNotice(terminalError, finalReplyToWrite),
1917
- );
1918
- }
1919
- setSessionChatAvatar(pp, activeErr, tool, "idle", sessionId).catch(() => {});
1920
- }
1921
- const errorOutcome = wasResourceStuck ? "resource_stuck" : "stream_error";
1922
- console.error(
1923
- `[${ts()}] Session ${sessionId} ended with ${errorOutcome}` +
1924
- `${terminalError ? ` (${terminalError.kind}: ${terminalError.title})` : ""} (content chunks: ${state.chunkCount})`,
1925
- );
1926
- if (tid) {
1927
- logTrace(tid, "SESSION_END", {
1928
- sessionId,
1929
- outcome: errorOutcome,
1930
- errorKind: terminalError?.kind,
1931
- errorTitle: terminalError?.title,
1932
- chunks: state.chunkCount,
1933
- });
1934
- }
1935
- } else {
1887
+ console.log(`[${ts()}] Session ${sessionId} process exited unexpectedly (content chunks: ${state.chunkCount})`);
1888
+ if (tid) logTrace(tid, "SESSION_END", { sessionId, outcome: "process_missing", chunks: state.chunkCount });
1889
+ } else if (streamErrored || wasResourceStuck) {
1890
+ for (const cid of finalizationChatIds) {
1891
+ const finfo = sessionInfoMap.get(cid);
1892
+ await recordSessionRegistry({
1893
+ chatId: cid,
1894
+ sessionId,
1895
+ tool,
1896
+ turnCount: finfo?.turnCount ?? nextTurnCount,
1897
+ lastContextTokens: finfo?.lastContextTokens ?? nextContextTokens,
1898
+ startTime: finfo?.startTime ?? now,
1899
+ running: false,
1900
+ });
1901
+ }
1902
+ const activeErr = getLastActiveChat(sessionId) ?? finalizationChatIds[0];
1903
+ if (activeErr) {
1904
+ const pp = platformForChat(activeErr) ?? platform;
1905
+ const terminalState = await readStreamState(sessionId);
1906
+ if (
1907
+ terminalError
1908
+ && !displayCards.has(activeErr)
1909
+ && (!terminalState || !isFinalReplySentForTurn(terminalState))
1910
+ ) {
1911
+ await sendFinalReplyTextOnce(
1912
+ pp,
1913
+ activeErr,
1914
+ sessionId,
1915
+ nextTurnCount,
1916
+ formatTerminalErrorNotice(terminalError, finalReplyToWrite),
1917
+ );
1918
+ }
1919
+ setSessionChatAvatar(pp, activeErr, tool, "idle", sessionId).catch(() => {});
1920
+ }
1921
+ const errorOutcome = wasResourceStuck ? "resource_stuck" : "stream_error";
1922
+ console.error(
1923
+ `[${ts()}] Session ${sessionId} ended with ${errorOutcome}` +
1924
+ `${terminalError ? ` (${terminalError.kind}: ${terminalError.title})` : ""} (content chunks: ${state.chunkCount})`,
1925
+ );
1926
+ if (tid) {
1927
+ logTrace(tid, "SESSION_END", {
1928
+ sessionId,
1929
+ outcome: errorOutcome,
1930
+ errorKind: terminalError?.kind,
1931
+ errorTitle: terminalError?.title,
1932
+ chunks: state.chunkCount,
1933
+ });
1934
+ }
1935
+ } else {
1936
1936
  for (const cid of finalizationChatIds) {
1937
1937
  const finfo = sessionInfoMap.get(cid);
1938
1938
  await recordSessionRegistry({
@@ -2044,9 +2044,9 @@ export async function runAgentSession(
2044
2044
  } finally {
2045
2045
  clearSessionFinalizing(sessionId);
2046
2046
  }
2047
- }
2048
- return runOutcome;
2049
- }
2047
+ }
2048
+ return runOutcome;
2049
+ }
2050
2050
 
2051
2051
  // ---------------------------------------------------------------------------
2052
2052
  // startUnifiedDisplayLoop — 全局统一 display 循环,遍历 displayCards 更新卡片
@@ -2123,13 +2123,13 @@ export function startUnifiedDisplayLoop(): void {
2123
2123
  if (activePrompts.has(sessionId)) continue;
2124
2124
 
2125
2125
  const tail = "━━━ 回答结束 ━━━";
2126
- const finalMsg = state.status === "auto_ended"
2127
- ? formatAutoEndedReply(remaining)
2128
- : state.status === "error"
2129
- ? formatTerminalReply(state.status, remaining, state.terminalError) ?? tail
2130
- : remaining
2131
- ? remaining + "\n" + tail
2132
- : tail;
2126
+ const finalMsg = state.status === "auto_ended"
2127
+ ? formatAutoEndedReply(remaining)
2128
+ : state.status === "error"
2129
+ ? formatTerminalReply(state.status, remaining, state.terminalError) ?? tail
2130
+ : remaining
2131
+ ? remaining + "\n" + tail
2132
+ : tail;
2133
2133
  if (!isFinalReplySentForTurn(state)) {
2134
2134
  await sendFinalReplyTextOnce(p, chatId, sessionId, state.turnCount, finalMsg);
2135
2135
  }
@@ -2151,8 +2151,8 @@ export function startUnifiedDisplayLoop(): void {
2151
2151
  let terminalCardUpdateAccepted = terminalCardAlreadyUpdated;
2152
2152
  if (!terminalCardAlreadyUpdated) {
2153
2153
  const nextSeq = display.sequence + 1;
2154
- const { title: headerTitle, template: headerTemplate } = formatTerminalHeader(state.status, state.terminalError);
2155
- const cardContent = truncateContent(formatTerminalCardContent(state)) || " ";
2154
+ const { title: headerTitle, template: headerTemplate } = formatTerminalHeader(state.status, state.terminalError);
2155
+ const cardContent = truncateContent(formatTerminalCardContent(state)) || " ";
2156
2156
  const doneCard = buildProgressCard(progressView({ text: cardContent, status: "done", showStop: false, headerTitle, headerTemplate }));
2157
2157
  await p.cardUpdate(display.cardId, doneCard, nextSeq).then(() => {
2158
2158
  display.sequence = nextSeq;
@@ -2179,19 +2179,19 @@ export function startUnifiedDisplayLoop(): void {
2179
2179
  }
2180
2180
 
2181
2181
  let terminalTextDelivered = true;
2182
- const terminalReply = formatTerminalReply(state.status, state.finalReply, state.terminalError);
2183
- const errorWasDeliveredByCard =
2184
- state.status === "error"
2185
- && !state.finalReply.trim()
2186
- && terminalCardUpdateAccepted;
2187
- if (errorWasDeliveredByCard) {
2188
- if (!isFinalReplySentForTurn(state)) {
2189
- await markFinalReplySent(sessionId, state.turnCount);
2190
- }
2191
- } else if (terminalReply) {
2192
- if (!isFinalReplySentForTurn(state)) {
2193
- terminalTextDelivered = await sendFinalReplyTextOnce(p, chatId, sessionId, state.turnCount, terminalReply);
2194
- }
2182
+ const terminalReply = formatTerminalReply(state.status, state.finalReply, state.terminalError);
2183
+ const errorWasDeliveredByCard =
2184
+ state.status === "error"
2185
+ && !state.finalReply.trim()
2186
+ && terminalCardUpdateAccepted;
2187
+ if (errorWasDeliveredByCard) {
2188
+ if (!isFinalReplySentForTurn(state)) {
2189
+ await markFinalReplySent(sessionId, state.turnCount);
2190
+ }
2191
+ } else if (terminalReply) {
2192
+ if (!isFinalReplySentForTurn(state)) {
2193
+ terminalTextDelivered = await sendFinalReplyTextOnce(p, chatId, sessionId, state.turnCount, terminalReply);
2194
+ }
2195
2195
  } else if (state.accumulatedContent.trim()) {
2196
2196
  const short = truncateContent(state.accumulatedContent, 30, 4000);
2197
2197
  terminalTextDelivered = await p.sendText(chatId, `[生成过程]\n${short}`).then((ok) => ok !== false).catch(() => false);
@@ -2422,11 +2422,11 @@ export function stopSession(sessionId: string): boolean {
2422
2422
  }
2423
2423
  return false;
2424
2424
  }
2425
- prompt.stopped = true;
2426
- clearPromptResponseStallMonitor(sessionId);
2427
- clearPromptProcessMonitor(sessionId);
2428
- clearPromptAvatarRefreshTimer(sessionId);
2429
- clearPromptFinalResponseCloseTimer(sessionId);
2425
+ prompt.stopped = true;
2426
+ clearPromptResponseStallMonitor(sessionId);
2427
+ clearPromptProcessMonitor(sessionId);
2428
+ clearPromptAvatarRefreshTimer(sessionId);
2429
+ clearPromptFinalResponseCloseTimer(sessionId);
2430
2430
  cancelQueuedMessage(sessionId);
2431
2431
 
2432
2432
  // 先发起整棵进程树清理,再触发 close/abort。Windows 上 CLI 由