evolclaw 3.1.2 → 3.1.4

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 (48) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +2 -6
  3. package/assets/.env.template +4 -0
  4. package/assets/config.json.template +6 -0
  5. package/assets/wechat-group-qr.jpeg +0 -0
  6. package/dist/agents/claude-runner.js +1 -1
  7. package/dist/agents/codex-runner.js +75 -19
  8. package/dist/agents/gemini-runner.js +0 -2
  9. package/dist/agents/kit-renderer.js +85 -22
  10. package/dist/aun/aid/agentmd.js +67 -74
  11. package/dist/aun/aid/client.js +22 -7
  12. package/dist/aun/aid/identity.js +314 -28
  13. package/dist/aun/aid/index.js +2 -2
  14. package/dist/aun/rpc/connection.js +8 -10
  15. package/dist/channels/aun.js +53 -41
  16. package/dist/cli/agent.js +28 -28
  17. package/dist/cli/bench.js +8 -14
  18. package/dist/cli/help.js +23 -0
  19. package/dist/cli/index.js +398 -73
  20. package/dist/cli/init-channel.js +2 -3
  21. package/dist/cli/init.js +13 -6
  22. package/dist/cli/link-rules.js +2 -1
  23. package/dist/cli/net-check.js +10 -11
  24. package/dist/core/command-handler.js +621 -541
  25. package/dist/core/evolagent.js +31 -0
  26. package/dist/core/message/im-renderer.js +10 -0
  27. package/dist/core/message/message-bridge.js +123 -24
  28. package/dist/core/message/message-processor.js +61 -31
  29. package/dist/core/relation/peer-identity.js +64 -21
  30. package/dist/core/session/session-manager.js +191 -44
  31. package/dist/core/trigger/manager.js +37 -0
  32. package/dist/index.js +4 -1
  33. package/dist/paths.js +87 -16
  34. package/dist/utils/npm-ops.js +18 -11
  35. package/kits/eck_manifest.json +9 -9
  36. package/kits/rules/02-navigation.md +1 -0
  37. package/kits/rules/05-venue.md +2 -2
  38. package/kits/rules/06-channel.md +2 -18
  39. package/kits/templates/system-fragments/baseagent.md +8 -2
  40. package/kits/templates/system-fragments/channel.md +20 -8
  41. package/kits/templates/system-fragments/identity.md +5 -6
  42. package/kits/templates/system-fragments/relation.md +10 -5
  43. package/kits/templates/system-fragments/session.md +20 -0
  44. package/kits/templates/system-fragments/venue.md +5 -3
  45. package/package.json +4 -2
  46. package/dist/net-check.js +0 -640
  47. package/dist/watch-msg.js +0 -544
  48. package/kits/templates/system-fragments/runtime.md +0 -19
@@ -36,8 +36,8 @@ export class SessionManager {
36
36
  // 来源2:群聊强制 proactive
37
37
  if (ct === 'group')
38
38
  return 'proactive';
39
- // 来源3:非 human 对端(ai/bot)强制 proactive,无视 agent 的默认 chatmode 配置
40
- if (peerType && peerType !== 'human' && peerType !== 'unknown')
39
+ // 来源3:非 human 对端强制 proactive,无视 agent 的默认 chatmode 配置
40
+ if (peerType && peerType !== 'human')
41
41
  return 'proactive';
42
42
  // 来源1:agent 配置默认值
43
43
  const resolved = this.sessionModeResolver?.(channel, ct, peerType);
@@ -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;
147
163
  }
