evolclaw 3.1.5 → 3.1.7

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 (51) hide show
  1. package/CHANGELOG.md +68 -3
  2. package/dist/agents/claude-runner.js +69 -24
  3. package/dist/agents/kit-renderer.js +78 -321
  4. package/dist/agents/manifest-engine.js +243 -0
  5. package/dist/agents/message-renderer.js +112 -0
  6. package/dist/aun/aid/agentmd.js +10 -3
  7. package/dist/aun/msg/group.js +2 -2
  8. package/dist/channels/aun.js +154 -18
  9. package/dist/channels/dingtalk.js +1 -1
  10. package/dist/channels/feishu.js +31 -9
  11. package/dist/channels/qqbot.js +1 -1
  12. package/dist/channels/wechat.js +1 -1
  13. package/dist/channels/wecom.js +1 -1
  14. package/dist/cli/agent.js +10 -11
  15. package/dist/cli/bench.js +1 -5
  16. package/dist/cli/help.js +8 -0
  17. package/dist/cli/index.js +91 -128
  18. package/dist/cli/init.js +37 -21
  19. package/dist/cli/link-rules.js +1 -7
  20. package/dist/cli/model.js +231 -6
  21. package/dist/config-store.js +1 -22
  22. package/dist/core/command-handler.js +181 -48
  23. package/dist/core/evolagent.js +0 -18
  24. package/dist/core/message/im-renderer.js +9 -20
  25. package/dist/core/message/message-bridge.js +9 -10
  26. package/dist/core/message/message-processor.js +188 -39
  27. package/dist/core/message/message-queue.js +15 -1
  28. package/dist/core/relation/peer-identity.js +23 -11
  29. package/dist/core/trigger/parser.js +4 -4
  30. package/dist/core/trigger/scheduler.js +43 -13
  31. package/dist/index.js +102 -52
  32. package/dist/ipc.js +1 -1
  33. package/dist/utils/error-utils.js +6 -0
  34. package/dist/utils/process-introspect.js +7 -5
  35. package/kits/docs/INDEX.md +4 -8
  36. package/kits/docs/context-assembly.md +1 -0
  37. package/kits/docs/evolclaw/INDEX.md +43 -0
  38. package/kits/docs/evolclaw/group.md +13 -6
  39. package/kits/docs/evolclaw/model.md +51 -0
  40. package/kits/docs/evolclaw/msg.md +5 -0
  41. package/kits/docs/venues/group.md +13 -1
  42. package/kits/eck_manifest.json +9 -0
  43. package/kits/eck_message_manifest.json +14 -0
  44. package/kits/rules/06-channel.md +5 -1
  45. package/kits/templates/message-fragments/item.md +2 -0
  46. package/kits/templates/system-fragments/baseagent.md +7 -1
  47. package/kits/templates/system-fragments/channel.md +7 -5
  48. package/kits/templates/system-fragments/commands.md +19 -0
  49. package/kits/templates/system-fragments/session.md +12 -0
  50. package/kits/templates/system-fragments/venue.md +15 -0
  51. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -1,13 +1,78 @@
1
1
  # Changelog
2
2
 
