evolclaw 3.2.0 → 3.3.0

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 (83) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +1 -2
  3. package/dist/agents/{resolve.js → baseagent.js} +34 -5
  4. package/dist/agents/claude-runner.js +120 -27
  5. package/dist/agents/codex-app-server-client.js +364 -0
  6. package/dist/agents/codex-runner.js +1069 -141
  7. package/dist/agents/gemini-runner.js +2 -2
  8. package/dist/agents/runner-types.js +28 -0
  9. package/dist/aun/aid/store.js +1 -1
  10. package/dist/aun/storage/download.js +1 -1
  11. package/dist/aun/storage/upload.js +13 -1
  12. package/dist/channels/aun.js +406 -293
  13. package/dist/channels/dingtalk.js +77 -140
  14. package/dist/channels/feishu.js +97 -150
  15. package/dist/channels/qqbot.js +75 -138
  16. package/dist/channels/wechat.js +75 -136
  17. package/dist/channels/wecom.js +75 -138
  18. package/dist/cli/agent.js +8 -5
  19. package/dist/cli/index.js +177 -44
  20. package/dist/cli/init.js +33 -6
  21. package/dist/cli/model.js +1 -1
  22. package/dist/cli/stats.js +558 -0
  23. package/dist/cli/version.js +87 -0
  24. package/dist/cli/watch-msg.js +5 -2
  25. package/dist/config-store.js +12 -6
  26. package/dist/core/channel-loader.js +84 -82
  27. package/dist/core/command-handler.js +473 -114
  28. package/dist/core/evolagent-registry.js +1 -0
  29. package/dist/core/evolagent.js +1 -1
  30. package/dist/core/interaction-router.js +8 -0
  31. package/dist/core/message/command-handler-agent-control.js +63 -1
  32. package/dist/core/message/im-renderer.js +35 -13
  33. package/dist/core/message/items-formatter.js +9 -1
  34. package/dist/core/message/message-bridge.js +49 -21
  35. package/dist/core/message/message-log.js +1 -0
  36. package/dist/core/message/message-processor.js +295 -35
  37. package/dist/core/message/message-queue.js +2 -2
  38. package/dist/core/message/pending-hints.js +232 -0
  39. package/dist/core/message/response-depth.js +56 -0
  40. package/dist/core/model/model-catalog.js +1 -1
  41. package/dist/core/model/model-scope.js +2 -2
  42. package/dist/core/permission.js +9 -12
  43. package/dist/core/relation/peer-identity.js +16 -1
  44. package/dist/core/session/adapters/codex-session-file-adapter.js +4 -2
  45. package/dist/core/session/session-manager.js +27 -13
  46. package/dist/core/session/session-title.js +26 -0
  47. package/dist/core/stats/billing.js +151 -0
  48. package/dist/core/stats/budget.js +93 -0
  49. package/dist/core/stats/db.js +314 -0
  50. package/dist/core/stats/eck-vars.js +84 -0
  51. package/dist/core/stats/index.js +10 -0
  52. package/dist/core/stats/normalizer.js +78 -0
  53. package/dist/core/stats/query.js +760 -0
  54. package/dist/core/stats/writer.js +115 -0
  55. package/dist/core/trigger/manager.js +34 -0
  56. package/dist/core/trigger/parser.js +9 -3
  57. package/dist/core/trigger/scheduler.js +20 -17
  58. package/dist/{agents → eck}/manifest-engine.js +20 -1
  59. package/dist/{agents → eck}/message-renderer.js +24 -1
  60. package/dist/index.js +130 -8
  61. package/dist/ipc.js +17 -1
  62. package/dist/utils/cross-platform.js +23 -5
  63. package/dist/utils/ecweb-pair.js +20 -0
  64. package/dist/utils/stats.js +14 -0
  65. package/kits/docs/evolclaw/INDEX.md +3 -1
  66. package/kits/docs/evolclaw/fs-architecture.md +1215 -0
  67. package/kits/docs/evolclaw/fs.md +131 -0
  68. package/kits/docs/evolclaw/group-fs.md +209 -0
  69. package/kits/docs/evolclaw/stats.md +70 -0
  70. package/kits/docs/venues/aun-group.md +29 -6
  71. package/kits/docs/venues/group.md +5 -4
  72. package/kits/eck_manifest.json +12 -0
  73. package/kits/eck_message_manifest.json +30 -3
  74. package/kits/rules/05-venue.md +1 -1
  75. package/kits/templates/message-fragments/inject-default.md +2 -0
  76. package/kits/templates/system-fragments/response-depth.md +16 -0
  77. package/package.json +4 -4
  78. package/dist/agents/baseagent-normalize.js +0 -19
  79. package/dist/core/relation/peer-key.js +0 -16
  80. package/dist/evolclaw-config.js +0 -11
  81. package/dist/utils/channel-helpers.js +0 -46
  82. /package/dist/core/{cache/file-cache.js → daemon-file-cache.js} +0 -0
  83. /package/dist/{agents → eck}/kit-renderer.js +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## v3.3.0 (2026-06-10)
