evolclaw 3.1.1 → 3.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +428 -0
  2. package/README.md +3 -7
  3. package/SKILLS.md +311 -0
  4. package/dist/agents/claude-runner.js +1 -1
  5. package/dist/agents/codex-runner.js +75 -19
  6. package/dist/agents/gemini-runner.js +0 -2
  7. package/dist/agents/kit-renderer.js +59 -10
  8. package/dist/aun/aid/agentmd.js +50 -27
  9. package/dist/aun/aid/client.js +5 -11
  10. package/dist/aun/aid/identity.js +32 -13
  11. package/dist/aun/aid/index.js +1 -1
  12. package/dist/aun/msg/group.js +1 -0
  13. package/dist/aun/msg/p2p.js +15 -2
  14. package/dist/aun/msg/upload.js +57 -18
  15. package/dist/aun/rpc/connection.js +3 -0
  16. package/dist/channels/aun.js +122 -48
  17. package/dist/channels/dingtalk.js +1 -0
  18. package/dist/channels/feishu.js +5 -4
  19. package/dist/channels/qqbot.js +1 -0
  20. package/dist/channels/wechat.js +1 -0
  21. package/dist/channels/wecom.js +1 -0
  22. package/dist/cli/agent.js +142 -40
  23. package/dist/cli/index.js +103 -58
  24. package/dist/cli/init-channel.js +4 -2
  25. package/dist/cli/init.js +55 -26
  26. package/dist/cli/watch-msg.js +3 -1
  27. package/dist/config-store.js +22 -1
  28. package/dist/core/channel-loader.js +4 -4
  29. package/dist/core/command-handler.js +626 -538
  30. package/dist/core/evolagent-registry.js +45 -9
  31. package/dist/core/evolagent.js +35 -4
  32. package/dist/core/message/im-renderer.js +14 -4
  33. package/dist/core/message/message-bridge.js +149 -25
  34. package/dist/core/message/message-processor.js +45 -38
  35. package/dist/core/session/session-fs-store.js +23 -0
  36. package/dist/core/session/session-manager.js +188 -42
  37. package/dist/index.js +15 -17
  38. package/dist/paths.js +35 -0
  39. package/dist/utils/cross-platform.js +2 -1
  40. package/kits/docs/INDEX.md +6 -0
  41. package/kits/eck_manifest.json +3 -3
  42. package/kits/rules/02-navigation.md +1 -0
  43. package/kits/rules/06-channel.md +2 -18
  44. package/kits/templates/system-fragments/baseagent.md +2 -2
  45. package/kits/templates/system-fragments/channel.md +18 -9
  46. package/kits/templates/system-fragments/eckruntime.md +14 -0
  47. package/kits/templates/system-fragments/identity.md +5 -6
  48. package/kits/templates/system-fragments/relation.md +7 -5
  49. package/kits/templates/system-fragments/venue.md +2 -3
  50. package/package.json +5 -2
  51. package/kits/templates/system-fragments/runtime.md +0 -19
