evolclaw 3.1.0 → 3.1.2

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 (45) hide show
  1. package/CHANGELOG.md +407 -0
  2. package/README.md +1 -1
  3. package/SKILLS.md +311 -0
  4. package/dist/agents/claude-runner.js +40 -3
  5. package/dist/aun/aid/agentmd.js +7 -6
  6. package/dist/aun/aid/client.js +5 -11
  7. package/dist/aun/aid/identity.js +32 -13
  8. package/dist/aun/msg/group.js +1 -0
  9. package/dist/aun/msg/p2p.js +51 -0
  10. package/dist/aun/msg/upload.js +57 -18
  11. package/dist/channels/aun.js +124 -50
  12. package/dist/channels/dingtalk.js +2 -0
  13. package/dist/channels/feishu.js +15 -6
  14. package/dist/channels/qqbot.js +2 -0
  15. package/dist/channels/wechat.js +2 -0
  16. package/dist/channels/wecom.js +2 -0
  17. package/dist/cli/agent.js +130 -35
  18. package/dist/cli/index.js +221 -48
  19. package/dist/cli/init-channel.js +4 -2
  20. package/dist/cli/init.js +44 -23
  21. package/dist/cli/watch-msg.js +109 -30
  22. package/dist/config-store.js +67 -1
  23. package/dist/core/channel-loader.js +4 -4
  24. package/dist/core/command-handler.js +95 -84
  25. package/dist/core/evolagent-registry.js +45 -9
  26. package/dist/core/evolagent.js +4 -4
  27. package/dist/core/message/im-renderer.js +47 -8
  28. package/dist/core/message/message-bridge.js +30 -1
  29. package/dist/core/message/message-log.js +6 -1
  30. package/dist/core/message/message-processor.js +29 -35
  31. package/dist/core/relation/peer-identity.js +161 -0
  32. package/dist/core/session/session-fs-store.js +23 -0
  33. package/dist/core/session/session-manager.js +11 -4
  34. package/dist/core/trigger/manager.js +16 -0
  35. package/dist/core/trigger/parser.js +110 -0
  36. package/dist/core/trigger/scheduler.js +6 -0
  37. package/dist/index.js +64 -20
  38. package/dist/paths.js +35 -0
  39. package/dist/utils/cross-platform.js +2 -1
  40. package/dist/utils/error-utils.js +17 -13
  41. package/dist/utils/stats.js +216 -2
  42. package/kits/docs/INDEX.md +6 -0
  43. package/kits/docs/evolclaw/MSG_PRIVATE.md +53 -6
  44. package/kits/rules/06-channel.md +30 -0
  45. package/package.json +6 -3