3
+ ## v3.1.7 (2026-06-04)
4
+
5
+ ### New Features
6
+
7
+ - **观察者模式(observable)** — `AgentConfig.observable` 开启后,AUN 入站/出站消息各转发一份给顶层 `owners[]`(`observer.forward` 格式),便于 owner 旁路监听 agent 会话
8
+
9
+ ### Improvements
10
+
11
+ - **Trigger channelType 运行时解析** — 移除存储的 `Trigger.targetChannelType`,`buildSyntheticMessage` 改为运行时从 channel key 解析;trigger scheduler 注入与 `__upgrade-check` 播种移到 channel 注册之后,确保 `channelTypeMap` 完整
12
+
13
+ ### Bug Fixes
14
+
15
+ - **watch 插件改名 ecweb → evolclaw-web** — npm 拒绝 `ecweb`(与 `rrweb` 过于相似),改为独立包 `evolclaw-web` 发布;`evolclaw watch` 的安装/调用引用同步更新
16
+ - **cron trigger 紧密循环** — 定时器提前 ~1ms 唤醒(如 `0 9 * * *` 在 08:59:59.999 触发)会使下次触发落入 `now+50` 窗口被同轮重复触发;`nextCronFireAt` 从窗口外重算,循环守卫改读实时时钟确保收敛
17
+
18
+ ## v3.1.6 (2026-06-03)
19
+
20
+ ### New Features
21
+
22
+ - **AUN 图片附件支持** — AUN 私聊/群聊附件中的图片自动检测(元数据/文件名/magic bytes 三重检测),base64 编码后直接传给 Agent,不再要求 Read 工具读取
23
+ - **selfAID session 注入** — 从 channel key 解析 selfAID,透传至 MessageBridge → CommandHandler → `getOrCreateSession`,修复 AUN 多身份场景 session 归属错误
24
+ - **watch-web 单实例保护** — 启动前清理旧实例(按 instance 文件 + 端口兜底),端口冲突时自动切换并提示;前端新增 token 失效自动回配对页、退出按钮、配对码过期刷新
25
+
26
+ ### Improvements
27
+
28
+ - **init 流程优化** — `cmdInit` 一次性写入所有可用 baseagents(不只写选中的那个);新增 projects 默认目录询问步骤;默认项目路径改为 `EVOLCLAW_HOME/projects`;取消 init 后自动进入 `agent new`,改为打印提示
29
+ - **1M 上下文窗口** — `claude-opus-4-8` / `claude-sonnet-4-6` 自动追加 `[1m]` 后缀,`autoCompactWindow` 随之调整为 900000
30
+ - **上下文用量追踪** — complete 事件新增 `contextUsage`(totalTokens/maxTokens/percentage),`usage` 字段重命名为 `tokenUsage`,随 `status.completed` 元数据下发
31
+ - **模型别名缓存 TTL** — 从 1h 缩短至 5min
32
+ - **`/cli` 远程透传** — 经消息通道远程执行 CLI,仅 owner 可用,白名单只读+配置命令,spawn 无 shell,15s 超时 + 128KB 截断
33
+ - **proximity ECK 注入** — `same_device/same_network/same_egress_ip` 从 V2 E2EE 解密结果提取,注入 venue.md 条件块渲染
34
+ - **fastaun 0.4.9** — 含 SPKI 双格式指纹匹配(0.4.8)和 watch-web 单实例相关修复(0.4.9)
35
+
36
+ ### Bug Fixes
37
+
38
+ - **Feishu merge_forward 内容重复** — 直接转发时丢弃 quotedText,避免内容重复
39
+ - **Feishu seenMsg 文件写入** — 仅在有记录被 GC 清理时才重写文件;seenMessages 清空时改为 unlink
40
+ - **peer-identity 验签** — 新增 `agentmd-unverified` 来源;缓存命中时从 `verify_status` 恢复验签状态,修复 source 误降级
41
+ - **process-introspect** — 改用 `/proc/stat btime`(绝对 epoch)替代 uptime 计算进程启动时间,修复长时间运行后 PID 复用误判
42
+ - **evolagent.load IPC 超时** — AUN WebSocket 冷启动常 >3s,IPC 超时从默认值调整为 30s
43
+ - **交互路由 markWaiting/unmarkWaiting** — 新增提前占位方法,适配异步发卡片场景的等待计数
44
+
3
45
  ## v3.1.5 (2026-06-02)
4
46
 
47
+ ### New Features
48
+
49
+ - **Web 监控面板(watch-web)** — 全新 `evolclaw watch` Web 面板:实时会话视图、对话视图、AID 状态、消息流,含上下文大小与费用估算;新增 `src/cli/watch-web/` server + 静态前端 + session/msg/aid 数据源
50
+ - **CLI model 命令** — 新增 `src/cli/model.ts`:动态模型目录管理,`/models` 端点拉取(1h 缓存)+ 静态表 fallback,注入已验证但未上线 API 的模型
51
+ - **飞书交互卡片 JSON 2.0 + 表单** — `FeishuCardManager` 管理 CardKit 实体生命周期,`buildActionCardV2()` 表单卡片,动态输入框追加(card.element.create API)
52
+ - **proactive→interactive 模式切换提示** — 模式切换时注入提示信息
53
+
54
+ ### Improvements
55
+
56
+ - **AUN/AID 三主体模型重构** — fastaun 0.3.3 → 0.4.7 大版本跨越:适配三主体模型与 slot 隔离键,`createAid` 拆分为 `registerAid` + `authenticate`,新增 `AIDStore`,`uploadAgentMd` 迁移至 AIDStore
57
+ - **ECK 上下文组装体系对齐** — 四阶段改造:channelKey 第二段 `selfPeerId`→`selfAID`,sessions 目录统一三层化 `<channelType>/<selfAID>/<channelId>/`,新增 `peer-key.ts` helper,ECK manifest 诊断输出
58
+ - **Session 持久化重构** — 统一 `persistSession(session, intent, opts)` 原语替代 `writeSessionIfChanged`/快照模式,内置去重,`markProcessing`/`clearProcessing` 不再扫描全部 chat 目录
59
+ - **idle-check 消息队列可靠性** — 提升空闲检测与消息队列稳定性
60
+ - **飞书卡片 schema 2.0 统一** — 交互卡片统一为 schema 2.0 inline,用户交互期间暂停 idle monitor,修复 V2 卡片回调 schema 不一致(error 200830/200810)
61
+ - **IPC 出站统计** — 新增 `aun-aid-stats-record-outbound` IPC handler,p2p/group/thought.put 出站消息统计追踪
62
+ - **/model 列表逻辑简化** — `listModels` 走缓存,model 显示标签同时展示完整 ID 与短别名
63
+ - **清理冗余日志与代码** — 移除过时诊断日志和未使用的 skills 代码
64
+
5
65
  ### Bug Fixes
