evolclaw 3.1.3 → 3.1.5

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 (100) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/assets/.env.template +4 -0
  3. package/assets/config.json.template +6 -0
  4. package/assets/wechat-group-qr.jpeg +0 -0
  5. package/dist/agents/claude-runner.js +348 -156
  6. package/dist/agents/kit-renderer.js +211 -42
  7. package/dist/aun/aid/agentmd.js +75 -139
  8. package/dist/aun/aid/client.js +1 -14
  9. package/dist/aun/aid/identity.js +381 -54
  10. package/dist/aun/aid/index.js +3 -2
  11. package/dist/aun/aid/store.js +74 -0
  12. package/dist/aun/msg/p2p.js +26 -2
  13. package/dist/aun/rpc/connection.js +23 -35
  14. package/dist/channels/aun.js +92 -144
  15. package/dist/channels/dingtalk.js +1 -0
  16. package/dist/channels/feishu.js +270 -190
  17. package/dist/channels/qqbot.js +1 -0
  18. package/dist/channels/wechat.js +1 -0
  19. package/dist/channels/wecom.js +1 -0
  20. package/dist/cli/agent.js +26 -27
  21. package/dist/cli/bench.js +45 -34
  22. package/dist/cli/help.js +23 -0
  23. package/dist/cli/index.js +538 -77
  24. package/dist/cli/init-channel.js +7 -4
  25. package/dist/cli/link-rules.js +2 -1
  26. package/dist/cli/model.js +324 -0
  27. package/dist/cli/net-check.js +138 -56
  28. package/dist/cli/watch-msg.js +7 -7
  29. package/dist/cli/watch-web/debug-log.js +18 -0
  30. package/dist/cli/watch-web/server.js +306 -0
  31. package/dist/cli/watch-web/sources/aid.js +63 -0
  32. package/dist/cli/watch-web/sources/msg.js +70 -0
  33. package/dist/cli/watch-web/sources/session.js +638 -0
  34. package/dist/cli/watch-web/sources/types.js +10 -0
  35. package/dist/cli/watch-web/static/app.js +546 -0
  36. package/dist/cli/watch-web/static/index.html +54 -0
  37. package/dist/cli/watch-web/static/style.css +247 -0
  38. package/dist/core/channel-loader.js +7 -4
  39. package/dist/core/command-handler.js +87 -93
  40. package/dist/core/evolagent-registry.js +1 -1
  41. package/dist/core/evolagent.js +4 -4
  42. package/dist/core/interaction-router.js +59 -0
  43. package/dist/core/message/message-bridge.js +6 -6
  44. package/dist/core/message/message-log.js +2 -2
  45. package/dist/core/message/message-processor.js +104 -118
  46. package/dist/core/message/stream-idle-monitor.js +21 -0
  47. package/dist/core/model/model-catalog.js +215 -0
  48. package/dist/core/model/model-scope.js +250 -0
  49. package/dist/core/relation/peer-identity.js +78 -44
  50. package/dist/core/relation/peer-key.js +16 -0
  51. package/dist/core/session/session-fs-store.js +34 -55
  52. package/dist/core/session/session-key.js +24 -0
  53. package/dist/core/session/session-manager.js +312 -251
  54. package/dist/core/session/session-mapper.js +9 -4
  55. package/dist/core/trigger/manager.js +37 -0
  56. package/dist/core/trigger/scheduler.js +2 -1
  57. package/dist/index.js +10 -3
  58. package/dist/ipc.js +22 -0
  59. package/dist/paths.js +87 -16
  60. package/dist/utils/npm-ops.js +18 -11
  61. package/kits/docs/GUIDE.md +2 -2
  62. package/kits/docs/INDEX.md +11 -7
  63. package/kits/docs/channels/aun.md +56 -17
  64. package/kits/docs/channels/feishu.md +41 -12
  65. package/kits/docs/context-assembly.md +181 -0
  66. package/kits/docs/evolclaw/agent.md +49 -0
  67. package/kits/docs/evolclaw/aid.md +49 -0
  68. package/kits/docs/evolclaw/ctl.md +46 -0
  69. package/kits/docs/evolclaw/group.md +82 -0
  70. package/kits/docs/evolclaw/msg.md +86 -0
  71. package/kits/docs/evolclaw/rpc.md +35 -0
  72. package/kits/docs/evolclaw/storage.md +49 -0
  73. package/kits/docs/venues/aun-group.md +10 -0
  74. package/kits/docs/venues/aun-private.md +10 -0
  75. package/kits/docs/venues/client-desktop.md +10 -0
  76. package/kits/docs/venues/client-mobile.md +10 -0
  77. package/kits/docs/venues/feishu-group.md +13 -0
  78. package/kits/docs/venues/feishu-private.md +9 -0
  79. package/kits/docs/venues/group.md +11 -0
  80. package/kits/docs/venues/private.md +10 -0
  81. package/kits/eck_manifest.json +75 -39
  82. package/kits/rules/01-overview.md +20 -10
  83. package/kits/rules/05-venue.md +2 -2
  84. package/kits/rules/06-channel.md +30 -27
  85. package/kits/templates/system-fragments/baseagent.md +7 -1
  86. package/kits/templates/system-fragments/channel.md +4 -1
  87. package/kits/templates/system-fragments/identity.md +4 -4
  88. package/kits/templates/system-fragments/relation.md +8 -5
  89. package/kits/templates/system-fragments/session.md +27 -0
  90. package/kits/templates/system-fragments/venue.md +13 -1
  91. package/package.json +13 -6
  92. package/dist/aun/aid/lifecycle-log.js +0 -33
  93. package/dist/net-check.js +0 -640
  94. package/dist/utils/aid-lifecycle-log.js +0 -33
  95. package/dist/watch-msg.js +0 -544
  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
  100. package/kits/templates/system-fragments/eckruntime.md +0 -14