148
- readActive(channel, channelId) {
149
- const dir = this.resolveChatDir(channel, channelId);
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.`);
209
+ }
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)
@@ -543,7 +646,7 @@ export class SessionManager {
543
646
  // 优先使用精确路径(channelType + selfId),避免 fallback 到错误目录
544
647
  const chatDir = (channelType && selfId)
545
648
  ? (() => { const d = chatDirPath(this.sessionsDir, channelType, channelId, selfId); fs.mkdirSync(d, { recursive: true }); fs.mkdirSync(path.join(d, '_threads'), { recursive: true }); return d; })()
546
- : this.ensureResolvedChatDir(channelType || channel, channelId);
649
+ : this.ensureResolvedChatDirSafe(channel, channelId, channelType);
547
650
  const threadIndex = readThreadIndex(chatDir);
548
651
  const existingMetaId = threadIndex[threadId];
549
652
  if (existingMetaId) {
@@ -560,7 +663,7 @@ export class SessionManager {
560
663
  }
561
664
  }
562
665
  // Inherit project path & chatType from active main session
563
- const activeMain = this.readActive(channel, channelId);
666
+ const activeMain = this.readActive(channel, channelId, channelType, selfId);
564
667
  const projectPath = (activeMain && !activeMain.threadId ? activeMain.projectPath : undefined) || defaultProjectPath;
565
668
  const inheritedChatType = (activeMain && !activeMain.threadId ? activeMain.chatType : undefined) || 'private';
566
669
  const session = {
@@ -597,7 +700,7 @@ export class SessionManager {
597
700
  const agentId = currentAgentId || 'claude';
598
701
  logger.info(`[SessionManager] switchProject: channel=${channel} channelId=${channelId} newPath=${newProjectPath} agent=${agentId}`);
599
702
  const inheritedChatType = this.getActiveChatType(channel, channelId);
600
- const chatDir = this.ensureResolvedChatDir(channel, channelId);
703
+ const chatDir = this.ensureResolvedChatDirSafe(channel, channelId);
601
704
  const allSessions = this.findAllSessionsInChat(chatDir, false);
602
705
  const target = allSessions
603
706
  .filter(s => s.projectPath === newProjectPath && (s.agentId || 'claude') === agentId && !s.threadId)
@@ -660,7 +763,7 @@ export class SessionManager {
660
763
  }
661
764
  async switchAgent(channel, channelId, projectPath, newAgentId) {
662
765
  const inheritedChatType = this.getActiveChatType(channel, channelId);
663
- const chatDir = this.ensureResolvedChatDir(channel, channelId);
766
+ const chatDir = this.ensureResolvedChatDirSafe(channel, channelId);
664
767
  const allSessions = this.findAllSessionsInChat(chatDir, false);
665
768
  const target = allSessions
666
769
  .filter(s => s.projectPath === projectPath && (s.agentId || 'claude') === newAgentId && !s.threadId)
@@ -747,8 +850,18 @@ export class SessionManager {
747
850
  async getActiveSession(channel, channelId) {
748
851
  return this.readActive(channel, channelId);
749
852
  }
853
+ /** 同步版 getActiveSession,支持精确路径定位(避免扫描) */
854
+ getActiveSessionSync(channel, channelId, channelType, selfId) {
855
+ return this.readActive(channel, channelId, channelType, selfId);
856
+ }
750
857
  async getThreadSession(channel, channelId, threadId) {
751
- const chatDir = this.resolveChatDir(channel, channelId);
858
+ let chatDir;
859
+ try {
860
+ chatDir = this.resolveChatDirSafe(channel, channelId);
861
+ }
862
+ catch {
863
+ return undefined;
864
+ }
752
865
  const threadIndex = readThreadIndex(chatDir);
753
866
  const metaId = threadIndex[threadId];
754
867
  if (!metaId)
@@ -761,12 +874,24 @@ export class SessionManager {
761
874
  return { ...session, agentSessionId: validSessionId };
762
875
  }
763
876
  async listSessions(channel, channelId) {
764
- const chatDir = this.resolveChatDir(channel, channelId);
877
+ let chatDir;
878
+ try {
879
+ chatDir = this.resolveChatDirSafe(channel, channelId);
880
+ }
881
+ catch {
882
+ return [];
883
+ }
765
884
  const sessions = this.findAllSessionsInChat(chatDir, true);
766
885
  return sessions.sort((a, b) => b.updatedAt - a.updatedAt);
767
886
  }
768
887
  async getSessionByProjectPath(channel, channelId, projectPath) {
769
- const chatDir = this.resolveChatDir(channel, channelId);
888
+ let chatDir;
889
+ try {
890
+ chatDir = this.resolveChatDirSafe(channel, channelId);
891
+ }
892
+ catch {
893
+ return undefined;
894
+ }
770
895
  const sessions = this.findAllSessionsInChat(chatDir, false);
771
896
  const matched = sessions.filter(s => s.projectPath === projectPath);
772
897
  if (matched.length === 0)
@@ -781,12 +906,24 @@ export class SessionManager {
781
906
  return matched[0];
782
907
  }
783
908
  async getSessionByName(channel, channelId, name) {
784
- const chatDir = this.resolveChatDir(channel, channelId);
909
+ let chatDir;
910
+ try {
911
+ chatDir = this.resolveChatDirSafe(channel, channelId);
912
+ }
913
+ catch {
914
+ return undefined;
915
+ }
785
916
  const sessions = this.findAllSessionsInChat(chatDir, true);
786
917
  return sessions.find(s => s.name === name);
787
918
  }
788
919
  async switchToSession(channel, channelId, targetSessionId) {
789
- const chatDir = this.resolveChatDir(channel, channelId);
920
+ let chatDir;
921
+ try {
922
+ chatDir = this.resolveChatDirSafe(channel, channelId);
923
+ }
924
+ catch {
925
+ return null;
926
+ }
790
927
  const sessions = this.findAllSessionsInChat(chatDir, true);
791
928
  const target = sessions.find(s => s.id === targetSessionId);
792
929
  if (!target)
@@ -903,12 +1040,22 @@ export class SessionManager {
903
1040
  }
904
1041
  async createNewSession(channel, channelId, projectPath, name, agentId) {
905
1042
  const inheritedChatType = this.getActiveChatType(channel, channelId);
1043
+ let inferredType;
1044
+ let inferredSelfId;
1045
+ try {
1046
+ inferredType = this.inferChannelType(channel, channelId);
1047
+ inferredSelfId = this.inferSelfId(channel, channelId);
1048
+ }
1049
+ catch {
1050
+ inferredType = channel;
1051
+ inferredSelfId = undefined;
1052
+ }
906
1053
  const session = {
907
1054
  id: generateSessionId(),
908
1055
  channel,
909
- channelType: this.inferChannelType(channel, channelId),
1056
+ channelType: inferredType,
910
1057
  channelId,
911
- selfId: this.inferSelfId(channel, channelId),
1058
+ selfId: inferredSelfId,
912
1059
  projectPath,
913
1060
  threadId: '',
914
1061
  agentId: agentId || 'claude',
@@ -1000,7 +1147,7 @@ export class SessionManager {
1000
1147
  return adapter.listSdkSessions(projectPath);
1001
1148
  }
1002
1149
  async getSessionByUuidPrefix(channel, channelId, uuidPrefix) {
1003
- const chatDir = this.resolveChatDir(channel, channelId);
1150
+ const chatDir = this.resolveChatDirSafe(channel, channelId);
1004
1151
  const sessions = this.findAllSessionsInChat(chatDir, true);
1005
1152
  const matched = sessions.filter(s => s.agentSessionId && s.agentSessionId.startsWith(uuidPrefix));
1006
1153
  if (matched.length === 0)
@@ -1046,7 +1193,7 @@ export class SessionManager {
1046
1193
  }
1047
1194
  // ─── Health status ───
1048
1195
  healthFilePath(channel, channelId) {
1049
- return path.join(this.ensureResolvedChatDir(channel, channelId), 'health.jsonl');
1196
+ return path.join(this.ensureResolvedChatDirSafe(channel, channelId), 'health.jsonl');
1050
1197
  }
1051
1198
  /** Find the chat dir containing a given session id */
1052
1199
  chatDirForSession(sessionId) {
@@ -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: "selfPeerId#type#name" → "type#selfPeerId#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 "selfPeerId#type#name" → current "type#selfPeerId#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, selfPeerId: first, name: third });
67
+ }
68
+ return key;
69
+ }
33
70
  save() {
34
71
  const data = {
35
72
  version: 1,
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';
@@ -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
  });
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
  * 完整升级流程:检查 → 比较 → 安装(失败重试一次)
@@ -17,8 +17,8 @@
17
17
  "order": 20,
18
18
  "needsInjection": true,
19
19
  "when": {
20
- "var": "scene",
21
- "neq": "coding"
20
+ "var": "chatType",
21
+ "neq": null
22
22
  },
23
23
  "description": "身份层:我是谁"
24
24
  },
@@ -29,7 +29,7 @@
29
29
  "order": 30,
30
30
  "needsInjection": true,
31
31
  "when": {
32
- "var": "scene",
32
+ "var": "chatType",
33
33
  "in": ["private", "group"]
34
34
  },
35
35
  "description": "关系层:跟我聊天的是谁"
@@ -56,7 +56,7 @@
56
56
  "var": "chatType",
57
57
  "neq": null
58
58
  },
59
- "description": "环境层:我在什么场景下"
59
+ "description": "环境层:当前对话场景"
60
60
  },
61
61
  {
62
62
  "id": "channel-layer",
@@ -68,16 +68,16 @@
68
68
  "var": "channel",
69
69
  "neq": null
70
70
  },
71
- "description": "渠道层:我通过什么通信"
71
+ "description": "渠道层:通信渠道及能力"
72
72
  },
73
73
  {
74
- "id": "runtime",
74
+ "id": "session",
75
75
  "type": "file",
76
- "file": "$PACKAGE_ROOT/kits/templates/system-fragments/runtime.md",
76
+ "file": "$PACKAGE_ROOT/kits/templates/system-fragments/session.md",
77
77
  "order": 60,
78
78
  "needsInjection": true,
79
79
  "when": "always",
80
- "description": "运行时参数"
80
+ "description": "会话层:当前会话实例状态"
81
81
  },
82
82
  {
83
83
  "id": "baseagent",
@@ -92,4 +92,4 @@
92
92
  "description": "Base Agent 特定配置"
93
93
  }
94
94
  ]
95
- }
95
+ }