4
+
5
+ ### New Features
6
+
7
+ - **Thread 会话继承 baseagent** — 创建 thread 会话时自动继承父会话的 `agentId` 及 baseagent 配置,多线程场景无需重新切换
8
+ - **Codex runner 增强** — 新增 CLI 版本检测、streaming delta 支持、server 可用性检查;codex-app-server-client 补充类型定义
9
+ - **Menu 话题管理** — command-handler 新增话题菜单的权限判定(`canReadTopics`/`canDeleteTopic`)与格式化(`buildTopicMenuItem`/`resolveMenuChatType`)
10
+
11
+ ### Improvements
12
+
13
+ - **Channel plugin 接口统一** — `ChannelPlugin` 从 `isEnabled/createChannel/createChannels` 收敛为单一 `createInstance(inst, ctx)` 单实例模型,新增 `ChannelBuildContext` 与 `showActivities` 共享策略;六个渠道(aun/feishu/wechat/dingtalk/qqbot/wecom)同步迁移
14
+ - **ECWeb 控制台重构** — control source 拆分为 `system`(evolclaw/fastaun/evolclaw-web 三包版本与健康检查)和 `triggers`(定时任务管理),前端联动更新
15
+ - **Trigger 失败统计** — Trigger 新增 `failCount`/`lastResult` 字段,scheduler/manager/parser 同步
16
+ - **Runner 类型模块化** — 抽出 `runner-types.ts` 统一共享类型,消除各 runner 重复声明
17
+ - **Agent AID 展示** — `AgentInfo` 新增 `aid` 字段,`ec agent` 命令展示 agent AID
18
+ - **缓存模块更名** — `read-cache` 重命名为 `daemon-file-cache`,语义更清晰
19
+
3
20
  ## v3.2.0 (2026-06-05)
4
21
 
5
22
  ### New Features
package/README.md CHANGED
@@ -250,7 +250,6 @@ evolclaw/
250
250
  - `/perm [mode]` - 查看或切换权限模式(auto / edit / default / readonly)
251
251
 
252
252
  **系统管理**:
253
- - `/clear` - 清空对话历史
254
253
  - `/compact` - 压缩会话上下文
255
254
  - `/rewind <turn>` - 回退会话到指定轮次
256
255
  - `/stop` - 中断当前任务