6
66
 
7
- - **Trigger channelType 传播** — Trigger 新增 `targetChannelType` 字段,`buildSyntheticMessage` 正确填充 `channelType`,修复触发器消息无法创建 session 的问题
8
- - **Session mapper 过滤 channelName** `sessionToFile` 不再将 `channelName` 写入 metadata
67
+ - **Trigger channelType 传播** — Trigger 新增 `targetChannelType` 字段,`buildSyntheticMessage` 正确填充 `channelType`,修复触发器消息无法创建 session
68
+ - **session selfAID/channelType 注入**修复 session 创建时 selfAID 与 channelType 注入缺失
69
+ - **/model 短别名解析** — `/model sonnet` 等短别名正确解析为完整 model ID
70
+ - **aidCreate 成功路径泄漏 AIDStore** — 修复 AID 创建成功后 AIDStore 资源泄漏
71
+ - **peer-identity type 解析** — 修复对端身份类型解析错误
72
+ - **飞书卡片标题重复 emoji** — 修复 resolved card 标题与 checkers summary 中的双 emoji
73
+ - **消息可靠性与 session 迁移** — 提升消息投递可靠性,改进 session 迁移逻辑
74
+ - **session-mapper 过滤 channelName** — `sessionToFile` 不再将 `channelName` 写入 metadata
9
75
  - **resolveChatDirFromSession 严格校验** — 缺失 `channelType` 时抛出明确错误而非静默 fallback
10
- - **/model 别名解析** — `/model sonnet` 等短别名正确解析为完整 model ID
11
76
  - **test 脚本修复** — `package.json` test 脚本改为 `vitest run`
12
77
 
13
78
  ## v3.1.4 (2026-05-27)
@@ -24,7 +24,7 @@ const STATIC_MODEL_ALIASES = {
24
24
  'sonnet': 'claude-sonnet-4-6',
25
25
  'haiku': 'claude-haiku-4-5-20251001',
26
26
  };
27
- const MODEL_ALIAS_TTL_MS = 60 * 60 * 1000; // 1h
27
+ const MODEL_ALIAS_TTL_MS = 5 * 60 * 1000; // 5min
28
28
  const modelAliasCache = new Map(); // key: baseUrl
29
29
  const modelAliasInFlight = new Set(); // 去重并发刷新
30
30
  /** 从模型 ID 列表中提取各 claude 系列的最新版本(按 major.minor 取最高) */
