chatccc 0.2.231 → 0.2.233

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 (130) hide show
  1. package/.agents/skills/create-chatccc-feishu-app/SKILL.md +85 -85
  2. package/.claude/skills/create-chatccc-feishu-app/SKILL.md +85 -85
  3. package/.cursor/skills/create-chatccc-feishu-app/SKILL.md +85 -85
  4. package/README.md +90 -90
  5. package/agent-prompts/claude_specific.md +45 -45
  6. package/agent-prompts/codex_specific.md +2 -2
  7. package/agent-prompts/cursor_specific.md +13 -13
  8. package/bin/cccagent.mjs +17 -17
  9. package/im-skills/feishu-skill/receive-send-file.md +63 -63
  10. package/im-skills/feishu-skill/receive-send-image.md +24 -24
  11. package/im-skills/feishu-skill/skill.md +3 -3
  12. package/im-skills/wechat-file-skill/receive-send-file.md +38 -38
  13. package/im-skills/wechat-file-skill/send-file.mjs +83 -83
  14. package/im-skills/wechat-file-skill/skill.md +10 -10
  15. package/im-skills/wechat-image-skill/skill.md +10 -10
  16. package/im-skills/wechat-video-skill/receive-send-video.md +38 -38
  17. package/im-skills/wechat-video-skill/send-video.mjs +79 -79
  18. package/im-skills/wechat-video-skill/skill.md +10 -10
  19. package/package.json +1 -1
  20. package/scripts/postinstall-sharp-check.mjs +58 -58
  21. package/src/__tests__/agent-activity.test.ts +86 -86
  22. package/src/__tests__/agent-delegate-task-rpc.test.ts +165 -165
  23. package/src/__tests__/agent-reload-config-rpc.test.ts +99 -99
  24. package/src/__tests__/builtin-chat-session.test.ts +449 -393
  25. package/src/__tests__/builtin-cli-json.test.ts +39 -39
  26. package/src/__tests__/builtin-config.test.ts +26 -26
  27. package/src/__tests__/builtin-context.test.ts +319 -202
  28. package/src/__tests__/builtin-file-tools.test.ts +240 -240
  29. package/src/__tests__/builtin-permissions.test.ts +211 -211
  30. package/src/__tests__/builtin-session-search.test.ts +228 -0
  31. package/src/__tests__/builtin-session-select.test.ts +116 -116
  32. package/src/__tests__/builtin-sigint.test.ts +56 -56
  33. package/src/__tests__/builtin-skills.test.ts +284 -284
  34. package/src/__tests__/builtin-web-tools.test.ts +220 -220
  35. package/src/__tests__/card-action-routing.test.ts +18 -18
  36. package/src/__tests__/cardkit.test.ts +60 -60
  37. package/src/__tests__/ccc-adapter.test.ts +170 -170
  38. package/src/__tests__/chatgpt-subscription-rpc.test.ts +89 -89
  39. package/src/__tests__/chatgpt-subscription.test.ts +135 -135
  40. package/src/__tests__/chrome-devtools-guard.test.ts +165 -165
  41. package/src/__tests__/claude-adapter.test.ts +614 -614
  42. package/src/__tests__/claude-raw-stream-log.test.ts +87 -87
  43. package/src/__tests__/codex-adapter.test.ts +58 -58
  44. package/src/__tests__/codex-raw-stream-log.test.ts +170 -170
  45. package/src/__tests__/codex-reset-actions.test.ts +146 -146
  46. package/src/__tests__/crash-logging.test.ts +95 -1
  47. package/src/__tests__/cursor-adapter.test.ts +268 -268
  48. package/src/__tests__/feishu-api.test.ts +60 -60
  49. package/src/__tests__/feishu-avatar.test.ts +164 -164
  50. package/src/__tests__/feishu-message-ingress.test.ts +138 -138
  51. package/src/__tests__/feishu-platform.test.ts +22 -22
  52. package/src/__tests__/format-message.test.ts +47 -47
  53. package/src/__tests__/jsonl-stream.test.ts +79 -79
  54. package/src/__tests__/package-files.test.ts +24 -24
  55. package/src/__tests__/privacy.test.ts +198 -198
  56. package/src/__tests__/progress-reducer.test.ts +121 -121
  57. package/src/__tests__/raw-stream-log.test.ts +106 -106
  58. package/src/__tests__/response-stall.test.ts +49 -49
  59. package/src/__tests__/restart.test.ts +55 -10
  60. package/src/__tests__/session.test.ts +199 -199
  61. package/src/__tests__/shared-prefix.test.ts +36 -36
  62. package/src/__tests__/sim-platform.test.ts +16 -16
  63. package/src/__tests__/startup-lifecycle.test.ts +231 -231
  64. package/src/__tests__/stop-session.test.ts +34 -34
  65. package/src/__tests__/stream-state.test.ts +42 -42
  66. package/src/__tests__/terminal-error.test.ts +54 -54
  67. package/src/__tests__/terminal-renderer.test.ts +247 -247
  68. package/src/__tests__/update-command-guard.test.ts +144 -144
  69. package/src/__tests__/web-ui.test.ts +326 -326
  70. package/src/adapters/adapter-interface.ts +33 -33
  71. package/src/adapters/ccc-adapter.ts +141 -141
  72. package/src/adapters/claude-adapter.ts +620 -620
  73. package/src/adapters/claude-session-meta-store.ts +120 -120
  74. package/src/adapters/codex-adapter.ts +426 -426
  75. package/src/adapters/cursor-adapter.ts +681 -681
  76. package/src/adapters/jsonl-stream.ts +157 -157
  77. package/src/adapters/raw-stream-log.ts +124 -124
  78. package/src/adapters/resource-monitor.ts +140 -140
  79. package/src/agent-activity.ts +175 -175
  80. package/src/agent-delegate-task-rpc.ts +153 -153
  81. package/src/agent-delegate-task.ts +91 -91
  82. package/src/agent-reload-config-rpc.ts +34 -34
  83. package/src/agent-stop-stuck.ts +129 -129
  84. package/src/builtin/cli.ts +6 -0
  85. package/src/builtin/config.ts +84 -84
  86. package/src/builtin/context.ts +465 -391
  87. package/src/builtin/file-log.ts +38 -38
  88. package/src/builtin/file-tools.ts +34 -0
  89. package/src/builtin/index.ts +24 -23
  90. package/src/builtin/proc-tree-kill.ts +61 -61
  91. package/src/builtin/progress/cards-helpers.ts +76 -76
  92. package/src/builtin/progress/reducer.ts +113 -113
  93. package/src/builtin/progress/terminal-renderer.ts +294 -294
  94. package/src/builtin/progress/view.ts +77 -77
  95. package/src/builtin/raw-stream-log.ts +124 -124
  96. package/src/builtin/session-search.ts +363 -0
  97. package/src/builtin/session-select.ts +48 -48
  98. package/src/builtin/sigint.ts +50 -50
  99. package/src/builtin/skills.ts +205 -205
  100. package/src/builtin/web-tools.ts +313 -313
  101. package/src/card-action-routing.ts +14 -14
  102. package/src/chatgpt-subscription-rpc.ts +27 -27
  103. package/src/chatgpt-subscription.ts +299 -299
  104. package/src/chrome-devtools-guard.ts +318 -318
  105. package/src/codex-reset-actions.ts +184 -184
  106. package/src/feishu-api.ts +193 -193
  107. package/src/feishu-message-ingress.ts +195 -195
  108. package/src/feishu-platform.ts +20 -20
  109. package/src/format-message.ts +293 -293
  110. package/src/index.ts +6 -1
  111. package/src/litellm-proxy.ts +374 -374
  112. package/src/orchestrator.ts +36 -13
  113. package/src/platform-adapter.ts +6 -6
  114. package/src/privacy.ts +118 -118
  115. package/src/progress/reducer.ts +113 -113
  116. package/src/progress/terminal-renderer.ts +294 -294
  117. package/src/progress/view.ts +77 -77
  118. package/src/response-stall.ts +28 -28
  119. package/src/runtime-reload.ts +34 -34
  120. package/src/session-chat-binding.ts +82 -82
  121. package/src/session-name.ts +8 -8
  122. package/src/session.ts +201 -201
  123. package/src/shared-prefix.ts +29 -29
  124. package/src/shared.ts +44 -0
  125. package/src/sim-platform.ts +20 -20
  126. package/src/startup-lifecycle.ts +250 -250
  127. package/src/stream-state.ts +21 -21
  128. package/src/terminal-error.ts +129 -129
  129. package/src/turn-cards.ts +117 -117
  130. package/src/update-command-guard.ts +165 -165
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,
@@ -181,8 +181,8 @@ const DEFAULT_FINAL_RESPONSE_CLOSE_TIMEOUT_MS = 10_000;
181
181
  export const RESPONSE_STALL_RECOVERY_PROMPT = "完成了吗?如果没完成继续";
