chatccc 0.2.201 → 0.2.203

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 (95) hide show
  1. package/README.md +23 -21
  2. package/agent-prompts/claude_specific.md +45 -45
  3. package/agent-prompts/codex_specific.md +2 -2
  4. package/agent-prompts/cursor_specific.md +13 -13
  5. package/bin/cccagent.mjs +17 -17
  6. package/config.sample.json +27 -27
  7. package/im-skills/feishu-skill/receive-send-file.md +63 -63
  8. package/im-skills/feishu-skill/receive-send-image.md +24 -24
  9. package/im-skills/feishu-skill/skill.md +3 -3
  10. package/im-skills/wechat-file-skill/receive-send-file.md +38 -38
  11. package/im-skills/wechat-file-skill/send-file.mjs +83 -83
  12. package/im-skills/wechat-file-skill/skill.md +10 -10
  13. package/im-skills/wechat-image-skill/skill.md +10 -10
  14. package/im-skills/wechat-video-skill/receive-send-video.md +38 -38
  15. package/im-skills/wechat-video-skill/send-video.mjs +79 -79
  16. package/im-skills/wechat-video-skill/skill.md +10 -10
  17. package/package.json +1 -1
  18. package/scripts/postinstall-sharp-check.mjs +58 -58
  19. package/src/__tests__/agent-delegate-task-rpc.test.ts +165 -165
  20. package/src/__tests__/agent-reload-config-rpc.test.ts +99 -99
  21. package/src/__tests__/builtin-chat-session.test.ts +277 -277
  22. package/src/__tests__/builtin-cli-json.test.ts +39 -39
  23. package/src/__tests__/builtin-config.test.ts +33 -33
  24. package/src/__tests__/builtin-context.test.ts +163 -163
  25. package/src/__tests__/builtin-file-tools.test.ts +224 -224
  26. package/src/__tests__/builtin-session-select.test.ts +116 -116
  27. package/src/__tests__/builtin-sigint.test.ts +56 -56
  28. package/src/__tests__/card-plain-text.test.ts +5 -5
  29. package/src/__tests__/cardkit.test.ts +60 -60
  30. package/src/__tests__/cards.test.ts +133 -122
  31. package/src/__tests__/ccc-adapter.test.ts +114 -114
  32. package/src/__tests__/chatgpt-subscription-rpc.test.ts +89 -89
  33. package/src/__tests__/chatgpt-subscription.test.ts +135 -135
  34. package/src/__tests__/chrome-devtools-guard.test.ts +165 -165
  35. package/src/__tests__/claude-adapter.test.ts +592 -592
  36. package/src/__tests__/claude-raw-stream-log.test.ts +87 -87
  37. package/src/__tests__/codex-raw-stream-log.test.ts +163 -163
  38. package/src/__tests__/codex-reset-actions.test.ts +146 -146
  39. package/src/__tests__/config-reload.test.ts +10 -10
  40. package/src/__tests__/config-sample.test.ts +18 -18
  41. package/src/__tests__/cursor-adapter.test.ts +226 -226
  42. package/src/__tests__/feishu-api.test.ts +60 -60
  43. package/src/__tests__/feishu-avatar.test.ts +90 -90
  44. package/src/__tests__/feishu-platform.test.ts +22 -22
  45. package/src/__tests__/format-message.test.ts +47 -47
  46. package/src/__tests__/jsonl-stream.test.ts +79 -79
  47. package/src/__tests__/orchestrator.test.ts +464 -301
  48. package/src/__tests__/privacy.test.ts +198 -198
  49. package/src/__tests__/raw-stream-log.test.ts +106 -106
  50. package/src/__tests__/session.test.ts +46 -44
  51. package/src/__tests__/shared-prefix.test.ts +36 -36
  52. package/src/__tests__/sim-platform.test.ts +12 -12
  53. package/src/__tests__/stream-state.test.ts +42 -42
  54. package/src/__tests__/update-command-guard.test.ts +144 -0
  55. package/src/__tests__/web-ui.test.ts +209 -209
  56. package/src/adapters/ccc-adapter.ts +121 -121
  57. package/src/adapters/claude-adapter.ts +603 -603
  58. package/src/adapters/claude-session-meta-store.ts +120 -120
  59. package/src/adapters/codex-adapter.ts +380 -380
  60. package/src/adapters/cursor-adapter.ts +673 -673
  61. package/src/adapters/jsonl-stream.ts +157 -157
  62. package/src/adapters/raw-stream-log.ts +124 -124
  63. package/src/adapters/resource-monitor.ts +140 -140
  64. package/src/agent-delegate-task-rpc.ts +153 -153
  65. package/src/agent-delegate-task.ts +81 -81
  66. package/src/agent-reload-config-rpc.ts +34 -34
  67. package/src/agent-stop-stuck.ts +129 -129
  68. package/src/builtin/cli.ts +473 -473
  69. package/src/builtin/context.ts +323 -323
  70. package/src/builtin/file-tools.ts +1072 -1072
  71. package/src/builtin/index.ts +404 -404
  72. package/src/builtin/session-select.ts +48 -48
  73. package/src/builtin/sigint.ts +50 -50
  74. package/src/cards.ts +222 -210
  75. package/src/chatgpt-subscription-rpc.ts +27 -27
  76. package/src/chatgpt-subscription.ts +299 -299
  77. package/src/chrome-devtools-guard.ts +318 -318
  78. package/src/codex-reset-actions.ts +184 -184
  79. package/src/config.ts +125 -125
  80. package/src/feishu-api.ts +118 -118
  81. package/src/feishu-platform.ts +20 -20
  82. package/src/format-message.ts +293 -293
  83. package/src/index.ts +48 -9
  84. package/src/litellm-proxy.ts +374 -374
  85. package/src/orchestrator.ts +391 -346
  86. package/src/privacy.ts +118 -118
  87. package/src/runtime-reload.ts +34 -34
  88. package/src/session-chat-binding.ts +6 -6
  89. package/src/session-name.ts +8 -8
  90. package/src/session.ts +177 -169
  91. package/src/shared-prefix.ts +29 -29
  92. package/src/sim-platform.ts +20 -20
  93. package/src/turn-cards.ts +117 -117
  94. package/src/update-command-guard.ts +165 -0
  95. package/src/web-ui.ts +205 -205
