evolclaw 3.1.4 → 3.1.6

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 (99) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/agents/claude-runner.js +398 -161
  3. package/dist/agents/kit-renderer.js +191 -25
  4. package/dist/aun/aid/agentmd.js +75 -103
  5. package/dist/aun/aid/client.js +1 -29
  6. package/dist/aun/aid/identity.js +105 -64
  7. package/dist/aun/aid/index.js +2 -1
  8. package/dist/aun/aid/store.js +74 -0
  9. package/dist/aun/msg/group.js +2 -2
  10. package/dist/aun/msg/p2p.js +26 -2
  11. package/dist/aun/rpc/connection.js +23 -30
  12. package/dist/channels/aun.js +174 -99
  13. package/dist/channels/dingtalk.js +2 -1
  14. package/dist/channels/feishu.js +301 -199
  15. package/dist/channels/qqbot.js +2 -1
  16. package/dist/channels/wechat.js +2 -1
  17. package/dist/channels/wecom.js +2 -1
  18. package/dist/cli/agent.js +21 -16
  19. package/dist/cli/bench.js +41 -28
  20. package/dist/cli/help.js +8 -0
  21. package/dist/cli/index.js +176 -87
  22. package/dist/cli/init-channel.js +5 -1
  23. package/dist/cli/init.js +37 -21
  24. package/dist/cli/link-rules.js +1 -7
  25. package/dist/cli/model.js +549 -0
  26. package/dist/cli/net-check.js +133 -50
  27. package/dist/cli/watch-msg.js +7 -7
  28. package/dist/cli/watch-web/debug-log.js +18 -0
  29. package/dist/cli/watch-web/server.js +306 -0
  30. package/dist/cli/watch-web/sources/aid.js +63 -0
  31. package/dist/cli/watch-web/sources/msg.js +70 -0
  32. package/dist/cli/watch-web/sources/session.js +638 -0
  33. package/dist/cli/watch-web/sources/types.js +10 -0
  34. package/dist/cli/watch-web/static/app.js +546 -0
  35. package/dist/cli/watch-web/static/index.html +54 -0
  36. package/dist/cli/watch-web/static/style.css +247 -0
  37. package/dist/config-store.js +1 -22
  38. package/dist/core/channel-loader.js +7 -4
  39. package/dist/core/command-handler.js +261 -133
  40. package/dist/core/evolagent-registry.js +1 -1
  41. package/dist/core/evolagent.js +4 -22
  42. package/dist/core/interaction-router.js +59 -0
  43. package/dist/core/message/im-renderer.js +9 -20
  44. package/dist/core/message/message-bridge.js +13 -9
  45. package/dist/core/message/message-log.js +2 -2
  46. package/dist/core/message/message-processor.js +211 -123
  47. package/dist/core/message/stream-idle-monitor.js +21 -0
  48. package/dist/core/model/model-catalog.js +215 -0
  49. package/dist/core/model/model-scope.js +250 -0
  50. package/dist/core/relation/peer-identity.js +58 -55
  51. package/dist/core/relation/peer-key.js +16 -0
  52. package/dist/core/session/session-fs-store.js +34 -55
  53. package/dist/core/session/session-key.js +24 -0
  54. package/dist/core/session/session-manager.js +308 -251
  55. package/dist/core/session/session-mapper.js +9 -4
  56. package/dist/core/trigger/manager.js +3 -3
  57. package/dist/core/trigger/parser.js +4 -4
  58. package/dist/core/trigger/scheduler.js +22 -7
  59. package/dist/index.js +61 -7
  60. package/dist/ipc.js +23 -1
  61. package/dist/utils/error-utils.js +6 -0
  62. package/dist/utils/process-introspect.js +7 -5
  63. package/kits/docs/GUIDE.md +2 -2
  64. package/kits/docs/INDEX.md +8 -8
  65. package/kits/docs/channels/aun.md +56 -17
  66. package/kits/docs/channels/feishu.md +41 -12
  67. package/kits/docs/context-assembly.md +182 -0
  68. package/kits/docs/evolclaw/INDEX.md +43 -0
  69. package/kits/docs/evolclaw/agent.md +49 -0
  70. package/kits/docs/evolclaw/aid.md +49 -0
  71. package/kits/docs/evolclaw/ctl.md +46 -0
  72. package/kits/docs/evolclaw/group.md +89 -0
  73. package/kits/docs/evolclaw/model.md +51 -0
  74. package/kits/docs/evolclaw/msg.md +91 -0
  75. package/kits/docs/evolclaw/rpc.md +35 -0
  76. package/kits/docs/evolclaw/storage.md +49 -0
  77. package/kits/docs/venues/aun-group.md +10 -0
  78. package/kits/docs/venues/aun-private.md +10 -0
  79. package/kits/docs/venues/client-desktop.md +10 -0
  80. package/kits/docs/venues/client-mobile.md +10 -0
  81. package/kits/docs/venues/feishu-group.md +13 -0
  82. package/kits/docs/venues/feishu-private.md +9 -0
  83. package/kits/docs/venues/group.md +23 -0
  84. package/kits/docs/venues/private.md +10 -0
  85. package/kits/eck_manifest.json +81 -36
  86. package/kits/rules/01-overview.md +20 -10
  87. package/kits/rules/06-channel.md +34 -27
  88. package/kits/templates/system-fragments/baseagent.md +7 -1
  89. package/kits/templates/system-fragments/channel.md +7 -5
  90. package/kits/templates/system-fragments/commands.md +19 -0
  91. package/kits/templates/system-fragments/session.md +19 -3
  92. package/kits/templates/system-fragments/venue.md +24 -0
  93. package/package.json +10 -5
  94. package/dist/aun/aid/lifecycle-log.js +0 -33
  95. package/dist/utils/aid-lifecycle-log.js +0 -33
  96. package/kits/docs/evolclaw/AGENT_CMD.md +0 -31
  97. package/kits/docs/evolclaw/MSG_GROUP.md +0 -30
  98. package/kits/docs/evolclaw/MSG_PRIVATE.md +0 -72
  99. package/kits/docs/evolclaw/tools.md +0 -25