182
182
  export const RESPONSE_STALL_RECOVERY_NOTICE =
183
183
  `检测到会话停滞,正在自动确认并继续。\n\n${RESPONSE_STALL_RECOVERY_PROMPT}`;
184
- export const RESPONSE_STALL_RECOVERY_EXHAUSTED_NOTICE =
185
- "⚠️ 自动续跑仍连续 3 分钟没有生成新回复,本次不再自动继续。";
184
+ export const RESPONSE_STALL_RECOVERY_EXHAUSTED_NOTICE =
185
+ "⚠️ 自动续跑仍连续 3 分钟没有生成新回复,本次不再自动继续。";
186
186
  const RESPONSE_STALL_RECOVERY_DELAY_MS = 200;
187
187
  let processMonitorIntervalMs = DEFAULT_PROCESS_MONITOR_INTERVAL_MS;
188
188
  let responseStallTimeoutMs = DEFAULT_RESPONSE_STALL_TIMEOUT_MS;
@@ -311,18 +311,18 @@ function scheduleFinalResponseCloseGuard(
311
311
  runningPrompt.finalResponseCloseTimer = handle;
312
312
  }
313
313
 
314
- function formatTerminalHeader(
315
- status: "running" | "done" | "stopped" | "error" | "auto_ended",
316
- terminalError?: TerminalErrorInfo,
317
- ): {
314
+ function formatTerminalHeader(
315
+ status: "running" | "done" | "stopped" | "error" | "auto_ended",
316
+ terminalError?: TerminalErrorInfo,
317
+ ): {
318
318
  title: string;
319
319
  template?: string;
320
320
  } {
321
321
  if (status === "auto_ended") return { title: "已自动结束 · 3分钟无新内容", template: "orange" };
322
322
  if (status === "stopped") return { title: "已停止", template: "red" };
323
- if (status === "error") {
324
- return { title: terminalError ? `异常结束 · ${terminalError.title}` : "异常结束", template: "red" };
325
- }
323
+ if (status === "error") {
324
+ return { title: terminalError ? `异常结束 · ${terminalError.title}` : "异常结束", template: "red" };
325
+ }
326
326
  return { title: "完成" };
327
327
  }
328
328
 
@@ -330,59 +330,59 @@ function turnFinalStatus(status: "running" | "done" | "stopped" | "error" | "aut
330
330
  return status === "stopped" || status === "error" || status === "auto_ended" ? "stopped" : "done";
331
331
  }
332
332
 
333
- function formatAutoEndedReply(finalReply: string): string {
334
- const reason = "⚠️ 已自动结束:生成回复阶段连续 3 分钟没有字符变化。";
333
+ function formatAutoEndedReply(finalReply: string): string {
334
+ const reason = "⚠️ 已自动结束:生成回复阶段连续 3 分钟没有字符变化。";
335
335
  return finalReply
336
336
  ? `${reason}以下回复可能不完整。\n\n${finalReply}`
337
337
  : `${reason}本轮没有可发送的回复内容。`;
338
338
  }
339
339
 
340
340
  /**
341
- * 只监控明确的回复生成阶段。启动、压缩、思考、工具调用和搜索都有各自的
342
- * 状态或资源保护,不能把它们消耗的时间算入回复停滞窗口。
341
+ * 只监控明确的回复生成阶段。启动、压缩、思考、工具调用和搜索都有各自的
342
+ * 状态或资源保护,不能把它们消耗的时间算入回复停滞窗口。
343
343
  */
344
- function monitorsOutputProgress(kind: AgentActivityKind): boolean {
345
- return kind === "responding";
346
- }
347
-
348
- function formatTerminalReply(
349
- status: "running" | "done" | "stopped" | "error" | "auto_ended",
350
- finalReply: string,
351
- terminalError?: TerminalErrorInfo,
352
- ): string | null {
353
- if (status === "auto_ended") return formatAutoEndedReply(finalReply);
354
- if (status === "error") {
355
- const error = terminalError ?? {
356
- kind: "unknown" as const,
357
- title: "原因未记录",
358
- message: "当前状态中没有可用的错误详情,请查看运行日志。",
359
- occurredAt: Date.now(),
360
- };
361
- return formatTerminalErrorNotice(error, finalReply);
362
- }
363
- return finalReply || null;
364
- }
365
-
366
- function formatTerminalCardContent(state: {
367
- status: "running" | "done" | "stopped" | "error" | "auto_ended";
368
- accumulatedContent: string;
369
- finalReply: string;
370
- terminalError?: TerminalErrorInfo;
371
- }): string {
372
- const content = state.accumulatedContent + state.finalReply;
373
- if (state.status !== "error") return content;
374
-
375
- const error = state.terminalError ?? {
376
- kind: "unknown" as const,
377
- title: "原因未记录",
378
- message: "当前状态中没有可用的错误详情,请查看运行日志。",
379
- occurredAt: Date.now(),
380
- };
381
- const reason = formatTerminalErrorReason(error);
382
- return content.trim()
383
- ? `${content}\n\n${reason}\n以上内容可能不完整。`
384
- : reason;
385
- }
344
+ function monitorsOutputProgress(kind: AgentActivityKind): boolean {
345
+ return kind === "responding";
346
+ }
347
+
348
+ function formatTerminalReply(
349
+ status: "running" | "done" | "stopped" | "error" | "auto_ended",
350
+ finalReply: string,
351
+ terminalError?: TerminalErrorInfo,
352
+ ): string | null {
353
+ if (status === "auto_ended") return formatAutoEndedReply(finalReply);
354
+ if (status === "error") {
355
+ const error = terminalError ?? {
356
+ kind: "unknown" as const,
357
+ title: "原因未记录",
358
+ message: "当前状态中没有可用的错误详情,请查看运行日志。",
359
+ occurredAt: Date.now(),
360
+ };
361
+ return formatTerminalErrorNotice(error, finalReply);
362
+ }
363
+ return finalReply || null;
364
+ }
365
+
366
+ function formatTerminalCardContent(state: {
367
+ status: "running" | "done" | "stopped" | "error" | "auto_ended";
368
+ accumulatedContent: string;
369
+ finalReply: string;
370
+ terminalError?: TerminalErrorInfo;
371
+ }): string {
372
+ const content = state.accumulatedContent + state.finalReply;
373
+ if (state.status !== "error") return content;
374
+
375
+ const error = state.terminalError ?? {
376
+ kind: "unknown" as const,
377
+ title: "原因未记录",
378
+ message: "当前状态中没有可用的错误详情,请查看运行日志。",
379
+ occurredAt: Date.now(),
380
+ };
381
+ const reason = formatTerminalErrorReason(error);
382
+ return content.trim()
383
+ ? `${content}\n\n${reason}\n以上内容可能不完整。`
384
+ : reason;
385
+ }
386
386
 
387
387
  function isCardKitSequenceConflict(err: unknown): boolean {
388
388
  return err instanceof Error && err.message.includes("300317");
@@ -948,16 +948,16 @@ export function accumulateBlockContent(
948
948
  // 覆盖而非追加:适配器已保证这是一段完整最终文本(如 Cursor 流末快照)
949
949
  state.finalCompleteText = block.text;
950
950
  break;
951
- case "compact_boundary": {
951
+ case "compact_boundary": {
952
952
  const triggerLabel = block.trigger === "manual" ? "手动" : "自动"; // 手动 / 自动
953
953
  state.accumulatedContent +=
954
954
  `\n\n🔄 上下文压缩(${triggerLabel}): **${block.pre_tokens}** → **${block.post_tokens}** tokens\n`; // 🔄 / →
955
- break;
956
- }
957
- case "agent_status":
958
- break;
959
- }
960
- }
955
+ break;
956
+ }
957
+ case "agent_status":
958
+ break;
959
+ }
960
+ }
961
961
 