package/src/session.ts CHANGED
@@ -2,9 +2,9 @@ import { readFile, writeFile, mkdir } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
3
 
4
4
  import {
5
- CLAUDE_API_KEY,
6
- CLAUDE_BASE_URL,
7
- CLAUDE_MAX_TURN,
5
+ CLAUDE_API_KEY,
6
+ CLAUDE_BASE_URL,
7
+ CLAUDE_MAX_TURN,
8
8
  CLAUDE_MODEL,
9
9
  CLAUDE_SUBAGENT_MODEL,
10
10
  CHATCCC_PORT,
@@ -14,23 +14,23 @@ import {
14
14
  addRecentDir,
15
15
  anthropicConfigDisplay,
16
16
  config,
17
- fileLog,
18
- getDefaultCwd,
19
- getDefaultEffortForTool,
20
- isAnthropicConfigEmpty,
21
- toolDisplayName,
22
- ts,
23
- } from "./config.ts";
17
+ fileLog,
18
+ getDefaultCwd,
19
+ getDefaultEffortForTool,
20
+ isAnthropicConfigEmpty,
21
+ toolDisplayName,
22
+ ts,
23
+ } from "./config.ts";
24
24
  import { buildProgressCard, getToolEmoji, isCodeBlockOpen, truncateContent } from "./cards.ts";
25
25
  import { simplifyToolUse, simplifyToolResult } from "./simplify.ts";
26
26
  import { logTrace } from "./trace.ts";
27
27
  import type { UnifiedBlock } from "./adapters/adapter-interface.ts";
28
28
  import type { ToolAdapter } from "./adapters/adapter-interface.ts";
29
29
  import type { ToolProcessInfo } from "./adapters/adapter-interface.ts";
30
- import { createClaudeAdapter } from "./adapters/claude-adapter.ts";
31
- import { createCursorAdapter } from "./adapters/cursor-adapter.ts";
32
- import { createCodexAdapter } from "./adapters/codex-adapter.ts";
33
- import { createCccAdapter } from "./adapters/ccc-adapter.ts";
30
+ import { createClaudeAdapter } from "./adapters/claude-adapter.ts";
31
+ import { createCursorAdapter } from "./adapters/cursor-adapter.ts";
32
+ import { createCodexAdapter } from "./adapters/codex-adapter.ts";
33
+ import { createCccAdapter } from "./adapters/ccc-adapter.ts";
34
34
  import { resourceMonitor, registerProcess, unregisterProcess } from "./adapters/resource-monitor.ts";
