evolclaw 3.0.0 → 3.1.0
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/README.md +1 -1
- package/bin/ec.js +29 -0
- package/dist/agents/baseagent-normalize.js +19 -0
- package/dist/agents/claude-runner.js +7 -9
- package/dist/agents/codex-runner.js +2 -0
- package/dist/agents/gemini-runner.js +9 -9
- package/dist/agents/kit-renderer.js +281 -0
- package/dist/aun/aid/identity.js +28 -0
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/lifecycle-log.js +33 -0
- package/dist/aun/msg/group.js +3 -1
- package/dist/aun/msg/p2p.js +4 -1
- package/dist/channels/aun.js +353 -125
- package/dist/channels/dingtalk.js +2 -1
- package/dist/channels/feishu.js +118 -5
- package/dist/channels/qqbot.js +2 -1
- package/dist/channels/wechat.js +3 -1
- package/dist/channels/wecom.js +2 -1
- package/dist/cli/bench.js +1219 -0
- package/dist/cli/index.js +279 -19
- package/dist/cli/link-rules.js +245 -0
- package/dist/cli/net-check.js +640 -0
- package/dist/cli/watch-msg.js +589 -0
- package/dist/config-store.js +37 -5
- package/dist/core/channel-loader.js +23 -10
- package/dist/core/command-handler.js +46 -22
- package/dist/core/evolagent.js +5 -10
- package/dist/core/message/im-renderer.js +50 -44
- package/dist/core/message/items-formatter.js +11 -4
- package/dist/core/message/message-bridge.js +7 -2
- package/dist/core/message/message-log.js +2 -0
- package/dist/core/message/message-processor.js +150 -99
- package/dist/core/message/message-queue.js +10 -3
- package/dist/core/permission.js +95 -3
- package/dist/core/session/session-manager.js +98 -64
- package/dist/core/trigger/scheduler.js +1 -1
- package/dist/data/error-dict.json +118 -0
- package/dist/eck/baseagent-caps.js +18 -0
- package/dist/eck/detect.js +47 -0
- package/dist/eck/init.js +77 -0
- package/dist/eck/rules-loader.js +28 -0
- package/dist/index.js +137 -16
- package/dist/net-check.js +640 -0
- package/dist/paths.js +31 -40
- package/dist/utils/aid-lifecycle-log.js +33 -0
- package/dist/utils/atomic-write.js +10 -0
- package/dist/utils/cross-platform.js +17 -8
- package/dist/utils/error-utils.js +10 -2
- package/dist/utils/instance-registry.js +6 -5
- package/dist/utils/log-writer.js +2 -1
- package/dist/utils/logger.js +10 -0
- package/dist/utils/npm-ops.js +35 -3
- package/dist/utils/process-introspect.js +16 -38
- package/dist/watch-msg.js +26 -11
- package/evolclaw-install-aun.md +14 -2
- package/kits/docs/GUIDE.md +20 -0
- package/kits/docs/INDEX.md +52 -0
- package/kits/docs/aun/CHEATSHEET.md +17 -0
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
- package/kits/docs/channels/feishu.md +27 -0
- package/kits/docs/eck_templates/GUIDE.template.md +22 -0
- package/kits/docs/eck_templates/INDEX.template.md +28 -0
- package/kits/docs/eck_templates/path-registry.template.md +33 -0
- package/kits/docs/eck_templates/runtime.template.md +19 -0
- package/kits/docs/evolclaw/MSG_GROUP.md +30 -0
- package/kits/docs/evolclaw/MSG_PRIVATE.md +25 -0
- package/kits/docs/identity/AID_PROFILE_SPEC.md +27 -0
- package/kits/docs/identity/PATH_OPS.md +16 -0
- package/kits/docs/identity/ROLE_DETAIL.md +20 -0
- package/kits/docs/path-registry.md +43 -0
- package/kits/eck_manifest.json +95 -0
- package/kits/rules/01-overview.md +120 -0
- package/kits/rules/02-navigation.md +75 -0
- package/kits/rules/03-identity.md +34 -0
- package/kits/rules/04-relation.md +49 -0
- package/kits/rules/05-venue.md +45 -0
- package/kits/rules/06-channel.md +43 -0
- package/kits/templates/system-fragments/baseagent.md +2 -0
- package/kits/templates/system-fragments/channel.md +10 -0
- package/kits/templates/system-fragments/identity.md +12 -0
- package/kits/templates/system-fragments/relation.md +9 -0
- package/kits/templates/system-fragments/runtime.md +19 -0
- package/kits/templates/system-fragments/venue.md +5 -0
- package/package.json +7 -5
- package/dist/agents/templates.js +0 -122
- package/dist/data/prompts.md +0 -137
- package/kits/aun/meta.md +0 -25
- package/kits/aun/role.md +0 -25
- package/kits/templates/group.md +0 -20
- package/kits/templates/private.md +0 -9
- package/kits/templates/system-fragments/personal-context.md +0 -3
- package/kits/templates/system-fragments/self-intro.md +0 -5
- package/kits/templates/system-fragments/speaker-intro.md +0 -5
- package/kits/templates/system-fragments/venue-intro.md +0 -5
- /package/kits/{channels → docs/channels}/aun.md +0 -0
- /package/kits/{evolclaw/commands.md → docs/evolclaw/AGENT_CMD.md} +0 -0
- /package/kits/{evolclaw → docs/evolclaw}/self-summary.md +0 -0
- /package/kits/{evolclaw → docs/evolclaw}/tools.md +0 -0
- /package/kits/{evolclaw → docs/identity}/identity-tools.md +0 -0
|
@@ -31,7 +31,10 @@ export class SessionManager {
|
|
|
31
31
|
setSessionModeResolver(resolver) {
|
|
32
32
|
this.sessionModeResolver = resolver;
|
|
33
33
|
}
|
|
34
|
-
resolveDefaultSessionMode(channel, chatType) {
|
|
34
|
+
resolveDefaultSessionMode(channel, chatType, peerType) {
|
|
35
|
+
// 非 human 对端(ai/bot)强制 proactive,无视 agent 的默认 chatmode 配置
|
|
36
|
+
if (peerType && peerType !== 'human' && peerType !== 'unknown')
|
|
37
|
+
return 'proactive';
|
|
35
38
|
const ct = chatType || 'private';
|
|
36
39
|
const resolved = this.sessionModeResolver?.(channel, ct);
|
|
37
40
|
return resolved || 'interactive';
|
|
@@ -176,12 +179,75 @@ export class SessionManager {
|
|
|
176
179
|
const metaPath = this.metaFilePath(targetDir, session.id);
|
|
177
180
|
appendJsonl(metaPath, file);
|
|
178
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* 比较两个 SessionFile 是否在内容上相等(忽略 updatedAt / updatedAtStr)。
|
|
184
|
+
* 用于跳过"没真变化"的写入,避免 jsonl 写放大。
|
|
185
|
+
*/
|
|
186
|
+
sessionFilesEqual(a, b) {
|
|
187
|
+
const stripVolatile = ({ updatedAt, updatedAtStr, ...rest }) => rest;
|
|
188
|
+
return JSON.stringify(stripVolatile(a)) === JSON.stringify(stripVolatile(b));
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Append meta + write active.json,但只在 session 内容(除 updatedAt 外)真正变化时才写。
|
|
192
|
+
* prev 是修改前的快照(用于 diff),next 是修改后的 session。
|
|
193
|
+
* 返回是否发生了写入。
|
|
194
|
+
*/
|
|
195
|
+
writeSessionIfChanged(channel, channelId, prev, next) {
|
|
196
|
+
if (prev) {
|
|
197
|
+
const prevFile = sessionToFile(prev);
|
|
198
|
+
const nextFile = sessionToFile(next);
|
|
199
|
+
if (this.sessionFilesEqual(prevFile, nextFile))
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
next.updatedAt = Date.now();
|
|
203
|
+
this.appendMeta(channel, channelId, next);
|
|
204
|
+
const active = this.readActive(channel, channelId);
|
|
205
|
+
if (active && active.id === next.id) {
|
|
206
|
+
// 保留 active.json 中已有的 activeTask(markProcessing 写入的处理状态)
|
|
207
|
+
if (active.processingState && !next.processingState) {
|
|
208
|
+
next.processingState = active.processingState;
|
|
209
|
+
}
|
|
210
|
+
this.writeActive(channel, channelId, next);
|
|
211
|
+
}
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
179
214
|
readMetaLatest(metaFilePath) {
|
|
180
215
|
const file = readLastJsonlLine(metaFilePath);
|
|
181
216
|
if (!file)
|
|
182
217
|
return undefined;
|
|
183
218
|
return fileToSession(file);
|
|
184
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* 为 by-sessionId 改方法加载"当前 session 状态"。
|
|
222
|
+
*
|
|
223
|
+
* 设计契约(docs/refactor/01-db-to-fs.md):
|
|
224
|
+
* active.json 是热路径权威源。.jsonl 是历史档案。
|
|
225
|
+
*
|
|
226
|
+
* 读取策略:
|
|
227
|
+
* 1. 先按 sessionId 定位 .jsonl 文件(确认 session 存在 + 拿到 channel/channelId)
|
|
228
|
+
* 2. 优先读 active.json(如果 active.id === sessionId)—— 当前状态
|
|
229
|
+
* 3. 否则 fallback 到 .jsonl 末行 —— 非活跃 session 的更新(如多 session 并存时改非 active 那个)
|
|
230
|
+
*
|
|
231
|
+
* 返回 { current, prev }:
|
|
232
|
+
* - current 用于 caller 修改后写回
|
|
233
|
+
* - prev 是 current 的初始快照(用于 writeSessionIfChanged 的 diff 检查)
|
|
234
|
+
*/
|
|
235
|
+
loadSessionForUpdate(sessionId) {
|
|
236
|
+
const found = this.findSessionFileById(sessionId);
|
|
237
|
+
if (!found)
|
|
238
|
+
return undefined;
|
|
239
|
+
// 先读 .jsonl 末行拿 channel/channelId(active.json 文件路径需要这两个)
|
|
240
|
+
const fromJsonl = this.readMetaLatest(found.metaPath);
|
|
241
|
+
if (!fromJsonl)
|
|
242
|
+
return undefined;
|
|
243
|
+
// 优先用 active.json 的当前状态(如果它就是这个 sessionId)
|
|
244
|
+
const active = this.readActive(fromJsonl.channel, fromJsonl.channelId);
|
|
245
|
+
const base = (active && active.id === sessionId) ? active : fromJsonl;
|
|
246
|
+
// 深拷贝避免 caller 改 current 时污染 prev
|
|
247
|
+
const current = JSON.parse(JSON.stringify(base));
|
|
248
|
+
const prev = JSON.parse(JSON.stringify(base));
|
|
249
|
+
return { current, prev };
|
|
250
|
+
}
|
|
185
251
|
validateSessionFile(session) {
|
|
186
252
|
const agentSessionId = session.agentSessionId;
|
|
187
253
|
if (!agentSessionId)
|
|
@@ -193,12 +259,9 @@ export class SessionManager {
|
|
|
193
259
|
if (adapter.checkExists(session.projectPath, agentSessionId))
|
|
194
260
|
return agentSessionId;
|
|
195
261
|
logger.warn(`Session file not found for ${agentId}: ${agentSessionId}, clearing session ID`);
|
|
262
|
+
const prev = JSON.parse(JSON.stringify(session));
|
|
196
263
|
session.agentSessionId = undefined;
|
|
197
|
-
this.
|
|
198
|
-
const active = this.readActive(session.channel, session.channelId);
|
|
199
|
-
if (active && active.id === session.id) {
|
|
200
|
-
this.writeActive(session.channel, session.channelId, session);
|
|
201
|
-
}
|
|
264
|
+
this.writeSessionIfChanged(session.channel, session.channelId, prev, session);
|
|
202
265
|
return undefined;
|
|
203
266
|
}
|
|
204
267
|
getActiveChatType(channel, channelId) {
|
|
@@ -332,9 +395,9 @@ export class SessionManager {
|
|
|
332
395
|
return result;
|
|
333
396
|
}
|
|
334
397
|
// ─── Session lifecycle ───
|
|
335
|
-
async getOrCreateSession(channel, channelId, defaultProjectPath, threadId, metadata, name, userId, chatType, agentId, selfId, channelType) {
|
|
398
|
+
async getOrCreateSession(channel, channelId, defaultProjectPath, threadId, metadata, name, userId, chatType, agentId, selfId, channelType, peerType) {
|
|
336
399
|
if (threadId) {
|
|
337
|
-
const session = this.getOrCreateThreadSession(channel, channelId, threadId, defaultProjectPath, metadata, name, agentId, selfId, channelType);
|
|
400
|
+
const session = this.getOrCreateThreadSession(channel, channelId, threadId, defaultProjectPath, metadata, name, agentId, selfId, channelType, peerType);
|
|
338
401
|
session.identity = this.resolveIdentity(channel, userId);
|
|
339
402
|
if (session.metadata && !session.metadata.permissionMode) {
|
|
340
403
|
session.metadata.permissionMode = DEFAULT_PERMISSION_MODE;
|
|
@@ -399,6 +462,7 @@ export class SessionManager {
|
|
|
399
462
|
.sort((a, b) => b.updatedAt - a.updatedAt)[0];
|
|
400
463
|
if (existing) {
|
|
401
464
|
const validSessionId = this.validateSessionFile(existing);
|
|
465
|
+
const prev = JSON.parse(JSON.stringify({ ...existing, agentSessionId: validSessionId }));
|
|
402
466
|
const session = { ...existing, agentSessionId: validSessionId };
|
|
403
467
|
session.identity = this.resolveIdentity(channel, userId);
|
|
404
468
|
if (!session.metadata)
|
|
@@ -416,9 +480,7 @@ export class SessionManager {
|
|
|
416
480
|
if (chatType === 'private' && metadata?.peerName && !session.metadata.peerName) {
|
|
417
481
|
session.metadata.peerName = metadata.peerName;
|
|
418
482
|
}
|
|
419
|
-
|
|
420
|
-
this.appendMeta(channel, channelId, session);
|
|
421
|
-
this.writeActive(channel, channelId, session);
|
|
483
|
+
this.writeSessionIfChanged(channel, channelId, prev, session);
|
|
422
484
|
return session;
|
|
423
485
|
}
|
|
424
486
|
// Create new session
|
|
@@ -435,7 +497,7 @@ export class SessionManager {
|
|
|
435
497
|
threadId: '',
|
|
436
498
|
agentId: agentId || 'claude',
|
|
437
499
|
chatType: chatType || 'private',
|
|
438
|
-
sessionMode: this.resolveDefaultSessionMode(channel, chatType || 'private'),
|
|
500
|
+
sessionMode: this.resolveDefaultSessionMode(channel, chatType || 'private', peerType),
|
|
439
501
|
metadata: sessionMetadata,
|
|
440
502
|
name: name || '默认会话',
|
|
441
503
|
createdAt: Date.now(),
|
|
@@ -457,12 +519,10 @@ export class SessionManager {
|
|
|
457
519
|
return session;
|
|
458
520
|
}
|
|
459
521
|
async updateSession(sessionId, updates) {
|
|
460
|
-
const
|
|
461
|
-
if (!
|
|
462
|
-
return;
|
|
463
|
-
const current = this.readMetaLatest(found.metaPath);
|
|
464
|
-
if (!current)
|
|
522
|
+
const loaded = this.loadSessionForUpdate(sessionId);
|
|
523
|
+
if (!loaded)
|
|
465
524
|
return;
|
|
525
|
+
const { current, prev } = loaded;
|
|
466
526
|
if (updates.chatType !== undefined)
|
|
467
527
|
current.chatType = updates.chatType;
|
|
468
528
|
if (updates.name !== undefined)
|
|
@@ -473,14 +533,9 @@ export class SessionManager {
|
|
|
473
533
|
current.metadata = updates.metadata;
|
|
474
534
|
if ('agentSessionId' in updates)
|
|
475
535
|
current.agentSessionId = updates.agentSessionId ?? undefined;
|
|
476
|
-
current.
|
|
477
|
-
this.appendMeta(current.channel, current.channelId, current);
|
|
478
|
-
const active = this.readActive(current.channel, current.channelId);
|
|
479
|
-
if (active && active.id === sessionId) {
|
|
480
|
-
this.writeActive(current.channel, current.channelId, current);
|
|
481
|
-
}
|
|
536
|
+
this.writeSessionIfChanged(current.channel, current.channelId, prev, current);
|
|
482
537
|
}
|
|
483
|
-
getOrCreateThreadSession(channel, channelId, threadId, defaultProjectPath, metadata, name, agentId, selfId, channelType) {
|
|
538
|
+
getOrCreateThreadSession(channel, channelId, threadId, defaultProjectPath, metadata, name, agentId, selfId, channelType, peerType) {
|
|
484
539
|
const chatDir = this.ensureResolvedChatDir(channel, channelId);
|
|
485
540
|
const threadIndex = readThreadIndex(chatDir);
|
|
486
541
|
const existingMetaId = threadIndex[threadId];
|
|
@@ -511,7 +566,7 @@ export class SessionManager {
|
|
|
511
566
|
threadId,
|
|
512
567
|
agentId: agentId || 'claude',
|
|
513
568
|
chatType: inheritedChatType,
|
|
514
|
-
sessionMode: this.resolveDefaultSessionMode(channel, inheritedChatType),
|
|
569
|
+
sessionMode: this.resolveDefaultSessionMode(channel, inheritedChatType, peerType),
|
|
515
570
|
metadata,
|
|
516
571
|
name: name || '话题会话',
|
|
517
572
|
createdAt: Date.now(),
|
|
@@ -581,25 +636,19 @@ export class SessionManager {
|
|
|
581
636
|
const active = this.readActive(channel, channelId);
|
|
582
637
|
if (!active)
|
|
583
638
|
return;
|
|
639
|
+
const prev = JSON.parse(JSON.stringify(active));
|
|
584
640
|
active.agentSessionId = agentSessionId;
|
|
585
|
-
|
|
586
|
-
this.appendMeta(channel, channelId, active);
|
|
587
|
-
this.writeActive(channel, channelId, active);
|
|
641
|
+
this.writeSessionIfChanged(channel, channelId, prev, active);
|
|
588
642
|
}
|
|
589
643
|
async updateAgentSessionIdBySessionId(sessionId, agentSessionId) {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
if (!found)
|
|
593
|
-
return;
|
|
594
|
-
const current = this.readMetaLatest(found.metaPath);
|
|
595
|
-
if (!current)
|
|
644
|
+
const loaded = this.loadSessionForUpdate(sessionId);
|
|
645
|
+
if (!loaded)
|
|
596
646
|
return;
|
|
647
|
+
const { current, prev } = loaded;
|
|
597
648
|
current.agentSessionId = agentSessionId;
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
if (active && active.id === sessionId) {
|
|
602
|
-
this.writeActive(current.channel, current.channelId, current);
|
|
649
|
+
const wrote = this.writeSessionIfChanged(current.channel, current.channelId, prev, current);
|
|
650
|
+
if (wrote) {
|
|
651
|
+
logger.info(`[SessionManager] Updating agent_session_id: sessionId=${sessionId}, agentSessionId=${agentSessionId}`);
|
|
603
652
|
}
|
|
604
653
|
}
|
|
605
654
|
async switchAgent(channel, channelId, projectPath, newAgentId) {
|
|
@@ -650,10 +699,9 @@ export class SessionManager {
|
|
|
650
699
|
const active = this.readActive(channel, channelId);
|
|
651
700
|
if (!active)
|
|
652
701
|
return;
|
|
702
|
+
const prev = JSON.parse(JSON.stringify(active));
|
|
653
703
|
active.agentSessionId = undefined;
|
|
654
|
-
|
|
655
|
-
this.appendMeta(channel, channelId, active);
|
|
656
|
-
this.writeActive(channel, channelId, active);
|
|
704
|
+
this.writeSessionIfChanged(channel, channelId, prev, active);
|
|
657
705
|
}
|
|
658
706
|
getOwnerChatId(targetChannel, ownerPeerId) {
|
|
659
707
|
const chatDirs = scanChatDirs(this.sessionsDir);
|
|
@@ -742,34 +790,20 @@ export class SessionManager {
|
|
|
742
790
|
return target;
|
|
743
791
|
}
|
|
744
792
|
updateMetadata(sessionId, metadata) {
|
|
745
|
-
const
|
|
746
|
-
if (!
|
|
747
|
-
return;
|
|
748
|
-
const current = this.readMetaLatest(found.metaPath);
|
|
749
|
-
if (!current)
|
|
793
|
+
const loaded = this.loadSessionForUpdate(sessionId);
|
|
794
|
+
if (!loaded)
|
|
750
795
|
return;
|
|
796
|
+
const { current, prev } = loaded;
|
|
751
797
|
current.metadata = metadata;
|
|
752
|
-
current.
|
|
753
|
-
this.appendMeta(current.channel, current.channelId, current);
|
|
754
|
-
const active = this.readActive(current.channel, current.channelId);
|
|
755
|
-
if (active && active.id === sessionId) {
|
|
756
|
-
this.writeActive(current.channel, current.channelId, current);
|
|
757
|
-
}
|
|
798
|
+
this.writeSessionIfChanged(current.channel, current.channelId, prev, current);
|
|
758
799
|
}
|
|
759
800
|
async renameSession(sessionId, newName) {
|
|
760
|
-
const
|
|
761
|
-
if (!
|
|
762
|
-
return false;
|
|
763
|
-
const current = this.readMetaLatest(found.metaPath);
|
|
764
|
-
if (!current)
|
|
801
|
+
const loaded = this.loadSessionForUpdate(sessionId);
|
|
802
|
+
if (!loaded)
|
|
765
803
|
return false;
|
|
804
|
+
const { current, prev } = loaded;
|
|
766
805
|
current.name = newName;
|
|
767
|
-
current.
|
|
768
|
-
this.appendMeta(current.channel, current.channelId, current);
|
|
769
|
-
const active = this.readActive(current.channel, current.channelId);
|
|
770
|
-
if (active && active.id === sessionId) {
|
|
771
|
-
this.writeActive(current.channel, current.channelId, current);
|
|
772
|
-
}
|
|
806
|
+
this.writeSessionIfChanged(current.channel, current.channelId, prev, current);
|
|
773
807
|
return true;
|
|
774
808
|
}
|
|
775
809
|
async unbindSession(sessionId) {
|
|
@@ -153,7 +153,7 @@ export class TriggerScheduler {
|
|
|
153
153
|
if (!top)
|
|
154
154
|
return;
|
|
155
155
|
const delay = Math.max(0, top.nextFireAt - Date.now());
|
|
156
|
-
this.timer = setTimeout(() => this.onFire(), delay);
|
|
156
|
+
this.timer = setTimeout(() => this.onFire(), delay).unref();
|
|
157
157
|
}
|
|
158
158
|
onFire() {
|
|
159
159
|
this.timer = null;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
{
|
|
2
|
+
"rules": [
|
|
3
|
+
{
|
|
4
|
+
"id": "context-too-long-cn",
|
|
5
|
+
"match": "上下文过长",
|
|
6
|
+
"action": "stop",
|
|
7
|
+
"type": "context_too_long",
|
|
8
|
+
"message": "⚠️ 上下文过长,请手动输入 /compact 压缩上下文"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "context-too-long-en",
|
|
12
|
+
"match": "context too long",
|
|
13
|
+
"action": "stop",
|
|
14
|
+
"type": "context_too_long",
|
|
15
|
+
"message": "⚠️ 上下文过长,请手动输入 /compact 压缩上下文"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "prompt-too-long",
|
|
19
|
+
"match": "prompt is too long",
|
|
20
|
+
"action": "stop",
|
|
21
|
+
"type": "context_too_long",
|
|
22
|
+
"message": "⚠️ 输入过长,请精简提问或使用 /compact 压缩上下文"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "invalid-api-key",
|
|
26
|
+
"match": "invalid api key",
|
|
27
|
+
"action": "stop",
|
|
28
|
+
"type": "auth_error",
|
|
29
|
+
"message": "❌ API Key 无效,请检查密钥配置。使用 /status 查看当前配置"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "key-not-found",
|
|
33
|
+
"match": "key_not_found",
|
|
34
|
+
"action": "stop",
|
|
35
|
+
"type": "auth_error",
|
|
36
|
+
"message": "❌ API Key 未找到,请检查密钥配置"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "credit-query-timeout",
|
|
40
|
+
"match": "积分查询超时",
|
|
41
|
+
"action": "retry",
|
|
42
|
+
"type": "api_error",
|
|
43
|
+
"message": "⚠️ 积分查询超时,正在重试..."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "api-error-400",
|
|
47
|
+
"match": "api error: 400",
|
|
48
|
+
"action": "stop",
|
|
49
|
+
"type": "api_error",
|
|
50
|
+
"message": "❌ 请求格式错误,请检查输入内容"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "api-error-403",
|
|
54
|
+
"match": "api error: 403",
|
|
55
|
+
"action": "retry",
|
|
56
|
+
"type": "api_error",
|
|
57
|
+
"message": "⚠️ API 访问受限,正在重试..."
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "api-error-429",
|
|
61
|
+
"match": "api error: 429",
|
|
62
|
+
"action": "retry",
|
|
63
|
+
"type": "api_error",
|
|
64
|
+
"message": "⚠️ 请求过于频繁,正在重试..."
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "api-error-500",
|
|
68
|
+
"match": "api error: 500",
|
|
69
|
+
"action": "retry",
|
|
70
|
+
"type": "api_error",
|
|
71
|
+
"message": "❌ API 服务暂时不可用,正在重试..."
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"id": "api-error-502",
|
|
75
|
+
"match": "api error: 502",
|
|
76
|
+
"action": "retry",
|
|
77
|
+
"type": "api_error"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"id": "api-error-503",
|
|
81
|
+
"match": "api error: 503",
|
|
82
|
+
"action": "retry",
|
|
83
|
+
"type": "api_error"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"id": "api-error-504",
|
|
87
|
+
"match": "api error: 504",
|
|
88
|
+
"action": "retry",
|
|
89
|
+
"type": "api_error"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"id": "not-valid-json",
|
|
93
|
+
"match": "is not valid json",
|
|
94
|
+
"action": "retry",
|
|
95
|
+
"type": "api_error",
|
|
96
|
+
"message": "⚠️ API 返回异常响应,正在重试..."
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"id": "feishu-permission",
|
|
100
|
+
"match": "im:resource",
|
|
101
|
+
"action": "stop",
|
|
102
|
+
"type": "unknown",
|
|
103
|
+
"message": "❌ 权限不足,请联系管理员配置应用权限"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"id": "stream-error",
|
|
107
|
+
"match": "stream",
|
|
108
|
+
"action": "retry",
|
|
109
|
+
"type": "stream_error"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"id": "request-aborted",
|
|
113
|
+
"match": "request was aborted",
|
|
114
|
+
"action": "ignore",
|
|
115
|
+
"type": "stream_error"
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const BASEAGENT_CAPS = {
|
|
2
|
+
'claude-code': {
|
|
3
|
+
autoLoadsRules: true,
|
|
4
|
+
supportsSystemPrompt: true,
|
|
5
|
+
},
|
|
6
|
+
'claude': {
|
|
7
|
+
autoLoadsRules: true,
|
|
8
|
+
supportsSystemPrompt: true,
|
|
9
|
+
},
|
|
10
|
+
'codex': {
|
|
11
|
+
autoLoadsRules: false,
|
|
12
|
+
supportsSystemPrompt: true,
|
|
13
|
+
},
|
|
14
|
+
'gemini': {
|
|
15
|
+
autoLoadsRules: false,
|
|
16
|
+
supportsSystemPrompt: true,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { BASEAGENT_CAPS } from './baseagent-caps.js';
|
|
4
|
+
const MAX_DEPTH = 5;
|
|
5
|
+
export function resolveEckInjection(agentConfig, projectPath, kitsRulesPath) {
|
|
6
|
+
const caps = BASEAGENT_CAPS[agentConfig.baseAgent]
|
|
7
|
+
?? { autoLoadsRules: false, supportsSystemPrompt: true };
|
|
8
|
+
if (!caps.autoLoadsRules) {
|
|
9
|
+
return { shouldInject: true, reason: 'baseagent-no-autoload' };
|
|
10
|
+
}
|
|
11
|
+
const symlinkActive = detectEckSymlink(projectPath, kitsRulesPath);
|
|
12
|
+
if (symlinkActive) {
|
|
13
|
+
return { shouldInject: false, reason: 'symlink-active' };
|
|
14
|
+
}
|
|
15
|
+
return { shouldInject: true, reason: 'symlink-not-found' };
|
|
16
|
+
}
|
|
17
|
+
export function detectEckSymlink(projectPath, kitsRulesPath) {
|
|
18
|
+
let dir = projectPath;
|
|
19
|
+
let depth = 0;
|
|
20
|
+
while (depth < MAX_DEPTH) {
|
|
21
|
+
const eckDir = path.join(dir, '.claude', 'rules', 'eck');
|
|
22
|
+
if (fs.existsSync(eckDir)) {
|
|
23
|
+
try {
|
|
24
|
+
const realPath = fs.realpathSync(eckDir);
|
|
25
|
+
const kitsRulesReal = fs.realpathSync(kitsRulesPath);
|
|
26
|
+
if (pathEquals(realPath, kitsRulesReal)) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// detection failure → conservatively assume not loaded
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const parent = path.dirname(dir);
|
|
35
|
+
if (parent === dir)
|
|
36
|
+
break;
|
|
37
|
+
dir = parent;
|
|
38
|
+
depth++;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
function pathEquals(a, b) {
|
|
43
|
+
if (process.platform === 'win32') {
|
|
44
|
+
return path.resolve(a).toLowerCase() === path.resolve(b).toLowerCase();
|
|
45
|
+
}
|
|
46
|
+
return path.resolve(a) === path.resolve(b);
|
|
47
|
+
}
|
package/dist/eck/init.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { resolvePaths, kitsDocsDir, agentDir, getPackageRoot } from '../paths.js';
|
|
4
|
+
import { atomicWriteText } from '../utils/atomic-write.js';
|
|
5
|
+
import { logger } from '../utils/logger.js';
|
|
6
|
+
export function initEck() {
|
|
7
|
+
const p = resolvePaths();
|
|
8
|
+
const eckDir = p.eckDir;
|
|
9
|
+
fs.mkdirSync(eckDir, { recursive: true });
|
|
10
|
+
initEckRuntime(eckDir);
|
|
11
|
+
initEckPathRegistry(eckDir);
|
|
12
|
+
}
|
|
13
|
+
export function initAgentIndex(aid) {
|
|
14
|
+
const indexDir = path.join(agentDir(aid), 'index');
|
|
15
|
+
fs.mkdirSync(indexDir, { recursive: true });
|
|
16
|
+
const indexFile = path.join(indexDir, 'INDEX.md');
|
|
17
|
+
const guideFile = path.join(indexDir, 'GUIDE.md');
|
|
18
|
+
if (!fs.existsSync(indexFile)) {
|
|
19
|
+
const template = loadTemplate('INDEX.template.md');
|
|
20
|
+
if (template) {
|
|
21
|
+
atomicWriteText(indexFile, renderTemplate(template, { AID: aid }));
|
|
22
|
+
logger.info(`[eck] Created ${indexFile}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (!fs.existsSync(guideFile)) {
|
|
26
|
+
const template = loadTemplate('GUIDE.template.md');
|
|
27
|
+
if (template) {
|
|
28
|
+
atomicWriteText(guideFile, renderTemplate(template, { AID: aid }));
|
|
29
|
+
logger.info(`[eck] Created ${guideFile}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function initEckRuntime(eckDir) {
|
|
34
|
+
const runtimeFile = path.join(eckDir, 'runtime.md');
|
|
35
|
+
if (fs.existsSync(runtimeFile))
|
|
36
|
+
return;
|
|
37
|
+
const template = loadTemplate('runtime.template.md');
|
|
38
|
+
if (!template)
|
|
39
|
+
return;
|
|
40
|
+
const vars = {
|
|
41
|
+
EVOLCLAW_HOME: resolvePaths().root,
|
|
42
|
+
PACKAGE_ROOT: getPackageRoot(),
|
|
43
|
+
};
|
|
44
|
+
atomicWriteText(runtimeFile, renderTemplate(template, vars));
|
|
45
|
+
logger.info(`[eck] Created ${runtimeFile}`);
|
|
46
|
+
}
|
|
47
|
+
function initEckPathRegistry(eckDir) {
|
|
48
|
+
const registryFile = path.join(eckDir, 'path-registry.md');
|
|
49
|
+
if (fs.existsSync(registryFile))
|
|
50
|
+
return;
|
|
51
|
+
const template = loadTemplate('path-registry.template.md');
|
|
52
|
+
if (!template)
|
|
53
|
+
return;
|
|
54
|
+
const vars = {
|
|
55
|
+
EVOLCLAW_HOME: resolvePaths().root,
|
|
56
|
+
PACKAGE_ROOT: getPackageRoot(),
|
|
57
|
+
};
|
|
58
|
+
atomicWriteText(registryFile, renderTemplate(template, vars));
|
|
59
|
+
logger.info(`[eck] Created ${registryFile}`);
|
|
60
|
+
}
|
|
61
|
+
function loadTemplate(filename) {
|
|
62
|
+
const templatePath = path.join(kitsDocsDir(), 'eck_templates', filename);
|
|
63
|
+
try {
|
|
64
|
+
return fs.readFileSync(templatePath, 'utf-8');
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
logger.warn(`[eck] Template not found: ${templatePath}`);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function renderTemplate(template, vars) {
|
|
72
|
+
let result = template;
|
|
73
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
74
|
+
result = result.replaceAll(`{{${key}}}`, value);
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { kitsRulesDir } from '../paths.js';
|
|
4
|
+
let _cachedRules = null;
|
|
5
|
+
export function loadRulesForInjection() {
|
|
6
|
+
if (_cachedRules !== null)
|
|
7
|
+
return _cachedRules;
|
|
8
|
+
const rulesDir = kitsRulesDir();
|
|
9
|
+
if (!fs.existsSync(rulesDir)) {
|
|
10
|
+
_cachedRules = '';
|
|
11
|
+
return '';
|
|
12
|
+
}
|
|
13
|
+
const files = fs.readdirSync(rulesDir)
|
|
14
|
+
.filter(f => f.endsWith('.md'))
|
|
15
|
+
.sort();
|
|
16
|
+
const parts = [];
|
|
17
|
+
for (const file of files) {
|
|
18
|
+
try {
|
|
19
|
+
parts.push(fs.readFileSync(path.join(rulesDir, file), 'utf-8'));
|
|
20
|
+
}
|
|
21
|
+
catch { /* skip unreadable files */ }
|
|
22
|
+
}
|
|
23
|
+
_cachedRules = parts.join('\n\n');
|
|
24
|
+
return _cachedRules;
|
|
25
|
+
}
|
|
26
|
+
export function invalidateRulesCache() {
|
|
27
|
+
_cachedRules = null;
|
|
28
|
+
}
|