962
962
  // ---------------------------------------------------------------------------
963
963
  // switchChatBinding — /newh、/session N 共用的事务式"切换 chat 绑定"
@@ -1146,7 +1146,7 @@ export async function initClaudeSession(tool: string, overrideCwd?: string, chat
1146
1146
  return { sessionId, cwd };
1147
1147
  }
1148
1148
 
1149
- export async function resumeAndPrompt(
1149
+ export async function resumeAndPrompt(
1150
1150
  sessionId: string,
1151
1151
  userText: string,
1152
1152
  platform: PlatformAdapter,
@@ -1154,25 +1154,25 @@ export async function resumeAndPrompt(
1154
1154
  msgTimestamp: number,
1155
1155
  tool: string,
1156
1156
  traceId?: string,
1157
- ): Promise<SessionRunOutcome> {
1158
- return runAgentSession(sessionId, userText, platform, chatId, msgTimestamp, tool, traceId);
1159
- }
1157
+ ): Promise<SessionRunOutcome> {
1158
+ return runAgentSession(sessionId, userText, platform, chatId, msgTimestamp, tool, traceId);
1159
+ }
1160
1160
 
1161
1161
  // ---------------------------------------------------------------------------
1162
1162
  // runAgentSession — session 中心的 agent prompt(文件持久化 + display 解耦)
1163
1163
  // ---------------------------------------------------------------------------
1164
1164
 
1165
- interface RunAgentSessionOptions {
1165
+ interface RunAgentSessionOptions {
1166
1166
  /**
1167
1167
  * 标记本轮是 response-stall 后的唯一一次内部续跑。若本轮再次因相同原因
1168
1168
  * 停滞,不再递归创建第三轮,避免服务异常期间无限消耗 token。
1169
1169
  */
1170
1170
  autoRecovery?: boolean;
1171
- }
1172
-
1173
- export type SessionRunOutcome = "busy" | "done" | "stopped" | "error" | "auto_ended";
1171
+ }
1172
+
1173
+ export type SessionRunOutcome = "busy" | "done" | "stopped" | "error" | "auto_ended";
1174
1174
 
1175
- export async function runAgentSession(
1175
+ export async function runAgentSession(
1176
1176
  sessionId: string,
1177
1177
  userText: string,
1178
1178
  platform: PlatformAdapter,
@@ -1181,7 +1181,7 @@ export async function runAgentSession(
1181
1181
  tool: string,
1182
1182
  traceId?: string,
1183
1183
  options: RunAgentSessionOptions = {},
1184
- ): Promise<SessionRunOutcome> {
1184
+ ): Promise<SessionRunOutcome> {
1185
1185
  const tid = traceId ?? "";
1186
1186
 
1187
1187
  // runAgentSession 是飞书用户消息、队列消息与内部自动恢复共同使用的唯一
@@ -1208,7 +1208,7 @@ export async function runAgentSession(
1208
1208
  ? "当前正在生成回复中,请等待完成后再发送消息。也可以发送 /stop 结束,已完成的步骤不会丢失。"
1209
1209
  : "该会话正在生成回复中,请等待完成后再发送消息。也可以发送 /stop 结束,已完成的步骤不会丢失。";
1210
1210
  await platform.sendText(_chatId, busyMsg).catch(() => {});
1211
- return "busy";
1211
+ return "busy";
1212
1212
  }
1213
1213
 
1214
1214
  // 立即标记活跃,确保 /sessions、isSessionRunning 等查询在异步准备阶段就能看到运行状态。
@@ -1336,7 +1336,7 @@ export async function runAgentSession(
1336
1336
  // 再开始缓存问题对应的任务"。
1337
1337
  const prevState = await readStreamState(sessionId);
1338
1338
  if (prevState && prevState.status !== "running") {
1339
- const prevTerminalReply = formatTerminalReply(prevState.status, prevState.finalReply, prevState.terminalError);
1339
+ const prevTerminalReply = formatTerminalReply(prevState.status, prevState.finalReply, prevState.terminalError);
1340
1340
  const displayChatId = pickDisplayChat(sessionId);
1341
1341
  if (displayChatId) {
1342
1342
  const pp = platformForChat(displayChatId);
@@ -1355,8 +1355,8 @@ export async function runAgentSession(
1355
1355
  setSessionChatAvatar(pp, displayChatId, prevState.tool, "idle", sessionId).catch(() => {});
1356
1356
  } else {
1357
1357
  const nextSeq = display.sequence + 1;
1358
- const { title: headerTitle, template: headerTemplate } = formatTerminalHeader(prevState.status, prevState.terminalError);
1359
- const cardContent = truncateContent(formatTerminalCardContent(prevState)) || " ";
1358
+ const { title: headerTitle, template: headerTemplate } = formatTerminalHeader(prevState.status, prevState.terminalError);
1359
+ const cardContent = truncateContent(formatTerminalCardContent(prevState)) || " ";
1360
1360
  const doneCard = buildProgressCard(progressView({ text: cardContent, status: "done", showStop: false, headerTitle, headerTemplate }));
1361
1361
  await pp.cardUpdate(display.cardId, doneCard, nextSeq).catch(err => {
1362
1362
  console.error(`[${ts()}] [DISPLAY] prevState final cardUpdate failed: ${(err as Error).message}`);
@@ -1449,21 +1449,21 @@ export async function runAgentSession(
1449
1449
 
1450
1450
  let lastFileWrite = Date.now();
1451
1451
  const FILE_WRITE_INTERVAL_MS = 2000;
1452
- const toolCallMap = new Map<string, { name: string; input: unknown }>();
1453
- let streamErrored = false;
1454
- let streamTerminalError: TerminalErrorInfo | undefined;
1455
- let runOutcome: SessionRunOutcome = "error";
1456
-
1457
- const runningPrompt = activePrompts.get(sessionId);
1458
- if (runningPrompt) {
1459
- // 在消费第一个事件前建立阶段感知的零字符基线;启动阶段不计时,只有后续
1460
- // 收到明确的 responding 状态后才会启动三分钟回复停滞保护。
1461
- runningPrompt.responseProgress = observeResponseProgress(
1462
- undefined,
1463
- monitorsOutputProgress(activityTracker.activity.kind),
1464
- 0,
1465
- activityTracker.activity.startedAt,
1466
- );
1452
+ const toolCallMap = new Map<string, { name: string; input: unknown }>();
1453
+ let streamErrored = false;
1454
+ let streamTerminalError: TerminalErrorInfo | undefined;
1455
+ let runOutcome: SessionRunOutcome = "error";
1456
+
1457
+ const runningPrompt = activePrompts.get(sessionId);
1458
+ if (runningPrompt) {
1459
+ // 在消费第一个事件前建立阶段感知的零字符基线;启动阶段不计时,只有后续
1460
+ // 收到明确的 responding 状态后才会启动三分钟回复停滞保护。
1461
+ runningPrompt.responseProgress = observeResponseProgress(
1462
+ undefined,
1463
+ monitorsOutputProgress(activityTracker.activity.kind),
1464
+ 0,
1465
+ activityTracker.activity.startedAt,
1466
+ );
1467
1467
 
1468
1468
  const checkResponseStall = async () => {
1469
1469
  const current = activePrompts.get(sessionId);
@@ -1533,7 +1533,7 @@ export async function runAgentSession(
1533
1533
  current.controller.abort();
1534
1534
  await killProcessTree(current.processPid);
1535
1535
  console.warn(
1536
- `[${ts()}] [RESPONSE-STALL] Session ${sessionId} auto-ended after 3 minutes without reply progress`,
1536
+ `[${ts()}] [RESPONSE-STALL] Session ${sessionId} auto-ended after 3 minutes without reply progress`,
1537
1537
  );
1538
1538
  };
1539
1539
 
@@ -1625,10 +1625,10 @@ export async function runAgentSession(
1625
1625
  });
1626
1626
  }
1627
1627
  }
1628
- } catch (streamErr) {
1629
- streamErrored = true;
1630
- streamTerminalError = classifyTerminalError(streamErr);
1631
- console.error(`[${ts()}] [STREAM] Error in stream loop for ${sessionId}: ${(streamErr as Error).message}`);
1628
+ } catch (streamErr) {
1629
+ streamErrored = true;
1630
+ streamTerminalError = classifyTerminalError(streamErr);
1631
+ console.error(`[${ts()}] [STREAM] Error in stream loop for ${sessionId}: ${(streamErr as Error).message}`);
1632
1632
  } finally {
1633
1633
  // 标记 prompt 结束
1634
1634
  resourceMonitor.off("stuck", onResourceStuck);
@@ -1676,24 +1676,24 @@ export async function runAgentSession(
1676
1676
  : wasStopped
1677
1677
  ? "stopped"
1678
1678
  : "done";
1679
- const finalReply = pickFinalReply(state).trim();
1680
- const terminalError = streamTerminalError
1681
- ?? (wasAbnormalExit
1682
- ? {
1683
- kind: "process" as const,
1684
- title: "Agent 进程意外退出",
1685
- message: "Agent CLI 进程已退出。若回复不完整,请重新发送上一条指令。",
1686
- occurredAt: Date.now(),
1687
- }
1688
- : wasResourceStuck
1689
- ? {
1690
- kind: "resource" as const,
1691
- title: "Agent 进程失去响应",
1692
- message: "Agent 进程长时间没有 CPU 或内存变化,已被强制停止。若回复不完整,请重新发送上一条指令。",
1693
- occurredAt: Date.now(),
1694
- }
1695
- : undefined);
1696
- runOutcome = finalStatus;
1679
+ const finalReply = pickFinalReply(state).trim();
1680
+ const terminalError = streamTerminalError
1681
+ ?? (wasAbnormalExit
1682
+ ? {
1683
+ kind: "process" as const,
1684
+ title: "Agent 进程意外退出",
1685
+ message: "Agent CLI 进程已退出。若回复不完整,请重新发送上一条指令。",
1686
+ occurredAt: Date.now(),
1687
+ }
1688
+ : wasResourceStuck
1689
+ ? {
1690
+ kind: "resource" as const,
1691
+ title: "Agent 进程失去响应",
1692
+ message: "Agent 进程长时间没有 CPU 或内存变化,已被强制停止。若回复不完整,请重新发送上一条指令。",
1693
+ occurredAt: Date.now(),
1694
+ }
1695
+ : undefined);
1696
+ runOutcome = finalStatus;
1697
1697
 
1698
1698
  // stop-stuck-loop 接口可能在 fire-and-forget 中已写入带 final_reply 的
1699
1699
  // stream state,finally 不应覆盖它。同时保留 stuckAt 标记,防止
@@ -1723,9 +1723,9 @@ export async function runAgentSession(
1723
1723
  cwd,
1724
1724
  tool,
1725
1725
  ...(preserveStuckAt ? { stuckAt: preserveStuckAt } : {}),
1726
- ...(autoEndedAt !== undefined ? { autoEndedAt } : {}),
1727
- ...(terminalError ? { terminalError } : {}),
1728
- });
1726
+ ...(autoEndedAt !== undefined ? { autoEndedAt } : {}),
1727
+ ...(terminalError ? { terminalError } : {}),
1728
+ });
1729
1729
 
1730
1730
  // display loop 下一轮会读到最终状态并发送消息
1731
1731
 
@@ -1796,9 +1796,9 @@ export async function runAgentSession(
1796
1796
  autoRecoveryTarget = { chatId: activeAutoEnded, platform: pp };
1797
1797
  }
1798
1798
  }
1799
- console.warn(`[${ts()}] Session ${sessionId} auto-ended after stalled response output (content chunks: ${state.chunkCount})`);
1799
+ console.warn(`[${ts()}] Session ${sessionId} auto-ended after stalled response output (content chunks: ${state.chunkCount})`);
1800
1800
  if (tid) logTrace(tid, "SESSION_END", { sessionId, outcome: "response_stall", chunks: state.chunkCount });
1801
- } else if (wasAbnormalExit) {
1801
+ } else if (wasAbnormalExit) {
1802
1802
  for (const cid of finalizationChatIds) {
1803
1803
  const finfo = sessionInfoMap.get(cid);
1804
1804
  await recordSessionRegistry({
@@ -1813,55 +1813,55 @@ export async function runAgentSession(
1813
1813
  }
1814
1814
  const activeErr = getLastActiveChat(sessionId) ?? finalizationChatIds[0];
1815
1815
  if (activeErr) setSessionChatAvatar(platform, activeErr, tool, "idle", sessionId).catch(() => {});
1816
- console.log(`[${ts()}] Session ${sessionId} process exited unexpectedly (content chunks: ${state.chunkCount})`);
1817
- if (tid) logTrace(tid, "SESSION_END", { sessionId, outcome: "process_missing", chunks: state.chunkCount });
1818
- } else if (streamErrored || wasResourceStuck) {
1819
- for (const cid of finalizationChatIds) {
1820
- const finfo = sessionInfoMap.get(cid);
1821
- await recordSessionRegistry({
1822
- chatId: cid,
1823
- sessionId,
1824
- tool,
1825
- turnCount: finfo?.turnCount ?? nextTurnCount,
1826
- lastContextTokens: finfo?.lastContextTokens ?? nextContextTokens,
1827
- startTime: finfo?.startTime ?? now,
1828
- running: false,
1829
- });
1830
- }
1831
- const activeErr = getLastActiveChat(sessionId) ?? finalizationChatIds[0];
1832
- if (activeErr) {
1833
- const pp = platformForChat(activeErr) ?? platform;
1834
- const terminalState = await readStreamState(sessionId);
1835
- if (
1836
- terminalError
1837
- && !displayCards.has(activeErr)
1838
- && (!terminalState || !isFinalReplySentForTurn(terminalState))
1839
- ) {
1840
- await sendFinalReplyTextOnce(
1841
- pp,
1842
- activeErr,
1843
- sessionId,
1844
- nextTurnCount,
1845
- formatTerminalErrorNotice(terminalError, finalReplyToWrite),
1846
- );
1847
- }
1848
- setSessionChatAvatar(pp, activeErr, tool, "idle", sessionId).catch(() => {});
1849
- }
1850
- const errorOutcome = wasResourceStuck ? "resource_stuck" : "stream_error";
1851
- console.error(
1852
- `[${ts()}] Session ${sessionId} ended with ${errorOutcome}` +
1853
- `${terminalError ? ` (${terminalError.kind}: ${terminalError.title})` : ""} (content chunks: ${state.chunkCount})`,
1854
- );
1855
- if (tid) {
1856
- logTrace(tid, "SESSION_END", {
1857
- sessionId,
1858
- outcome: errorOutcome,
1859
- errorKind: terminalError?.kind,
1860
- errorTitle: terminalError?.title,
1861
- chunks: state.chunkCount,
1862
- });
1863
- }
1864
- } else {
1816
+ console.log(`[${ts()}] Session ${sessionId} process exited unexpectedly (content chunks: ${state.chunkCount})`);
1817
+ if (tid) logTrace(tid, "SESSION_END", { sessionId, outcome: "process_missing", chunks: state.chunkCount });
1818
+ } else if (streamErrored || wasResourceStuck) {
1819
+ for (const cid of finalizationChatIds) {
1820
+ const finfo = sessionInfoMap.get(cid);
1821
+ await recordSessionRegistry({
1822
+ chatId: cid,
1823
+ sessionId,
1824
+ tool,
1825
+ turnCount: finfo?.turnCount ?? nextTurnCount,
1826
+ lastContextTokens: finfo?.lastContextTokens ?? nextContextTokens,
1827
+ startTime: finfo?.startTime ?? now,
1828
+ running: false,
1829
+ });
1830
+ }
1831
+ const activeErr = getLastActiveChat(sessionId) ?? finalizationChatIds[0];
1832
+ if (activeErr) {
1833
+ const pp = platformForChat(activeErr) ?? platform;
1834
+ const terminalState = await readStreamState(sessionId);
1835
+ if (
1836
+ terminalError
1837
+ && !displayCards.has(activeErr)
1838
+ && (!terminalState || !isFinalReplySentForTurn(terminalState))
1839
+ ) {
1840
+ await sendFinalReplyTextOnce(
1841
+ pp,
1842
+ activeErr,
1843
+ sessionId,
1844
+ nextTurnCount,
1845
+ formatTerminalErrorNotice(terminalError, finalReplyToWrite),
1846
+ );
1847
+ }
1848
+ setSessionChatAvatar(pp, activeErr, tool, "idle", sessionId).catch(() => {});
1849
+ }
1850
+ const errorOutcome = wasResourceStuck ? "resource_stuck" : "stream_error";
1851
+ console.error(
1852
+ `[${ts()}] Session ${sessionId} ended with ${errorOutcome}` +
1853
+ `${terminalError ? ` (${terminalError.kind}: ${terminalError.title})` : ""} (content chunks: ${state.chunkCount})`,
1854
+ );
1855
+ if (tid) {
1856
+ logTrace(tid, "SESSION_END", {
1857
+ sessionId,
1858
+ outcome: errorOutcome,
1859
+ errorKind: terminalError?.kind,
1860
+ errorTitle: terminalError?.title,
1861
+ chunks: state.chunkCount,
1862
+ });
1863
+ }
1864
+ } else {
1865
1865
  for (const cid of finalizationChatIds) {
1866
1866
  const finfo = sessionInfoMap.get(cid);
1867
1867
  await recordSessionRegistry({
@@ -1973,9 +1973,9 @@ export async function runAgentSession(
1973
1973
  } finally {
1974
1974
  clearSessionFinalizing(sessionId);
1975
1975
  }
1976
- }
1977
- return runOutcome;
1978
- }
1976
+ }
1977
+ return runOutcome;
1978
+ }
1979
1979
 
1980
1980
  // ---------------------------------------------------------------------------
1981
1981
  // startUnifiedDisplayLoop — 全局统一 display 循环,遍历 displayCards 更新卡片
@@ -2052,13 +2052,13 @@ export function startUnifiedDisplayLoop(): void {
2052
2052
  if (activePrompts.has(sessionId)) continue;
2053
2053
 
2054
2054
  const tail = "━━━ 回答结束 ━━━";
2055
- const finalMsg = state.status === "auto_ended"
2056
- ? formatAutoEndedReply(remaining)
2057
- : state.status === "error"
2058
- ? formatTerminalReply(state.status, remaining, state.terminalError) ?? tail
2059
- : remaining
2060
- ? remaining + "\n" + tail
2061
- : tail;
2055
+ const finalMsg = state.status === "auto_ended"
2056
+ ? formatAutoEndedReply(remaining)
2057
+ : state.status === "error"
2058
+ ? formatTerminalReply(state.status, remaining, state.terminalError) ?? tail
2059
+ : remaining
2060
+ ? remaining + "\n" + tail
2061
+ : tail;
2062
2062
  if (!isFinalReplySentForTurn(state)) {
2063
2063
  await sendFinalReplyTextOnce(p, chatId, sessionId, state.turnCount, finalMsg);
2064
2064
  }
@@ -2080,8 +2080,8 @@ export function startUnifiedDisplayLoop(): void {
2080
2080
  let terminalCardUpdateAccepted = terminalCardAlreadyUpdated;
2081
2081
  if (!terminalCardAlreadyUpdated) {
2082
2082
  const nextSeq = display.sequence + 1;
2083
- const { title: headerTitle, template: headerTemplate } = formatTerminalHeader(state.status, state.terminalError);
2084
- const cardContent = truncateContent(formatTerminalCardContent(state)) || " ";
2083
+ const { title: headerTitle, template: headerTemplate } = formatTerminalHeader(state.status, state.terminalError);
2084
+ const cardContent = truncateContent(formatTerminalCardContent(state)) || " ";
2085
2085
  const doneCard = buildProgressCard(progressView({ text: cardContent, status: "done", showStop: false, headerTitle, headerTemplate }));
2086
2086
  await p.cardUpdate(display.cardId, doneCard, nextSeq).then(() => {
2087
2087
  display.sequence = nextSeq;
@@ -2108,19 +2108,19 @@ export function startUnifiedDisplayLoop(): void {
2108
2108
  }
2109
2109
 
2110
2110
  let terminalTextDelivered = true;
2111
- const terminalReply = formatTerminalReply(state.status, state.finalReply, state.terminalError);
2112
- const errorWasDeliveredByCard =
2113
- state.status === "error"
2114
- && !state.finalReply.trim()
2115
- && terminalCardUpdateAccepted;
2116
- if (errorWasDeliveredByCard) {
2117
- if (!isFinalReplySentForTurn(state)) {
2118
- await markFinalReplySent(sessionId, state.turnCount);
2119
- }
2120
- } else if (terminalReply) {
2121
- if (!isFinalReplySentForTurn(state)) {
2122
- terminalTextDelivered = await sendFinalReplyTextOnce(p, chatId, sessionId, state.turnCount, terminalReply);
2123
- }
2111
+ const terminalReply = formatTerminalReply(state.status, state.finalReply, state.terminalError);
2112
+ const errorWasDeliveredByCard =
2113
+ state.status === "error"
2114
+ && !state.finalReply.trim()
2115
+ && terminalCardUpdateAccepted;
2116
+ if (errorWasDeliveredByCard) {
2117
+ if (!isFinalReplySentForTurn(state)) {
2118
+ await markFinalReplySent(sessionId, state.turnCount);
2119
+ }
2120
+ } else if (terminalReply) {
2121
+ if (!isFinalReplySentForTurn(state)) {
2122
+ terminalTextDelivered = await sendFinalReplyTextOnce(p, chatId, sessionId, state.turnCount, terminalReply);
2123
+ }
2124
2124
  } else if (state.accumulatedContent.trim()) {
2125
2125
  const short = truncateContent(state.accumulatedContent, 30, 4000);
2126
2126
  terminalTextDelivered = await p.sendText(chatId, `[生成过程]\n${short}`).then((ok) => ok !== false).catch(() => false);
@@ -1,29 +1,29 @@
1
- export const ABD_PREFIX = "/abd";
2
-
3
- export const ABD_APPEND_PROMPT =
4
- "请从第一性原理出发挖掘我的真实需求。你觉得我的需求合理吗?开始实现之前有什么问题要问我的?";
5
-
6
- export const ABD_HELP_LINE =
7
- `发送 **${ABD_PREFIX}** 前缀消息,将以第一性原理追问真实需求、合理性和实现前问题`;
8
-
9
- export interface SharedPrefixResult {
10
- matched: boolean;
11
- text: string;
12
- body: string;
13
- prefix?: typeof ABD_PREFIX;
14
- }
15
-
16
- export function applySharedPrefix(text: string): SharedPrefixResult {
17
- if (!text.toLowerCase().startsWith(ABD_PREFIX)) {
18
- return { matched: false, text, body: text };
19
- }
20
-
21
- const body = text.slice(ABD_PREFIX.length).replace(/^\s+/, "");
22
- const appendix = ["---", ABD_APPEND_PROMPT].join("\n");
23
- return {
24
- matched: true,
25
- prefix: ABD_PREFIX,
26
- body,
27
- text: body ? [body, appendix].join("\n\n") : appendix,
28
- };
29
- }
1
+ export const ABD_PREFIX = "/abd";
2
+
3
+ export const ABD_APPEND_PROMPT =
4
+ "请从第一性原理出发挖掘我的真实需求。你觉得我的需求合理吗?开始实现之前有什么问题要问我的?";
5
+
6
+ export const ABD_HELP_LINE =
7
+ `发送 **${ABD_PREFIX}** 前缀消息,将以第一性原理追问真实需求、合理性和实现前问题`;
8
+
9
+ export interface SharedPrefixResult {
10
+ matched: boolean;
11
+ text: string;
12
+ body: string;
13
+ prefix?: typeof ABD_PREFIX;
14
+ }
15
+
16
+ export function applySharedPrefix(text: string): SharedPrefixResult {
17
+ if (!text.toLowerCase().startsWith(ABD_PREFIX)) {
18
+ return { matched: false, text, body: text };
19
+ }
20
+
21
+ const body = text.slice(ABD_PREFIX.length).replace(/^\s+/, "");
22
+ const appendix = ["---", ABD_APPEND_PROMPT].join("\n");
23
+ return {
24
+ matched: true,
25
+ prefix: ABD_PREFIX,
26
+ body,
27
+ text: body ? [body, appendix].join("\n\n") : appendix,
28
+ };
29
+ }