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.
- package/CHANGELOG.md +38 -0
- package/README.md +2 -6
- package/assets/.env.template +4 -0
- package/assets/config.json.template +6 -0
- package/assets/wechat-group-qr.jpeg +0 -0
- package/dist/agents/claude-runner.js +1 -1
- package/dist/agents/codex-runner.js +75 -19
- package/dist/agents/gemini-runner.js +0 -2
- package/dist/agents/kit-renderer.js +85 -22
- package/dist/aun/aid/agentmd.js +67 -74
- package/dist/aun/aid/client.js +22 -7
- package/dist/aun/aid/identity.js +314 -28
- package/dist/aun/aid/index.js +2 -2
- package/dist/aun/rpc/connection.js +8 -10
- package/dist/channels/aun.js +53 -41
- package/dist/cli/agent.js +28 -28
- package/dist/cli/bench.js +8 -14
- package/dist/cli/help.js +23 -0
- package/dist/cli/index.js +398 -73
- package/dist/cli/init-channel.js +2 -3
- package/dist/cli/init.js +13 -6
- package/dist/cli/link-rules.js +2 -1
- package/dist/cli/net-check.js +10 -11
- package/dist/core/command-handler.js +621 -541
- package/dist/core/evolagent.js +31 -0
- package/dist/core/message/im-renderer.js +10 -0
- package/dist/core/message/message-bridge.js +123 -24
- package/dist/core/message/message-processor.js +61 -31
- package/dist/core/relation/peer-identity.js +64 -21
- package/dist/core/session/session-manager.js +191 -44
- package/dist/core/trigger/manager.js +37 -0
- package/dist/index.js +4 -1
- package/dist/paths.js +87 -16
- package/dist/utils/npm-ops.js +18 -11
- package/kits/eck_manifest.json +9 -9
- package/kits/rules/02-navigation.md +1 -0
- package/kits/rules/05-venue.md +2 -2
- package/kits/rules/06-channel.md +2 -18
- package/kits/templates/system-fragments/baseagent.md +8 -2
- package/kits/templates/system-fragments/channel.md +20 -8
- package/kits/templates/system-fragments/identity.md +5 -6
- package/kits/templates/system-fragments/relation.md +10 -5
- package/kits/templates/system-fragments/session.md +20 -0
- package/kits/templates/system-fragments/venue.md +5 -3
- package/package.json +4 -2
- package/dist/net-check.js +0 -640
- package/dist/watch-msg.js +0 -544
- 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
|
|
40
|
-
if (peerType && peerType !== 'human'
|
|
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
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
141
|
-
|
|
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
|
-
|
|
146
|
-
|
|
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
|
-
|
|
149
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
1056
|
+
channelType: inferredType,
|
|
910
1057
|
channelId,
|
|
911
|
-
selfId:
|
|
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.
|
|
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.
|
|
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'
|
|
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
|
-
|
|
120
|
+
seedConfigTemplates();
|
|
121
|
+
migrateFromAun();
|
|
113
122
|
}
|
|
123
|
+
const MIGRATION_MARKER = '.migrated-from-aun';
|
|
114
124
|
/**
|
|
115
|
-
*
|
|
116
|
-
*
|
|
125
|
+
* 一次性迁移:把 ~/.aun 下的 SDK 数据搬到 $EVOLCLAW_HOME 下。
|
|
126
|
+
* 通过标记文件判断是否已迁移,完成后写入标记,后续版本可删除此函数。
|
|
117
127
|
*/
|
|
118
|
-
function
|
|
119
|
-
const
|
|
120
|
-
const
|
|
121
|
-
if (
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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() {
|
package/dist/utils/npm-ops.js
CHANGED
|
@@ -84,19 +84,26 @@ export function getLocalVersion() {
|
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
86
|
* 查询 npm registry 上指定包的最新版本。
|
|
87
|
-
*
|
|
87
|
+
* 使用 HTTP fetch 直接查 registry API,不依赖 npm CLI。
|
|
88
|
+
* 超时 10 秒,失败返回 null。
|
|
88
89
|
*/
|
|
89
|
-
export function checkLatestVersion(pkg = 'evolclaw') {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
* 完整升级流程:检查 → 比较 → 安装(失败重试一次)
|
package/kits/eck_manifest.json
CHANGED
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"order": 20,
|
|
18
18
|
"needsInjection": true,
|
|
19
19
|
"when": {
|
|
20
|
-
"var": "
|
|
21
|
-
"neq":
|
|
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": "
|
|
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": "
|
|
74
|
+
"id": "session",
|
|
75
75
|
"type": "file",
|
|
76
|
-
"file": "$PACKAGE_ROOT/kits/templates/system-fragments/
|
|
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
|
+
}
|