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
@@ -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,
@@ -23,7 +23,7 @@ export class TriggerManager {
23
23
  const raw = fs.readFileSync(this.triggersPath, 'utf8');
24
24
  const data = JSON.parse(raw);
25
25
  this.triggers = new Map(Object.entries(data.triggers ?? {}));
26
- // Migrate old channel key format: "selfPeerId#type#name" → "type#selfPeerId#name"
26
+ // Migrate old channel key format: "selfAID#type#name" → "type#selfAID#name"
27
27
  let migrated = false;
28
28
  for (const trigger of this.triggers.values()) {
29
29
  const fixed = this.migrateChannelKey(trigger.targetChannel);
@@ -52,7 +52,7 @@ export class TriggerManager {
52
52
  }
53
53
  }
54
54
  /**
55
- * Detect and fix old format "selfPeerId#type#name" → current "type#selfPeerId#name".
55
+ * Detect and fix old format "selfAID#type#name" → current "type#selfAID#name".
56
56
  * Old format: first segment contains '.' (AID like "evolai.agentid.pub").
57
57
  */
58
58
  migrateChannelKey(key) {
@@ -63,7 +63,7 @@ export class TriggerManager {
63
63
  return key;
64
64
  const [first, second, third] = parts;
65
65
  if (first.includes('.') && !second.includes('.')) {
66
- return formatChannelKey({ type: second, selfPeerId: first, name: third });
66
+ return formatChannelKey({ type: second, selfAID: first, name: third });
67
67
  }
68
68
  return key;
69
69
  }
@@ -127,8 +127,8 @@ export function parseTriggerUpdate(args) {
127
127
  }
128
128
  if (flags.has('session')) {
129
129
  const sv = flags.get('session');
130
- if (sv !== 'latest' && sv !== 'silent')
131
- return { ok: false, error: '--session 只接受 latest 或 silent' };
130
+ if (sv !== 'latest' && sv !== 'current' && sv !== 'thread')
131
+ return { ok: false, error: '--session 只接受 latest、currentthread' };
132
132
  result.targetSessionStrategy = sv;
133
133
  }
134
134
  if (flags.has('agent')) {
@@ -216,8 +216,8 @@ export function parseTriggerSet(args) {
216
216
  let targetSessionStrategy = 'latest';
217
217
  if (hasSession) {
218
218
  const sv = flags.get('session');
219
- if (sv !== 'latest' && sv !== 'silent') {
220
- return { ok: false, error: '--session 只接受 latest 或 silent' };
219
+ if (sv !== 'latest' && sv !== 'current' && sv !== 'thread') {
220
+ return { ok: false, error: '--session 只接受 latest、currentthread' };
221
221
  }
222
222
  targetSessionStrategy = sv;
223
223
  }
@@ -209,11 +209,12 @@ export class TriggerScheduler {
209
209
  this.inflightCron.delete(triggerId);
210
210
  }
211
211
  buildSyntheticMessage(trigger, messageId) {
212
- return {
212
+ const base = {
213
213
  channel: trigger.targetChannel,
214
+ channelType: trigger.targetChannelType,
214
215
  channelId: trigger.targetChannelId,
215
- selfId: this.aid,
216
- threadId: trigger.targetThreadId ?? '',
216
+ selfAID: this.aid,
217
+ threadId: '',
217
218
  agentId: trigger.agentId,
218
219
  chatType: 'private',
219
220
  peerId: `__trigger__:${trigger.id}`, // unique per trigger to prevent greedy merge
@@ -221,10 +222,24 @@ export class TriggerScheduler {
221
222
  messageId,
222
223
  timestamp: Date.now(),
223
224
  source: 'trigger',
224
- triggerMeta: {
225
- triggerId: trigger.id,
226
- silent: trigger.targetSessionStrategy === 'silent',
227
- },
228
225
  };
226
+ if (trigger.targetSessionStrategy === 'current') {
227
+ base.triggerMeta = { triggerId: trigger.id, boundSessionId: trigger.boundSessionId };
228
+ }
229
+ else if (trigger.targetSessionStrategy === 'thread') {
230
+ if (trigger.threadKind === 'feishu' && trigger.pendingThread) {
231
+ base.triggerMeta = { triggerId: trigger.id, pendingThread: true, rootMessageId: trigger.rootMessageId };
232
+ // threadId intentionally empty — first fire builds the thread via reply_in_thread
233
+ }
234
+ else {
235
+ base.threadId = trigger.targetThreadId ?? '';
236
+ base.triggerMeta = { triggerId: trigger.id };
237
+ }
238
+ }
239
+ else {
240
+ // latest
241
+ base.triggerMeta = { triggerId: trigger.id };
242
+ }
243
+ return base;
229
244
  }
230
245
  }
package/dist/index.js CHANGED
@@ -26,7 +26,7 @@ import { StatsCollector } from './utils/stats.js';
26
26
  import { AidStatsCollector } from './utils/stats.js';
27
27
  import { PermissionGateway } from './core/permission.js';
28
28
  import { InteractionRouter } from './core/interaction-router.js';
29
- import { ChannelLoader } from './core/channel-loader.js';
29
+ import { ChannelLoader, tryParseChannelKey } from './core/channel-loader.js';
30
30
  import { AgentLoader } from './core/baseagent-loader.js';
31
31
  import { EvolAgentRegistry } from './core/evolagent-registry.js';
32
32
  import { buildReloadHooks } from './core/channel-loader.js';
@@ -452,11 +452,58 @@ async function main() {
452
452
  continue;
453
453
  const scheduler = agent.triggerScheduler;
454
454
  const primaryProjectPath = agent.config.projects?.defaultPath ?? primaryAgent.projectPath;
455
+ function scheduleRetryWhenIdle(boundId, msg, trigger) {
456
+ let done = false;
457
+ const handler = (ev) => {
458
+ if (ev.sessionId !== boundId || done)
459
+ return;
460
+ done = true;
461
+ clearTimeout(timer);
462
+ eventBus.unsubscribe('task:completed', handler);
463
+ retry();
464
+ };
465
+ const timer = setTimeout(() => {
466
+ if (done)
467
+ return;
468
+ done = true;
469
+ eventBus.unsubscribe('task:completed', handler);
470
+ retry();
471
+ }, 30_000);
472
+ eventBus.subscribe('task:completed', handler);
473
+ function retry() {
474
+ if (messageQueue.isProcessing(boundId)) {
475
+ scheduleRetryWhenIdle(boundId, msg, trigger);
476
+ return;
477
+ }
478
+ sessionManager.getSessionById(boundId).then(bound => {
479
+ if (!bound) {
480
+ logger.warn(`[Trigger] Bound session ${boundId} deleted, aborting`);
481
+ return;
482
+ }
483
+ messageQueue.enqueue(boundId, msg, bound.projectPath, { interruptible: false })
484
+ .catch(err => logger.error(`[Trigger] Retry failed ${trigger.id}: ${err}`));
485
+ });
486
+ }
487
+ }
455
488
  scheduler.setFireCallback((msg, trigger) => {
456
- const sessionKey = `${msg.channel}:${msg.channelId}`;
457
- messageQueue.enqueue(sessionKey, msg, primaryProjectPath, { interruptible: false }).catch(err => {
458
- logger.error(`[Trigger] Failed to enqueue trigger ${trigger.id}: ${err}`);
459
- });
489
+ if (trigger.targetSessionStrategy === 'current' && trigger.boundSessionId) {
490
+ const boundId = trigger.boundSessionId;
491
+ if (messageQueue.isProcessing(boundId)) {
492
+ scheduleRetryWhenIdle(boundId, msg, trigger);
493
+ return;
494
+ }
495
+ sessionManager.getSessionById(boundId).then(bound => {
496
+ if (!bound) {
497
+ logger.warn(`[Trigger] Bound session ${boundId} not found`);
498
+ return;
499
+ }
500
+ messageQueue.enqueue(boundId, msg, bound.projectPath, { interruptible: false })
501
+ .catch(err => logger.error(`[Trigger] Enqueue failed ${trigger.id}: ${err}`));
502
+ });
503
+ return;
504
+ }
505
+ messageQueue.enqueue(`${msg.channel}:${msg.channelId}`, msg, primaryProjectPath, { interruptible: false })
506
+ .catch(err => logger.error(`[Trigger] Enqueue failed ${trigger.id}: ${err}`));
460
507
  });
461
508
  // Subscribe to trigger:completed/failed/skipped to update cron inflight state
462
509
  eventBus.subscribe('trigger:completed', (ev) => scheduler.onTriggerComplete(ev.triggerId, 'completed'));
@@ -504,7 +551,7 @@ async function main() {
504
551
  nextFireAt: calcNextFireAt('cron', cronExpr),
505
552
  targetChannel: firstChannel,
506
553
  targetChannelId: '__system__',
507
- targetSessionStrategy: 'silent',
554
+ targetSessionStrategy: 'latest',
508
555
  prompt: '检查 evolclaw 是否有新版本可用。执行 `npm view evolclaw version` 获取最新版本,与当前版本(执行 `evolclaw --version`)对比。如果有新版本,执行 /restart 进行升级。如果已是最新版本,无需任何操作。',
509
556
  createdByPeerId: '__system__',
510
557
  createdByChannel: '__system__',
@@ -553,7 +600,8 @@ async function main() {
553
600
  const owningAgent = agentRegistry.resolveByChannel(inst.adapter.channelKey);
554
601
  const effectiveDefault = owningAgent?.projectPath
555
602
  ?? primaryAgent.projectPath;
556
- const session = await sessionManager.getOrCreateSession(inst.adapter.channelKey, channelId, effectiveDefault, undefined, undefined, undefined, undefined);
603
+ const parsedKey = tryParseChannelKey(inst.adapter.channelKey);
604
+ const session = await sessionManager.getOrCreateSession(inst.adapter.channelKey, channelId, effectiveDefault, undefined, undefined, undefined, undefined, undefined, undefined, parsedKey?.selfAID, parsedKey?.type);
557
605
  return path.isAbsolute(session.projectPath)
558
606
  ? session.projectPath
559
607
  : path.resolve(process.cwd(), session.projectPath);
@@ -755,8 +803,11 @@ async function main() {
755
803
  continue;
756
804
  }
757
805
  logger.info(`[Resume] Resuming session: ${session.id} (agent: ${evolName}::${baseagentName})`);
806
+ const parsedResumeKey = tryParseChannelKey(session.channel);
758
807
  const resumeMessage = {
759
808
  channel: session.channel,
809
+ channelType: session.channelType || parsedResumeKey?.type,
810
+ selfAID: parsedResumeKey?.selfAID,
760
811
  channelId: session.channelId,
761
812
  content: '服务已重启,请继续之前未完成的任务。',
762
813
  timestamp: Date.now(),
@@ -866,6 +917,9 @@ async function main() {
866
917
  queued: messageQueue.getQueueLengthByAgent(agentName),
867
918
  }));
868
919
  ipcServer.setAunAidStatsProvider(() => aidStatsCollector.getAllSnapshots());
920
+ ipcServer.setAunAidStatsRecorder((params) => {
921
+ aidStatsCollector.recordOutbound(params.aid, params.toPeer, Buffer.byteLength(params.text || '', 'utf-8'), params.text, false, params.encrypt, params.chatmode);
922
+ });
869
923
  // ── Reload hooks: enable agentRegistry.reload() to drain/disconnect/restart channels ──
870
924
  const reloadHooks = buildReloadHooks({
871
925
  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)) {
@@ -88,7 +93,7 @@ export class IpcServer {
88
93
  case 'status':
89
94
  return this.getStatus();
90
95
  case 'ping':
91
- return { pong: true, pid: process.pid };
96
+ return { pong: true, pid: process.pid, protocolVersion: 1 };
92
97
  case 'aun-aids': {
93
98
  const aids = this.aunAidProvider ? this.aunAidProvider() : [];
94
99
  return { ok: true, aids };
@@ -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' };
@@ -10,6 +10,7 @@ export var ErrorType;
10
10
  ErrorType["FILE_CORRUPT"] = "file_corrupt";
11
11
  ErrorType["STREAM_ERROR"] = "stream_error";
12
12
  ErrorType["CONTEXT_TOO_LONG"] = "context_too_long";
13
+ ErrorType["MODEL_UNAVAILABLE"] = "model_unavailable";
13
14
  ErrorType["UNKNOWN"] = "unknown";
14
15
  })(ErrorType || (ErrorType = {}));
15
16
  /**
@@ -210,6 +211,11 @@ export function classifyError(error) {
210
211
  || msg.includes('上下文过长')) {
211
212
  return ErrorType.CONTEXT_TOO_LONG;
212
213
  }
214
+ if (msg.includes('invalid_model') || msg.includes('model_not_found')
215
+ || msg.includes('no such model') || msg.includes('unknown model')
216
+ || /api error: 404\b/.test(msg)) {
217
+ return ErrorType.MODEL_UNAVAILABLE;
218
+ }
213
219
  if (msg.includes('401') || msg.includes('authentication_error')) {
214
220
  return ErrorType.AUTH_ERROR;
215
221
  }
@@ -51,18 +51,20 @@ function getStartTimeLinux(pid) {
51
51
  const starttimeJiffies = parseInt(fields[19], 10);
52
52
  if (isNaN(starttimeJiffies))
53
53
  return null;
54
- let uptimeSec;
54
+ let btimeSec;
55
55
  try {
56
- uptimeSec = parseFloat(fs.readFileSync('/proc/uptime', 'utf-8').split(' ')[0]);
56
+ const m = fs.readFileSync('/proc/stat', 'utf-8').match(/^btime (\d+)/m);
57
+ if (!m)
58
+ return null;
59
+ btimeSec = parseInt(m[1], 10);
57
60
  }
58
61
  catch {
59
62
  return null;
60
63
  }
61
- if (isNaN(uptimeSec))
64
+ if (isNaN(btimeSec))
62
65
  return null;
63
66
  const clkTck = 100;
64
- const bootTimeMs = Date.now() - uptimeSec * 1000;
65
- return bootTimeMs + (starttimeJiffies / clkTck) * 1000;
67
+ return (btimeSec + starttimeJiffies / clkTck) * 1000;
66
68
  }
67
69
  // ── macOS ──
68
70
  function getStartTimeMacOS(pid) {
@@ -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
 
@@ -18,13 +19,12 @@
18
19
 
19
20
  ## EvolClaw 命令
20
21
 
22
+ 所有 `ec` 命令集的专属目录(用途/触发词/适用场景/文档)见 `evolclaw/INDEX.md`。
23
+ 运行时由 `commands` fragment 按场景注入精简能力卡。
24
+
21
25
  | 文档 | 路径 | 说明 |
22
26
  |------|------|------|
23
- | Agent 命令 | `evolclaw/AGENT_CMD.md` | agent 全生命周期管理命令 |
24
- | 运行时工具 | `evolclaw/tools.md` | ctl 命令集 |
25
- | 自我总结 | `evolclaw/self-summary.md` | 自我总结流程指南 |
26
- | 私聊消息 | `evolclaw/MSG_PRIVATE.md` | 私聊消息命令 |
27
- | 群聊消息 | `evolclaw/MSG_GROUP.md` | 群聊消息命令 |
27
+ | 命令集目录 | `evolclaw/INDEX.md` | msg/group/agent/aid/storage/ctl/rpc/model/bench 全集索引 |
28
28
 
29
29
  ## 身份
30
30
 
@@ -35,12 +35,12 @@
35
35
  | AID 档案规范 | `identity/AID_PROFILE_SPEC.md` | AID 档案格式规范 |
36
36
  | 路径运维 | `identity/PATH_OPS.md` | 路径运维操作 |
37
37
 
38
- ## 渠道
38
+ ## 渠道(知识文档·按需加载,不依赖当前渠道)
39
39
 
40
40
  | 文档 | 路径 | 说明 |
41
41
  |------|------|------|
42
- | AUN 渠道 | `channels/aun.md` | AUN 通信约定 |
43
- | 飞书渠道 | `channels/feishu.md` | 飞书渠道接入 |
42
+ | AUN 渠道 | `channels/aun.md` | AUN 渠道配置、参数与特有机制(网关发现/E2EE/群 ID/证书链) |
43
+ | 飞书渠道 | `channels/feishu.md` | 飞书渠道配置、参数与特有机制(appId/合并转发/卡片/user_id) |
44
44
 
45
45
  ## ECK 模板
46
46
 
@@ -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`。