@@ -92,22 +92,12 @@ export class SessionManager {
92
92
  *
93
93
  * 这样保持兼容:不知道 channelType 的 caller 仍可以用 (channel, channelId) 调用。
94
94
  */
95
- resolveChatDir(channel, channelId) {
96
- // 优先尝试从已有目录里找
97
- const dirs = scanChatDirs(this.sessionsDir);
98
- for (const d of dirs) {
99
- if (d.channelId !== channelId)
100
- continue;
101
- // 验证 active.json 或 meta 文件里 channel(实例名)匹配
102
- const active = readJsonFile(path.join(d.dirPath, 'active.json'));
103
- if (active && active.channel === channel)
104
- return d.dirPath;
105
- // 没 active.json 时,看 channelType 是否能匹配 channel
106
- if (!active && d.channelType === channel)
107
- return d.dirPath;
95
+ resolveChatDir(channel, channelId, channelType, selfId) {
96
+ // 必须有明确 channelType 才能确定路径
97
+ if (!channelType) {
98
+ throw new Error(`[SessionManager] resolveChatDir requires channelType. Got channel="${channel}" channelId="${channelId}". Caller must pass channelType (e.g. 'aun', 'feishu').`);
108
99
  }
109
- // Fallback:按 channel channelType 创建(旧路径布局兼容)
110
- return chatDirPath(this.sessionsDir, channel, channelId);
100
+ return chatDirPath(this.sessionsDir, channelType, channelId, selfId);
111
101
  }
112
102
  /**
113
103
  * 给定明确的 channelType + selfId 时直接计算路径(不扫描)。
@@ -117,7 +107,7 @@ export class SessionManager {
117
107
  if (channelType) {
118
108
  return chatDirPath(this.sessionsDir, channelType, channelId, selfId);
119
109
  }
120
- return this.resolveChatDir(channel, channelId);
110
+ return this.resolveChatDirSafe(channel, channelId);
121
111
  }
122
112
  resolveChatDirFromSession(session) {
123
113
  const channelType = session.channelType || session.channel;
@@ -128,25 +118,129 @@ export class SessionManager {
128
118
  return this.resolveChatDirFromSession(session);
129
119
  }
130
120
  /** Like resolveChatDir but also ensures the dir + _threads + _trash exist. */
131
- ensureResolvedChatDir(channel, channelId) {
132
- const dir = this.resolveChatDir(channel, channelId);
121
+ ensureResolvedChatDir(channel, channelId, channelType, selfId) {
122
+ const dir = this.resolveChatDir(channel, channelId, channelType, selfId);
133
123
  fs.mkdirSync(dir, { recursive: true });
134
124
  fs.mkdirSync(path.join(dir, '_threads'), { recursive: true });
135
125
  fs.mkdirSync(path.join(dir, '_trash'), { recursive: true });
136
126
  return dir;
137
127
  }
138
128
  /** 推断给定 chat 的 channelType(优先取 active.json)。无活跃时回落到 channel 实例名。 */
139
- inferChannelType(channel, channelId) {
140
- const active = this.readActive(channel, channelId);
141
- return active?.channelType || channel;
129
+ inferChannelType(channel, channelId, chatDir) {
130
+ if (chatDir) {
131
+ const active = readJsonFile(path.join(chatDir, 'active.json'));
132
+ if (active?.channelType)
133
+ return active.channelType;
134
+ }
135
+ // 扫描已有目录
136
+ const dirs = scanChatDirs(this.sessionsDir);
137
+ for (const d of dirs) {
138
+ if (d.channelId !== channelId)
139
+ continue;
140
+ const active = readJsonFile(path.join(d.dirPath, 'active.json'));
141
+ if (active && active.channel === channel && active.channelType)
142
+ return active.channelType;
143
+ }
144
+ throw new Error(`[SessionManager] Cannot infer channelType for channel="${channel}" channelId="${channelId}". No existing session found.`);
142
145
  }
143
146
  /** 从 active 推断 selfId(已有 session 的复用) */
144
- inferSelfId(channel, channelId) {
145
- const active = this.readActive(channel, channelId);
146
- return active?.selfId;
147
+ inferSelfId(channel, channelId, chatDir) {
148
+ if (chatDir) {
149
+ const active = readJsonFile(path.join(chatDir, 'active.json'));
150
+ if (active?.selfId)
151
+ return active.selfId;
152
+ }
153
+ // 扫描已有目录
154
+ const dirs = scanChatDirs(this.sessionsDir);
155
+ for (const d of dirs) {
156
+ if (d.channelId !== channelId)
157
+ continue;
158
+ const active = readJsonFile(path.join(d.dirPath, 'active.json'));
159
+ if (active && active.channel === channel)
160
+ return active.selfId || undefined;
161
+ }
162
+ return undefined;
163
+ }
164
+ /**
165
+ * 扫描已有 chat 目录,找到匹配 channel+channelId 的目录并返回其 chatDir 路径。
166
+ * 用于不知道 channelType/selfId 的 caller 在调用 resolveChatDir 前定位已有目录。
167
+ */
168
+ findExistingChatDir(channel, channelId) {
169
+ const dirs = scanChatDirs(this.sessionsDir);
170
+ for (const d of dirs) {
171
+ if (d.channelId !== channelId)
172
+ continue;
173
+ const active = readJsonFile(path.join(d.dirPath, 'active.json'));
174
+ if (active && active.channel === channel)
175
+ return d.dirPath;
176
+ // 即使没有 active.json,也检查 meta 文件
177
+ const metaFiles = scanMetaFiles(d.dirPath);
178
+ for (const mf of metaFiles) {
179
+ const meta = readLastJsonlLine(path.join(d.dirPath, mf));
180
+ if (meta && meta.channel === channel)
181
+ return d.dirPath;
182
+ }
183
+ // 仅 thread session 场景:主目录无 active.json 也无 main meta,但 _threads/ 有内容
184
+ const threadsDir = path.join(d.dirPath, '_threads');
185
+ if (fs.existsSync(threadsDir)) {
186
+ const threadMetas = scanMetaFiles(threadsDir);
187
+ for (const mf of threadMetas) {
188
+ const meta = readLastJsonlLine(path.join(threadsDir, mf));
189
+ if (meta && meta.channel === channel)
190
+ return d.dirPath;
191
+ }
192
+ }
193
+ }
194
+ return undefined;
195
+ }
196
+ /**
197
+ * 安全版 resolveChatDir:先尝试用提供的 channelType/selfId,
198
+ * 如果没有则扫描已有目录推断。用于操作已有 session 的公共方法。
199
+ */
200
+ resolveChatDirSafe(channel, channelId, channelType, selfId) {
201
+ if (channelType) {
202
+ return this.resolveChatDir(channel, channelId, channelType, selfId);
203
+ }
204
+ // 尝试从已有目录推断
205
+ const existingDir = this.findExistingChatDir(channel, channelId);
206
+ if (existingDir)
207
+ return existingDir;
208
+ throw new Error(`[SessionManager] Cannot resolve chat dir for channel="${channel}" channelId="${channelId}". No channelType provided and no existing session found.`);
147
209
  }
148
- readActive(channel, channelId) {
149
- const dir = this.resolveChatDir(channel, channelId);
210
+ /**
211
+ * 安全版 ensureResolvedChatDir:先尝试用提供的 channelType/selfId,
212
+ * 如果没有则扫描已有目录推断。确保目录存在。
213
+ */
214
+ ensureResolvedChatDirSafe(channel, channelId, channelType, selfId) {
215
+ if (channelType) {
216
+ return this.ensureResolvedChatDir(channel, channelId, channelType, selfId);
217
+ }
218
+ // 尝试从已有目录推断
219
+ const existingDir = this.findExistingChatDir(channel, channelId);
220
+ if (existingDir) {
221
+ // 确保子目录存在
222
+ fs.mkdirSync(existingDir, { recursive: true });
223
+ fs.mkdirSync(path.join(existingDir, '_threads'), { recursive: true });
224
+ fs.mkdirSync(path.join(existingDir, '_trash'), { recursive: true });
225
+ return existingDir;
226
+ }
227
+ // 回退:推断 channelType 和 selfId
228
+ const inferredType = this.inferChannelType(channel, channelId);
229
+ const inferredSelfId = this.inferSelfId(channel, channelId);
230
+ return this.ensureResolvedChatDir(channel, channelId, inferredType, inferredSelfId);
231
+ }
232
+ readActive(channel, channelId, channelType, selfId) {
233
+ let dir;
234
+ try {
235
+ dir = this.resolveChatDir(channel, channelId, channelType, selfId);
236
+ }
237
+ catch {
238
+ // channelType not provided — try to find existing dir
239
+ const existingDir = this.findExistingChatDir(channel, channelId);
240
+ if (!existingDir)
241
+ return undefined;
242
+ dir = existingDir;
243
+ }
150
244
  const file = readJsonFile(path.join(dir, 'active.json'));
151
245
  if (!file)
152
246
  return undefined;
@@ -157,8 +251,17 @@ export class SessionManager {
157
251
  const file = sessionToFile(session);
158
252
  atomicWriteJson(path.join(dir, 'active.json'), file);
159
253
  }
160
- clearActive(channel, channelId) {
161
- const dir = this.resolveChatDir(channel, channelId);
254
+ clearActive(channel, channelId, channelType, selfId) {
255
+ let dir;
256
+ try {
257
+ dir = this.resolveChatDir(channel, channelId, channelType, selfId);
258
+ }
259
+ catch {
260
+ const existingDir = this.findExistingChatDir(channel, channelId);
261
+ if (!existingDir)
262
+ return;
263
+ dir = existingDir;
264
+ }
162
265
  const activePath = path.join(dir, 'active.json');
163
266
  try {
164
267
  fs.unlinkSync(activePath);
@@ -459,7 +562,7 @@ export class SessionManager {
459
562
  return session;
460
563
  }
461
564
  // Find existing session for default project path
462
- const chatDir = this.resolveChatDir(channel, channelId);
565
+ const chatDir = this.resolveChatDir(channel, channelId, channelType, selfId);
463
566
  const allSessions = this.findAllSessionsInChat(chatDir, false);
464
567
  const existing = allSessions
465
568
  .filter(s => s.projectPath === defaultProjectPath && !s.threadId)
@@ -540,7 +643,10 @@ export class SessionManager {
540
643
  this.writeSessionIfChanged(current.channel, current.channelId, prev, current);
541
644
  }
542
645
  getOrCreateThreadSession(channel, channelId, threadId, defaultProjectPath, metadata, name, agentId, selfId, channelType, peerType) {
543
- const chatDir = this.ensureResolvedChatDir(channel, channelId);
646
+ // 优先使用精确路径(channelType + selfId),避免 fallback 到错误目录
647
+ const chatDir = (channelType && selfId)
648
+ ? (() => { const d = chatDirPath(this.sessionsDir, channelType, channelId, selfId); fs.mkdirSync(d, { recursive: true }); fs.mkdirSync(path.join(d, '_threads'), { recursive: true }); return d; })()
649
+ : this.ensureResolvedChatDirSafe(channel, channelId, channelType);
544
650
  const threadIndex = readThreadIndex(chatDir);
545
651
  const existingMetaId = threadIndex[threadId];
546
652
  if (existingMetaId) {
@@ -557,7 +663,7 @@ export class SessionManager {
557
663
  }
558
664
  }
559
665
  // Inherit project path & chatType from active main session
560
- const activeMain = this.readActive(channel, channelId);
666
+ const activeMain = this.readActive(channel, channelId, channelType, selfId);
561
667
  const projectPath = (activeMain && !activeMain.threadId ? activeMain.projectPath : undefined) || defaultProjectPath;
562
668
  const inheritedChatType = (activeMain && !activeMain.threadId ? activeMain.chatType : undefined) || 'private';
563
669
  const session = {
@@ -594,7 +700,7 @@ export class SessionManager {
594
700
  const agentId = currentAgentId || 'claude';
595
701
  logger.info(`[SessionManager] switchProject: channel=${channel} channelId=${channelId} newPath=${newProjectPath} agent=${agentId}`);
596
702
  const inheritedChatType = this.getActiveChatType(channel, channelId);
597
- const chatDir = this.ensureResolvedChatDir(channel, channelId);
703
+ const chatDir = this.ensureResolvedChatDirSafe(channel, channelId);
598
704
  const allSessions = this.findAllSessionsInChat(chatDir, false);
599
705
  const target = allSessions
600
706
  .filter(s => s.projectPath === newProjectPath && (s.agentId || 'claude') === agentId && !s.threadId)
@@ -657,7 +763,7 @@ export class SessionManager {
657
763
  }
658
764
  async switchAgent(channel, channelId, projectPath, newAgentId) {
659
765
  const inheritedChatType = this.getActiveChatType(channel, channelId);
660
- const chatDir = this.ensureResolvedChatDir(channel, channelId);
766
+ const chatDir = this.ensureResolvedChatDirSafe(channel, channelId);
661
767
  const allSessions = this.findAllSessionsInChat(chatDir, false);
662
768
  const target = allSessions
663
769
  .filter(s => s.projectPath === projectPath && (s.agentId || 'claude') === newAgentId && !s.threadId)
@@ -745,7 +851,13 @@ export class SessionManager {
745
851
  return this.readActive(channel, channelId);
746
852
  }
747
853
  async getThreadSession(channel, channelId, threadId) {
748
- const chatDir = this.resolveChatDir(channel, channelId);
854
+ let chatDir;
855
+ try {
856
+ chatDir = this.resolveChatDirSafe(channel, channelId);
857
+ }
858
+ catch {
859
+ return undefined;
860
+ }
749
861
  const threadIndex = readThreadIndex(chatDir);
750
862
  const metaId = threadIndex[threadId];
751
863
  if (!metaId)
@@ -758,12 +870,24 @@ export class SessionManager {
758
870
  return { ...session, agentSessionId: validSessionId };
759
871
  }
760
872
  async listSessions(channel, channelId) {
761
- const chatDir = this.resolveChatDir(channel, channelId);
873
+ let chatDir;
874
+ try {
875
+ chatDir = this.resolveChatDirSafe(channel, channelId);
876
+ }
877
+ catch {
878
+ return [];
879
+ }
762
880
  const sessions = this.findAllSessionsInChat(chatDir, true);
763
881
  return sessions.sort((a, b) => b.updatedAt - a.updatedAt);
764
882
  }
765
883
  async getSessionByProjectPath(channel, channelId, projectPath) {
766
- const chatDir = this.resolveChatDir(channel, channelId);
884
+ let chatDir;
885
+ try {
886
+ chatDir = this.resolveChatDirSafe(channel, channelId);
887
+ }
888
+ catch {
889
+ return undefined;
890
+ }
767
891
  const sessions = this.findAllSessionsInChat(chatDir, false);
768
892
  const matched = sessions.filter(s => s.projectPath === projectPath);
769
893
  if (matched.length === 0)
@@ -778,12 +902,24 @@ export class SessionManager {
778
902
  return matched[0];
779
903
  }
780
904
  async getSessionByName(channel, channelId, name) {
781
- const chatDir = this.resolveChatDir(channel, channelId);
905
+ let chatDir;
906
+ try {
907
+ chatDir = this.resolveChatDirSafe(channel, channelId);
908
+ }
909
+ catch {
910
+ return undefined;
911
+ }
782
912
  const sessions = this.findAllSessionsInChat(chatDir, true);
783
913
  return sessions.find(s => s.name === name);
784
914
  }
785
915
  async switchToSession(channel, channelId, targetSessionId) {
786
- const chatDir = this.resolveChatDir(channel, channelId);
916
+ let chatDir;
917
+ try {
918
+ chatDir = this.resolveChatDirSafe(channel, channelId);
919
+ }
920
+ catch {
921
+ return null;
922
+ }
787
923
  const sessions = this.findAllSessionsInChat(chatDir, true);
788
924
  const target = sessions.find(s => s.id === targetSessionId);
789
925
  if (!target)
@@ -900,12 +1036,22 @@ export class SessionManager {
900
1036
  }
901
1037
  async createNewSession(channel, channelId, projectPath, name, agentId) {
902
1038
  const inheritedChatType = this.getActiveChatType(channel, channelId);
1039
+ let inferredType;
1040
+ let inferredSelfId;
1041
+ try {
1042
+ inferredType = this.inferChannelType(channel, channelId);
1043
+ inferredSelfId = this.inferSelfId(channel, channelId);
1044
+ }
1045
+ catch {
1046
+ inferredType = channel;
1047
+ inferredSelfId = undefined;
1048
+ }
903
1049
  const session = {
904
1050
  id: generateSessionId(),
905
1051
  channel,
906
- channelType: this.inferChannelType(channel, channelId),
1052
+ channelType: inferredType,
907
1053
  channelId,
908
- selfId: this.inferSelfId(channel, channelId),
1054
+ selfId: inferredSelfId,
909
1055
  projectPath,
910
1056
  threadId: '',
911
1057
  agentId: agentId || 'claude',
@@ -997,7 +1143,7 @@ export class SessionManager {
997
1143
  return adapter.listSdkSessions(projectPath);
998
1144
  }
999
1145
  async getSessionByUuidPrefix(channel, channelId, uuidPrefix) {
1000
- const chatDir = this.resolveChatDir(channel, channelId);
1146
+ const chatDir = this.resolveChatDirSafe(channel, channelId);
1001
1147
  const sessions = this.findAllSessionsInChat(chatDir, true);
1002
1148
  const matched = sessions.filter(s => s.agentSessionId && s.agentSessionId.startsWith(uuidPrefix));
1003
1149
  if (matched.length === 0)
@@ -1043,7 +1189,7 @@ export class SessionManager {
1043
1189
  }
1044
1190
  // ─── Health status ───
1045
1191
  healthFilePath(channel, channelId) {
1046
- return path.join(this.ensureResolvedChatDir(channel, channelId), 'health.jsonl');
1192
+ return path.join(this.ensureResolvedChatDirSafe(channel, channelId), 'health.jsonl');
1047
1193
  }
1048
1194
  /** Find the chat dir containing a given session id */
1049
1195
  chatDirForSession(sessionId) {
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ClaudeSessionFileAdapter } from './core/session/adapters/claude-session-file-adapter.js';
2
2
  import { CodexSessionFileAdapter } from './core/session/adapters/codex-session-file-adapter.js';
3
3
  import { GeminiSessionFileAdapter } from './core/session/adapters/gemini-session-file-adapter.js';
4
- import { ensureDataDirs, resolvePaths, getPackageRoot } from './paths.js';
4
+ import { ensureDataDirs, resolvePaths, getPackageRoot, agentMdPath } from './paths.js';
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';
@@ -41,7 +41,6 @@ import { agentTriggersDir } from './paths.js';
41
41
  import { isLinkedInstall } from './utils/npm-ops.js';
42
42
  import path from 'path';
43
43
  import fs from 'fs';
44
- import os from 'os';
45
44
  import crypto from 'crypto';
46
45
  import { fileURLToPath } from 'url';
47
46
  /** 出站 payload 摘要(用于 channel-out.log) */
@@ -473,7 +472,7 @@ async function main() {
473
472
  logger.error(`[Trigger] Scheduler init failed for ${agent.aid}: ${err}`);
474
473
  }
475
474
  }
476
- // Inject primary agent's trigger scheduler into cmdHandler
475
+ // Inject primary agent's trigger scheduler as fallback (used when owning agent has no scheduler)
477
476
  const primaryAgentForTrigger = agentRegistry.runnableAgents()[0];
478
477
  if (primaryAgentForTrigger?.triggerScheduler && primaryAgentForTrigger?.triggerManager) {
479
478
  cmdHandler.setTriggerScheduler(primaryAgentForTrigger.triggerScheduler, primaryAgentForTrigger.triggerManager);
@@ -548,10 +547,10 @@ async function main() {
548
547
  if (inst.onProjectPathRequest && inst.channel.onProjectPathRequest) {
549
548
  inst.channel.onProjectPathRequest(async (channelId) => {
550
549
  // Effective default path: use the agent that owns this channel.
551
- const owningAgent = agentRegistry.resolveByChannel(inst.adapter.channelName);
550
+ const owningAgent = agentRegistry.resolveByChannel(inst.adapter.channelKey);
552
551
  const effectiveDefault = owningAgent?.projectPath
553
552
  ?? primaryAgent.projectPath;
554
- const session = await sessionManager.getOrCreateSession(inst.adapter.channelName, channelId, effectiveDefault, undefined, undefined, undefined, undefined);
553
+ const session = await sessionManager.getOrCreateSession(inst.adapter.channelKey, channelId, effectiveDefault, undefined, undefined, undefined, undefined);
555
554
  return path.isAbsolute(session.projectPath)
556
555
  ? session.projectPath
557
556
  : path.resolve(process.cwd(), session.projectPath);
@@ -593,10 +592,10 @@ async function main() {
593
592
  }
594
593
  // Bind adapters to their owning agents and mark running
595
594
  for (const inst of channelInstances) {
596
- const agent = agentRegistry.resolveByChannel(inst.adapter.channelName);
595
+ const agent = agentRegistry.resolveByChannel(inst.adapter.channelKey);
597
596
  if (!agent || agent.status === 'error')
598
597
  continue;
599
- agent.channels.set(inst.adapter.channelName, inst.adapter);
598
+ agent.channels.set(inst.adapter.channelKey, inst.adapter);
600
599
  if (agent.status === 'stopped') {
601
600
  agent.status = 'running';
602
601
  }
@@ -611,7 +610,7 @@ async function main() {
611
610
  for (const inst of channelInstances) {
612
611
  const channelType = inst.channelType || inst.adapter.channelName;
613
612
  if (channelType === 'feishu' && 'preloadThreads' in inst.channel) {
614
- const threadIds = sessionManager.getKnownThreadIds(inst.adapter.channelName);
613
+ const threadIds = sessionManager.getKnownThreadIds(inst.adapter.channelKey);
615
614
  inst.channel.preloadThreads(threadIds);
616
615
  }
617
616
  }
@@ -643,9 +642,8 @@ async function main() {
643
642
  // 尝试从 agent.md 读取 name
644
643
  let agentName = agent.aid;
645
644
  try {
646
- const aunPath = process.env.AUN_HOME || path.join(os.homedir(), '.aun');
647
- const agentMdPath = path.join(aunPath, 'AIDs', agent.aid, 'agent.md');
648
- const content = fs.readFileSync(agentMdPath, 'utf-8');
645
+ const mdPath = agentMdPath(agent.aid);
646
+ const content = fs.readFileSync(mdPath, 'utf-8');
649
647
  const nameMatch = content.match(/^name:\s*"?([^"\n]+)/m);
650
648
  if (nameMatch)
651
649
  agentName = nameMatch[1].trim().replace(/"$/, '');
@@ -682,14 +680,14 @@ async function main() {
682
680
  continue; // 跳过同类型通道
683
681
  if (notified.has(otherType))
684
682
  continue; // 同类型已通知过
685
- const ownerId = agentRegistry.getOwner(other.adapter.channelName);
683
+ const ownerId = agentRegistry.getOwner(other.adapter.channelKey);
686
684
  if (!ownerId)
687
685
  continue;
688
686
  notified.add(otherType);
689
- const owningAgent = agentRegistry.resolveByChannel(other.adapter.channelName);
687
+ const owningAgent = agentRegistry.resolveByChannel(other.adapter.channelKey);
690
688
  const envelope = buildEnvelope({
691
689
  taskId: `system-channel-down-${crypto.randomBytes(5).toString('hex')}`,
692
- channel: other.adapter.channelName,
690
+ channel: other.adapter.channelKey,
693
691
  channelId: ownerId,
694
692
  agentName: owningAgent?.aid || 'evolclaw',
695
693
  });
@@ -780,10 +778,10 @@ async function main() {
780
778
  const replyContext = pending.rootId
781
779
  ? { replyToMessageId: pending.rootId, replyInThread: !!pending.threadId }
782
780
  : undefined;
783
- const owningAgent = agentRegistry.resolveByChannel(adapter.channelName);
781
+ const owningAgent = agentRegistry.resolveByChannel(adapter.channelKey);
784
782
  const envelope = buildEnvelope({
785
783
  taskId: `system-restart-${process.pid}`,
786
- channel: adapter.channelName,
784
+ channel: adapter.channelKey,
787
785
  channelId: pending.channelId,
788
786
  agentName: owningAgent?.aid || 'evolclaw',
789
787
  replyContext,
@@ -883,7 +881,7 @@ async function main() {
883
881
  const instances = await channelLoader.createForAgent(agent);
884
882
  for (const inst of instances) {
885
883
  registerChannelInstance(inst);
886
- agent.channels.set(inst.adapter.channelName, inst.adapter);
884
+ agent.channels.set(inst.adapter.channelKey, inst.adapter);
887
885
  channelInstances.push(inst);
888
886
  }
889
887
  agent.status = 'running';
package/dist/paths.js CHANGED
@@ -45,6 +45,16 @@ export function resolvePaths() {
45
45
  aidLogsDir: path.join(root, 'logs', 'aids'),
46
46
  };
47
47
  }
48
+ // ── AID 路径(agent.md 存放在 $EVOLCLAW_HOME/AIDs/<aid>/)──
49
+ export function aidsDir() {
50
+ return path.join(resolveRoot(), 'AIDs');
51
+ }
52
+ export function aidLocalDir(aid) {
53
+ return path.join(resolveRoot(), 'AIDs', aid);
54
+ }
55
+ export function agentMdPath(aid) {
56
+ return path.join(resolveRoot(), 'AIDs', aid, 'agent.md');
57
+ }
48
58
  // ── per-agent 路径(参数化,不进 resolvePaths() 的固定 map)──
49
59
  export function agentDir(aid) {
50
60
  return path.join(resolveRoot(), 'agents', aid);
@@ -98,6 +108,31 @@ export function ensureDataDirs() {
98
108
  fs.mkdirSync(p.outboxDir, { recursive: true });
99
109
  fs.mkdirSync(p.eckDir, { recursive: true });
100
110
  fs.mkdirSync(eckDebugDir(), { recursive: true });
111
+ fs.mkdirSync(aidsDir(), { recursive: true });
112
+ migrateAgentMdFromAun();
113
+ }
114
+ /**
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.
117
+ */
118
+ function migrateAgentMdFromAun() {
119
+ const aunAidsDir = path.join(os.homedir(), '.aun', 'AIDs');
120
+ const ecAids = aidsDir();
121
+ if (!fs.existsSync(aunAidsDir) || aunAidsDir === ecAids)
122
+ return;
123
+ 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);
132
+ }
133
+ }
134
+ }
135
+ catch { /* best-effort migration */ }
101
136
  }
102
137
  // ── kits 路径(始终从包内读取,不复制到 EVOLCLAW_HOME)──
103
138
  export function kitsDir() {
@@ -12,7 +12,8 @@ export const isWindows = process.platform === 'win32';
12
12
  * C:\Users\project -> C--Users-project
13
13
  */
14
14
  export function encodePath(projectPath) {
15
- return projectPath.replace(/[/\\:]/g, '-');
15
+ const normalized = projectPath.replace(/[/\\]+$/, '');
16
+ return normalized.replace(/[/\\:]/g, '-');
16
17
  }
17
18
  /**
18
19
  * Cross-platform process liveness check.
@@ -50,3 +50,9 @@
50
50
  | 路径注册表模板 | `eck_templates/path-registry.template.md` | 路径实例模板 |
51
51
  | 索引模板 | `eck_templates/INDEX.template.md` | agent 级索引模板 |
52
52
  | 指南模板 | `eck_templates/GUIDE.template.md` | agent 级查阅指南模板 |
53
+
54
+ ## Base Agent
55
+
56
+ | 文档 | 路径 | 说明 |
57
+ |------|------|------|
58
+ | Claude Code 日志 | `baseagent/cc-logs.md` | CC 会话日志查阅(找完整对话/工具调用/注入) |
@@ -71,9 +71,9 @@
71
71
  "description": "渠道层:我通过什么通信"
72
72
  },
73
73
  {
74
- "id": "runtime",
74
+ "id": "eckruntime",
75
75
  "type": "file",
76
- "file": "$PACKAGE_ROOT/kits/templates/system-fragments/runtime.md",
76
+ "file": "$PACKAGE_ROOT/kits/templates/system-fragments/eckruntime.md",
77
77
  "order": 60,
78
78
  "needsInjection": true,
79
79
  "when": "always",
@@ -92,4 +92,4 @@
92
92
  "description": "Base Agent 特定配置"
93
93
  }
94
94
  ]
95
- }
95
+ }
@@ -18,6 +18,7 @@
18
18
  | `$KITS_RULES` | `$KITS/rules` | 自动载入部分(本目录) |
19
19
  | `$KITS_DOCS` | `$KITS/docs` | 按需载入文档 |
20
20
  | `$KITS_TEMPLATES` | `$KITS/templates` | prompt 模板 |
21
+ | `$KITS_FRAGMENTS` | `$KITS_TEMPLATES/system-fragments` | ECK 动态注入 fragment 模板 |
21
22
  | `$ECK` | `$EVOLCLAW_HOME/eck` | 运行时配置 |
22
23
  | `$AGENTS_DIR` | `$EVOLCLAW_HOME/agents` | per-agent 数据根 |
23
24
  | `$AGENT_DIR` | `$AGENTS_DIR/<self-aid>` | 当前 agent 根 |
@@ -13,29 +13,15 @@
13
13
 
14
14
  与其他主体通信时,**必须调用 CLI 命令**发消息,不要把输出当成发送给对方的内容。
15
15
 
16
- ### 必须使用 `ec msg send`(首选)
16
+ 当前渠道的发消息命令已在上下文中注入(见 `[aun]` 或对应渠道块)。
17
17
 
18
- ```bash
19
- # 明文
20
- ec msg send <self-aid> <to-aid> "<text>"
21
-
22
- # 密文
23
- ec msg send <self-aid> <to-aid> "<text>" --encrypt
24
- ```
25
-
26
- `<self-aid>` 是注入上下文里的 selfAid,`<to-aid>` 是 peerKey 解析出的对端 AID。
27
-
28
- ### 仅在无法获取 selfAid 时才用 `ec ctl send`
18
+ ### 仅在无法获取 self-aid 时才用 `ec ctl send`
29
19
 
30
20
  ```bash
31
21
  ec ctl send "<text>"
32
22
  ec ctl send --encrypt "<text>"
33
23
  ```
34
24
 
35
- ### 加密策略
36
-
37
- 对端发来密文消息时回复必须加密;明文消息默认明文回复。
38
-
39
25
  ### 命令返回值
40
26
 
41
27
  - 成功:`ok` 或包含 `✓ 已发送 ...` 的输出(exit 0)
@@ -43,8 +29,6 @@ ec ctl send --encrypt "<text>"
43
29
 
44
30
  发送成功后**继续后续处理**。一次任务可能发 0 到多条消息,不要因为看到"已发送"就反复发送同一条消息。
45
31
 
46
- 不同渠道有不同的命令行工具,使用方式参见各渠道文档。
47
-
48
32
  ## Agent 管理命令
49
33
 
50
34
  `evolclaw agent` — agent 全生命周期管理。
@@ -1,2 +1,2 @@
1
- [Base Agent]
2
- 当前基座:{{baseAgentName}}({{baseAgent}})
1
+ [baseagent]
2
+ base-agent: {{baseAgentName}}
@@ -1,10 +1,19 @@
1
- [渠道层]
2
- 通信渠道:{{channel}}
3
-
4
- {{?canSendFile}}
5
- [文件发送] 当前渠道支持文件发送
6
- {{/}}
7
-
8
- {{?capabilities}}
9
- [渠道能力] {{capabilities}}
1
+ [channel]
2
+ channel: {{channel}}
3
+ {{?peerId}}
4
+ {{?channel=aun}}
5
+ ec msg send {{selfAid}} {{peerId}} "<text>" [--encrypt] [--file <path> --as image|video|voice|file] [--link <url> --title "<title>"] [--payload '<json>']
6
+ encrypt: 跟随对端消息加密状态(密文回密文,明文回明文);本端主动发时依据会话 encrypt 配置(待实现)
7
+ {{/}}
8
+ {{?channel!=aun}}
9
+ aun 渠道:回复由 evolclaw 自动完成,无需调用 CLI
10
+ {{/}}
11
+ {{/}}
12
+ {{?groupId}}
13
+ {{?channel=aun}}
14
+ ec group send {{selfAid}} {{groupId}} "<text>" [--file <path> --as image|video|voice|file] [--mention <aid>]
15
+ {{/}}
16
+ {{?channel!=aun}}
17
+ 非 aun 渠道:回复由 evolclaw 自动完成,无需调用 CLI
18
+ {{/}}
10
19
  {{/}}
@@ -0,0 +1,14 @@
1
+ [runtime]
2
+ project: {{project}}
3
+ {{?sessionName}}
4
+ session-name: {{sessionName}}
5
+ {{/}}
6
+ chatmode: {{chatmode}}
7
+ chatType: {{chatType}}
8
+ {{?readonly}}
9
+ readonly: true — 禁止修改项目文件,如需生成文件请写入 .evolclaw/tmp/
10
+ {{/}}
11
+ {{?chatmode=proactive}}
12
+ proactive-send: evolclaw ctl send "<text>"
13
+ proactive-file: evolclaw ctl file <path>
14
+ {{/}}