@@ -51,7 +51,7 @@ export function detectDuplicates(agents) {
51
51
  export class EvolAgentRegistry {
52
52
  _agentsDir;
53
53
  agents = new Map();
54
- /** channel key (`<type>#<selfPeerId>#<name>`) → agent aid */
54
+ /** channel key (`<type>#<selfAID>#<name>`) → agent aid */
55
55
  channelIndex = new Map();
56
56
  /** 启动期被 ConfigStore 跳过的目录(命名非法 / 缺 config.json / 校验失败等) */
57
57
  skipped = [];
@@ -62,11 +62,11 @@ export class EvolAgent {
62
62
  }
63
63
  // ── Channels ──────────────────────────────────────────────────────────
64
64
  /**
65
- * effective channel key:`<type>#<urlEncode(selfPeerId)>#<name>`。
66
- * AUN channel 的 selfPeerId 是 agent.aid,name 固定为 'main'。
65
+ * effective channel key:`<type>#<selfAID>#<name>`。
66
+ * AUN channel 的 selfAID 是 agent.aid,name 固定为 'main'。
67
67
  */
68
68
  effectiveChannelName(type, rawName) {
69
- return formatChannelKey({ type, selfPeerId: this.aid, name: rawName });
69
+ return formatChannelKey({ type, selfAID: this.aid, name: rawName });
70
70
  }
71
71
  channelInstanceNames() {
72
72
  // AUN channel 隐式存在(从 agent.aid 派生),不需要在 channels[] 里声明
@@ -97,7 +97,7 @@ export class EvolAgent {
97
97
  */
98
98
  isAunChannelKey(channelKey) {
99
99
  const parsed = tryParseChannelKey(channelKey);
100
- return parsed?.type === 'aun' && parsed.selfPeerId === this.aid;
100
+ return parsed?.type === 'aun' && parsed.selfAID === this.aid;
101
101
  }
102
102
  getOwner(channelKey) {
103
103
  if (this.isAunChannelKey(channelKey)) {
@@ -213,24 +213,6 @@ export class EvolAgent {
213
213
  this.merged.dispatch = value;
214
214
  this.persist();
215
215
  }
216
- // ── Projects ──────────────────────────────────────────────────────────
217
- getProjects() {
218
- const list = this.merged.projects?.list;
219
- if (list && Object.keys(list).length > 0)
220
- return { ...list };
221
- const dp = this.merged.projects?.defaultPath;
222
- if (dp)
223
- return { [path.basename(dp)]: dp };
224
- return {};
225
- }
226
- addProject(name, projectPath) {
227
- if (!this.rawAgent.projects)
228
- this.rawAgent.projects = { defaultPath: projectPath, list: {} };
229
- if (!this.rawAgent.projects.list)
230
- this.rawAgent.projects.list = {};
231
- this.rawAgent.projects.list[name] = projectPath;
232
- this.persist();
233
- }
234
216
  // ── Personal layer ────────────────────────────────────────────────────
235
217
  _personaCache = undefined;
236
218
  /**
@@ -1,14 +1,55 @@
1
1
  import { logger } from '../utils/logger.js';
2
2
  export class InteractionRouter {
3
3
  handlers = new Map();
4
+ /** sessionId → 该会话当前待应答的交互数量,用于触发 wait 生命周期钩子 */
5
+ pendingBySession = new Map();
6
+ waitHooks;
7
+ setWaitHooks(hooks) {
8
+ this.waitHooks = hooks;
9
+ }
10
+ /**
11
+ * 在 register() 之前提前标记 session 为等待状态(适用于发卡片有异步延迟的场景)。
12
+ * 必须与 unmarkWaiting() 配对使用,或后续 register() 会接管计数。
13
+ */
14
+ markWaiting(sessionId) {
15
+ this.incPending(sessionId);
16
+ }
17
+ /** 取消 markWaiting() 的占位(后续若有 register() 接管则不需调用此方法) */
18
+ unmarkWaiting(sessionId) {
19
+ this.decPending(sessionId);
20
+ }
21
+ /** 登记一个待应答交互;session 计数 0→1 时触发 onWaitStart */
22
+ incPending(sessionId) {
23
+ const next = (this.pendingBySession.get(sessionId) ?? 0) + 1;
24
+ this.pendingBySession.set(sessionId, next);
25
+ if (next === 1)
26
+ this.waitHooks?.onWaitStart(sessionId);
27
+ }
28
+ /** 注销一个待应答交互;session 计数 1→0 时触发 onWaitEnd */
29
+ decPending(sessionId) {
30
+ const cur = this.pendingBySession.get(sessionId) ?? 0;
31
+ if (cur <= 0)
32
+ return;
33
+ const next = cur - 1;
34
+ if (next === 0) {
35
+ this.pendingBySession.delete(sessionId);
36
+ this.waitHooks?.onWaitEnd(sessionId);
37
+ }
38
+ else {
39
+ this.pendingBySession.set(sessionId, next);
40
+ }
41
+ }
4
42
  register(id, sessionId, callback, opts) {
43
+ // 同 id 替换:槽位本就占用,计数不变,不触发 wait 钩子
5
44
  const existing = this.handlers.get(id);
6
45
  if (existing?.timer)
7
46
  clearTimeout(existing.timer);
47
+ const isReplacement = !!existing;
8
48
  let timer;
9
49
  if (opts?.timeoutMs && opts.timeoutMs > 0) {
10
50
  timer = setTimeout(() => {
11
51
  this.handlers.delete(id);
52
+ this.decPending(sessionId);
12
53
  logger.debug(`[InteractionRouter] Timeout for interaction: ${id}`);
13
54
  opts.onTimeout?.();
14
55
  }, opts.timeoutMs);
@@ -20,6 +61,8 @@ export class InteractionRouter {
20
61
  initiatorId: opts?.initiatorId,
21
62
  fallbackCommand: opts?.fallbackCommand,
22
63
  });
64
+ if (!isReplacement)
65
+ this.incPending(sessionId);
23
66
  }
24
67
  handle(response) {
25
68
  const handler = this.handlers.get(response.id);
@@ -28,6 +71,7 @@ export class InteractionRouter {
28
71
  if (handler.timer)
29
72
  clearTimeout(handler.timer);
30
73
  this.handlers.delete(response.id);
74
+ this.decPending(handler.sessionId);
31
75
  try {
32
76
  const result = handler.callback(response.action, response.values, response.operatorId);
33
77
  if (result && typeof result.catch === 'function') {
@@ -47,6 +91,7 @@ export class InteractionRouter {
47
91
  if (handler.timer)
48
92
  clearTimeout(handler.timer);
49
93
  this.handlers.delete(id);
94
+ this.decPending(handler.sessionId);
50
95
  }
51
96
  }
52
97
  }
@@ -56,6 +101,7 @@ export class InteractionRouter {
56
101
  if (handler.timer)
57
102
  clearTimeout(handler.timer);
58
103
  this.handlers.delete(id);
104
+ this.decPending(handler.sessionId);
59
105
  }
60
106
  }
61
107
  getPending(sessionId) {
@@ -100,6 +146,16 @@ export function renderActionAsText(req) {
100
146
  const lines = [action.title];
101
147
  if (action.body)
102
148
  lines.push(action.body);
149
+ // checkers 多选:渲染选项列表
150
+ if (action.checkers?.length) {
151
+ lines.push('');
152
+ action.checkers.forEach((chk, idx) => {
153
+ const desc = chk.description ? ` — ${chk.description}` : '';
154
+ lines.push(` ${idx + 1}. ${chk.label}${desc}`);
155
+ });
156
+ lines.push('', '回复选项编号(多选用逗号分隔),或输入自定义内容');
157
+ return lines.join('\n');
158
+ }
103
159
  if (!fb) {
104
160
  return lines.join('\n');
105
161
  }
@@ -111,5 +167,8 @@ export function renderActionAsText(req) {
111
167
  if (fb.acceptFreeText && fb.freeTextHint) {
112
168
  lines.push(` ${fb.freeTextHint}`);
113
169
  }
170
+ if (action.allowCustomInput) {
171
+ lines.push(` 或直接输入自定义内容`);
172
+ }
114
173
  return lines.join('\n');
115
174
  }
@@ -132,26 +132,6 @@ export class IMRenderer {
132
132
  getRemainingText() {
133
133
  return this.textBuffer;
134
134
  }
135
- /** 从 buffer 中移除指定 pattern(用于文件标记预处理) */
136
- stripFromBuffer(pattern) {
137
- this.textBuffer = this.textBuffer.replace(pattern, '').trim();
138
- // itemsQueue 中的 text items 也同步过滤
139
- for (const item of this.itemsQueue) {
140
- if (item.kind === 'text') {
141
- item.text = item.text.replace(pattern, '');
142
- }
143
- }
144
- }
145
- /** 清除上下文过长错误文本(从 buffer + allText 中移除) */
146
- stripContextError(pattern) {
147
- this.textBuffer = this.textBuffer.replace(pattern, '').trim();
148
- this.allText = this.allText.replace(pattern, '').trim();
149
- for (const item of this.itemsQueue) {
150
- if (item.kind === 'text') {
151
- item.text = item.text.replace(pattern, '');
152
- }
153
- }
154
- }
155
135
  // ── 文本/活动注入(替代 StreamFlusher.addText/addActivity)──
156
136
  /** 添加文本片段(流式 text) */
157
137
  addText(text, outputTokens, turn) {
@@ -328,6 +308,15 @@ export class IMRenderer {
328
308
  clearTimeout(this.timer);
329
309
  this.timer = undefined;
330
310
  }
311
+ // 上下文错误短语过滤:剔除错误关键词本身,保留前后内容
312
+ const ctxErrPattern = /prompt is too long|input is too long|context too long|context limit|context_length_exceeded|上下文过长/gi;
313
+ const stripCtxErr = (s) => s.replace(ctxErrPattern, '').trim();
314
+ this.textBuffer = stripCtxErr(this.textBuffer);
315
+ this.allText = stripCtxErr(this.allText);
316
+ for (const item of this.itemsQueue) {
317
+ if (item.kind === 'text')
318
+ item.text = stripCtxErr(item.text);
319
+ }
331
320
  // 文件标记过滤
332
321
  if (this.opts.fileMarkerPattern) {
333
322
  this.textBuffer = this.textBuffer.replace(this.opts.fileMarkerPattern, '').trim();
@@ -81,12 +81,12 @@ export class MessageBridge {
81
81
  logger.debug(`[MessageBridge] Command detected: "${cmdContent}", routing to handler`);
82
82
  // 命令也要记录入方向 jsonl(不创建 session,直接用 chatDirPath 计算路径)
83
83
  try {
84
- const chatDir = chatDirPath(resolvePaths().sessionsDir, msg.channelType || effectiveChannelType, msg.channelId, msg.selfId);
84
+ const chatDir = chatDirPath(resolvePaths().sessionsDir, msg.channelType || effectiveChannelType, msg.channelId, msg.selfAID || '');
85
85
  const inboundEncrypt = msg.replyContext?.metadata?.encrypted != null ? !!(msg.replyContext.metadata.encrypted) : undefined;
86
86
  const inboundChatmode = msg.replyContext?.metadata?.chatmode;
87
87
  appendMessageLog(chatDir, buildInboundEntry({
88
88
  from: msg.peerId || 'unknown',
89
- to: msg.selfId || 'self',
89
+ to: msg.selfAID || 'self',
90
90
  chatType: msg.chatType || 'private',
91
91
  groupId: msg.groupId ?? null,
92
92
  msgId: msg.messageId ?? null,
@@ -105,7 +105,7 @@ export class MessageBridge {
105
105
  if (await this.handleCommand(cmdContent, channelName, msg.channelId, (text) => {
106
106
  logger.channelOut({ channel: channelName, channelId: msg.channelId, taskId: `cmd-${msg.messageId || Date.now()}`, payload: { kind: 'command.result', text } });
107
107
  return sendReply(msg.channelId, text, msg.replyContext);
108
- }, msg.peerId, msg.threadId, msg.chatType, msg.source, msg.replyContext))
108
+ }, msg.peerId, msg.threadId, msg.chatType, msg.source, msg.replyContext, msg.messageId, msg.selfAID))
109
109
  return;
110
110
  // 3. session 解析(使用 Channel 层填充的 chatType)
111
111
  const chatType = msg.chatType || 'private';
@@ -114,7 +114,7 @@ export class MessageBridge {
114
114
  if (msg.threadId && msg.replyContext)
115
115
  metadata.replyContext = msg.replyContext;
116
116
  // 写入实例名(审计 + 精确出站路由)
117
- metadata.channelName = channelName;
117
+ metadata.channelKey = channelName;
118
118
  if (chatType === 'private' && msg.peerId) {
119
119
  metadata.peerId = msg.peerId;
120
120
  if (msg.peerName)
@@ -134,7 +134,7 @@ export class MessageBridge {
134
134
  const owningAgent = this.agentRegistry?.resolveByChannel(channelName);
135
135
  const effectiveProjectPath = owningAgent?.projectPath
136
136
  ?? this.defaultProjectPath;
137
- const session = await this.sessionManager.getOrCreateSession(channelName, msg.channelId, effectiveProjectPath, msg.threadId, Object.keys(metadata).length ? metadata : undefined, undefined, msg.peerId, chatType, undefined, msg.selfId, msg.channelType || effectiveChannelType, msg.peerType);
137
+ const session = await this.sessionManager.getOrCreateSession(channelName, msg.channelId, effectiveProjectPath, msg.threadId, Object.keys(metadata).length ? metadata : undefined, undefined, msg.peerId, chatType, undefined, msg.selfAID, msg.channelType || effectiveChannelType, msg.peerType);
138
138
  // 4. 消息前缀(由 policy 决定)
139
139
  const channelInfo = this.processor.getChannelInfo?.(channelName);
140
140
  if (channelInfo?.policy) {
@@ -147,11 +147,14 @@ export class MessageBridge {
147
147
  channel: channelName,
148
148
  channelType: msg.channelType || effectiveChannelType,
149
149
  channelId: msg.channelId, content,
150
- selfId: msg.selfId,
150
+ selfAID: msg.selfAID,
151
151
  chatType,
152
152
  images: msg.images, timestamp: Date.now(),
153
153
  peerId: msg.peerId, peerName: msg.peerName,
154
154
  peerType: msg.peerType,
155
+ sameDevice: msg.sameDevice,
156
+ sameNetwork: msg.sameNetwork,
157
+ sameEgressIp: msg.sameEgressIp,
155
158
  messageId: msg.messageId,
156
159
  mentions: msg.mentions, threadId: msg.threadId,
157
160
  replyContext: msg.replyContext,
@@ -162,7 +165,7 @@ export class MessageBridge {
162
165
  const inboundChatmode = msg.replyContext?.metadata?.chatmode;
163
166
  appendMessageLog(chatDir, buildInboundEntry({
164
167
  from: msg.peerId || 'unknown',
165
- to: msg.selfId || 'self',
168
+ to: msg.selfAID || 'self',
166
169
  chatType,
167
170
  groupId: msg.groupId ?? null,
168
171
  msgId: msg.messageId ?? null,
@@ -219,6 +222,7 @@ export class MessageBridge {
219
222
  permission: '/perm',
220
223
  activity: '/activity',
221
224
  system: '/system',
225
+ cli: '/cli',
222
226
  };
223
227
  resolveCmd(name, cmd) {
224
228
  if (cmd)
@@ -374,11 +378,11 @@ export class MessageBridge {
374
378
  }
375
379
  }
376
380
  /** 命令快速路径:返回 true 表示已处理 */
377
- async handleCommand(content, channel, channelId, sendReply, userId, threadId, chatType, source, replyContext) {
381
+ async handleCommand(content, channel, channelId, sendReply, userId, threadId, chatType, source, replyContext, messageId, selfAID) {
378
382
  if (!this.cmdHandler.isCommand(content))
379
383
  return false;
380
384
  logger.info(`[${channel}] ${channelId}: ${content}${source === 'card-trigger' ? ' [card]' : ''}`);
381
- const cmdResult = await this.cmdHandler.handle(content, channel, channelId, (_cid, text, opts) => sendReply(text), userId, threadId, chatType, source);
385
+ const cmdResult = await this.cmdHandler.handle(content, channel, channelId, (_cid, text, opts) => sendReply(text), userId, threadId, chatType, source, messageId, selfAID);
382
386
  logger.debug(`[MessageBridge] handleCommand: result type=${typeof cmdResult}`);
383
387
  if (cmdResult === undefined)
384
388
  return false;
@@ -31,8 +31,8 @@ function formatTimestampMs(epochMs) {
31
31
  export function messageLogPath(chatDir) {
32
32
  return path.join(chatDir, MESSAGE_LOG_FILE);
33
33
  }
34
- export function resolveChatDir(sessionsDir, channelType, channelId, selfId) {
35
- return chatDirPath(sessionsDir, channelType, channelId, selfId);
34
+ export function resolveChatDir(sessionsDir, channelType, channelId, selfAID) {
35
+ return chatDirPath(sessionsDir, channelType, channelId, selfAID);
36
36
  }
37
37
  export function appendMessageLog(chatDir, entry) {
38
38
  if (entry.dir === 'in' && isDuplicate(entry.msgId)) {