35
35
  import { buildImSkillsPromptCached, exportSkillSubDocs, clearImSkillsPromptCache } from "./im-skills.ts";
36
36
  import type { PlatformAdapter } from "./platform-adapter.ts";
@@ -334,14 +334,14 @@ export function resetState(): void {
334
334
  for (const prompt of activePrompts.values()) {
335
335
  if (prompt.processMonitor) clearInterval(prompt.processMonitor);
336
336
  }
337
- activePrompts.clear();
338
- displayCards.clear();
339
- sessionModelOverrides.clear();
340
- sessionEffortOverrides.clear();
341
- adapterCache.clear();
342
- stopUnifiedDisplayLoop();
343
- console.log(`[${ts()}] [RESET] State cleared (dedup + active sessions + bindings)`);
344
- }
337
+ activePrompts.clear();
338
+ displayCards.clear();
339
+ sessionModelOverrides.clear();
340
+ sessionEffortOverrides.clear();
341
+ adapterCache.clear();
342
+ stopUnifiedDisplayLoop();
343
+ console.log(`[${ts()}] [RESET] State cleared (dedup + active sessions + bindings)`);
344
+ }
345
345
 
346
346
  // 注:`rebuildBindingsFromRegistry` 定义在下方与 loadSessionRegistry 同区域,
347
347
  // 是 onReady/onReconnected 取代 resetState 的正确入口。