@@ -70,9 +70,9 @@ async function refreshModelAliases(baseUrl, apiKey) {
70
70
  ? json.data.map((m) => m?.id).filter((x) => typeof x === 'string')
71
71
  : [];
72
72
  const aliases = deriveAliasesFromModelIds(ids);
73
- if (Object.keys(aliases).length > 0) {
74
- modelAliasCache.set(baseUrl, { aliases, fetchedAt: Date.now() });
75
- logger.info(`[AgentRunner] Refreshed model aliases from ${url}: ${JSON.stringify(aliases)}`);
73
+ if (ids.length > 0 || Object.keys(aliases).length > 0) {
74
+ modelAliasCache.set(baseUrl, { aliases, ids, fetchedAt: Date.now() });
75
+ logger.info(`[AgentRunner] Refreshed models from ${url}: ${ids.length} ids, aliases ${JSON.stringify(aliases)}`);
76
76
  }
77
77
  }
78
78
  catch {
@@ -97,6 +97,27 @@ function resolveModelAlias(model, baseUrl) {
97
97
  // 回退静态表
98
98
  return STATIC_MODEL_ALIASES[model] || model;
99
99
  }
100
+ /** 支持 1M 上下文窗口的模型 ID 前缀(SDK 通过 `[1m]` 后缀启用)。 */
101
+ const ONE_M_CONTEXT_PREFIXES = ['claude-opus-4-8', 'claude-sonnet-4-6'];
102
+ /**
103
+ * 为支持 1M 上下文的模型追加 `[1m]` 后缀——仅在交给 SDK query() 时调用。
104
+ * 目录与校验层始终使用不带后缀的基础 ID,避免与网关 /models 返回值(无 `[1m]`)冲突。
105
+ */
106
+ function applyContextWindow(modelId) {
107
+ if (/\[1m\]$/.test(modelId))
108
+ return modelId; // 已带后缀
109
+ if (ONE_M_CONTEXT_PREFIXES.some(p => modelId === p))
110
+ return `${modelId}[1m]`;
111
+ return modelId;
112
+ }
113
+ /** 根据 SDK model 串(含 [1m] 后缀)返回合适的 autoCompactWindow 值。 */
114
+ function contextWindowFor(sdkModel) {
115
+ return /\[1m\]$/.test(sdkModel) ? 900000 : 200000;
116
+ }
117
+ /** 解析别名 + 追加 1M 后缀,得到最终交给 SDK 的 model 串。 */
118
+ function resolveSdkModel(model, baseUrl) {
119
+ return applyContextWindow(resolveModelAlias(model, baseUrl));
120
+ }
100
121
  class MessageStream {
101
122
  queue = [];
102
123
  waiting = null;
@@ -213,16 +234,23 @@ export class AgentRunner {
213
234
  getModel() {
214
235
  return this.model;
215
236
  }
216
- listModels() {
217
- // 触发异步刷新(不阻塞)
218
- if (this.baseUrl)
219
- refreshModelAliases(this.baseUrl, this.apiKey);
220
- // 有缓存时返回完整 ID 列表,否则返回短别名
237
+ async listModels() {
221
238
  if (this.baseUrl) {
222
- const cached = modelAliasCache.get(this.baseUrl);
223
- if (cached)
224
- return Object.values(cached.aliases);
239
+ let cached = modelAliasCache.get(this.baseUrl);
240
+ const stale = !cached || (Date.now() - cached.fetchedAt > MODEL_ALIAS_TTL_MS);
241
+ // 缓存为空(首次打开)→ 等待刷新;缓存仅过期 → 后台刷新不阻塞
242
+ if (!cached) {
243
+ await refreshModelAliases(this.baseUrl, this.apiKey);
244
+ cached = modelAliasCache.get(this.baseUrl);
245
+ }
246
+ else if (stale) {
247
+ refreshModelAliases(this.baseUrl, this.apiKey);
248
+ }
249
+ // 有缓存时返回网关 /models 的全量原始 ID
250
+ if (cached && cached.ids.length > 0)
251
+ return cached.ids;
225
252
  }
253
+ // 无 baseUrl / 刷新超时或失败 → 回退短别名
226
254
  return Object.values(STATIC_MODEL_ALIASES);
227
255
  }
228
256
  /** 将短别名解析为当前代理实际使用的完整 model ID(仅用于展示,不改变持久化值) */
@@ -413,11 +441,8 @@ export class AgentRunner {
413
441
  }
414
442
  continue;
415
443
  }
416
- // 等待用户交互(unmark 占位,register 接管计数)
417
- if (waitMarked) {
418
- permCtx?.interactionRouter?.unmarkWaiting(sessionId);
419
- waitMarked = false;
420
- }
444
+ // 等待用户交互:先 register 接管计数,再 unmark 占位,消除空窗期
445
+ // (unmark 必须在 register 之后,否则计数短暂降为 0 触发 onWaitEnd→resume,idle 时钟被重置)
421
446
  const answer = await new Promise((resolve) => {
422
447
  permCtx?.interactionRouter?.register(requestId, sessionId, (action, values) => {
423
448
  if (action === 'cancel') {
@@ -442,6 +467,11 @@ export class AgentRunner {
442
467
  resolve(action);
443
468
  }
444
469
  });
470
+ // register 已接管计数(计数 +1),现在才能安全释放 markWaiting 占位(计数 -1),避免空窗
471
+ if (waitMarked) {
472
+ permCtx?.interactionRouter?.unmarkWaiting(sessionId);
473
+ waitMarked = false;
474
+ }
445
475
  });
446
476
  if (answer === null) {
447
477
  const firstLabel = q.options[0]?.label || '';
@@ -656,7 +686,7 @@ export class AgentRunner {
656
686
  * SDK 原始事件 → 标准 AgentEvent 转换
657
687
  * 所有 SDK 特有的事件类型引用封装在此方法内
658
688
  */
659
- async *transformStream(sdkStream, sessionId) {
689
+ async *transformStream(sdkStream, sessionId, callModel, callEffort, sdkModel) {
660
690
  let lastSessionId;
661
691
  // tool_use_id → tool_name 映射,用于从 SDKUserMessage 的 tool_result 块中还原工具名
662
692
  const toolUseNames = new Map();
@@ -760,6 +790,19 @@ export class AgentRunner {
760
790
  const cleanResult = typeof event.result === 'string'
761
791
  ? event.result.replace(/<thinking>[\s\S]*?<\/thinking>\s*/g, '').trim()
762
792
  : event.result;
793
+ // 从 usage 三项求和得到当前上下文占用(与 claude-hud getTotalTokens 相同算法)
794
+ 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;
799
+ const contextUsage = totalTokens > 0 ? {
800
+ totalTokens,
801
+ maxTokens,
802
+ percentage: Math.round((totalTokens / maxTokens) * 100),
803
+ model: callModel ?? this.model,
804
+ effort: callEffort ?? this.effort,
805
+ } : undefined;
763
806
  yield {
764
807
  type: 'complete',
765
808
  result: cleanResult,
@@ -772,7 +815,8 @@ export class AgentRunner {
772
815
  terminalReason: event.terminal_reason,
773
816
  sessionTitle: event.session_title,
774
817
  numTurns: event.num_turns,
775
- usage: event.usage,
818
+ tokenUsage: event.usage,
819
+ contextUsage,
776
820
  };
777
821
  // result 是 SDK 流的终结事件,不再等待后续(防止 interrupt 后流不关闭导致挂起)
778
822
  return;
@@ -980,12 +1024,13 @@ export class AgentRunner {
980
1024
  else {
981
1025
  logger.info(`[AgentRunner] systemPromptAppend: none`);
982
1026
  }
1027
+ const sdkModel = resolveSdkModel(callModel, this.baseUrl);
983
1028
  const commonOptions = {
984
1029
  cwd: projectPath,
985
- model: resolveModelAlias(callModel, this.baseUrl),
1030
+ model: sdkModel,
986
1031
  ...(callEffort ? { effort: callEffort } : {}),
987
1032
  ...(this.claudeExecutablePath ? { pathToClaudeCodeExecutable: this.claudeExecutablePath } : {}),
988
- autoCompactWindow: 200000,
1033
+ autoCompactWindow: contextWindowFor(sdkModel),
989
1034
  advisorModel: 'haiku',
990
1035
  canUseTool: canUseToolCallback,
991
1036
  permissionMode: sdkPermissionMode,
@@ -1110,7 +1155,7 @@ export class AgentRunner {
1110
1155
  }
1111
1156
  let sdkStream;
1112
1157
  if (images && images.length > 0) {
1113
- logger.debug('[AgentRunner] Creating query with images, images:', images.length);
1158
+ logger.info('[AgentRunner] Creating query with images:', images.length, 'first image size:', images[0]?.data?.length ?? 0);
1114
1159
  logger.debug('[AgentRunner] Skipping resume for image message to avoid history conflict');
1115
1160
  const stream = new MessageStream();
1116
1161
  stream.push(prompt, images);
@@ -1126,7 +1171,7 @@ export class AgentRunner {
1126
1171
  this.interruptFns.set(sessionId, () => sdkStream.interrupt());
1127
1172
  }
1128
1173
  // 返回标准 AgentEvent 流(重试由 MessageProcessor 层负责)
1129
- return this.transformStream(sdkStream, sessionId);
1174
+ return this.transformStream(sdkStream, sessionId, callModel, callEffort, sdkModel);
1130
1175
  }
1131
1176
  async interrupt(sessionId) {
1132
1177
  const fn = this.interruptFns.get(sessionId);
@@ -1165,7 +1210,7 @@ export class AgentRunner {
1165
1210
  prompt,
1166
1211
  options: {
1167
1212
  cwd: projectPath,
1168
- model: resolveModelAlias(this.model, this.baseUrl),
1213
+ model: resolveSdkModel(this.model, this.baseUrl),
1169
1214
  resume: agentSessionId,
1170
1215
  maxTurns: 1,
1171
1216
  permissionMode: this.toSdkPermissionMode(),