@@ -296,7 +295,7 @@ v3.2 新增进程级身份标识。启动时自动生成 `ec+5位数字.agentid.
296
295
  ## 技术栈
297
296
 
298
297
  - **运行时**:Node.js >= 22 + TypeScript(ES modules)
299
- - **AI SDK**:@anthropic-ai/claude-agent-sdk >= 0.2.100、@openai/codex-sdk、Gemini CLI
298
+ - **AI 后端**:@anthropic-ai/claude-agent-sdk >= 0.2.100、Codex CLI app-server、Gemini CLI
300
299
  - **消息渠道**:飞书(@larksuiteoapi/node-sdk)、微信(ClawBot ilink API)、钉钉(dingtalk-stream)、QQ频道(pure-qqbot)、企业微信(AI Bot API)、AUN 网络
301
300
  - **数据存储**:文件系统(per-chat 目录) + JSONL(CLI 共用)
302
301
  - **测试框架**:Vitest
@@ -1,14 +1,37 @@
1
1
  /**
2
- * Baseagent credential resolvers.
2
+ * Baseagent identity + credential resolution.
3
3
  *
4
- * 输入是 Config 形态(`config.agents.<baseagent>` + override)。启动期由 index.ts
5
- * primaryAgent.config.baseagents 构造一个 syntheticConfig 喂入;各 plugin
6
- * createAgent 也各自构造 syntheticConfig。
4
+ * 两部分:
5
+ * 1. normalizeBaseagent —— 把用户输入的各种别名(cc / claude-code / gemini cli …)
6
+ * 归一到 canonical 标识 + 展示名。
7
+ * 2. resolve*Config —— 各后端的凭证解析。输入是 Config 形态
8
+ * (`config.agents.<baseagent>` + override)。启动期由 index.ts 从
9
+ * primaryAgent.config.baseagents 构造一个 syntheticConfig 喂入;各 plugin 的
10
+ * createAgent 也各自构造 syntheticConfig。
7
11
  */
8
12
  import fs from 'fs';
9
13
  import path from 'path';
10
14
  import os from 'os';
11
15
  import { commandExists } from '../utils/cross-platform.js';
16
+ const BASEAGENT_ALIASES = {
17
+ claude: { canonical: 'claude', displayName: 'Claude Code' },
18
+ cc: { canonical: 'claude', displayName: 'Claude Code' },
19
+ 'claude-code': { canonical: 'claude', displayName: 'Claude Code' },
20
+ 'claude code': { canonical: 'claude', displayName: 'Claude Code' },
21
+ claudecode: { canonical: 'claude', displayName: 'Claude Code' },
22
+ codex: { canonical: 'codex', displayName: 'Codex' },
23
+ 'codex-cli': { canonical: 'codex', displayName: 'Codex' },
24
+ 'codex cli': { canonical: 'codex', displayName: 'Codex' },
25
+ gemini: { canonical: 'gemini', displayName: 'Gemini CLI' },
26
+ 'gemini-cli': { canonical: 'gemini', displayName: 'Gemini CLI' },
27
+ 'gemini cli': { canonical: 'gemini', displayName: 'Gemini CLI' },
28
+ geminicli: { canonical: 'gemini', displayName: 'Gemini CLI' },
29
+ hermes: { canonical: 'hermes', displayName: 'Hermes' },
30
+ };
31
+ export function normalizeBaseagent(input) {
32
+ const key = String(input || '').trim().toLowerCase().replace(/_/g, '-');
33
+ return BASEAGENT_ALIASES[key] || { canonical: 'unknown', displayName: input ? String(input) : 'Unknown' };
34
+ }
12
35
  function loadClaudeSettings() {
13
36
  try {
14
37
  const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
@@ -109,7 +132,13 @@ export function resolveOpenaiConfig(config, override) {
109
132
  || config.agents?.codex?.effort
110
133
  || config.agents?.codex?.reasoning
111
134
  || undefined;
112
- return { apiKey, baseUrl, model, effort };
135
+ const enableRequestUserInput = override?.enableRequestUserInput
136
+ ?? config.agents?.codex?.enableRequestUserInput
137
+ ?? true;
138
+ const approvalsReviewer = override?.approvalsReviewer
139
+ ?? config.agents?.codex?.approvalsReviewer
140
+ ?? undefined;
141
+ return { apiKey, baseUrl, model, effort, enableRequestUserInput, approvalsReviewer };
113
142
  }
114
143
  export function resolveGoogleConfig(config, override) {
115
144
  const googleCfg = config.agents?.gemini;
@@ -1,6 +1,6 @@
1
1
  import { query, forkSession as sdkForkSession, getSessionMessages as sdkGetSessionMessages } from '@anthropic-ai/claude-agent-sdk';
2
2
  import { ensureDir } from '../utils/atomic-write.js';
3
- import { resolveAnthropicConfig } from './resolve.js';
3
+ import { resolveAnthropicConfig } from './baseagent.js';
4
4
  import { DEFAULT_PERMISSION_MODE } from '../types.js';
5
5
  import { renderActionAsText } from '../core/interaction-router.js';
6
6
  import { buildEnvelope, sendInteractionPayload } from '../core/message/message-processor.js';
@@ -10,6 +10,8 @@ import os from 'os';
10
10
  import { logger } from '../utils/logger.js';
11
11
  import { checkBlacklist, checkReadonly, summarizeToolInput } from '../core/permission.js';
12
12
  import { encodePath } from '../utils/cross-platform.js';
13
+ import { contextTokensForUsage, usageForContext } from './runner-types.js';
14
+ export { hasCompact, hasModelSwitcher, hasPermissionController } from './runner-types.js';
13
15
  // ── 模型别名解析 ──
14
16
  // SDK 内置的别名表可能落后于代理实际可用的最新模型,
15
17
  // 因此优先从 {baseUrl}/models 动态获取各系列最新版本,失败则回退静态表。
@@ -110,9 +112,21 @@ function applyContextWindow(modelId) {
110
112
  return `${modelId}[1m]`;
111
113
  return modelId;
112
114
  }
113
- /** 根据 SDK model 串(含 [1m] 后缀)返回合适的 autoCompactWindow 值。 */
114
- function contextWindowFor(sdkModel) {
115
- return /\[1m\]$/.test(sdkModel) ? 900000 : 200000;
115
+ /** 真实上下文窗口大小:1M 模型 = 1000000,否则 200000 */
116
+ function realContextWindowFor(sdkModel) {
117
+ if (/\[1m\]$/.test(sdkModel))
118
+ return 1000000;
119
+ if (/deepseek-v4/i.test(sdkModel))
120
+ return 1000000; // deepseek-v4 系列原生 1M 窗口(无 [1m] 后缀)
121
+ return 200000;
122
+ }
123
+ /** autoCompact 触发阈值:1M 模型 = 900000(留 ~100k buffer),否则 200000 */
124
+ function autoCompactWindowFor(sdkModel) {
125
+ if (/\[1m\]$/.test(sdkModel))
126
+ return 900000;
127
+ if (/deepseek-v4/i.test(sdkModel))
128
+ return 900000;
129
+ return 200000;
116
130
  }
117
131
  /** 解析别名 + 追加 1M 后缀,得到最终交给 SDK 的 model 串。 */
118
132
  function resolveSdkModel(model, baseUrl) {
@@ -168,19 +182,9 @@ class MessageStream {
168
182
  }
169
183
  }
170
184
  }
171
- // ── 类型守卫 ──
172
- export function hasModelSwitcher(agent) {
173
- return typeof agent.setModel === 'function' && typeof agent.listModels === 'function';
174
- }
175
- export function hasPermissionController(agent) {
176
- return typeof agent.setMode === 'function' && typeof agent.listModes === 'function';
177
- }
178
- export function hasCompact(agent) {
179
- return typeof agent.compact === 'function';
180
- }
181
185
  export class AgentRunner {
182
186
  name = 'claude';
183
- capabilities = { clear: true, compact: true, fork: true };
187
+ capabilities = { clear: true, compact: true, fork: true, askUserQuestion: true, planApproval: true, fileRewind: 'checkpoint' };
184
188
  apiKey;
185
189
  model;
186
190
  effort;
@@ -383,7 +387,6 @@ export class AgentRunner {
383
387
  },
384
388
  channelId: permCtx.channelId,
385
389
  sessionId,
386
- expiresAt: Date.now() + 5 * 60 * 1000,
387
390
  };
388
391
  }
389
392
  else {
@@ -411,11 +414,11 @@ export class AgentRunner {
411
414
  },
412
415
  channelId: permCtx.channelId,
413
416
  sessionId,
414
- expiresAt: Date.now() + 5 * 60 * 1000,
415
417
  };
416
418
  }
417
419
  let cardSent = false;
418
420
  try {
421
+ await permCtx.flushPending?.();
419
422
  const envelope = buildEnvelope({
420
423
  taskId: permCtx.taskId,
421
424
  channel: permCtx.channel ?? permCtx.adapter.channelName,
@@ -433,6 +436,7 @@ export class AgentRunner {
433
436
  logger.warn(`[AgentRunner] AskUserQuestion card send failed for q${i}:`, err);
434
437
  }
435
438
  if (!cardSent) {
439
+ await permCtx.flushPending?.();
436
440
  const firstLabel = q.options[0]?.label || '';
437
441
  answers[q.question] = q.multiSelect ? [firstLabel] : firstLabel;
438
442
  if (sendPrompt) {
@@ -530,7 +534,7 @@ export class AgentRunner {
530
534
  else {
531
535
  resolve(action.trim());
532
536
  }
533
- }, { timeoutMs: 120_000, onTimeout: () => resolve(q.options[0]?.label || ''), initiatorId: permCtx.userId, fallbackCommand: 'ask' });
537
+ }, { initiatorId: permCtx.userId, fallbackCommand: 'ask' });
534
538
  });
535
539
  answers[q.question] = answer;
536
540
  }
@@ -606,6 +610,7 @@ export class AgentRunner {
606
610
  },
607
611
  };
608
612
  try {
613
+ await permCtx.flushPending?.();
609
614
  const envelope = buildEnvelope({
610
615
  taskId: permCtx.taskId,
611
616
  channel: permCtx.channel ?? permCtx.adapter.channelName,
@@ -663,6 +668,7 @@ export class AgentRunner {
663
668
  buttonArgMap: { approve: '1', reject: '2' },
664
669
  },
665
670
  };
671
+ await permCtx.flushPending?.();
666
672
  await sendPrompt(renderActionAsText(fallbackInteraction));
667
673
  permCtx.interactionRouter.unmarkWaiting(sessionId);
668
674
  return new Promise((resolve) => {
@@ -674,11 +680,12 @@ export class AgentRunner {
674
680
  else {
675
681
  resolve({ behavior: 'allow', updatedInput: input, decisionClassification: 'user_temporary' });
676
682
  }
677
- }, { timeoutMs: 300_000, onTimeout: () => resolve({ behavior: 'allow', updatedInput: input, decisionClassification: 'user_temporary' }), initiatorId: permCtx.userId, fallbackCommand: 'ask' });
683
+ }, { initiatorId: permCtx.userId, fallbackCommand: 'ask' });
678
684
  });
679
685
  }
680
686
  // 无交互能力,发提示后直接 allow
681
687
  permCtx?.interactionRouter?.unmarkWaiting(sessionId);
688
+ await permCtx.flushPending?.();
682
689
  await sendPrompt('📋 计划审批\nAI 已完成规划,自动批准执行。');
683
690
  return { behavior: 'allow', updatedInput: input, decisionClassification: 'user_temporary' };
684
691
  }
@@ -692,6 +699,9 @@ export class AgentRunner {
692
699
  const toolUseNames = new Map();
693
700
  let turnCount = 0;
694
701
  const seenMessageIds = new Set();
702
+ let lastModelCall;
703
+ // 流式收集各次大模型调用(fallback:SDK iterations 为空时使用)
704
+ const collectedCalls = [];
695
705
  try {
696
706
  for await (const event of sdkStream) {
697
707
  // 提取 session_id(任意 SDK 事件都可能携带)
@@ -700,6 +710,38 @@ export class AgentRunner {
700
710
  this.updateSessionId(sessionId, event.session_id);
701
711
  yield { type: 'session_id', sessionId: event.session_id };
702
712
  }
713
+ if (event.type === 'stream_event') {
714
+ const streamEvent = event.event;
715
+ if (streamEvent?.type === 'message_start' && streamEvent.message?.usage) {
716
+ lastModelCall = {
717
+ uuid: event.uuid,
718
+ model: streamEvent.message.model,
719
+ tokenUsage: streamEvent.message.usage,
720
+ };
721
+ // 流式收集:每个 message_start = 一次新的大模型调用
722
+ collectedCalls.push({
723
+ call_index: collectedCalls.length,
724
+ model: streamEvent.message.model ?? callModel ?? this.model,
725
+ request_id: event.request_id,
726
+ tokenUsage: { ...streamEvent.message.usage },
727
+ });
728
+ }
729
+ else if (streamEvent?.type === 'message_delta' && streamEvent.usage) {
730
+ lastModelCall = {
731
+ ...lastModelCall,
732
+ uuid: lastModelCall?.uuid ?? event.uuid,
733
+ tokenUsage: {
734
+ ...(lastModelCall?.tokenUsage ?? {}),
735
+ ...streamEvent.usage,
736
+ },
737
+ };
738
+ // 将 message_delta 的 usage 合并进当前(最后一次)收集的调用
739
+ const last = collectedCalls[collectedCalls.length - 1];
740
+ if (last)
741
+ last.tokenUsage = { ...last.tokenUsage, ...streamEvent.usage };
742
+ }
743
+ continue;
744
+ }
703
745
  // system: compact_boundary → compact
704
746
  if (event.type === 'system' && event.subtype === 'compact_boundary') {
705
747
  yield {
@@ -730,6 +772,18 @@ export class AgentRunner {
730
772
  seenMessageIds.add(msgId);
731
773
  turnCount++;
732
774
  }
775
+ if (event.message.usage) {
776
+ lastModelCall = {
777
+ ...lastModelCall,
778
+ messageId: event.message.id,
779
+ requestId: event.request_id,
780
+ model: event.message.model,
781
+ tokenUsage: {
782
+ ...event.message.usage,
783
+ ...(lastModelCall?.tokenUsage ?? {}),
784
+ },
785
+ };
786
+ }
733
787
  // 统计本轮 base agent 全部输出字符数(text + tool_use input)
734
788
  let turnOutputChars = 0;
735
789
  for (const content of event.message.content) {
@@ -790,19 +844,55 @@ export class AgentRunner {
790
844
  const cleanResult = typeof event.result === 'string'
791
845
  ? event.result.replace(/<thinking>[\s\S]*?<\/thinking>\s*/g, '').trim()
792
846
  : event.result;
793
- // 从 usage 三项求和得到当前上下文占用(与 claude-hud getTotalTokens 相同算法)
847
+ // 从 usage 求当前上下文占用。
848
+ // Claude:input_tokens 是净输入(不含 cache),三项求和 = 实际上下文长度。
849
+ // 非 Claude(DeepSeek/OpenAI 兼容):cache_read 是服务端 KV cache 不占上下文窗口,
850
+ // input_tokens 本身就是完整的上下文输入量。
794
851
  const u = event.usage;
795
- const totalTokens = u
796
- ? (u.input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0)
797
- : 0;
798
- const maxTokens = sdkModel ? contextWindowFor(sdkModel) : 200000;
852
+ const effectiveModel = callModel ?? this.model;
853
+ const isClaudeModel = effectiveModel?.startsWith('claude');
854
+ const totalTokens = contextTokensForUsage(u, !!isClaudeModel);
855
+ const contextWindowTokens = sdkModel ? realContextWindowFor(sdkModel) : 200000;
856
+ const autoCompactTokens = sdkModel ? autoCompactWindowFor(sdkModel) : 200000;
799
857
  const contextUsage = totalTokens > 0 ? {
800
858
  totalTokens,
801
- maxTokens,
802
- percentage: Math.round((totalTokens / maxTokens) * 100),
859
+ maxTokens: contextWindowTokens,
860
+ percentage: Math.round((totalTokens / contextWindowTokens) * 100),
861
+ autoCompactTokens,
803
862
  model: callModel ?? this.model,
804
863
  effort: callEffort ?? this.effort,
805
864
  } : undefined;
865
+ if (lastModelCall?.tokenUsage) {
866
+ const lastUsageForContext = usageForContext(lastModelCall.tokenUsage);
867
+ const lastTotalTokens = contextTokensForUsage(lastUsageForContext, !!isClaudeModel);
868
+ lastModelCall = {
869
+ ...lastModelCall,
870
+ contextUsage: lastTotalTokens > 0 ? {
871
+ totalTokens: lastTotalTokens,
872
+ maxTokens: contextWindowTokens,
873
+ percentage: Math.round((lastTotalTokens / contextWindowTokens) * 100),
874
+ autoCompactTokens,
875
+ model: callModel ?? this.model,
876
+ effort: callEffort ?? this.effort,
877
+ } : undefined,
878
+ };
879
+ }
880
+ // 组装 modelCalls:优先 SDK iterations,fallback 流式收集,兜底降级单行。
881
+ const callModel_ = callModel ?? this.model;
882
+ let modelCalls;
883
+ const iterArr = Array.isArray(u?.iterations) && u.iterations.length > 0 ? u.iterations : null;
884
+ if (iterArr) {
885
+ modelCalls = iterArr.map((it, i) => ({
886
+ call_index: i, model: callModel_, tokenUsage: it,
887
+ }));
888
+ }
889
+ else if (collectedCalls.length > 0) {
890
+ modelCalls = collectedCalls;
891
+ }
892
+ else if (u) {
893
+ // 降级:无逐次数据,写一条累计行
894
+ modelCalls = [{ call_index: 0, model: callModel_, tokenUsage: u, degraded: true }];
895
+ }
806
896
  yield {
807
897
  type: 'complete',
808
898
  result: cleanResult,
@@ -817,6 +907,8 @@ export class AgentRunner {
817
907
  numTurns: event.num_turns,
818
908
  tokenUsage: event.usage,
819
909
  contextUsage,
910
+ lastModelCall,
911
+ modelCalls,
820
912
  };
821
913
  // result 是 SDK 流的终结事件,不再等待后续(防止 interrupt 后流不关闭导致挂起)
822
914
  return;
@@ -1030,11 +1122,12 @@ export class AgentRunner {
1030
1122
  model: sdkModel,
1031
1123
  ...(callEffort ? { effort: callEffort } : {}),
1032
1124
  ...(this.claudeExecutablePath ? { pathToClaudeCodeExecutable: this.claudeExecutablePath } : {}),
1033
- autoCompactWindow: contextWindowFor(sdkModel),
1125
+ autoCompactWindow: autoCompactWindowFor(sdkModel),
1034
1126
  advisorModel: 'haiku',
1035
1127
  canUseTool: canUseToolCallback,
1036
1128
  permissionMode: sdkPermissionMode,
1037
1129
  persistSession: true,
1130
+ includePartialMessages: true,
1038
1131
  enableFileCheckpointing: true,
1039
1132
  hooks: {
1040
1133
  PreCompact: [{ matcher: '.*', hooks: [preCompactHook] }],