@@ -353,8 +353,8 @@ export function resetState(): void {
353
353
  const adapterCache = new Map<string, ToolAdapter>();
354
354
 
355
355
  // Per-session 模型覆盖(/model 命令设置,不持久化)
356
- const sessionModelOverrides = new Map<string, string>();
357
- const sessionEffortOverrides = new Map<string, string>();
356
+ const sessionModelOverrides = new Map<string, string>();
357
+ const sessionEffortOverrides = new Map<string, string>();
358
358
 
359
359
  /** 返回 session 的生效模型:优先 per-session 覆盖,其次全局配置(Claude) */
360
360
  function getModelForSession(sessionId?: string): string {
@@ -366,27 +366,27 @@ function getModelForSession(sessionId?: string): string {
366
366
  }
367
367
 
368
368
  /** 返回指定 tool 的生效模型:优先 per-session 覆盖,其次 tool 默认配置 */
369
- export function getEffectiveModelForTool(tool: string, sessionId?: string): string {
370
- if (sessionId) {
371
- const override = sessionModelOverrides.get(sessionId);
372
- if (override) return override;
373
- }
374
- if (tool === "cursor") return config.cursor.model;
375
- if (tool === "codex") return config.codex.model;
376
- if (tool === "ccc") return config.ccc.model;
377
- return CLAUDE_MODEL;
378
- }
379
-
380
- export function getEffectiveEffortForTool(tool: string, sessionId?: string): string {
381
- if (sessionId) {
382
- const override = sessionEffortOverrides.get(sessionId);
383
- if (override) return override;
384
- }
385
- if (tool === "claude" || tool === "codex") {
386
- return getDefaultEffortForTool(tool);
387
- }
388
- return "";
389
- }
369
+ export function getEffectiveModelForTool(tool: string, sessionId?: string): string {
370
+ if (sessionId) {
371
+ const override = sessionModelOverrides.get(sessionId);
372
+ if (override) return override;
373
+ }
374
+ if (tool === "cursor") return config.cursor.model;
375
+ if (tool === "codex") return config.codex.model;
376
+ if (tool === "ccc") return config.ccc.model;
377
+ return CLAUDE_MODEL;
378
+ }
379
+
380
+ export function getEffectiveEffortForTool(tool: string, sessionId?: string): string {
381
+ if (sessionId) {
382
+ const override = sessionEffortOverrides.get(sessionId);
383
+ if (override) return override;
384
+ }
385
+ if (tool === "claude" || tool === "codex") {
386
+ return getDefaultEffortForTool(tool);
387
+ }
388
+ return "";
389
+ }
390
390
 
391
391
  /** 为指定 session 设置模型覆盖(/model <name>) */
392
392
  export function setSessionModelOverride(sessionId: string, model: string): void {
@@ -395,43 +395,43 @@ export function setSessionModelOverride(sessionId: string, model: string): void
395
395
  }
396
396
 
397
397
  /** 清除指定 session 的模型覆盖(/model clear) */
398
- export function clearSessionModelOverride(sessionId: string): void {
399
- sessionModelOverrides.delete(sessionId);
400
- adapterCache.clear();
401
- }
402
-
403
- export function setSessionEffortOverride(sessionId: string, effort: string): void {
404
- sessionEffortOverrides.set(sessionId, effort);
405
- adapterCache.clear();
406
- }
407
-
408
- export function clearSessionEffortOverride(sessionId: string): void {
409
- sessionEffortOverrides.delete(sessionId);
410
- adapterCache.clear();
411
- }
412
-
413
- export function getAdapterForTool(tool: string, sessionId?: string): ToolAdapter {
414
- const effectiveModel = getEffectiveModelForTool(tool, sessionId);
415
- const effectiveEffort = getEffectiveEffortForTool(tool, sessionId);
416
- const cacheKey = `${tool}:${effectiveModel || ""}:${effectiveEffort || ""}`;
417
- const cached = adapterCache.get(cacheKey);
418
- if (cached) return cached;
419
-
420
- let adapter: ToolAdapter;
421
- if (tool === "cursor") {
422
- adapter = createCursorAdapter({ model: effectiveModel || undefined });
423
- } else if (tool === "codex") {
424
- adapter = createCodexAdapter({ model: effectiveModel || undefined, effort: effectiveEffort || undefined });
425
- } else if (tool === "ccc") {
426
- adapter = createCccAdapter({ model: effectiveModel || undefined });
427
- } else {
428
- adapter = createClaudeAdapter({
429
- model: effectiveModel,
430
- subagentModel: CLAUDE_SUBAGENT_MODEL,
431
- effort: effectiveEffort,
432
- apiKey: CLAUDE_API_KEY,
433
- baseUrl: CLAUDE_BASE_URL,
434
- isEmpty: isAnthropicConfigEmpty,
398
+ export function clearSessionModelOverride(sessionId: string): void {
399
+ sessionModelOverrides.delete(sessionId);
400
+ adapterCache.clear();
401
+ }
402
+
403
+ export function setSessionEffortOverride(sessionId: string, effort: string): void {
404
+ sessionEffortOverrides.set(sessionId, effort);
405
+ adapterCache.clear();
406
+ }
407
+
408
+ export function clearSessionEffortOverride(sessionId: string): void {
409
+ sessionEffortOverrides.delete(sessionId);
410
+ adapterCache.clear();
411
+ }
412
+
413
+ export function getAdapterForTool(tool: string, sessionId?: string): ToolAdapter {
414
+ const effectiveModel = getEffectiveModelForTool(tool, sessionId);
415
+ const effectiveEffort = getEffectiveEffortForTool(tool, sessionId);
416
+ const cacheKey = `${tool}:${effectiveModel || ""}:${effectiveEffort || ""}`;
417
+ const cached = adapterCache.get(cacheKey);
418
+ if (cached) return cached;
419
+
420
+ let adapter: ToolAdapter;
421
+ if (tool === "cursor") {
422
+ adapter = createCursorAdapter({ model: effectiveModel || undefined });
423
+ } else if (tool === "codex") {
424
+ adapter = createCodexAdapter({ model: effectiveModel || undefined, effort: effectiveEffort || undefined });
425
+ } else if (tool === "ccc") {
426
+ adapter = createCccAdapter({ model: effectiveModel || undefined });
427
+ } else {
428
+ adapter = createClaudeAdapter({
429
+ model: effectiveModel,
430
+ subagentModel: CLAUDE_SUBAGENT_MODEL,
431
+ effort: effectiveEffort,
432
+ apiKey: CLAUDE_API_KEY,
433
+ baseUrl: CLAUDE_BASE_URL,
434
+ isEmpty: isAnthropicConfigEmpty,
435
435
  maxTurn: CLAUDE_MAX_TURN,
436
436
  });
437
437
  }
@@ -503,11 +503,13 @@ export function _resetSessionToolsFileForTest(): void {
503
503
  export const SESSION_REGISTRY_FILE = join(USER_DATA_DIR, "state", "session-registry.json");
504
504
  let sessionRegistryFile = SESSION_REGISTRY_FILE;
505
505
 
506
- export interface SessionRegistryUpdate {
507
- chatId: string;
508
- sessionId: string;
509
- tool: string;
510
- chatName?: string;
506
+ export interface SessionRegistryUpdate {
507
+ chatId: string;
508
+ sessionId: string;
509
+ tool: string;
510
+ /** 会话容器类型;旧 registry 没有该字段,读取时必须兼容。 */
511
+ chatType?: string;
512
+ chatName?: string;
511
513
  turnCount?: number;
512
514
  lastContextTokens?: number;
513
515
  startTime?: number;
@@ -515,11 +517,12 @@ export interface SessionRegistryUpdate {
515
517
  running?: boolean;
516
518
  }
517
519
 
518
- interface SessionRegistryRecord {
519
- chatId: string;
520
- sessionId: string;
521
- tool: string;
522
- chatName: string;
520
+ interface SessionRegistryRecord {
521
+ chatId: string;
522
+ sessionId: string;
523
+ tool: string;
524
+ chatType?: string;
525
+ chatName: string;
523
526
  turnCount: number;
524
527
  lastContextTokens: number;
525
528
  startTime: number;
@@ -580,10 +583,11 @@ export async function recordSessionRegistry(update: SessionRegistryUpdate): Prom
580
583
  const now = update.updatedAt ?? Date.now();
581
584
 
582
585
  data[update.chatId] = {
583
- chatId: update.chatId,
584
- sessionId: update.sessionId,
585
- tool: update.tool,
586
- chatName: update.chatName ?? existing?.chatName ?? "",
586
+ chatId: update.chatId,
587
+ sessionId: update.sessionId,
588
+ tool: update.tool,
589
+ chatType: update.chatType ?? existing?.chatType,
590
+ chatName: update.chatName ?? existing?.chatName ?? "",
587
591
  turnCount: update.turnCount ?? existing?.turnCount ?? 0,
588
592
  lastContextTokens: update.lastContextTokens ?? existing?.lastContextTokens ?? 0,
589
593
  startTime: update.startTime ?? existing?.startTime ?? now,
@@ -832,11 +836,12 @@ export async function switchChatBinding(args: SwitchChatBindingArgs): Promise<Sw
832
836
 
833
837
  // Step 3: 持久化(registry + sessions.json)。
834
838
  // 这两步即使失败也不影响内存正确性,下次 prompt 会再写一次。
835
- await recordSessionRegistry({
836
- chatId,
837
- sessionId: newSessionId,
838
- tool,
839
- chatName,
839
+ await recordSessionRegistry({
840
+ chatId,
841
+ sessionId: newSessionId,
842
+ tool,
843
+ chatType,
844
+ chatName,
840
845
  turnCount: initialTurnCount,
841
846
  lastContextTokens: initialContextTokens,
842
847
  startTime: now,
@@ -859,23 +864,23 @@ export async function switchChatBinding(args: SwitchChatBindingArgs): Promise<Sw
859
864
  function formatToolConfigForLog(tool: string, sessionModel?: string, sessionId?: string): string {
860
865
  if (tool === "cursor") {
861
866
  return `model=${sessionModel ?? "(由 cursor-agent 决定,init 事件后学习)"}`;
862
- }
863
- if (tool === "codex") {
864
- const m = getEffectiveModelForTool(tool, sessionId);
865
- const e = getEffectiveEffortForTool(tool, sessionId);
866
- const modelStr = m.trim() !== "" ? m : "(由 codex config.toml 决定)";
867
- const effortStr = e.trim() !== ""
868
- ? `effort=${e}`
869
- : "effort=(由 codex config.toml 决定)";
870
- return `model=${modelStr}, ${effortStr}`;
871
- }
872
- if (tool === "ccc") {
873
- const m = getEffectiveModelForTool(tool, sessionId);
874
- const modelStr = m.trim() !== "" ? m : "(not configured)";
875
- return `model=${modelStr}, baseURL=${config.ccc.DEEPSEEK_BASE_URL}`;
876
- }
877
- return `model=${anthropicConfigDisplay(getModelForSession(sessionId))}, subagentModel=${anthropicConfigDisplay(CLAUDE_SUBAGENT_MODEL)}, effort=${anthropicConfigDisplay(getEffectiveEffortForTool(tool, sessionId))}`;
878
- }
867
+ }
868
+ if (tool === "codex") {
869
+ const m = getEffectiveModelForTool(tool, sessionId);
870
+ const e = getEffectiveEffortForTool(tool, sessionId);
871
+ const modelStr = m.trim() !== "" ? m : "(由 codex config.toml 决定)";
872
+ const effortStr = e.trim() !== ""
873
+ ? `effort=${e}`
874
+ : "effort=(由 codex config.toml 决定)";
875
+ return `model=${modelStr}, ${effortStr}`;
876
+ }
877
+ if (tool === "ccc") {
878
+ const m = getEffectiveModelForTool(tool, sessionId);
879
+ const modelStr = m.trim() !== "" ? m : "(not configured)";
880
+ return `model=${modelStr}, baseURL=${config.ccc.DEEPSEEK_BASE_URL}`;
881
+ }
882
+ return `model=${anthropicConfigDisplay(getModelForSession(sessionId))}, subagentModel=${anthropicConfigDisplay(CLAUDE_SUBAGENT_MODEL)}, effort=${anthropicConfigDisplay(getEffectiveEffortForTool(tool, sessionId))}`;
883
+ }
879
884
 
880
885
  export async function initClaudeSession(tool: string, overrideCwd?: string, chatId?: string): Promise<{ sessionId: string; cwd: string }> {
881
886
  const cwd = overrideCwd ?? (await getDefaultCwd(chatId));
@@ -990,12 +995,12 @@ export async function runAgentSession(
990
995
  const imSkillsCacheDir = join(USER_DATA_DIR, "im-skills");
991
996
  const skillVariables = {
992
997
  cwd,
993
- session_id: sessionId,
994
- im_skills_cache_dir: imSkillsCacheDir,
995
- delegate_task_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/delegate-task`,
996
- send_image_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/send-image`,
997
- send_file_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/send-file`,
998
- send_image_script: join(feishuSkillDir, "send-image.mjs"),
998
+ session_id: sessionId,
999
+ im_skills_cache_dir: imSkillsCacheDir,
1000
+ delegate_task_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/delegate-task`,
1001
+ send_image_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/send-image`,
1002
+ send_file_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/send-file`,
1003
+ send_image_script: join(feishuSkillDir, "send-image.mjs"),
999
1004
  send_file_script: join(feishuSkillDir, "send-file.mjs"),
1000
1005
  download_video_script: join(feishuSkillDir, "download-video.mjs"),
1001
1006
  wechat_send_image_script: join(wechatImageSkillDir, "send-image.mjs"),
@@ -1168,12 +1173,12 @@ export async function runAgentSession(
1168
1173
  chunkCount: 0,
1169
1174
  };
1170
1175
 
1171
- let lastFileWrite = Date.now();
1172
- const FILE_WRITE_INTERVAL_MS = 2000;
1173
- const toolCallMap = new Map<string, { name: string; input: unknown }>();
1174
- let streamErrored = false;
1175
-
1176
- try {
1176
+ let lastFileWrite = Date.now();
1177
+ const FILE_WRITE_INTERVAL_MS = 2000;
1178
+ const toolCallMap = new Map<string, { name: string; input: unknown }>();
1179
+ let streamErrored = false;
1180
+
1181
+ try {
1177
1182
  for await (const unifiedMsg of adapter.prompt(sessionId, userTextWithCapabilities, cwd, controller.signal, {
1178
1183
  onProcessStart: (processInfo) => {
1179
1184
  startPromptProcessMonitor(sessionId, processInfo);
@@ -1223,11 +1228,11 @@ export async function runAgentSession(
1223
1228
  tool,
1224
1229
  });
1225
1230
  }
1226
- }
1227
- } catch (streamErr) {
1228
- streamErrored = true;
1229
- console.error(`[${ts()}] [STREAM] Error in stream loop for ${sessionId}: ${(streamErr as Error).message}`);
1230
- } finally {
1231
+ }
1232
+ } catch (streamErr) {
1233
+ streamErrored = true;
1234
+ console.error(`[${ts()}] [STREAM] Error in stream loop for ${sessionId}: ${(streamErr as Error).message}`);
1235
+ } finally {
1231
1236
  // 标记 prompt 结束
1232
1237
  resourceMonitor.off("stuck", onResourceStuck);
1233
1238
  const prompt = activePrompts.get(sessionId);
@@ -1241,7 +1246,7 @@ export async function runAgentSession(
1241
1246
  // 读到新状态并终结旧卡片。否则 setImmediate 在 CHECK 阶段先于
1242
1247
  // writeFile I/O(POLL 阶段)执行,display loop 会误以为旧轮仍在
1243
1248
  // 运行中并更新旧卡片,而不是新建卡片。
1244
- const finalStatus = (streamErrored || wasAbnormalExit || wasResourceStuck) ? "error" : wasStopped ? "stopped" : "done";
1249
+ const finalStatus = (streamErrored || wasAbnormalExit || wasResourceStuck) ? "error" : wasStopped ? "stopped" : "done";
1245
1250
  const finalReply = pickFinalReply(state).trim();
1246
1251
 
1247
1252
  // stop-stuck-loop 接口可能在 fire-and-forget 中已写入带 final_reply 的
@@ -1791,27 +1796,27 @@ async function resolveModelEffort(
1791
1796
  // adapter 异常时降级为占位符(不阻塞 /state 卡片)
1792
1797
  }
1793
1798
  return { model, effort: null };
1794
- }
1795
- if (tool === "codex") {
1796
- const m = getEffectiveModelForTool(tool, sessionId);
1797
- const e = getEffectiveEffortForTool(tool, sessionId);
1798
- return {
1799
- model: m.trim() !== "" ? m : UNKNOWN_MODEL_PLACEHOLDER,
1800
- effort: e.trim() !== "" ? e : UNKNOWN_MODEL_PLACEHOLDER,
1801
- };
1802
- }
1803
- if (tool === "ccc") {
1804
- const m = getEffectiveModelForTool(tool, sessionId);
1805
- return {
1806
- model: m.trim() !== "" ? m : UNKNOWN_MODEL_PLACEHOLDER,
1807
- effort: null,
1808
- };
1809
- }
1810
- return {
1811
- model: anthropicConfigDisplay(getModelForSession(sessionId)),
1812
- effort: anthropicConfigDisplay(getEffectiveEffortForTool(tool, sessionId)),
1813
- };
1814
- }
1799
+ }
1800
+ if (tool === "codex") {
1801
+ const m = getEffectiveModelForTool(tool, sessionId);
1802
+ const e = getEffectiveEffortForTool(tool, sessionId);
1803
+ return {
1804
+ model: m.trim() !== "" ? m : UNKNOWN_MODEL_PLACEHOLDER,
1805
+ effort: e.trim() !== "" ? e : UNKNOWN_MODEL_PLACEHOLDER,
1806
+ };
1807
+ }
1808
+ if (tool === "ccc") {
1809
+ const m = getEffectiveModelForTool(tool, sessionId);
1810
+ return {
1811
+ model: m.trim() !== "" ? m : UNKNOWN_MODEL_PLACEHOLDER,
1812
+ effort: null,
1813
+ };
1814
+ }
1815
+ return {
1816
+ model: anthropicConfigDisplay(getModelForSession(sessionId)),
1817
+ effort: anthropicConfigDisplay(getEffectiveEffortForTool(tool, sessionId)),
1818
+ };
1819
+ }
1815
1820
 
1816
1821
  export async function getSessionStatus(chatId: string): Promise<SessionStatus | null> {
1817
1822
  const info = sessionInfoMap.get(chatId);
@@ -1844,9 +1849,10 @@ export async function getSessionStatus(chatId: string): Promise<SessionStatus |
1844
1849
  };
1845
1850
  }
1846
1851
 
1847
- export interface SessionsListEntry {
1848
- chatId: string;
1849
- sessionId: string;
1852
+ export interface SessionsListEntry {
1853
+ chatId: string;
1854
+ chatType?: string;
1855
+ sessionId: string;
1850
1856
  chatName: string;
1851
1857
  active: boolean;
1852
1858
  turnCount: number;
@@ -1869,9 +1875,10 @@ export async function getAllSessionsStatus(): Promise<SessionsListEntry[]> {
1869
1875
  .map(([sessionId, record]) => {
1870
1876
  const createdAt = Number.isFinite(record.createdAt) ? record.createdAt : 0;
1871
1877
  const active = activePrompts.get(sessionId);
1872
- return {
1873
- chatId: "",
1874
- sessionId,
1878
+ return {
1879
+ chatId: "",
1880
+ chatType: undefined,
1881
+ sessionId,
1875
1882
  tool: record.tool,
1876
1883
  chatName: record.chatName ?? "",
1877
1884
  turnCount: 0,
@@ -1889,9 +1896,10 @@ export async function getAllSessionsStatus(): Promise<SessionsListEntry[]> {
1889
1896
  return Promise.all(
1890
1897
  entries.map(async (info) => {
1891
1898
  const { model, effort } = await resolveModelEffort(info.tool, info.sessionId);
1892
- return {
1893
- chatId: info.chatId,
1894
- sessionId: info.sessionId,
1899
+ return {
1900
+ chatId: info.chatId,
1901
+ chatType: info.chatType,
1902
+ sessionId: info.sessionId,
1895
1903
  chatName: info.chatName || "",
1896
1904
  active: !!activePrompts.get(info.sessionId) &&
1897
1905
  !activePrompts.get(info.sessionId)?.stopped &&
@@ -1917,9 +1925,9 @@ export function _setAdapterForToolForTest(tool: string, adapter: ToolAdapter): v
1917
1925
  adapterCache.set(tool, adapter);
1918
1926
  // 同时设置当前配置模型对应的 key(getAdapterForTool 会优先 lookup 含 model 的 key)
1919
1927
  const effective = getEffectiveModelForTool(tool);
1920
- const effort = getEffectiveEffortForTool(tool);
1921
- adapterCache.set(`${tool}:${effective || ""}:${effort || ""}`, adapter);
1922
- if (effective) adapterCache.set(`${tool}:${effective}`, adapter);
1928
+ const effort = getEffectiveEffortForTool(tool);
1929
+ adapterCache.set(`${tool}:${effective || ""}:${effort || ""}`, adapter);
1930
+ if (effective) adapterCache.set(`${tool}:${effective}`, adapter);
1923
1931
  }
1924
1932
 
1925
1933
  export function clearAdapterCache(): void {
@@ -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
+ }
@@ -123,25 +123,25 @@ export const SimulatedPlatform: FeishuPlatform = {
123
123
  },
124
124
 
125
125
  // ---- 头像 ----
126
- async setChatAvatar(_token, _chatId, _tool, _status) {
127
- // 模拟模式不需要头像
128
- },
129
-
130
- async getCodexUsageSummary() {
131
- return {
132
- fiveHour: { usedPercent: 0, remainingPercent: 100, resetAtEpochSeconds: null, resetAfterSeconds: null },
133
- weekly: { usedPercent: 0, remainingPercent: 100, resetAtEpochSeconds: null, resetAfterSeconds: null },
134
- rateLimitResetCreditsAvailable: null,
135
- rateLimitResetCredits: null,
136
- };
137
- },
138
-
139
- async consumeCodexRateLimitResetCredit(_redeemRequestId) {
140
- return { code: "no_credit" as const, windowsReset: 0 };
141
- },
142
-
143
- // ---- 图片下载 ----
144
- async getOrDownloadImage(_token, _messageId, fileKey) {
126
+ async setChatAvatar(_token, _chatId, _tool, _status) {
127
+ // 模拟模式不需要头像
128
+ },
129
+
130
+ async getCodexUsageSummary() {
131
+ return {
132
+ fiveHour: { usedPercent: 0, remainingPercent: 100, resetAtEpochSeconds: null, resetAfterSeconds: null },
133
+ weekly: { usedPercent: 0, remainingPercent: 100, resetAtEpochSeconds: null, resetAfterSeconds: null },
134
+ rateLimitResetCreditsAvailable: null,
135
+ rateLimitResetCredits: null,
136
+ };
137
+ },
138
+
139
+ async consumeCodexRateLimitResetCredit(_redeemRequestId) {
140
+ return { code: "no_credit" as const, windowsReset: 0 };
141
+ },
142
+
143
+ // ---- 图片下载 ----
144
+ async getOrDownloadImage(_token, _messageId, fileKey) {
145
145
  return join(SIM_DIR, "images", fileKey);
146
146
  },
147
147
 
@@ -174,4 +174,4 @@ export const SimulatedPlatform: FeishuPlatform = {
174
174
  };
175
175
 
176
176
  /** 模拟模式下的默认 chat_id(重新导出以保持向后兼容) */
177
- export { SIM_DEFAULT_CHAT_ID } from "./sim-store.ts";
177
+ export { SIM_DEFAULT_CHAT_ID } from "./sim-store.ts";