@@ -1,4 +1,5 @@
1
1
  import { formatTimestamp } from './session-fs-store.js';
2
+ import { formatSessionKey, DEFAULT_THREAD_ID } from './session-key.js';
2
3
  export function sessionToFile(session) {
3
4
  const metadata = {};
4
5
  if (session.metadata) {
@@ -15,19 +16,22 @@ export function sessionToFile(session) {
15
16
  if (session.metadata.resumeAt)
16
17
  metadata.resumeAt = session.metadata.resumeAt;
17
18
  for (const [k, v] of Object.entries(session.metadata)) {
18
- if (['isActive', 'channelName', 'permissionMode', 'peerId', 'peerName', 'groupId', 'replyContext', 'agentSessions', 'resumeAt'].includes(k))
19
+ if (['isActive', 'channelKey', 'channelName', 'permissionMode', 'peerId', 'peerName', 'groupId', 'replyContext', 'agentSessions', 'resumeAt'].includes(k))
19
20
  continue;
20
21
  if (v !== undefined)
21
22
  metadata[k] = v;
22
23
  }
23
24
  }
24
25
  const now = session.updatedAt || Date.now();
26
+ const channelType = session.channelType || session.channel;
27
+ const threadId = session.threadId || DEFAULT_THREAD_ID;
25
28
  return {
26
29
  id: session.id,
27
30
  channel: session.channel,
28
- channelType: session.channelType || session.channel,
31
+ channelType,
29
32
  channelId: session.channelId,
30
- selfId: session.selfId ?? null,
33
+ sessionKey: formatSessionKey(channelType, session.channelId, threadId),
34
+ selfAID: session.selfAID,
31
35
  agentType: session.agentId || 'claude',
32
36
  threadId: session.threadId || '',
33
37
  chatType: session.chatType || 'private',
@@ -71,7 +75,8 @@ export function fileToSession(file) {
71
75
  channel: file.channel,
72
76
  channelType: file.channelType,
73
77
  channelId: file.channelId,
74
- selfId: file.selfId ?? undefined,
78
+ sessionKey: file.sessionKey || formatSessionKey(file.channelType, file.channelId, file.threadId || DEFAULT_THREAD_ID),
79
+ selfAID: file.selfAID,
75
80
  agentId: file.agentType,
76
81
  threadId: file.threadId,
77
82
  chatType: file.chatType,
@@ -1,6 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { atomicWriteJson, appendJsonl } from '../session/session-fs-store.js';
4
+ import { formatChannelKey } from '../channel-loader.js';
4
5
  import { logger } from '../../utils/logger.js';
5
6
  export class TriggerManager {
6
7
  aid;
@@ -22,6 +23,26 @@ export class TriggerManager {
22
23
  const raw = fs.readFileSync(this.triggersPath, 'utf8');
23
24
  const data = JSON.parse(raw);
24
25
  this.triggers = new Map(Object.entries(data.triggers ?? {}));
26
+ // Migrate old channel key format: "selfAID#type#name" → "type#selfAID#name"
27
+ let migrated = false;
28
+ for (const trigger of this.triggers.values()) {
29
+ const fixed = this.migrateChannelKey(trigger.targetChannel);
30
+ if (fixed !== trigger.targetChannel) {
31
+ trigger.targetChannel = fixed;
32
+ migrated = true;
33
+ }
34
+ if (trigger.createdByChannel) {
35
+ const fixedBy = this.migrateChannelKey(trigger.createdByChannel);
36
+ if (fixedBy !== trigger.createdByChannel) {
37
+ trigger.createdByChannel = fixedBy;
38
+ migrated = true;
39
+ }
40
+ }
41
+ }
42
+ if (migrated) {
43
+ logger.info(`[TriggerManager] Migrated old channel key format`);
44
+ this.save();
45
+ }
25
46
  return [...this.triggers.values()];
26
47
  }
27
48
  catch (e) {
@@ -30,6 +51,22 @@ export class TriggerManager {
30
51
  return [];
31
52
  }
32
53
  }
54
+ /**
55
+ * Detect and fix old format "selfAID#type#name" → current "type#selfAID#name".
56
+ * Old format: first segment contains '.' (AID like "evolai.agentid.pub").
57
+ */
58
+ migrateChannelKey(key) {
59
+ if (!key || !key.includes('#'))
60
+ return key;
61
+ const parts = key.split('#');
62
+ if (parts.length !== 3)
63
+ return key;
64
+ const [first, second, third] = parts;
65
+ if (first.includes('.') && !second.includes('.')) {
66
+ return formatChannelKey({ type: second, selfAID: first, name: third });
67
+ }
68
+ return key;
69
+ }
33
70
  save() {
34
71
  const data = {
35
72
  version: 1,
@@ -211,8 +211,9 @@ export class TriggerScheduler {
211
211
  buildSyntheticMessage(trigger, messageId) {
212
212
  return {
213
213
  channel: trigger.targetChannel,
214
+ channelType: trigger.targetChannelType,
214
215
  channelId: trigger.targetChannelId,
215
- selfId: this.aid,
216
+ selfAID: this.aid,
216
217
  threadId: trigger.targetThreadId ?? '',
217
218
  agentId: trigger.agentId,
218
219
  chatType: 'private',
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import { ensureDataDirs, resolvePaths, getPackageRoot, agentMdPath } from './pat
5
5
  import { resolveAnthropicConfig } from './agents/resolve.js';
6
6
  import { loadDefaults, autoMigrateIfNeeded, migrateIdentitiesIfNeeded } from './config-store.js';
7
7
  import { CONFIG_SCHEMA_VERSION } from './types.js';
8
+ import dotenv from 'dotenv';
8
9
  import { SessionManager } from './core/session/session-manager.js';
9
10
  import { ClaudeAgentPlugin } from './agents/claude-runner.js';
10
11
  import { CodexAgentPlugin } from './agents/codex-runner.js';
@@ -25,7 +26,7 @@ import { StatsCollector } from './utils/stats.js';
25
26
  import { AidStatsCollector } from './utils/stats.js';
26
27
  import { PermissionGateway } from './core/permission.js';
27
28
  import { InteractionRouter } from './core/interaction-router.js';
28
- import { ChannelLoader } from './core/channel-loader.js';
29
+ import { ChannelLoader, tryParseChannelKey } from './core/channel-loader.js';
29
30
  import { AgentLoader } from './core/baseagent-loader.js';
30
31
  import { EvolAgentRegistry } from './core/evolagent-registry.js';
31
32
  import { buildReloadHooks } from './core/channel-loader.js';
@@ -183,6 +184,8 @@ async function main() {
183
184
  logger.info(`EvolClaw v${readEvolclawVersion()} starting... (fastaun v${readFastaunVersion()})`);
184
185
  // 确保数据目录存在
185
186
  ensureDataDirs();
187
+ // 加载 $EVOLCLAW_HOME/.env 到 process.env(不覆盖已存在的变量)
188
+ dotenv.config({ path: path.join(resolvePaths().root, '.env') });
186
189
  // ── 单实例保护(pre-check + post-write self-check)──
187
190
  // pre-check:发现已有活 main 直接退出,避免起任何副作用
188
191
  {
@@ -313,7 +316,7 @@ async function main() {
313
316
  // 优先级:群聊 > nothuman > private
314
317
  if (chatType === 'group')
315
318
  return cm.group;
316
- if (peerType && peerType !== 'human' && peerType !== 'unknown')
319
+ if (peerType && peerType !== 'human')
317
320
  return cm.nothuman;
318
321
  return cm.private;
319
322
  });
@@ -550,7 +553,8 @@ async function main() {
550
553
  const owningAgent = agentRegistry.resolveByChannel(inst.adapter.channelKey);
551
554
  const effectiveDefault = owningAgent?.projectPath
552
555
  ?? primaryAgent.projectPath;
553
- const session = await sessionManager.getOrCreateSession(inst.adapter.channelKey, channelId, effectiveDefault, undefined, undefined, undefined, undefined);
556
+ const parsedKey = tryParseChannelKey(inst.adapter.channelKey);
557
+ const session = await sessionManager.getOrCreateSession(inst.adapter.channelKey, channelId, effectiveDefault, undefined, undefined, undefined, undefined, undefined, undefined, parsedKey?.selfAID, parsedKey?.type);
554
558
  return path.isAbsolute(session.projectPath)
555
559
  ? session.projectPath
556
560
  : path.resolve(process.cwd(), session.projectPath);
@@ -863,6 +867,9 @@ async function main() {
863
867
  queued: messageQueue.getQueueLengthByAgent(agentName),
864
868
  }));
865
869
  ipcServer.setAunAidStatsProvider(() => aidStatsCollector.getAllSnapshots());
870
+ ipcServer.setAunAidStatsRecorder((params) => {
871
+ aidStatsCollector.recordOutbound(params.aid, params.toPeer, Buffer.byteLength(params.text || '', 'utf-8'), params.text, false, params.encrypt, params.chatmode);
872
+ });
866
873
  // ── Reload hooks: enable agentRegistry.reload() to drain/disconnect/restart channels ──
867
874
  const reloadHooks = buildReloadHooks({
868
875
  channelLoader,
package/dist/ipc.js CHANGED
@@ -11,6 +11,7 @@ export class IpcServer {
11
11
  agentRegistry;
12
12
  aunAidProvider;
13
13
  aunAidStatsProvider;
14
+ aunAidStatsRecorder;
14
15
  constructor(socketPath, getStatus, commandExecutor) {
15
16
  this.socketPath = socketPath;
16
17
  this.getStatus = getStatus;
@@ -28,6 +29,10 @@ export class IpcServer {
28
29
  setAunAidStatsProvider(provider) {
29
30
  this.aunAidStatsProvider = provider;
30
31
  }
32
+ /** Inject AUN AID stats recorder for aun-aid-stats-record-outbound IPC handler */
33
+ setAunAidStatsRecorder(recorder) {
34
+ this.aunAidStatsRecorder = recorder;
35
+ }
31
36
  start() {
32
37
  // Remove stale socket file (Unix only — named pipes auto-cleanup on process exit)
33
38
  if (!isNamedPipe(this.socketPath)) {
@@ -97,6 +102,23 @@ export class IpcServer {
97
102
  const stats = this.aunAidStatsProvider ? this.aunAidStatsProvider() : [];
98
103
  return { ok: true, stats };
99
104
  }
105
+ case 'aun-aid-stats-record-outbound': {
106
+ if (!this.aunAidStatsRecorder)
107
+ return { ok: false, error: 'recorder not configured' };
108
+ try {
109
+ this.aunAidStatsRecorder({
110
+ aid: cmd.aid,
111
+ toPeer: cmd.toPeer,
112
+ text: cmd.text ?? '',
113
+ encrypt: cmd.encrypt,
114
+ chatmode: cmd.chatmode,
115
+ });
116
+ return { ok: true };
117
+ }
118
+ catch (e) {
119
+ return { ok: false, error: String(e?.message || e) };
120
+ }
121
+ }
100
122
  case 'ctl': {
101
123
  if (!this.commandExecutor)
102
124
  return { ok: false, error: 'ctl not configured' };
package/dist/paths.js CHANGED
@@ -46,6 +46,14 @@ export function resolvePaths() {
46
46
  };
47
47
  }
48
48
  // ── AID 路径(agent.md 存放在 $EVOLCLAW_HOME/AIDs/<aid>/)──
49
+ /**
50
+ * AUN SDK 数据根路径(密钥/证书/AgentMDs 等)。
51
+ * 与 evolclaw 数据共用 $EVOLCLAW_HOME 根,避免散落到 ~/.aun。
52
+ * 启动时 SDK 通过 aun_path 选项指向这里;agent.md 子目录通过 setAgentMdPath(aidsDir()) 单独设置。
53
+ */
54
+ export function aunPath() {
55
+ return resolveRoot();
56
+ }
49
57
  export function aidsDir() {
50
58
  return path.join(resolveRoot(), 'AIDs');
51
59
  }
@@ -109,30 +117,93 @@ export function ensureDataDirs() {
109
117
  fs.mkdirSync(p.eckDir, { recursive: true });
110
118
  fs.mkdirSync(eckDebugDir(), { recursive: true });
111
119
  fs.mkdirSync(aidsDir(), { recursive: true });
112
- migrateAgentMdFromAun();
120
+ seedConfigTemplates();
121
+ migrateFromAun();
113
122
  }
123
+ const MIGRATION_MARKER = '.migrated-from-aun';
114
124
  /**
115
- * One-time migration: copy agent.md from ~/.aun/AIDs/<aid>/ to $EVOLCLAW_HOME/AIDs/<aid>/
116
- * if the new location doesn't have it yet.
125
+ * 一次性迁移:把 ~/.aun 下的 SDK 数据搬到 $EVOLCLAW_HOME 下。
126
+ * 通过标记文件判断是否已迁移,完成后写入标记,后续版本可删除此函数。
117
127
  */
118
- function migrateAgentMdFromAun() {
119
- const aunAidsDir = path.join(os.homedir(), '.aun', 'AIDs');
120
- const ecAids = aidsDir();
121
- if (!fs.existsSync(aunAidsDir) || aunAidsDir === ecAids)
128
+ function migrateFromAun() {
129
+ const newRoot = resolveRoot();
130
+ const markerPath = path.join(newRoot, MIGRATION_MARKER);
131
+ if (fs.existsSync(markerPath))
132
+ return;
133
+ const oldRoot = path.join(os.homedir(), '.aun');
134
+ if (!fs.existsSync(oldRoot) || oldRoot === newRoot) {
135
+ try {
136
+ fs.writeFileSync(markerPath, new Date().toISOString(), 'utf-8');
137
+ }
138
+ catch { }
122
139
  return;
140
+ }
141
+ const copyFileIfMissing = (src, dst) => {
142
+ if (!fs.existsSync(src) || fs.existsSync(dst))
143
+ return;
144
+ fs.mkdirSync(path.dirname(dst), { recursive: true });
145
+ fs.copyFileSync(src, dst);
146
+ };
147
+ const copyDirIfMissing = (src, dst) => {
148
+ if (!fs.existsSync(src))
149
+ return;
150
+ if (!fs.statSync(src).isDirectory())
151
+ return;
152
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
153
+ const s = path.join(src, entry.name);
154
+ const d = path.join(dst, entry.name);
155
+ if (entry.isDirectory()) {
156
+ copyDirIfMissing(s, d);
157
+ }
158
+ else if (entry.isFile()) {
159
+ copyFileIfMissing(s, d);
160
+ }
161
+ }
162
+ };
123
163
  try {
124
- for (const entry of fs.readdirSync(aunAidsDir, { withFileTypes: true })) {
125
- if (!entry.isDirectory())
126
- continue;
127
- const oldMd = path.join(aunAidsDir, entry.name, 'agent.md');
128
- const newMd = path.join(ecAids, entry.name, 'agent.md');
129
- if (fs.existsSync(oldMd) && !fs.existsSync(newMd)) {
130
- fs.mkdirSync(path.join(ecAids, entry.name), { recursive: true });
131
- fs.copyFileSync(oldMd, newMd);
164
+ const oldAids = path.join(oldRoot, 'AIDs');
165
+ const newAids = aidsDir();
166
+ if (fs.existsSync(oldAids)) {
167
+ for (const entry of fs.readdirSync(oldAids, { withFileTypes: true })) {
168
+ if (!entry.isDirectory())
169
+ continue;
170
+ copyDirIfMissing(path.join(oldAids, entry.name), path.join(newAids, entry.name));
132
171
  }
133
172
  }
173
+ copyDirIfMissing(path.join(oldRoot, 'CA'), path.join(newRoot, 'CA'));
174
+ for (const f of ['.seed', '.device_id']) {
175
+ copyFileIfMissing(path.join(oldRoot, f), path.join(newRoot, f));
176
+ }
177
+ }
178
+ catch { /* best-effort */ }
179
+ try {
180
+ fs.writeFileSync(markerPath, new Date().toISOString(), 'utf-8');
181
+ }
182
+ catch { }
183
+ }
184
+ /**
185
+ * 首次运行时从 assets/ 模板拷贝 config.json 和 .env 到 $EVOLCLAW_HOME。
186
+ * 已存在则跳过,不覆盖用户修改。
187
+ */
188
+ function seedConfigTemplates() {
189
+ const root = resolveRoot();
190
+ const assetsDir = path.join(getPackageRoot(), 'assets');
191
+ const templates = [
192
+ { src: 'config.json.template', dst: 'config.json' },
193
+ { src: '.env.template', dst: '.env' },
194
+ ];
195
+ for (const { src, dst } of templates) {
196
+ const dstPath = path.join(root, dst);
197
+ if (fs.existsSync(dstPath))
198
+ continue;
199
+ const srcPath = path.join(assetsDir, src);
200
+ if (!fs.existsSync(srcPath))
201
+ continue;
202
+ try {
203
+ fs.copyFileSync(srcPath, dstPath);
204
+ }
205
+ catch { /* best-effort */ }
134
206
  }
135
- catch { /* best-effort migration */ }
136
207
  }
137
208
  // ── kits 路径(始终从包内读取,不复制到 EVOLCLAW_HOME)──
138
209
  export function kitsDir() {
@@ -84,19 +84,26 @@ export function getLocalVersion() {
84
84
  }
85
85
  /**
86
86
  * 查询 npm registry 上指定包的最新版本。
87
- * 超时 15 秒,失败返回 null
87
+ * 使用 HTTP fetch 直接查 registry API,不依赖 npm CLI
88
+ * 超时 10 秒,失败返回 null。
88
89
  */
89
- export function checkLatestVersion(pkg = 'evolclaw') {
90
- return new Promise((resolve) => {
91
- execFile('npm', ['view', pkg, 'version'], { timeout: 15000 }, (err, stdout) => {
92
- if (err) {
93
- resolve(null);
94
- return;
95
- }
96
- const ver = stdout.trim();
97
- resolve(ver || null);
90
+ export async function checkLatestVersion(pkg = 'evolclaw') {
91
+ try {
92
+ const controller = new AbortController();
93
+ const timer = setTimeout(() => controller.abort(), 10000);
94
+ const res = await fetch(`https://registry.npmjs.org/${pkg}/latest`, {
95
+ signal: controller.signal,
96
+ headers: { 'Accept': 'application/json' },
98
97
  });
99
- });
98
+ clearTimeout(timer);
99
+ if (!res.ok)
100
+ return null;
101
+ const data = await res.json();
102
+ return data.version || null;
103
+ }
104
+ catch {
105
+ return null;
106
+ }
100
107
  }
101
108
  /**
102
109
  * 完整升级流程:检查 → 比较 → 安装(失败重试一次)
@@ -2,14 +2,14 @@
2
2
 
3
3
  ## 查阅流程
4
4
 
5
- 1. 先看 `$KITS_RULES`(自动加载的 8 个规则文件)了解机制骨架
5
+ 1. 先看 `$KITS_RULES`(自动加载的 6 个规则文件 01-06)了解机制骨架
6
6
  2. 需要详细信息时,按 `INDEX.md` 找到对应文档路径
7
7
  3. Read 对应文档
8
8
 
9
9
  ## 路径解析
10
10
 
11
11
  文档中用 `$名称` 引用路径。解析步骤:
12
- 1. 查 `$KITS_RULES/01-entry.md` 的路径体系速查表
12
+ 1. 查 `$KITS_RULES/02-navigation.md` 的路径体系速查表
13
13
  2. 如需完整定义,Read `$KITS_DOCS/path-registry.md`
14
14
  3. 如需运行时实际值,Read `$ECK/path-registry.md`
15
15
 
@@ -8,6 +8,7 @@
8
8
  |------|------|------|
9
9
  | 查阅指南 | `GUIDE.md` | 文档查阅流程 |
10
10
  | 路径定义 | `path-registry.md` | 所有预定义路径及派生规则 |
11
+ | 上下文组装机制 | `context-assembly.md` | manifest 装配机制:when 条件/合并覆盖/模板渲染/运行时变量/调试输出 |
11
12
 
12
13
  ## AUN 协议
13
14
 
@@ -20,11 +21,14 @@
20
21
 
21
22
  | 文档 | 路径 | 说明 |
22
23
  |------|------|------|
23
- | Agent 命令 | `evolclaw/AGENT_CMD.md` | agent 全生命周期管理命令 |
24
- | 运行时工具 | `evolclaw/tools.md` | ctl 命令集 |
24
+ | 私聊消息 | `evolclaw/msg.md` | `ec msg` 私聊收发消息 |
25
+ | 群聊消息 | `evolclaw/group.md` | `ec group` 群聊收发与群管理 |
26
+ | Agent 生命周期 | `evolclaw/agent.md` | `ec agent` 创建/启停/配置/热重载 |
27
+ | AID 身份 | `evolclaw/aid.md` | `ec aid` 身份/证书/名片/探测对端 |
28
+ | 文件存储 | `evolclaw/storage.md` | `ec storage` 上传/下载/配额 |
29
+ | 运行时自管理 | `evolclaw/ctl.md` | `ec ctl` 切模型/推理强度/压缩/重启 |
30
+ | 底层 RPC | `evolclaw/rpc.md` | `ec rpc` 直接调 AUN 协议方法 |
25
31
  | 自我总结 | `evolclaw/self-summary.md` | 自我总结流程指南 |
26
- | 私聊消息 | `evolclaw/MSG_PRIVATE.md` | 私聊消息命令 |
27
- | 群聊消息 | `evolclaw/MSG_GROUP.md` | 群聊消息命令 |
28
32
 
29
33
  ## 身份
30
34
 
@@ -35,12 +39,12 @@
35
39
  | AID 档案规范 | `identity/AID_PROFILE_SPEC.md` | AID 档案格式规范 |
36
40
  | 路径运维 | `identity/PATH_OPS.md` | 路径运维操作 |
37
41
 
38
- ## 渠道
42
+ ## 渠道(知识文档·按需加载,不依赖当前渠道)
39
43
 
40
44
  | 文档 | 路径 | 说明 |
41
45
  |------|------|------|
42
- | AUN 渠道 | `channels/aun.md` | AUN 通信约定 |
43
- | 飞书渠道 | `channels/feishu.md` | 飞书渠道接入 |
46
+ | AUN 渠道 | `channels/aun.md` | AUN 渠道配置、参数与特有机制(网关发现/E2EE/群 ID/证书链) |
47
+ | 飞书渠道 | `channels/feishu.md` | 飞书渠道配置、参数与特有机制(appId/合并转发/卡片/user_id) |
44
48
 
45
49
  ## ECK 模板
46
50
 
@@ -1,25 +1,64 @@
1
- # AUN 通信约定
1
+ # AUN 渠道
2
2
 
3
- ## 消息收发
3
+ > 知识性文档:AUN 渠道的配置、参数与特有机制。**不依赖当前会话渠道**——任意会话都可按需 Read。
4
+ > 运行时"怎么发消息"由注入的 `[channel]` 段决定(aun 渠道用 `ec msg send` / `ec group send`),不在本文。
5
+ > 底层协议方法与 RPC 逃生通道见 `evolclaw/rpc.md`。
4
6
 
5
- - 私聊:`message.send` / `message.receive`
6
- - 群聊:`group.message.send` / `group.message.receive`
7
- - 消息格式:纯文本 / Markdown / 文件引用
7
+ ## 概述
8
8
 
9
- ## 身份识别
9
+ AUN 渠道把 agent 接入 AUN(Agent Union Network),对端以 **AID** 为身份标识(既是身份也是地址)。evolclaw 通过 AUN SDK(`@agentunion/fastaun`)维护到 Gateway 的 WebSocket 长连接,支持私聊、群聊、文件、E2EE 加密。
10
10
 
11
- - 每条入站消息携带发送者 AID
12
- - 通过 `https://<aid>/agent.md` 获取对端名片
13
- - 名片包含:名称、能力声明、联系方式
11
+ ## 配置
14
12
 
15
- ## 连接管理
13
+ AUN 渠道是**隐式**的——它不写在 `channels[]` 数组里,而是从 agent 自身的 AID 自动创建。一个 agent 就是一个 AID,启动时 evolclaw 用顶层 `aid` 字段隐式构造唯一的 aun 实例(`channel-loader.ts`)。所以真实的 agent config.json 里 `channels` 数组通常是空的(飞书等非 aun 渠道才往里加)。
16
14
 
17
- - evolclaw 自动维护 WebSocket 长连接
18
- - 断线自动重连(SDK 内置退避策略)
19
- - 连接状态可通过 `evolclaw ctl aid` 查看
15
+ ### agent 配置(`agents/<aid>/config.json`)
20
16
 
21
- ## 群聊
17
+ ```json
18
+ {
19
+ "$schema_version": 1,
20
+ "aid": "myagent.agentid.pub",
21
+ "enabled": true,
22
+ "owners": ["owner.agentid.pub"],
23
+ "channels": [],
24
+ "active_baseagent": "claude"
25
+ }
26
+ ```
22
27
 
23
- - ID 格式:`<issuer>/<group-name>`
24
- - 被 @ 时才默认响应(可通过 venue policy 配置)
25
- - 群消息按窗口批量推送(batch_window_seconds)
28
+ | 字段 | 说明 |
29
+ |------|------|
30
+ | `aid` | agent 的 AID,**即 aun 渠道身份**(隐式创建,无需在 channels[] 声明) |
31
+ | `enabled` | agent 是否启用 |
32
+ | `owners` | owner 的 **AID** 列表(最高权限);首个 owner 用于首次连接发欢迎消息 |
33
+ | `admins` | admin 的 AID 列表 |
34
+ | `channels` | 非 aun 渠道(飞书等)的实例数组;aun 不在此 |
35
+
36
+ > `owners`/`admins` 用 **AID**,不是渠道原生 ID。
37
+
38
+ ### 进程级配置(`$EVOLCLAW_HOME/config.json`)
39
+
40
+ E2EE 加密种子是**进程级**的,所有 agent 共享,不在 agent config 里:
41
+
42
+ ```json
43
+ {
44
+ "aun": {
45
+ "encryptionSeed": "<seed>"
46
+ }
47
+ }
48
+ ```
49
+
50
+ 留空时 SDK 依次回退 `AUN_ENCRYPTION_SEED` 环境变量 → 默认 `'evol'`。
51
+
52
+ > Gateway 不在配置里手填:连接时从 AID 自动发现(见下)。Keystore 默认落在 `$EVOLCLAW_HOME`。
53
+
54
+ ## 特有机制
55
+
56
+ - **隐式创建**:aun 渠道由顶层 `aid` 派生,无独立配置实例;这是它与飞书等渠道最大的不同。
57
+ - **网关发现**:连接前查询 `https://<aid>/.well-known/aun-gateway` 获取网关地址(AID 本身即域名);per-agent 流程不提供手填 gateway 的入口,发现失败即连接失败。
58
+ - **身份与信任**:每条入站消息携带发送者 AID,经四级 X.509 证书链(Root CA → Registry CA → Issuer CA → Agent)验签。
59
+ - **agent.md 缓存**:对端名片 `https://<aid>/agent.md` 由 **AUN SDK 自动拉取并缓存**(`AgentMdManager`,带 ETag/TTL,默认 1 天内不重复探测)。缓存落在 **`$EVOLCLAW_HOME/AIDs/<aid>/`**,每个 AID 两个文件:`agent.md`(带签名正文)+ `agentmd.json`(元数据:etag/last_modified/checked_at/verify_status)。本端与对端的 agent.md 都缓存于此。
60
+ - 关系层另存一份**派生**的精简身份 `relations/<peerKey>/peer-identity.json`(type/isAgent/name),由 evolclaw 从 agent.md 提取,不是 agent.md 本体。
61
+ - **E2EE 加密**:可选端到端加密,回复默认跟随对端消息加密状态(密文回密文,明文回明文);种子由进程级 `aun.encryptionSeed` 提供。
62
+ - **断线重连**:SDK 内置退避策略自动重连。实时连接状态用 `ec watch aid` 查看(`ec aid` 是身份/证书管理,不显示连接状态)。
63
+ - **群 ID 格式**:`group.{issuer}/{group_name}`(新格式)或 `{group_no}.{issuer}`(数字群号,如 `11117.agentid.pub`)。
64
+ - **身份体系**:对端以 AID 标识,关系层 peerKey 形如 `aun#alice.aid.pub`。
@@ -1,27 +1,56 @@
1
1
  # 飞书渠道
2
2
 
3
- <!-- TODO: 填充飞书渠道接入文档 -->
3
+ > 知识性文档:飞书渠道的配置、参数与特有机制。**不依赖当前会话渠道**——任意会话都可按需 Read(在 aun 会话里也可查飞书)。
4
+ > 运行时"怎么发消息"由注入的 `[channel]` 段决定(飞书是 evolclaw 自动回复,agent 不调 CLI),不在本文。
4
5
 
5
6
  ## 概述
6
7
 
7
- 飞书渠道通过 evolclaw 的 feishu channel 实现,支持:
8
- - 单聊消息收发
9
- - 群聊消息收发
10
- - 合并转发消息解析
11
- - 文件/图片/视频消息
8
+ 飞书渠道通过 evolclaw 的 feishu channel 插件接入,对端以飞书 `user_id`(如 `ou_xxx`)为身份标识。支持单聊/群聊收发、合并转发解析、富文本卡片、文件/图片/视频消息。
12
9
 
13
10
  ## 配置
14
11
 
15
- evolclaw 配置中启用飞书渠道:
12
+ 飞书渠道按 **per-agent** 配置,写在 `agents/<aid>/config.json` 的 `channels` 数组里。每个元素是一个独立实例,靠 `name` 区分;同一 agent 可配多个飞书实例。
16
13
 
17
14
  ```json
18
15
  {
19
- "channels": {
20
- "feishu": {
16
+ "aid": "myagent.aid.pub",
17
+ "channels": [
18
+ {
19
+ "type": "feishu",
20
+ "name": "main",
21
21
  "enabled": true,
22
- "appId": "<app-id>",
23
- "appSecret": "<app-secret>"
22
+ "appId": "cli_xxx",
23
+ "appSecret": "<app-secret>",
24
+ "owners": ["ou_owner_user_id"],
25
+ "admins": ["ou_admin_user_id"]
24
26
  }
25
- }
27
+ ]
26
28
  }
27
29
  ```
30
+
31
+ ### 实例字段
32
+
33
+ | 字段 | 必填 | 说明 |
34
+ |------|------|------|
35
+ | `type` | 是 | 固定 `"feishu"` |
36
+ | `name` | 是(数组形式) | 该 agent 内飞书实例的本地标识,不含 `#` |
37
+ | `enabled` | 否 | 默认启用;`false` 关闭该实例 |
38
+ | `appId` | 是 | 飞书自建应用 App ID |
39
+ | `appSecret` | 是 | 飞书自建应用 App Secret |
40
+ | `owners` | 否 | owner 的飞书 `user_id` 列表(最高权限) |
41
+ | `admins` | 否 | admin 的飞书 `user_id` 列表 |
42
+ | `flushDelay` | 否 | flush 间隔(秒),覆盖全局值 |
43
+ | `debounce` | 否 | 入站消息去抖间隔(秒),覆盖全局值 |
44
+ | `showActivities` | 否 | 思考过程展示范围:`all` / `dm-only` / `owner-dm-only` / `none` |
45
+
46
+ > `owners`/`admins` 用飞书原生 `user_id`,**不是 AID**。
47
+
48
+ agent 级(config.json 顶层)相关开关:
49
+ - `enable_rich_content`:启用富文本卡片渲染,默认关闭。
50
+
51
+ ## 特有机制
52
+
53
+ - **合并转发解析**:飞书的 `merge_forward` 消息会被自动展开为文本注入上下文(含"以下是引用的原消息"包裹)。
54
+ - **交互卡片**:富内容开启时,部分回复以飞书卡片形式发送;卡片按钮**仅发起者可操作**,他人点击返回提示。
55
+ - **身份体系**:对端以 `user_id` 标识,关系层 peerKey 形如 `feishu#ou_xxx`。
56
+ - **自动回复**:飞书是非 aun 渠道,回复由 evolclaw 自动完成,agent 无需调用 `ec msg send`。