evolclaw 3.1.11 → 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.
- package/CHANGELOG.md +41 -0
- package/README.md +27 -2
- package/dist/agents/{resolve.js → baseagent.js} +34 -5
- package/dist/agents/claude-runner.js +120 -27
- package/dist/agents/codex-app-server-client.js +364 -0
- package/dist/agents/codex-runner.js +1069 -141
- package/dist/agents/gemini-runner.js +2 -2
- package/dist/agents/runner-types.js +28 -0
- package/dist/aun/aid/control-aid.js +67 -0
- package/dist/aun/aid/identity.js +20 -7
- package/dist/aun/aid/store.js +2 -2
- package/dist/aun/storage/download.js +1 -1
- package/dist/aun/storage/upload.js +13 -1
- package/dist/channels/aun.js +538 -325
- package/dist/channels/dingtalk.js +77 -140
- package/dist/channels/feishu.js +98 -151
- package/dist/channels/qqbot.js +75 -138
- package/dist/channels/wechat.js +75 -136
- package/dist/channels/wecom.js +75 -138
- package/dist/cli/agent.js +44 -13
- package/dist/cli/index.js +207 -46
- package/dist/cli/init-channel.js +38 -148
- package/dist/cli/init.js +192 -85
- package/dist/cli/model.js +1 -1
- package/dist/cli/stats.js +558 -0
- package/dist/cli/version.js +87 -0
- package/dist/cli/watch-msg.js +5 -2
- package/dist/config-store.js +48 -11
- package/dist/core/channel-loader.js +84 -82
- package/dist/core/command-handler.js +754 -172
- package/dist/core/daemon-file-cache.js +216 -0
- package/dist/core/evolagent-registry.js +4 -0
- package/dist/core/evolagent.js +28 -23
- package/dist/core/interaction-router.js +8 -0
- package/dist/core/message/command-handler-agent-control.js +215 -0
- package/dist/core/message/create-status.js +67 -0
- package/dist/core/message/im-renderer.js +35 -13
- package/dist/core/message/items-formatter.js +9 -1
- package/dist/core/message/message-bridge.js +52 -22
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-processor.js +336 -68
- package/dist/core/message/message-queue.js +15 -8
- package/dist/core/message/pending-hints.js +232 -0
- package/dist/core/message/response-depth.js +56 -0
- package/dist/core/model/model-catalog.js +1 -1
- package/dist/core/model/model-scope.js +40 -7
- package/dist/core/permission.js +9 -12
- package/dist/core/relation/peer-identity.js +16 -1
- package/dist/core/session/adapters/claude-session-file-adapter.js +48 -5
- package/dist/core/session/adapters/codex-session-file-adapter.js +4 -2
- package/dist/core/session/session-manager.js +27 -13
- package/dist/core/session/session-title.js +26 -0
- package/dist/core/stats/billing.js +151 -0
- package/dist/core/stats/budget.js +93 -0
- package/dist/core/stats/db.js +314 -0
- package/dist/core/stats/eck-vars.js +84 -0
- package/dist/core/stats/index.js +10 -0
- package/dist/core/stats/normalizer.js +78 -0
- package/dist/core/stats/query.js +760 -0
- package/dist/core/stats/writer.js +115 -0
- package/dist/core/trigger/manager.js +34 -0
- package/dist/core/trigger/parser.js +9 -3
- package/dist/core/trigger/scheduler.js +20 -17
- package/dist/{agents → eck}/kit-renderer.js +5 -1
- package/dist/{agents → eck}/manifest-engine.js +127 -35
- package/dist/{agents → eck}/message-renderer.js +26 -1
- package/dist/index.js +185 -8
- package/dist/ipc.js +22 -0
- package/dist/paths.js +7 -3
- package/dist/utils/cross-platform.js +23 -5
- package/dist/utils/ecweb-pair.js +20 -0
- package/dist/utils/stats.js +14 -0
- package/kits/docs/evolclaw/INDEX.md +3 -1
- package/kits/docs/evolclaw/fs-architecture.md +1215 -0
- package/kits/docs/evolclaw/fs.md +131 -0
- package/kits/docs/evolclaw/group-fs.md +209 -0
- package/kits/docs/evolclaw/stats.md +70 -0
- package/kits/docs/venues/aun-group.md +29 -6
- package/kits/docs/venues/group.md +5 -4
- package/kits/eck_manifest.json +12 -0
- package/kits/eck_message_manifest.json +30 -3
- package/kits/rules/05-venue.md +1 -1
- package/kits/templates/message-fragments/inject-default.md +2 -0
- package/kits/templates/message-fragments/item.md +1 -1
- package/kits/templates/system-fragments/response-depth.md +16 -0
- package/package.json +4 -4
- package/dist/agents/baseagent-normalize.js +0 -19
- package/dist/core/relation/peer-key.js +0 -16
- package/dist/utils/channel-helpers.js +0 -46
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
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
|
+
|
|
20
|
+
## v3.2.0 (2026-06-05)
|
|
21
|
+
|
|
22
|
+
### New Features
|
|
23
|
+
|
|
24
|
+
- **控制 AID(control AID)** — 进程级控制身份,启动时以 `pureIdentity` 模式连接 AUN(跳过 evolagent onboarding)。生成采用 `ec+5位数字` 候选 + PKI 权威查重 + fail-fast;缺失时进入 init(TTY 守卫,headless 仅告警)。`evolclaw status` 新增控制 AID 连接状态展示
|
|
25
|
+
- **Menu 协议 / agent 控制面** — `/system`、`/agent` 迁移到 owners 鉴权;trigger 接入菜单协议(直连 manager/scheduler,无文本拼装);agent 创建支持 accepted-return + 构建进度(`create-status.json` + `onPhase` 回调 + model/chatmode);新增 agent query/options 与 project 兜底
|
|
26
|
+
- **进程级 owners 配置层** — 新增 `evolclaw.json` 进程级配置,`config.json` 合并入 `evolclaw.json`(弃用 `ProcessConfig`);process-level owners 从 `defaults.json` 迁移到 `evolclaw.json`;新增 `isProcessLevelOwner` 鉴权辅助
|
|
27
|
+
- **Observer 模式重构** — AUN owner 间消息互转发;`evolclaw init aun` 简化为 owner-only 配置;`mergeForAgent` 输出补全 `dispatch`/`observable`
|
|
28
|
+
|
|
29
|
+
### Improvements
|
|
30
|
+
|
|
31
|
+
- **统一 FileCache** — 新增 mtime-gated 统一文件缓存,迁移 relation prefs、manifest+fragment、persona/working 读取与 model-scope 缓存;新增 Cache watch 视图监控缓存命中
|
|
32
|
+
- **消息信封渲染补全** — @AID 列表与群名补全,修复 proximity 信息丢失
|
|
33
|
+
- **Idle 监控解耦** — idle notify/warn 改为事件总线发布(`runner:idle-notify`/`runner:idle-warn`,携带 idleSec/事件数/工具名),与通道发送解耦;超时诊断信息下沉到事件 payload
|
|
34
|
+
|
|
35
|
+
### Bug Fixes
|
|
36
|
+
|
|
37
|
+
- **多 agent 群广播去重** — 消息队列改按 `sessionKey:messageId` 去重,允许同一消息广播给多个 agent
|
|
38
|
+
- **AUN owner 入站转发** — owner 来源的入站消息正确转发给其它 owners
|
|
39
|
+
- **单 agent reload 缓存失效** — reload 时失效 identity 层缓存
|
|
40
|
+
- **CLI /slist 过滤** — 过滤掉程序化 SDK session,不在 `/slist` 显示
|
|
41
|
+
- **控制 AID 查重走 GET** — 改用 `store.resolve`(GET)替代 `store.exists`(HEAD),规避部分 Gateway 对 HEAD 空响应断连导致的误判
|
|
42
|
+
- **启动门控修复** — gate 直接调 `initTail` 而非完整 `cmdInit`;AID 生成失败时跳过 owners 提示;抑制控制 AID gate 路径的 SDK keystore 日志
|
|
43
|
+
|
|
3
44
|
## v3.1.11 (2026-06-04)
|
|
4
45
|
|
|
5
46
|
### Improvements
|
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` - 中断当前任务
|
|
@@ -267,10 +266,36 @@ evolclaw/
|
|
|
267
266
|
- `/restart` - 重启服务(自愈机制)
|
|
268
267
|
- `/repair` - 检查并修复会话
|
|
269
268
|
|
|
269
|
+
### ⚠️ 进程级 menu 操作鉴权(v3.2 Breaking)
|
|
270
|
+
|
|
271
|
+
进程级 menu 操作(`/system restart/upgrade`、`/agent` agent 生命周期管理)的鉴权已迁移到
|
|
272
|
+
`evolclaw.json` 顶层 `owners` 字段(v3.2 起,不再读 `agents/defaults.json`)。
|
|
273
|
+
升级后**必须**在 `evolclaw.json` 配置 `owners`,否则这些操作一律返回 `FORBIDDEN`(daemon 启动时也会 warn 提示)。
|
|
274
|
+
|
|
275
|
+
```json
|
|
276
|
+
{
|
|
277
|
+
"owners": ["eleans-2022.agentid.pub"]
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
`evolclaw init` 交互流程会在生成控制 AID 后提示录入 owners(可跳过后手动编辑)。
|
|
282
|
+
|
|
283
|
+
- **`owners`**:进程级管理者 AID 名单。可执行 `/system`(重启/升级)与 `/agent`
|
|
284
|
+
(create / delete / enable / disable / list / show)。
|
|
285
|
+
- 关系级的 `/trigger`(set / cancel / update / list)仍走 channel 角色(owner/admin)+ scoped 鉴权,**不**受 `owners` 影响。
|
|
286
|
+
- `/agent create` 为「受理即返回」:立即回 `{ accepted: true, aid }`,后台跑完整创建流程并把各
|
|
287
|
+
环节写入 `agents/<aid>/create-status.json`;客户端用 `menu.query name=agent args={aid}` 轮询
|
|
288
|
+
`createProgress.status` 直到 `ready` / `failed`。
|
|
289
|
+
|
|
290
|
+
### 控制 AID(control AID)
|
|
291
|
+
|
|
292
|
+
v3.2 新增进程级身份标识。启动时自动生成 `ec+5位数字.agentid.pub` 格式的控制 AID,以
|
|
293
|
+
`pureIdentity` 模式接入 AUN 网络(跳过 evolagent onboarding)。`evolclaw status` 可查看控制 AID 连接状态。
|
|
294
|
+
|
|
270
295
|
## 技术栈
|
|
271
296
|
|
|
272
297
|
- **运行时**:Node.js >= 22 + TypeScript(ES modules)
|
|
273
|
-
- **AI
|
|
298
|
+
- **AI 后端**:@anthropic-ai/claude-agent-sdk >= 0.2.100、Codex CLI app-server、Gemini CLI
|
|
274
299
|
- **消息渠道**:飞书(@larksuiteoapi/node-sdk)、微信(ClawBot ilink API)、钉钉(dingtalk-stream)、QQ频道(pure-qqbot)、企业微信(AI Bot API)、AUN 网络
|
|
275
300
|
- **数据存储**:文件系统(per-chat 目录) + JSONL(CLI 共用)
|
|
276
301
|
- **测试框架**:Vitest
|
|
@@ -1,14 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Baseagent credential
|
|
2
|
+
* Baseagent identity + credential resolution.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
|
|
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 './
|
|
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
|
-
/**
|
|
114
|
-
function
|
|
115
|
-
|
|
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
|
-
}, {
|
|
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
|
-
}, {
|
|
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
|
|
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
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
const
|
|
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 /
|
|
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:
|
|
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] }],
|