evolclaw 3.1.1 → 3.1.2
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 +407 -0
- package/README.md +1 -1
- package/SKILLS.md +311 -0
- package/dist/aun/aid/agentmd.js +7 -6
- package/dist/aun/aid/client.js +5 -11
- package/dist/aun/aid/identity.js +32 -13
- package/dist/aun/msg/group.js +1 -0
- package/dist/aun/msg/p2p.js +15 -2
- package/dist/aun/msg/upload.js +57 -18
- package/dist/channels/aun.js +56 -35
- package/dist/channels/dingtalk.js +1 -0
- package/dist/channels/feishu.js +5 -4
- package/dist/channels/qqbot.js +1 -0
- package/dist/channels/wechat.js +1 -0
- package/dist/channels/wecom.js +1 -0
- package/dist/cli/agent.js +130 -35
- package/dist/cli/index.js +76 -21
- package/dist/cli/init-channel.js +4 -2
- package/dist/cli/init.js +42 -20
- package/dist/cli/watch-msg.js +3 -1
- package/dist/config-store.js +22 -1
- package/dist/core/channel-loader.js +4 -4
- package/dist/core/command-handler.js +11 -4
- package/dist/core/evolagent-registry.js +45 -9
- package/dist/core/evolagent.js +4 -4
- package/dist/core/message/im-renderer.js +4 -4
- package/dist/core/message/message-bridge.js +26 -1
- package/dist/core/message/message-processor.js +2 -24
- package/dist/core/session/session-fs-store.js +23 -0
- package/dist/core/session/session-manager.js +4 -1
- package/dist/index.js +15 -17
- package/dist/paths.js +35 -0
- package/dist/utils/cross-platform.js +2 -1
- package/kits/docs/INDEX.md +6 -0
- package/package.json +5 -2
|
@@ -2,7 +2,6 @@ import path from 'path';
|
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import crypto from 'crypto';
|
|
4
4
|
import { hasCompact } from '../../agents/claude-runner.js';
|
|
5
|
-
import { appendMessageLog, buildOutboundEntry } from './message-log.js';
|
|
6
5
|
import { IMRenderer } from './im-renderer.js';
|
|
7
6
|
import { StreamIdleMonitor } from './stream-idle-monitor.js';
|
|
8
7
|
import { logger } from '../../utils/logger.js';
|
|
@@ -1161,29 +1160,8 @@ export class MessageProcessor {
|
|
|
1161
1160
|
}
|
|
1162
1161
|
// 记录完成状态 + 最后一轮回复文本(后续 complete 覆盖前序)
|
|
1163
1162
|
completeResult = { isError: !!event.isError, subtype: event.subtype, errors: event.errors, terminalReason: event.terminalReason, lastReplyText, fullText: event.result || '', hasReceivedText, numTurns: event.numTurns, usage: event.usage };
|
|
1164
|
-
//
|
|
1165
|
-
//
|
|
1166
|
-
if (session.sessionMode === 'proactive' && lastReplyText) {
|
|
1167
|
-
try {
|
|
1168
|
-
const chatDir = this.sessionManager.getChatDir(session);
|
|
1169
|
-
const sessionEncrypt = this.sessionManager.getSessionEncrypt(session.id);
|
|
1170
|
-
appendMessageLog(chatDir, buildOutboundEntry({
|
|
1171
|
-
from: session.selfId || 'self',
|
|
1172
|
-
to: session.metadata?.peerId ?? session.channelId,
|
|
1173
|
-
chatType: (session.chatType ?? 'private'),
|
|
1174
|
-
groupId: session.metadata?.groupId ?? null,
|
|
1175
|
-
msgId: `thought-${session.id}-${Date.now()}`,
|
|
1176
|
-
content: lastReplyText,
|
|
1177
|
-
agent: session.agentId || null,
|
|
1178
|
-
model: null,
|
|
1179
|
-
durationMs: null,
|
|
1180
|
-
encrypt: sessionEncrypt ?? undefined,
|
|
1181
|
-
chatmode: 'proactive',
|
|
1182
|
-
msgType: 'thought',
|
|
1183
|
-
}));
|
|
1184
|
-
}
|
|
1185
|
-
catch { }
|
|
1186
|
-
}
|
|
1163
|
+
// thought jsonl 写入已下沉到 aun.ts:sendThought 成功后,
|
|
1164
|
+
// 由那里按 LLM 输出的每个 text item 单独写一条,此处不再写。
|
|
1187
1165
|
// 失败且无前置错误输出:显示 errors 摘要
|
|
1188
1166
|
// 但用户主动中断(新消息打断 或 /stop 命令)时不显示错误提示
|
|
1189
1167
|
// 上下文过长的错误留给外层 isPromptTooLong 触发 auto-compact,不在此处输出
|
|
@@ -146,6 +146,29 @@ export function scanChatDirs(sessionsDir) {
|
|
|
146
146
|
if (!typeEntry.isDirectory())
|
|
147
147
|
continue;
|
|
148
148
|
const channelType = typeEntry.name;
|
|
149
|
+
// 包含 '#' 的目录是旧 channelKey 格式(如 'aun#dddd.agentid.pub#main'),
|
|
150
|
+
// 按通用 channel 布局扫描(sessionsDir/{channelKey}/{encodedChannelId}/),保持兼容
|
|
151
|
+
if (channelType.includes('#')) {
|
|
152
|
+
const typeDir = path.join(sessionsDir, channelType);
|
|
153
|
+
let chatEntries;
|
|
154
|
+
try {
|
|
155
|
+
chatEntries = fs.readdirSync(typeDir, { withFileTypes: true });
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
for (const chatEntry of chatEntries) {
|
|
161
|
+
if (!chatEntry.isDirectory())
|
|
162
|
+
continue;
|
|
163
|
+
results.push({
|
|
164
|
+
channelType,
|
|
165
|
+
selfId: null,
|
|
166
|
+
channelId: decodeSegment(chatEntry.name),
|
|
167
|
+
dirPath: path.join(typeDir, chatEntry.name),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
149
172
|
const typeDir = path.join(sessionsDir, channelType);
|
|
150
173
|
if (channelType === 'aun') {
|
|
151
174
|
// aun 下还有一层 selfId
|
|
@@ -540,7 +540,10 @@ export class SessionManager {
|
|
|
540
540
|
this.writeSessionIfChanged(current.channel, current.channelId, prev, current);
|
|
541
541
|
}
|
|
542
542
|
getOrCreateThreadSession(channel, channelId, threadId, defaultProjectPath, metadata, name, agentId, selfId, channelType, peerType) {
|
|
543
|
-
|
|
543
|
+
// 优先使用精确路径(channelType + selfId),避免 fallback 到错误目录
|
|
544
|
+
const chatDir = (channelType && selfId)
|
|
545
|
+
? (() => { 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);
|
|
544
547
|
const threadIndex = readThreadIndex(chatDir);
|
|
545
548
|
const existingMetaId = threadIndex[threadId];
|
|
546
549
|
if (existingMetaId) {
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
595
|
+
const agent = agentRegistry.resolveByChannel(inst.adapter.channelKey);
|
|
597
596
|
if (!agent || agent.status === 'error')
|
|
598
597
|
continue;
|
|
599
|
-
agent.channels.set(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.
|
|
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
|
|
647
|
-
const
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
781
|
+
const owningAgent = agentRegistry.resolveByChannel(adapter.channelKey);
|
|
784
782
|
const envelope = buildEnvelope({
|
|
785
783
|
taskId: `system-restart-${process.pid}`,
|
|
786
|
-
channel: adapter.
|
|
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.
|
|
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
|
-
|
|
15
|
+
const normalized = projectPath.replace(/[/\\]+$/, '');
|
|
16
|
+
return normalized.replace(/[/\\:]/g, '-');
|
|
16
17
|
}
|
|
17
18
|
/**
|
|
18
19
|
* Cross-platform process liveness check.
|
package/kits/docs/INDEX.md
CHANGED
|
@@ -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 会话日志查阅(找完整对话/工具调用/注入) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "evolclaw",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.2",
|
|
4
4
|
"description": "Lightweight AI Agent gateway connecting Claude Agent SDK to messaging channels (Feishu, ACP) with multi-project session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
"!dist/experimental/",
|
|
15
15
|
"kits/",
|
|
16
16
|
"!kits/.kits-version",
|
|
17
|
-
"
|
|
17
|
+
"*.md",
|
|
18
|
+
"!CLAUDE.md",
|
|
19
|
+
"!DEPLOYMENT.md",
|
|
20
|
+
"!ONBOARDING.md"
|
|
18
21
|
],
|
|
19
22
|
"scripts": {
|
|
20
23
|
"dev": "tsx watch src/index.ts",
|