package/SKILLS.md ADDED
@@ -0,0 +1,311 @@
1
+ ---
2
+ name: evolclaw
3
+ version: 1.2.0
4
+ description: EvolClaw 完整使用手册 — CLI 命令参考 + 运行时控制 (ctl) + 定时触发器 (trigger)
5
+ trigger: 用户询问或需要使用 evolclaw CLI、切换模型、调整推理强度、查看运行状态、压缩上下文、检查通道健康、管理权限模式、重启服务、重连渠道、注册定时触发器等
6
+ ---
7
+
8
+ # EvolClaw
9
+
10
+ EvolClaw 是连接 Claude Agent SDK 和消息渠道(飞书 / 微信 / AUN / 钉钉 / QQ / 企业微信)的 AI Agent 网关,支持多项目会话管理。
11
+
12
+ 本文档分三部分:
13
+
14
+ 1. [CLI 命令](#cli-命令) — 终端命令参考
15
+ 2. [Ctl 运行时控制](#ctl-运行时控制) — Agent 自主管理指令(仅在 evolclaw 托管环境中可用)
16
+ 3. [Trigger 定时触发器](#trigger-定时触发器) — 设置延迟或定时任务
17
+
18
+ ---
19
+
20
+ ## CLI 命令
21
+
22
+ ### 全局选项
23
+
24
+ ```
25
+ evolclaw --version / -v / -V 查看版本号
26
+ ```
27
+
28
+ ### 服务生命周期
29
+
30
+ ```
31
+ evolclaw start 启动服务(默认命令)
32
+ evolclaw stop 停止服务
33
+ evolclaw restart 重启服务(含自动升级检查)
34
+ evolclaw status 查看运行状态、进程信息、会话统计、渠道连接状态
35
+ evolclaw restart-monitor 启动重启监控守护进程
36
+ ```
37
+
38
+ ### 日志与诊断
39
+
40
+ ```
41
+ evolclaw logs [选项]
42
+ --level error|warn 只显示指定级别及以上
43
+ --module <name> 只显示指定模块
44
+ --raw 原始输出,不着色
45
+ --no-color 禁用颜色输出
46
+
47
+ evolclaw watch 汇总监控 logs/ 下所有 .log 文件
48
+ evolclaw diagnose 诊断启动环境
49
+ ```
50
+
51
+ ### 初始化
52
+
53
+ ```
54
+ evolclaw init [渠道]
55
+
56
+ 渠道:
57
+ aun AUN 交互式配置(默认)
58
+ feishu 飞书扫码登录
59
+ wechat 微信扫码登录
60
+ dingtalk 钉钉扫码登录
61
+ qqbot QQ 机器人扫码绑定
62
+ wecom 企业微信 AI Bot 配置
63
+
64
+ 非交互式选项:
65
+ --non-interactive
66
+ --default-path <path>
67
+ --channel <name>
68
+ --aun-aid <aid>
69
+ --aun-owner <aid>
70
+
71
+ 示例:
72
+ evolclaw init
73
+ evolclaw init aun
74
+ evolclaw init --non-interactive --channel aun \
75
+ --aun-aid mybot.agentid.pub --aun-owner me.agentid.pub \
76
+ --default-path ~/projects/default
77
+ ```
78
+
79
+ ### Agent 管理
80
+
81
+ ```
82
+ evolclaw agent 列出所有 agent
83
+ evolclaw agent <name> 查看指定 agent 详情
84
+ evolclaw agent reload <name> 热重载 agent 配置
85
+
86
+ evolclaw agent new <name> [选项]
87
+ --baseagent <claude|codex|gemini|hermes> 必填
88
+ --project <absolute-path> 必填
89
+
90
+ 渠道选项(至少一个):
91
+ --aun-aid <aid> --aun-owner <aid>
92
+ --feishu-app-id <id> --feishu-app-secret <secret>
93
+ --wechat-token <token>
94
+ --wecom-bot-id <id> --wecom-secret <secret>
95
+ --dingtalk-client-id <id> --dingtalk-client-secret <secret>
96
+ --qqbot-app-id <id> --qqbot-client-secret <secret>
97
+
98
+ 行为选项:
99
+ --chatmode-private <interactive|proactive> 默认: interactive
100
+ --chatmode-group <interactive|proactive> 默认: proactive
101
+
102
+ --non-interactive
103
+
104
+ 示例:
105
+ evolclaw agent new mybot --baseagent claude \
106
+ --project ~/projects/mybot \
107
+ --aun-aid mybot.agentid.pub --aun-owner me.agentid.pub
108
+ ```
109
+
110
+ ### AID 身份管理
111
+
112
+ ```
113
+ evolclaw aid list 列出本地所有 AID
114
+ evolclaw aid show <aid> 查看 AID 详情
115
+ evolclaw aid new <aid> 创建新 AID 身份
116
+ evolclaw aid delete <aid> 删除 AID
117
+ evolclaw aid lookup <aid> 查询 AUN 网络上的 AID 信息
118
+ evolclaw aid agentmd put <aid> 上传本地 agent.md 到 AUN 网络
119
+ evolclaw aid agentmd get <aid> 从 AUN 网络获取 agent.md
120
+ ```
121
+
122
+ ### RPC 调用
123
+
124
+ ```
125
+ evolclaw rpc --as <aid> --params <json|jsonl-file>
126
+
127
+ 选项:
128
+ --as <aid> 发送方 AID(必填)
129
+ --params <value> JSON 字符串或 .jsonl 文件路径(必填)
130
+
131
+ 示例:
132
+ evolclaw rpc --as alice.agentid.pub \
133
+ --params '{"method":"message.send","params":{"to":"bob.agentid.pub","payload":{"type":"text","text":"hello"}}}'
134
+ ```
135
+
136
+ ### 存储管理
137
+
138
+ ```
139
+ evolclaw storage upload <aid> <local-path> [remote-path]
140
+ evolclaw storage download <aid> <remote-path> [local-path]
141
+ evolclaw storage ls <aid> [prefix]
142
+ evolclaw storage rm <aid> <remote-path>
143
+ evolclaw storage quota <aid>
144
+ ```
145
+
146
+ ### 项目管理
147
+
148
+ ```
149
+ evolclaw mv <old-path> <new-path> 迁移项目目录(保留会话数据)
150
+ ```
151
+
152
+ ### 环境变量
153
+
154
+ | 变量 | 默认值 | 说明 |
155
+ |------|--------|------|
156
+ | `EVOLCLAW_HOME` | `~/.evolclaw` | 数据目录 |
157
+ | `LOG_LEVEL` | `INFO` | 日志级别 |
158
+ | `MESSAGE_LOG` | `true` | 是否记录消息日志 |
159
+ | `EVENT_LOG` | `true` | 是否记录事件日志 |
160
+
161
+ ---
162
+
163
+ ## Ctl 运行时控制
164
+
165
+ 通过 `evolclaw ctl <command> [args]` 管理运行时配置。仅在 evolclaw 托管环境中可用(`EVOLCLAW_SESSION_ID` 已设置)。
166
+
167
+ ### 查询类(所有用户)
168
+
169
+ - `evolclaw ctl help` — 显示帮助
170
+ - `evolclaw ctl status` — 显示会话状态
171
+ - `evolclaw ctl check` — 检查渠道健康状态
172
+
173
+ ### 配置类(管理员)
174
+
175
+ - `evolclaw ctl model` — 查看当前模型和可选列表
176
+ - `evolclaw ctl model <model-id>` — 切换模型(如 `opus`, `sonnet`, `haiku`)
177
+ - `evolclaw ctl effort` — 查看当前推理强度
178
+ - `evolclaw ctl effort <low|medium|high|max>` — 切换推理强度
179
+ - `evolclaw ctl compact` — 压缩当前会话上下文
180
+ - `evolclaw ctl chatmode [interactive|proactive]` — 查看/切换会话模式
181
+
182
+ ### 权限类
183
+
184
+ - `evolclaw ctl perm` — 查看当前权限模式(管理员)
185
+ - `evolclaw ctl perm <mode>` — 切换权限模式(仅 owner)
186
+
187
+ ### 项目
188
+
189
+ - `evolclaw ctl bind <path>` — 注册项目目录(不切换当前会话)
190
+
191
+ ### 消息(仅 owner)
192
+
193
+ - `evolclaw ctl send "<消息>"` — 主动发送文本消息(proactive 模式)
194
+ - `evolclaw ctl file [channel] <路径>` — 发送项目内文件(仅限项目目录内)
195
+ - `evolclaw ctl activity <all|dm|owner|none>` — 查看/控制中间输出显示模式
196
+
197
+ ### 运维(仅 owner)
198
+
199
+ - `evolclaw ctl agentmd` — 查看当前 agent.md
200
+ - `evolclaw ctl agentmd put` — 发布本地 agent.md
201
+ - `evolclaw ctl agentmd set <内容>` — 直接设置 agent.md 内容
202
+ - `evolclaw ctl aid` — 列出所有 AUN 实例及连接状态
203
+ - `evolclaw ctl aid new <aid>` — 创建新 AID 并热加载(仅 AUN 通道)
204
+ - `evolclaw ctl restart` — 重启服务(中断所有会话,慎用)
205
+ - `evolclaw ctl restart <channel>` — 重连指定渠道(管理员可用)
206
+
207
+ ### 使用场景
208
+
209
+ - Agent 自主判断需要切换模型、调整配置
210
+ - 用户自然语言指示(如"切到 opus"、"压缩上下文")
211
+ - Proactive 模式下发送消息给用户(文本输出被静默丢弃,必须用 `ctl send`)
212
+
213
+ ### 注意事项
214
+
215
+ - 仅在 evolclaw 托管环境中可用(`EVOLCLAW_SESSION_ID` 已设置)
216
+ - 权限继承当前会话用户角色(owner / admin / guest)
217
+ - `compact` 不能在活跃流期间执行
218
+ - `file` 只能发送项目目录下的文件(路径越界会被拒绝)
219
+ - `restart` 会中断所有会话
220
+
221
+ ### 使用示例
222
+
223
+ ```bash
224
+ evolclaw ctl model opus # 切换到 opus
225
+ evolclaw ctl effort low # 降低推理强度
226
+ evolclaw ctl compact # 压缩上下文
227
+ evolclaw ctl status # 查看服务状态
228
+ evolclaw ctl chatmode proactive # 切换为主动模式
229
+ evolclaw ctl send "你好" # proactive 模式发送消息
230
+ ```
231
+
232
+ ---
233
+
234
+ ## Trigger 定时触发器
235
+
236
+ 通过 `/trigger` 命令设置延迟或定时任务,系统在指定时间重新激活 Agent 执行。
237
+
238
+ ### 注册
239
+
240
+ ```
241
+ /trigger set --delay <时长> --prompt "<任务内容>"
242
+ /trigger set --at <ISO时间> --prompt "<任务内容>"
243
+ /trigger set --cron <表达式> --prompt "<任务内容>"
244
+ ```
245
+
246
+ **时间格式:**
247
+ - `--delay`:`30m`、`2h`、`1d`、`2h30m`
248
+ - `--at`:ISO 格式,如 `2026-05-15T09:00`
249
+ - `--cron`:标准 cron 表达式,如 `0 9 * * *`(每天 9 点)
250
+
251
+ ### 可选参数
252
+
253
+ | 参数 | 说明 | 默认值 |
254
+ |------|------|--------|
255
+ | `--channel <实例名>` | 目标通道实例 | 当前通道 |
256
+ | `--channelid <id>` | 目标对话 ID | 当前对话 |
257
+ | `--thread <id>` | 目标 thread(与 --session 互斥) | 无 |
258
+ | `--session latest` | 续接最后活跃会话(用户可见输出) | 默认 |
259
+ | `--session silent` | 新建独立会话静默执行 | - |
260
+ | `--name <标识>` | 触发器名称 | 自动生成 |
261
+ | `--agent <名称>` | 目标 agent | 当前 agent |
262
+
263
+ ### 管理
264
+
265
+ ```
266
+ /trigger 查看活跃触发器
267
+ /trigger list 查看所有触发器(含历史)
268
+ /trigger update <名称|ID> <参数> 修改触发器
269
+ /trigger cancel <名称|ID> 取消触发器
270
+ ```
271
+
272
+ ### 修改触发器
273
+
274
+ ```
275
+ /trigger update <名称|ID> [--delay <时长>] [--at <ISO时间>] [--cron <表达式>]
276
+ [--prompt "<任务内容>"] [--name <新名称>]
277
+ [--session latest|silent] [--agent <名称>]
278
+ [--channel <实例名> --channelid <id>]
279
+ ```
280
+
281
+ 至少指定一个修改参数。未指定的字段保持不变。
282
+
283
+ ### 会话策略
284
+
285
+ - **latest**:续接已有会话,输出对用户可见。适合提醒、跟进对话。
286
+ - **silent**:新建独立 autonomous 会话,不打扰用户。适合后台清理、扫描、生成文件。
287
+
288
+ ### 权限
289
+
290
+ - 所有用户可注册触发器
291
+ - cancel 自己的触发器:按名称或 ID
292
+ - cancel 他人的触发器:需 owner/admin 权限
293
+
294
+ ### 示例
295
+
296
+ ```
297
+ /trigger set --delay 30m --prompt "检查构建状态并汇报"
298
+ /trigger set --at 2026-05-16T09:00 --prompt "生成日报" --session silent
299
+ /trigger set --cron "0 */6 * * *" --prompt "检查服务健康" --session silent --name health-check
300
+ /trigger update health-check --cron "0 */4 * * *"
301
+ /trigger update health-check --prompt "检查服务健康并清理过期日志" --name health-check-v2
302
+ /trigger cancel health-check-v2
303
+ ```
304
+
305
+ ### 注意事项
306
+
307
+ - `--thread` 与 `--session` 互斥
308
+ - `--channel` 与 `--channelid` 必须同时指定或同时省略
309
+ - delay/at 类型触发一次后自动归档;cron 类型持续触发直到 cancel
310
+ - 修改触发器时,未指定的字段保持不变
311
+ - 修改 cron 触发器的时间表达式会立即重新计算下次触发时间
@@ -398,6 +398,25 @@ export class AgentRunner {
398
398
  // 尝试发送交互卡片
399
399
  let cardSent = false;
400
400
  if (permCtx.adapter?.send) {
401
+ // 发送计划内容:找 plans 目录中最新修改的 .md 文件
402
+ if (sendPrompt) {
403
+ try {
404
+ const plansDir = path.join(process.env.HOME || '/root', '.claude', 'plans');
405
+ const files = fs.readdirSync(plansDir)
406
+ .filter((f) => f.endsWith('.md'))
407
+ .map((f) => ({ name: f, mtime: fs.statSync(path.join(plansDir, f)).mtimeMs }))
408
+ .sort((a, b) => b.mtime - a.mtime);
409
+ if (files.length > 0) {
410
+ const planContent = fs.readFileSync(path.join(plansDir, files[0].name), 'utf-8');
411
+ if (planContent.trim()) {
412
+ await sendPrompt(`📋 **计划内容**\n\n${planContent}`);
413
+ }
414
+ }
415
+ }
416
+ catch {
417
+ // 读取失败不影响后续审批流程
418
+ }
419
+ }
401
420
  const requestId = `plan-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
402
421
  const interaction = {
403
422
  type: 'interaction',
@@ -497,6 +516,8 @@ export class AgentRunner {
497
516
  let lastSessionId;
498
517
  // tool_use_id → tool_name 映射,用于从 SDKUserMessage 的 tool_result 块中还原工具名
499
518
  const toolUseNames = new Map();
519
+ let turnCount = 0;
520
+ const seenMessageIds = new Set();
500
521
  for await (const event of sdkStream) {
501
522
  // 提取 session_id(任意 SDK 事件都可能携带)
502
523
  if (event.session_id && event.session_id !== lastSessionId) {
@@ -523,15 +544,31 @@ export class AgentRunner {
523
544
  }
524
545
  // assistant: 提取 tool_use 和文本(仅无 text_delta 时提取文本)
525
546
  if (event.type === 'assistant' && event.message?.content) {
547
+ const msgId = event.message.id;
548
+ if (!msgId || !seenMessageIds.has(msgId)) {
549
+ if (msgId)
550
+ seenMessageIds.add(msgId);
551
+ turnCount++;
552
+ }
553
+ // 统计本轮 base agent 全部输出字符数(text + tool_use input)
554
+ let turnOutputChars = 0;
555
+ for (const content of event.message.content) {
556
+ if (content.type === 'tool_use') {
557
+ const inputStr = typeof content.input === 'string' ? content.input : JSON.stringify(content.input || '');
558
+ turnOutputChars += inputStr.length;
559
+ }
560
+ else if (content.type === 'text' && content.text) {
561
+ turnOutputChars += content.text.length;
562
+ }
563
+ }
526
564
  for (const content of event.message.content) {
527
565
  if (content.type === 'tool_use') {
528
- // 记录 id → name 映射,供后续 tool_result 使用
529
566
  if (content.id)
530
567
  toolUseNames.set(content.id, content.name);
531
- yield { type: 'tool_use', name: content.name, input: content.input, callId: content.id };
568
+ yield { type: 'tool_use', name: content.name, input: content.input, callId: content.id, turn: turnCount, outputTokens: turnOutputChars };
532
569
  }
533
570
  else if (content.type === 'text' && content.text) {
534
- yield { type: 'text', text: content.text };
571
+ yield { type: 'text', text: content.text, outputTokens: turnOutputChars, turn: turnCount };
535
572
  }
536
573
  }
537
574
  }
@@ -2,6 +2,7 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import os from 'os';
4
4
  import { getAunClient } from './client.js';
5
+ import { agentMdPath, aidLocalDir } from '../../paths.js';
5
6
  export function buildInitialAgentMd(opts) {
6
7
  const agentName = opts.aid.split('.')[0];
7
8
  const agentType = opts.type || 'ai';
@@ -91,7 +92,7 @@ async function createBareClient(aunPath) {
91
92
  }
92
93
  export async function agentmdGet(aid, opts) {
93
94
  const aunPath = opts?.aunPath ?? path.join(os.homedir(), '.aun');
94
- const localPath = path.join(aunPath, 'AIDs', aid, 'agent.md');
95
+ const localPath = agentMdPath(aid);
95
96
  // === Path A: local agent.md exists ===
96
97
  if (fs.existsSync(localPath)) {
97
98
  const content = fs.readFileSync(localPath, 'utf-8');
@@ -135,7 +136,7 @@ export async function agentmdGet(aid, opts) {
135
136
  const raw = await client.auth.downloadAgentMd(aid);
136
137
  if (!opts?.withVerification) {
137
138
  // Persist without verification
138
- const aidDir = path.join(aunPath, 'AIDs', aid);
139
+ const aidDir = aidLocalDir(aid);
139
140
  fs.mkdirSync(aidDir, { recursive: true });
140
141
  fs.writeFileSync(path.join(aidDir, 'agent.md'), raw, 'utf-8');
141
142
  return raw;
@@ -143,7 +144,7 @@ export async function agentmdGet(aid, opts) {
143
144
  const certPem = await obtainCertPem(aid, aunPath, client);
144
145
  const verification = await verifyContent(raw, aid, certPem, client);
145
146
  // Persist to local
146
- const aidDir = path.join(aunPath, 'AIDs', aid);
147
+ const aidDir = aidLocalDir(aid);
147
148
  fs.mkdirSync(aidDir, { recursive: true });
148
149
  fs.writeFileSync(path.join(aidDir, 'agent.md'), raw, 'utf-8');
149
150
  return { content: raw, verification };
@@ -172,9 +173,9 @@ export async function agentmdPut(content, opts) {
172
173
  signed = content;
173
174
  }
174
175
  await client.auth.uploadAgentMd(signed);
175
- const aidDir = path.join(aunPath, 'AIDs', opts.aid);
176
- fs.mkdirSync(aidDir, { recursive: true });
177
- fs.writeFileSync(path.join(aidDir, 'agent.md'), signed, 'utf-8');
176
+ const dir = aidLocalDir(opts.aid);
177
+ fs.mkdirSync(dir, { recursive: true });
178
+ fs.writeFileSync(path.join(dir, 'agent.md'), signed, 'utf-8');
178
179
  }
179
180
  finally {
180
181
  if (ownClient)
@@ -14,21 +14,15 @@ export function suppressSdkLogs() {
14
14
  const _origLog = console.log;
15
15
  const _origInfo = console.info;
16
16
  const _origWarn = console.warn;
17
- const _origError = console.error;
18
- const SDK_LOG_RE = /^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+\]\[(?:DEBUG|INFO|WARN)\]/;
19
- const SDK_ERROR_RE = /^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+\]\[ERROR\]/;
20
- console.log = (...args) => { if (typeof args[0] === 'string') {
21
- if (SDK_LOG_RE.test(args[0]))
22
- return;
23
- if (SDK_ERROR_RE.test(args[0])) {
24
- _origError(...args);
25
- return;
26
- }
27
- } _origLog(...args); };
17
+ const SDK_LOG_RE = /^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+\]\[(?:DEBUG|INFO|WARN|ERROR)\]/;
18
+ console.log = (...args) => { if (typeof args[0] === 'string' && SDK_LOG_RE.test(args[0]))
19
+ return; _origLog(...args); };
28
20
  console.info = (...args) => { if (typeof args[0] === 'string' && SDK_LOG_RE.test(args[0]))
29
21
  return; _origInfo(...args); };
30
22
  console.warn = (...args) => { if (typeof args[0] === 'string' && SDK_LOG_RE.test(args[0]))
31
23
  return; _origWarn(...args); };
24
+ console.error = (...args) => { if (typeof args[0] === 'string' && SDK_LOG_RE.test(args[0]))
25
+ return; process.stderr.write(args.map(String).join(' ') + '\n'); };
32
26
  }
33
27
  // ==================== Constants ====================
34
28
  export const MIN_AUN_CORE_SDK = [0, 2, 17];
@@ -3,7 +3,7 @@ import path from 'path';
3
3
  import os from 'os';
4
4
  import crypto from 'crypto';
5
5
  import { getAunClient, downloadCaRoot } from './client.js';
6
- import { resolvePaths } from '../../paths.js';
6
+ import { resolvePaths, aidsDir as evolclawAidsDir, agentMdPath } from '../../paths.js';
7
7
  // ==================== Validation ====================
8
8
  export function isValidAid(name) {
9
9
  const labels = name.split('.');
@@ -11,17 +11,36 @@ export function isValidAid(name) {
11
11
  }
12
12
  // ==================== AID Operations ====================
13
13
  export function aidList(aunPath) {
14
- const aidsDir = path.join(aunPath ?? path.join(os.homedir(), '.aun'), 'AIDs');
15
- if (!fs.existsSync(aidsDir))
16
- return [];
17
- const entries = fs.readdirSync(aidsDir, { withFileTypes: true });
18
- return entries
19
- .filter(e => e.isDirectory())
20
- .map(e => ({
21
- aid: e.name,
22
- hasPrivateKey: fs.existsSync(path.join(aidsDir, e.name, 'private')),
23
- hasAgentMd: fs.existsSync(path.join(aidsDir, e.name, 'agent.md')),
24
- }));
14
+ const aunAidsDir = path.join(aunPath ?? path.join(os.homedir(), '.aun'), 'AIDs');
15
+ const ecAidsDir = evolclawAidsDir();
16
+ const seen = new Map();
17
+ // Scan ~/.aun/AIDs (private keys live here)
18
+ if (fs.existsSync(aunAidsDir)) {
19
+ for (const e of fs.readdirSync(aunAidsDir, { withFileTypes: true })) {
20
+ if (!e.isDirectory())
21
+ continue;
22
+ seen.set(e.name, {
23
+ aid: e.name,
24
+ hasPrivateKey: fs.existsSync(path.join(aunAidsDir, e.name, 'private')),
25
+ hasAgentMd: fs.existsSync(agentMdPath(e.name)),
26
+ });
27
+ }
28
+ }
29
+ // Scan $EVOLCLAW_HOME/AIDs (agent.md lives here)
30
+ if (fs.existsSync(ecAidsDir) && ecAidsDir !== aunAidsDir) {
31
+ for (const e of fs.readdirSync(ecAidsDir, { withFileTypes: true })) {
32
+ if (!e.isDirectory())
33
+ continue;
34
+ if (seen.has(e.name))
35
+ continue;
36
+ seen.set(e.name, {
37
+ aid: e.name,
38
+ hasPrivateKey: fs.existsSync(path.join(aunAidsDir, e.name, 'private')),
39
+ hasAgentMd: fs.existsSync(agentMdPath(e.name)),
40
+ });
41
+ }
42
+ }
43
+ return [...seen.values()];
25
44
  }
26
45
  export async function aidCreate(aid, opts) {
27
46
  const aunPath = opts?.aunPath ?? path.join(os.homedir(), '.aun');
@@ -71,7 +90,7 @@ export function aidShow(aid, opts) {
71
90
  const aunPath = opts?.aunPath ?? path.join(os.homedir(), '.aun');
72
91
  const aidDir = path.join(aunPath, 'AIDs', aid);
73
92
  const hasPrivateKey = fs.existsSync(path.join(aidDir, 'private'));
74
- const hasAgentMd = fs.existsSync(path.join(aidDir, 'agent.md'));
93
+ const hasAgentMd = fs.existsSync(agentMdPath(aid));
75
94
  let certExpiresAt = null;
76
95
  let certSubject = null;
77
96
  const certPath = path.join(aidDir, 'public', 'cert.pem');
@@ -17,6 +17,7 @@ export async function groupSend(args) {
17
17
  contentType: args.body.contentType,
18
18
  text: args.body.text,
19
19
  transcript: args.body.transcript,
20
+ onProgress: args.onUploadProgress,
20
21
  });
21
22
  payload = built.payload;
22
23
  break;
@@ -1,8 +1,21 @@
1
+ import path from 'path';
1
2
  import { createShortConnection } from '../rpc/index.js';
2
3
  import { uploadFileAndBuildPayload } from './upload.js';
4
+ import { appendMessageLog, buildOutboundEntry } from '../../core/message/message-log.js';
5
+ import { chatDirPath } from '../../core/session/session-fs-store.js';
6
+ import { resolvePaths } from '../../paths.js';
3
7
  export async function msgSend(args) {
4
8
  const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
5
9
  try {
10
+ // 1. 解析对端身份(30天缓存)
11
+ const { agentsDir } = resolvePaths();
12
+ const selfAgentDir = path.join(agentsDir, args.from);
13
+ const { PeerIdentityCache } = await import('../../core/relation/peer-identity.js');
14
+ const peerIdentity = await PeerIdentityCache.resolve('aun', args.to, selfAgentDir, conn, false);
15
+ // 2. 决定 chatmode(遵循来源1-3)
16
+ // 私聊:非 human 对端 → proactive,human 对端 → interactive
17
+ const chatmode = peerIdentity.isAgent ? 'proactive' : 'interactive';
18
+ // 3. 构建 payload
6
19
  let payload;
7
20
  switch (args.body.mode) {
8
21
  case 'text':
@@ -24,15 +37,53 @@ export async function msgSend(args) {
24
37
  contentType: args.body.contentType,
25
38
  text: args.body.text,
26
39
  transcript: args.body.transcript,
40
+ onProgress: args.onUploadProgress,
27
41
  });
28
42
  payload = built.payload;
29
43
  break;
30
44
  }
31
45
  }
46
+ // 4. 写入 payload.chatmode
47
+ payload.chatmode = chatmode;
48
+ // 5. 写入 payload.thread_id(如果指定)
49
+ if (args.thread) {
50
+ payload.thread_id = args.thread;
51
+ }
32
52
  const sendParams = { to: args.to, payload };
33
53
  // Default: plaintext. Set encrypt: true to enable E2EE.
34
54
  sendParams.encrypt = args.encrypt === true;
35
55
  const result = await conn.call('message.send', sendParams);
56
+ // 5. 写出方向 jsonl(与 daemon 一致格式,标记 source)
57
+ // source 标记:
58
+ // - 'cli': 用户手动调用 ec msg send
59
+ // - 'msg': agent 在会话中调用 ec msg send
60
+ if (result?.message_id) {
61
+ try {
62
+ const sessionsDir = resolvePaths().sessionsDir;
63
+ const chatDir = chatDirPath(sessionsDir, 'aun', args.to, args.from);
64
+ const textContent = args.body.mode === 'text' ? args.body.text
65
+ : args.body.mode === 'link' ? `[link] ${args.body.url}`
66
+ : args.body.mode === 'file' ? `[file] ${args.body.filePath}`
67
+ : `[payload]`;
68
+ // 判断是 agent 调用还是用户手动调用
69
+ // 优先通过 --thread 参数判断(有 thread → msg,无 → cli)
70
+ // 兜底通过环境变量判断(向后兼容)
71
+ const isInSession = !!process.env.EVOLCLAW_SESSION_ID;
72
+ const source = args.thread ? 'msg' : (isInSession ? 'msg' : 'cli');
73
+ appendMessageLog(chatDir, buildOutboundEntry({
74
+ from: args.from,
75
+ to: args.to,
76
+ chatType: 'private',
77
+ msgId: result.message_id,
78
+ content: textContent,
79
+ encrypt: args.encrypt === true,
80
+ chatmode, // 使用解析出的 chatmode
81
+ msgType: 'text',
82
+ source,
83
+ }));
84
+ }
85
+ catch { }
86
+ }
36
87
  return {
37
88
  ok: true,
38
89
  message_id: result?.message_id,