evolclaw 2.8.3 → 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 +21 -12
- package/bin/ec.js +29 -0
- package/dist/agents/baseagent-normalize.js +19 -0
- package/dist/agents/claude-runner.js +108 -46
- package/dist/agents/codex-runner.js +13 -14
- package/dist/agents/gemini-runner.js +15 -17
- package/dist/agents/kit-renderer.js +281 -0
- package/dist/agents/resolve.js +134 -0
- package/dist/aun/aid/agentmd.js +186 -0
- package/dist/aun/aid/client.js +134 -0
- package/dist/aun/aid/identity.js +159 -0
- package/dist/aun/aid/index.js +3 -0
- package/dist/aun/aid/lifecycle-log.js +33 -0
- package/dist/aun/aid/types.js +1 -0
- package/dist/aun/aid/validation.js +21 -0
- package/dist/aun/msg/group.js +293 -0
- package/dist/aun/msg/index.js +4 -0
- package/dist/aun/msg/p2p.js +147 -0
- package/dist/aun/msg/payload-type.js +27 -0
- package/dist/aun/msg/upload.js +98 -0
- package/dist/aun/outbox.js +138 -0
- package/dist/aun/rpc/caller.js +42 -0
- package/dist/aun/rpc/connection.js +34 -0
- package/dist/aun/rpc/index.js +2 -0
- package/dist/aun/storage/download.js +29 -0
- package/dist/aun/storage/index.js +3 -0
- package/dist/aun/storage/manage.js +10 -0
- package/dist/aun/storage/upload.js +35 -0
- package/dist/channels/aun.js +1340 -349
- package/dist/channels/dingtalk.js +59 -5
- package/dist/channels/feishu.js +381 -32
- package/dist/channels/qqbot.js +68 -12
- package/dist/channels/wechat.js +63 -4
- package/dist/channels/wecom.js +59 -5
- package/dist/cli/agent.js +800 -0
- package/dist/cli/bench.js +1219 -0
- package/dist/cli/index.js +4513 -0
- package/dist/{utils → cli}/init-channel.js +211 -621
- package/dist/cli/init.js +178 -0
- 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 +645 -0
- package/dist/core/{agent-loader.js → baseagent-loader.js} +6 -12
- package/dist/core/channel-loader.js +176 -12
- package/dist/core/command-handler.js +883 -848
- package/dist/core/evolagent-registry.js +191 -371
- package/dist/core/evolagent.js +202 -238
- package/dist/core/interaction-router.js +52 -5
- package/dist/core/message/im-renderer.js +486 -0
- package/dist/core/message/items-formatter.js +68 -0
- package/dist/core/message/message-bridge.js +109 -56
- package/dist/core/message/message-log.js +93 -0
- package/dist/core/message/message-processor.js +430 -212
- package/dist/core/message/message-queue.js +13 -6
- package/dist/core/permission.js +116 -11
- package/dist/core/session/adapters/codex-session-file-adapter.js +24 -2
- package/dist/core/session/session-fs-store.js +230 -0
- package/dist/core/session/session-manager.js +740 -777
- package/dist/core/session/session-mapper.js +87 -0
- package/dist/core/trigger/manager.js +122 -0
- package/dist/core/trigger/parser.js +128 -0
- package/dist/core/trigger/scheduler.js +224 -0
- 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 +560 -283
- package/dist/ipc.js +49 -0
- package/dist/net-check.js +640 -0
- package/dist/paths.js +73 -9
- package/dist/types.js +8 -2
- package/dist/utils/aid-lifecycle-log.js +33 -0
- package/dist/utils/atomic-write.js +89 -0
- package/dist/utils/channel-helpers.js +46 -0
- package/dist/utils/cross-platform.js +17 -26
- package/dist/utils/error-utils.js +10 -2
- package/dist/utils/instance-registry.js +434 -0
- package/dist/utils/log-writer.js +217 -0
- package/dist/utils/logger.js +34 -77
- package/dist/utils/media-cache.js +23 -0
- package/dist/utils/npm-ops.js +163 -0
- package/dist/utils/process-introspect.js +122 -0
- package/dist/utils/stats.js +192 -0
- package/dist/watch-msg.js +544 -0
- package/evolclaw-install-aun.md +127 -47
- 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/aun.md +25 -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/AGENT_CMD.md +31 -0
- package/kits/docs/evolclaw/MSG_GROUP.md +30 -0
- package/kits/docs/evolclaw/MSG_PRIVATE.md +25 -0
- package/kits/docs/evolclaw/self-summary.md +29 -0
- package/kits/docs/evolclaw/tools.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/identity/identity-tools.md +26 -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 +10 -6
- package/data/evolclaw.sample.json +0 -60
- package/dist/agents/templates.js +0 -122
- package/dist/channels/aun-ops.js +0 -275
- package/dist/cli.js +0 -2178
- package/dist/config.js +0 -591
- package/dist/core/agent-registry.js +0 -450
- package/dist/core/evolagent-schema.js +0 -72
- package/dist/core/message/stream-flusher.js +0 -238
- package/dist/core/message/thought-emitter.js +0 -162
- package/dist/core/reload-hooks.js +0 -87
- package/dist/prompts/templates.js +0 -122
- package/dist/templates/prompts.md +0 -104
- package/dist/templates/skills.md +0 -66
- package/dist/utils/channel-fingerprint.js +0 -59
- package/dist/utils/error-dict.js +0 -63
- package/dist/utils/format.js +0 -32
- package/dist/utils/init.js +0 -645
- package/dist/utils/migrate-project.js +0 -122
- package/dist/utils/reload-hooks.js +0 -87
- package/dist/utils/stats-collector.js +0 -99
- package/dist/utils/upgrade.js +0 -100
|
@@ -1,333 +1,245 @@
|
|
|
1
|
-
import
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { EvolAgent, validateEvolAgentConfig } from './evolagent.js';
|
|
1
|
+
import { EvolAgent } from './evolagent.js';
|
|
4
2
|
import { logger } from '../utils/logger.js';
|
|
3
|
+
import { loadDefaults, loadAllAgents, mergeForAgent, ensureAgentDirSkeleton, loadAgent, validateAgentConfig, } from '../config-store.js';
|
|
5
4
|
// ── Channel Fingerprint ────────────────────────────────────────────────────
|
|
6
|
-
//
|
|
5
|
+
// 用于检测多 agent 之间复用同一外部凭证的冲突(appId、aid、token 等)。
|
|
7
6
|
// 格式:{type}:{primaryKey}
|
|
8
7
|
const PRIMARY_KEY_MAP = {
|
|
9
8
|
feishu: 'appId',
|
|
10
|
-
aun: '
|
|
9
|
+
aun: '__aid__', // AUN 实例的"凭证"就是 agent 自身 aid
|
|
11
10
|
wechat: 'token',
|
|
12
11
|
wecom: 'botId',
|
|
13
12
|
dingtalk: 'clientId',
|
|
14
13
|
qqbot: 'appId',
|
|
15
14
|
};
|
|
16
|
-
export function extractFingerprint(channelType, instance) {
|
|
15
|
+
export function extractFingerprint(channelType, instance, agentAid) {
|
|
17
16
|
const keyField = PRIMARY_KEY_MAP[channelType];
|
|
18
17
|
if (!keyField)
|
|
19
18
|
return null;
|
|
19
|
+
if (keyField === '__aid__')
|
|
20
|
+
return `${channelType}:${agentAid}`;
|
|
20
21
|
const value = instance[keyField];
|
|
21
22
|
if (!value || typeof value !== 'string')
|
|
22
23
|
return null;
|
|
23
24
|
return `${channelType}:${value}`;
|
|
24
25
|
}
|
|
25
|
-
|
|
26
|
+
/**
|
|
27
|
+
* 跨 agent 检查同一外部凭证是否被多次声明(飞书 appId、AUN aid 等)。
|
|
28
|
+
*/
|
|
29
|
+
export function detectDuplicates(agents) {
|
|
26
30
|
const seen = new Map();
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const instances = Array.isArray(raw) ? raw : [raw];
|
|
32
|
-
for (const inst of instances) {
|
|
33
|
-
if (!inst || typeof inst !== 'object')
|
|
34
|
-
continue;
|
|
35
|
-
const fingerprint = extractFingerprint(type, inst);
|
|
36
|
-
if (!fingerprint)
|
|
31
|
+
for (const agent of agents) {
|
|
32
|
+
for (const inst of agent.config.channels) {
|
|
33
|
+
const fp = extractFingerprint(inst.type, inst, agent.aid);
|
|
34
|
+
if (!fp)
|
|
37
35
|
continue;
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
entry.instances.push(instName);
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
seen.set(fingerprint, { channelType: type, instances: [instName] });
|
|
45
|
-
}
|
|
36
|
+
const arr = seen.get(fp) ?? [];
|
|
37
|
+
arr.push({ aid: agent.aid, channelName: agent.effectiveChannelName(inst.type, inst.name) });
|
|
38
|
+
seen.set(fp, arr);
|
|
46
39
|
}
|
|
47
40
|
}
|
|
48
|
-
const
|
|
49
|
-
for (const [
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
channelType: entry.channelType,
|
|
54
|
-
instances: entry.instances,
|
|
55
|
-
});
|
|
41
|
+
const out = [];
|
|
42
|
+
for (const [fp, arr] of seen) {
|
|
43
|
+
if (arr.length > 1) {
|
|
44
|
+
const [type] = fp.split(':');
|
|
45
|
+
out.push({ fingerprint: fp, channelType: type, agents: arr });
|
|
56
46
|
}
|
|
57
47
|
}
|
|
58
|
-
return
|
|
48
|
+
return out;
|
|
59
49
|
}
|
|
50
|
+
// ── Registry ───────────────────────────────────────────────────────────────
|
|
60
51
|
export class EvolAgentRegistry {
|
|
61
|
-
|
|
52
|
+
_agentsDir;
|
|
62
53
|
agents = new Map();
|
|
63
|
-
|
|
54
|
+
/** channel key (`<aid>#<type>#<name>`) → agent aid */
|
|
64
55
|
channelIndex = new Map();
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
56
|
+
/** 启动期被 ConfigStore 跳过的目录(命名非法 / 缺 config.json / 校验失败等) */
|
|
57
|
+
skipped = [];
|
|
58
|
+
/**
|
|
59
|
+
* agentsDir 参数保留作 ctor 兼容,但实际加载走 ConfigStore(基于 paths.ts)。
|
|
60
|
+
* globalWriter 已废弃——构造期接受但忽略,阶段 2c 删除。
|
|
61
|
+
*/
|
|
62
|
+
constructor(_agentsDir, _globalWriter) {
|
|
63
|
+
this._agentsDir = _agentsDir;
|
|
64
|
+
void _globalWriter;
|
|
69
65
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
this.globalWriter = writer;
|
|
66
|
+
setGlobalWriter(_writer) {
|
|
67
|
+
void _writer; // no-op,废弃 API
|
|
73
68
|
}
|
|
74
|
-
|
|
69
|
+
/**
|
|
70
|
+
* 扫描 agents/ 目录加载所有 self-agent 配置(合并 defaults),构造 EvolAgent
|
|
71
|
+
* 实例并建立 channel 路由索引。
|
|
72
|
+
*
|
|
73
|
+
* `globalConfig` 参数保留作签名兼容,但被忽略——defaults 由 ConfigStore 自己加载。
|
|
74
|
+
*/
|
|
75
|
+
loadAll(_globalConfig) {
|
|
76
|
+
void _globalConfig;
|
|
75
77
|
this.agents.clear();
|
|
76
78
|
this.channelIndex.clear();
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
this.skipped = [];
|
|
80
|
+
const defaults = loadDefaults();
|
|
81
|
+
const { agents: rawAgents, skipped } = loadAllAgents();
|
|
82
|
+
this.skipped = skipped;
|
|
83
|
+
for (const raw of rawAgents) {
|
|
82
84
|
try {
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const errorAgent = new EvolAgent(fullPath, { ...raw, name });
|
|
88
|
-
errorAgent.status = 'error';
|
|
89
|
-
errorAgent.error = validation.errors.join('; ');
|
|
90
|
-
this.agents.set(name, errorAgent);
|
|
91
|
-
logger.warn(`[EvolAgentRegistry] ${file}: ${validation.errors.join('; ')}`);
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
const agent = new EvolAgent(fullPath, raw);
|
|
95
|
-
this.agents.set(agent.name, agent);
|
|
85
|
+
const merged = mergeForAgent(raw, defaults);
|
|
86
|
+
const agent = new EvolAgent(raw, merged);
|
|
87
|
+
ensureAgentDirSkeleton(raw.aid);
|
|
88
|
+
this.agents.set(agent.aid, agent);
|
|
96
89
|
}
|
|
97
90
|
catch (e) {
|
|
98
|
-
logger.warn(`[EvolAgentRegistry]
|
|
91
|
+
logger.warn(`[EvolAgentRegistry] failed to construct agent ${raw.aid}: ${e}`);
|
|
99
92
|
}
|
|
100
93
|
}
|
|
101
|
-
this.defaultAgent = this.buildDefaultAgent(globalConfig);
|
|
102
94
|
this.detectAndFlagConflicts();
|
|
103
95
|
this.buildChannelIndex();
|
|
104
96
|
}
|
|
105
|
-
buildDefaultAgent(globalConfig) {
|
|
106
|
-
const agents = globalConfig.agents || {};
|
|
107
|
-
const defaultName = agents.defaultAgent || 'claude';
|
|
108
|
-
// Include ALL declared baseagents (not just defaultName) so that
|
|
109
|
-
// AgentLoader creates runners for each, enabling /agent switching.
|
|
110
|
-
const baseagentBlock = {};
|
|
111
|
-
const KNOWN_BASEAGENTS = ['claude', 'codex', 'gemini', 'hermes'];
|
|
112
|
-
for (const ba of KNOWN_BASEAGENTS) {
|
|
113
|
-
if (agents[ba] !== undefined)
|
|
114
|
-
baseagentBlock[ba] = agents[ba];
|
|
115
|
-
}
|
|
116
|
-
if (Object.keys(baseagentBlock).length === 0) {
|
|
117
|
-
baseagentBlock[defaultName] = agents[defaultName] || {};
|
|
118
|
-
}
|
|
119
|
-
const cfg = {
|
|
120
|
-
name: '[default]',
|
|
121
|
-
enabled: true,
|
|
122
|
-
agents: baseagentBlock,
|
|
123
|
-
channels: globalConfig.channels || {},
|
|
124
|
-
projects: { defaultPath: globalConfig.projects?.defaultPath || process.cwd() },
|
|
125
|
-
chatmode: globalConfig.chatmode,
|
|
126
|
-
};
|
|
127
|
-
return new EvolAgent(null, cfg, { isDefault: true });
|
|
128
|
-
}
|
|
129
97
|
detectAndFlagConflicts() {
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
continue;
|
|
139
|
-
const fp = extractFingerprint(type, inst);
|
|
140
|
-
if (!fp)
|
|
141
|
-
continue;
|
|
142
|
-
const instName = inst.name ?? type;
|
|
143
|
-
const entry = seen.get(fp) || [];
|
|
144
|
-
entry.push({ agent: agentName, instance: instName });
|
|
145
|
-
seen.set(fp, entry);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
for (const agent of this.agents.values()) {
|
|
150
|
-
if (agent.status === 'error')
|
|
151
|
-
continue;
|
|
152
|
-
record(agent.name, agent.config.channels);
|
|
153
|
-
}
|
|
154
|
-
if (this.defaultAgent) {
|
|
155
|
-
record(this.defaultAgent.name, this.defaultAgent.config.channels);
|
|
156
|
-
}
|
|
157
|
-
for (const [_fp, occurrences] of seen) {
|
|
158
|
-
if (occurrences.length <= 1)
|
|
159
|
-
continue;
|
|
160
|
-
const msg = `Channel conflict: fingerprint claimed by ${occurrences.map(o => `${o.agent}(${o.instance})`).join(', ')}`;
|
|
161
|
-
const involvedNames = [...new Set(occurrences.map(o => o.agent))];
|
|
162
|
-
for (const name of involvedNames) {
|
|
163
|
-
if (name === '[default]')
|
|
164
|
-
continue;
|
|
165
|
-
const a = this.agents.get(name);
|
|
98
|
+
const dups = detectDuplicates([...this.agents.values()]);
|
|
99
|
+
for (const d of dups) {
|
|
100
|
+
const owners = d.agents.map(o => `${o.aid}(${o.channelName})`).join(', ');
|
|
101
|
+
const msg = `Channel conflict: ${d.fingerprint} claimed by ${owners}`;
|
|
102
|
+
logger.error(`[EvolAgentRegistry] ${msg}`);
|
|
103
|
+
// 把所有涉及的 agent 标 error;首个保留为 active 也不安全——直接全部 error
|
|
104
|
+
for (const o of d.agents) {
|
|
105
|
+
const a = this.agents.get(o.aid);
|
|
166
106
|
if (a && a.status !== 'error') {
|
|
167
107
|
a.status = 'error';
|
|
168
108
|
a.error = msg;
|
|
169
109
|
}
|
|
170
110
|
}
|
|
171
|
-
logger.error(`[EvolAgentRegistry] ${msg}`);
|
|
172
111
|
}
|
|
173
112
|
}
|
|
174
113
|
buildChannelIndex() {
|
|
175
114
|
for (const agent of this.agents.values()) {
|
|
176
115
|
if (agent.status === 'error' || agent.status === 'disabled')
|
|
177
116
|
continue;
|
|
178
|
-
for (const
|
|
179
|
-
this.channelIndex.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (this.channelIndex.has(name))
|
|
185
|
-
continue;
|
|
186
|
-
this.channelIndex.set(name, '[default]');
|
|
117
|
+
for (const key of agent.channelInstanceNames()) {
|
|
118
|
+
const prev = this.channelIndex.get(key);
|
|
119
|
+
if (prev && prev !== agent.aid) {
|
|
120
|
+
logger.warn(`[EvolAgentRegistry] channel key "${key}" claimed by both ${prev} and ${agent.aid}`);
|
|
121
|
+
}
|
|
122
|
+
this.channelIndex.set(key, agent.aid);
|
|
187
123
|
}
|
|
188
124
|
}
|
|
189
125
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
126
|
+
// ── Lookup / Routing ─────────────────────────────────────────────────
|
|
127
|
+
resolveByChannel(channelKey) {
|
|
128
|
+
const aid = this.channelIndex.get(channelKey);
|
|
129
|
+
if (!aid)
|
|
193
130
|
return null;
|
|
194
|
-
|
|
195
|
-
return this.defaultAgent;
|
|
196
|
-
return this.agents.get(agentName) || null;
|
|
131
|
+
return this.agents.get(aid) ?? null;
|
|
197
132
|
}
|
|
198
133
|
/**
|
|
199
|
-
*
|
|
200
|
-
* - For named EvolAgent: reads agent.config (memory; reflects agent.json).
|
|
201
|
-
* - For DefaultAgent or unknown channel: invokes `globalFallback`, which
|
|
202
|
-
* should consult the global config (evolclaw.json) via `config.ts:isOwner`.
|
|
134
|
+
* `globalFallback` 参数保留作签名兼容(EvolAgentRegistryHandle),新结构下不再使用。
|
|
203
135
|
*/
|
|
204
|
-
isOwner(
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
return globalFallback(channelName, userId);
|
|
136
|
+
isOwner(channelKey, userId, _globalFallback) {
|
|
137
|
+
void _globalFallback;
|
|
138
|
+
const agent = this.resolveByChannel(channelKey);
|
|
139
|
+
return agent?.isOwner(channelKey, userId) ?? false;
|
|
209
140
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const agent = this.resolveByChannel(
|
|
213
|
-
|
|
214
|
-
return agent.isAdmin(channelName, userId);
|
|
215
|
-
return globalFallback(channelName, userId);
|
|
141
|
+
isAdmin(channelKey, userId, _globalFallback) {
|
|
142
|
+
void _globalFallback;
|
|
143
|
+
const agent = this.resolveByChannel(channelKey);
|
|
144
|
+
return agent?.isAdmin(channelKey, userId) ?? false;
|
|
216
145
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const agent = this.resolveByChannel(channelName);
|
|
220
|
-
if (!agent)
|
|
221
|
-
return undefined;
|
|
222
|
-
return agent.getOwner(channelName);
|
|
146
|
+
getOwner(channelKey) {
|
|
147
|
+
return this.resolveByChannel(channelKey)?.getOwner(channelKey);
|
|
223
148
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
* via the configured `globalWriter` for DefaultAgent. No-ops with a warning
|
|
227
|
-
* when the channel is unknown or no global writer is wired.
|
|
228
|
-
*/
|
|
229
|
-
setChannelOwner(channelName, userId) {
|
|
230
|
-
const agent = this.resolveByChannel(channelName);
|
|
149
|
+
setChannelOwner(channelKey, userId) {
|
|
150
|
+
const agent = this.resolveByChannel(channelKey);
|
|
231
151
|
if (!agent) {
|
|
232
|
-
logger.warn(`[EvolAgentRegistry] setChannelOwner: channel "${
|
|
152
|
+
logger.warn(`[EvolAgentRegistry] setChannelOwner: channel "${channelKey}" not found`);
|
|
233
153
|
return;
|
|
234
154
|
}
|
|
235
|
-
|
|
236
|
-
if (!this.globalWriter) {
|
|
237
|
-
logger.warn(`[EvolAgentRegistry] setChannelOwner: no globalWriter wired for default channel "${channelName}"`);
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
this.globalWriter.setOwner(channelName, userId);
|
|
241
|
-
}
|
|
242
|
-
else {
|
|
243
|
-
agent.setOwner(channelName, userId);
|
|
244
|
-
}
|
|
155
|
+
agent.setOwner(channelKey, userId);
|
|
245
156
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
* unknown — matches the prior behavior of `config.ts:getChannelShowActivities`.
|
|
249
|
-
*/
|
|
250
|
-
getShowActivities(channelName) {
|
|
251
|
-
const agent = this.resolveByChannel(channelName);
|
|
252
|
-
if (!agent)
|
|
253
|
-
return 'all';
|
|
254
|
-
return agent.getShowActivities(channelName);
|
|
157
|
+
getShowActivities(channelKey) {
|
|
158
|
+
return this.resolveByChannel(channelKey)?.getShowActivities(channelKey) ?? 'all';
|
|
255
159
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const agent = this.resolveByChannel(channelName);
|
|
160
|
+
setShowActivities(channelKey, mode) {
|
|
161
|
+
const agent = this.resolveByChannel(channelKey);
|
|
259
162
|
if (!agent) {
|
|
260
|
-
logger.warn(`[EvolAgentRegistry] setShowActivities: channel "${
|
|
163
|
+
logger.warn(`[EvolAgentRegistry] setShowActivities: channel "${channelKey}" not found`);
|
|
261
164
|
return;
|
|
262
165
|
}
|
|
263
|
-
|
|
264
|
-
if (!this.globalWriter?.setShowActivities) {
|
|
265
|
-
logger.warn(`[EvolAgentRegistry] setShowActivities: no globalWriter wired for default channel "${channelName}"`);
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
this.globalWriter.setShowActivities(channelName, mode);
|
|
269
|
-
}
|
|
270
|
-
else {
|
|
271
|
-
agent.setShowActivities(channelName, mode);
|
|
272
|
-
}
|
|
166
|
+
agent.setShowActivities(channelKey, mode);
|
|
273
167
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
return this.agents.get(name) || null;
|
|
168
|
+
// ── Agent enumeration ────────────────────────────────────────────────
|
|
169
|
+
get(aidOrName) {
|
|
170
|
+
return this.agents.get(aidOrName) ?? null;
|
|
278
171
|
}
|
|
279
172
|
list() {
|
|
280
|
-
|
|
281
|
-
for (const agent of this.agents.values()) {
|
|
282
|
-
result.push(this.toInfo(agent));
|
|
283
|
-
}
|
|
284
|
-
if (this.defaultAgent) {
|
|
285
|
-
result.push(this.toInfo(this.defaultAgent));
|
|
286
|
-
}
|
|
287
|
-
return result;
|
|
173
|
+
return [...this.agents.values()].map(a => this.toInfo(a));
|
|
288
174
|
}
|
|
175
|
+
/** 启动后还能跑(status === 'stopped')的 agents——给 AgentLoader 起 runner 用。 */
|
|
289
176
|
runnableAgents() {
|
|
290
177
|
return [...this.agents.values()].filter(a => a.status === 'stopped');
|
|
291
178
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
if (
|
|
302
|
-
|
|
179
|
+
getSkipped() {
|
|
180
|
+
return [...this.skipped];
|
|
181
|
+
}
|
|
182
|
+
// ── 热加载新 agent ──────────────────────────────────────────────────
|
|
183
|
+
/**
|
|
184
|
+
* 动态加载一个新 agent(磁盘上已有 config.json 但运行时还没加载)。
|
|
185
|
+
* 返回新创建的 EvolAgent,或 null(已存在 / 校验失败)。
|
|
186
|
+
*/
|
|
187
|
+
loadNewAgent(aid) {
|
|
188
|
+
if (this.agents.has(aid)) {
|
|
189
|
+
logger.info(`[EvolAgentRegistry] agent ${aid} already loaded, skipping`);
|
|
190
|
+
return this.agents.get(aid);
|
|
303
191
|
}
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
logger.warn(`[EvolAgentRegistry] Agent "${name}" projectPath changed: ${oldAgent.projectPath} → ${newAgent.projectPath}. ` +
|
|
309
|
-
`Existing sessions retain the old path; only new sessions will use the new path. ` +
|
|
310
|
-
`To migrate, manually UPDATE sessions SET project_path=? WHERE id=? (warning: SDK conversation history may be lost).`);
|
|
192
|
+
const raw = loadAgent(aid);
|
|
193
|
+
if (!raw) {
|
|
194
|
+
logger.warn(`[EvolAgentRegistry] loadNewAgent: ${aid}/config.json not found`);
|
|
195
|
+
return null;
|
|
311
196
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
197
|
+
const errs = validateAgentConfig(raw);
|
|
198
|
+
if (errs.length > 0) {
|
|
199
|
+
logger.warn(`[EvolAgentRegistry] loadNewAgent ${aid}: ${errs.join('; ')}`);
|
|
200
|
+
return null;
|
|
316
201
|
}
|
|
317
|
-
|
|
202
|
+
const defaults = loadDefaults();
|
|
203
|
+
const merged = mergeForAgent(raw, defaults);
|
|
204
|
+
const agent = new EvolAgent(raw, merged);
|
|
205
|
+
ensureAgentDirSkeleton(aid);
|
|
206
|
+
this.agents.set(aid, agent);
|
|
207
|
+
// 重建 channel index
|
|
208
|
+
for (const key of agent.channelInstanceNames()) {
|
|
209
|
+
this.channelIndex.set(key, aid);
|
|
210
|
+
}
|
|
211
|
+
logger.info(`[EvolAgentRegistry] ✓ Hot-loaded agent: ${aid}`);
|
|
212
|
+
return agent;
|
|
213
|
+
}
|
|
214
|
+
// ── Reload ───────────────────────────────────────────────────────────
|
|
215
|
+
async reload(aidOrName, hooks) {
|
|
216
|
+
const oldAgent = this.agents.get(aidOrName);
|
|
217
|
+
if (!oldAgent)
|
|
218
|
+
throw new Error(`Agent "${aidOrName}" not found`);
|
|
219
|
+
const raw = loadAgent(oldAgent.aid);
|
|
220
|
+
if (!raw)
|
|
221
|
+
throw new Error(`Agent ${oldAgent.aid}/config.json missing on reload`);
|
|
222
|
+
const errs = validateAgentConfig(raw);
|
|
223
|
+
if (errs.length > 0)
|
|
224
|
+
throw new Error(`Invalid config after edit: ${errs.join('; ')}`);
|
|
225
|
+
const defaults = loadDefaults();
|
|
226
|
+
const merged = mergeForAgent(raw, defaults);
|
|
227
|
+
const conflict = this.checkConflictForReload(raw, oldAgent.aid);
|
|
228
|
+
if (conflict)
|
|
229
|
+
throw new Error(`Channel conflict: ${conflict}`);
|
|
318
230
|
const oldChannels = new Set(oldAgent.channelInstanceNames());
|
|
319
|
-
|
|
231
|
+
// 计算新 channel keys(用 EvolAgent 的格式化)
|
|
232
|
+
const newChannels = new Set(raw.channels.map(c => oldAgent.effectiveChannelName(c.type, c.name)));
|
|
320
233
|
const toRemove = [...oldChannels].filter(c => !newChannels.has(c));
|
|
321
234
|
const toAdd = [...newChannels].filter(c => !oldChannels.has(c));
|
|
322
235
|
const kept = [...oldChannels].filter(c => newChannels.has(c));
|
|
323
|
-
//
|
|
324
|
-
// the channel reconnects with new credentials (e.g. appSecret rotated).
|
|
236
|
+
// 凭证变化的 kept channel 当 remove+add 处理(强制重建以使用新凭证)
|
|
325
237
|
const credentialsChanged = [];
|
|
326
238
|
const trulyKept = [];
|
|
327
239
|
for (const ch of kept) {
|
|
328
|
-
const
|
|
329
|
-
const
|
|
330
|
-
if (
|
|
240
|
+
const oldInst = oldAgent.findChannelInstance(ch);
|
|
241
|
+
const newInst = findInstanceByKey(raw, oldAgent, ch);
|
|
242
|
+
if (oldInst && newInst && JSON.stringify(oldInst) !== JSON.stringify(newInst)) {
|
|
331
243
|
credentialsChanged.push(ch);
|
|
332
244
|
}
|
|
333
245
|
else {
|
|
@@ -336,179 +248,87 @@ export class EvolAgentRegistry {
|
|
|
336
248
|
}
|
|
337
249
|
toRemove.push(...credentialsChanged);
|
|
338
250
|
toAdd.push(...credentialsChanged);
|
|
339
|
-
// Track what was removed/added so we can roll back on failure
|
|
340
251
|
const removedSuccessfully = [];
|
|
341
252
|
const addedSuccessfully = [];
|
|
342
253
|
try {
|
|
343
|
-
|
|
344
|
-
for (const ch of toRemove) {
|
|
254
|
+
for (const ch of toRemove)
|
|
345
255
|
await hooks.drainChannel(ch);
|
|
346
|
-
}
|
|
347
|
-
// 5. Disconnect removed channels
|
|
348
256
|
for (const ch of toRemove) {
|
|
349
257
|
await hooks.disconnectChannel(ch);
|
|
350
258
|
removedSuccessfully.push(ch);
|
|
351
259
|
}
|
|
352
|
-
//
|
|
260
|
+
// swap config 后再起新 channel —— startChannel hook 需要看到新 config
|
|
261
|
+
oldAgent.swapConfig(raw, merged);
|
|
353
262
|
for (const ch of toAdd) {
|
|
354
|
-
await hooks.startChannel(
|
|
263
|
+
await hooks.startChannel(oldAgent, ch);
|
|
355
264
|
addedSuccessfully.push(ch);
|
|
356
265
|
}
|
|
357
|
-
//
|
|
358
|
-
for (const ch of trulyKept) {
|
|
359
|
-
const adapter = oldAgent.channels.get(ch);
|
|
360
|
-
if (adapter)
|
|
361
|
-
newAgent.channels.set(ch, adapter);
|
|
362
|
-
}
|
|
363
|
-
// 8. Preserve runtime state
|
|
364
|
-
// I5: only set 'running' when oldAgent was running; preserve error/disabled
|
|
365
|
-
newAgent.activeSessions = oldAgent.activeSessions;
|
|
366
|
-
newAgent.lastActivity = oldAgent.lastActivity;
|
|
266
|
+
// truly kept 的 adapter 实例已经在 oldAgent.channels 里,无需迁移
|
|
367
267
|
if (oldAgent.status === 'error' || oldAgent.status === 'disabled') {
|
|
368
|
-
|
|
369
|
-
newAgent.error = oldAgent.error;
|
|
268
|
+
// 保持原态——swap 不改 status
|
|
370
269
|
}
|
|
371
270
|
else {
|
|
372
|
-
|
|
271
|
+
oldAgent.status = 'running';
|
|
272
|
+
}
|
|
273
|
+
// 重启触发器调度器(如果已初始化)
|
|
274
|
+
if (oldAgent.triggerScheduler) {
|
|
275
|
+
oldAgent.triggerScheduler.stop();
|
|
276
|
+
oldAgent.triggerScheduler.init().catch(err => {
|
|
277
|
+
logger.error(`[Reload] TriggerScheduler re-init failed for ${oldAgent.aid}: ${err}`);
|
|
278
|
+
});
|
|
373
279
|
}
|
|
374
|
-
// 9. Swap in registry
|
|
375
|
-
this.agents.set(name, newAgent);
|
|
376
|
-
// 10. Rebuild channel index
|
|
377
280
|
this.channelIndex.clear();
|
|
378
281
|
this.buildChannelIndex();
|
|
379
282
|
}
|
|
380
283
|
catch (err) {
|
|
381
|
-
|
|
382
|
-
logger.error(`[Reload] Failed: ${err}. Attempting rollback for "${name}".`);
|
|
284
|
+
logger.error(`[Reload] Failed: ${err}. Attempting rollback for "${aidOrName}".`);
|
|
383
285
|
for (const ch of addedSuccessfully) {
|
|
384
286
|
try {
|
|
385
287
|
await hooks.disconnectChannel(ch);
|
|
386
288
|
}
|
|
387
|
-
catch
|
|
289
|
+
catch { /* best effort */ }
|
|
388
290
|
}
|
|
389
|
-
|
|
390
|
-
try {
|
|
391
|
-
await hooks.startChannel(oldAgent, ch);
|
|
392
|
-
}
|
|
393
|
-
catch (_) { /* best effort */ }
|
|
394
|
-
}
|
|
395
|
-
// Don't swap registry — oldAgent stays in place
|
|
291
|
+
// 这里没法 rollback 到旧 raw(已经被 swapConfig 覆盖)——记录错误,让 oldAgent 进 error 态
|
|
396
292
|
oldAgent.status = 'error';
|
|
397
|
-
oldAgent.error = `Reload failed (rollback
|
|
293
|
+
oldAgent.error = `Reload failed (rollback partial): ${err instanceof Error ? err.message : String(err)}`;
|
|
398
294
|
throw err;
|
|
399
295
|
}
|
|
400
296
|
}
|
|
401
|
-
checkConflictForReload(
|
|
402
|
-
const
|
|
403
|
-
for (const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
for (const inst of instances) {
|
|
408
|
-
if (!inst || typeof inst !== 'object')
|
|
409
|
-
continue;
|
|
410
|
-
const fp = extractFingerprint(type, inst);
|
|
411
|
-
if (!fp)
|
|
412
|
-
continue;
|
|
413
|
-
const instName = inst.name ?? type;
|
|
414
|
-
newFingerprints.set(fp, instName);
|
|
415
|
-
}
|
|
297
|
+
checkConflictForReload(newRaw, excludeAid) {
|
|
298
|
+
const newFps = new Set();
|
|
299
|
+
for (const inst of newRaw.channels) {
|
|
300
|
+
const fp = extractFingerprint(inst.type, inst, newRaw.aid);
|
|
301
|
+
if (fp)
|
|
302
|
+
newFps.add(fp);
|
|
416
303
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
if (agentName === excludeName)
|
|
304
|
+
for (const [aid, agent] of this.agents) {
|
|
305
|
+
if (aid === excludeAid)
|
|
420
306
|
continue;
|
|
421
307
|
if (agent.status === 'error' || agent.status === 'disabled')
|
|
422
308
|
continue;
|
|
423
|
-
for (const
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
for (const inst of instances) {
|
|
428
|
-
if (!inst || typeof inst !== 'object')
|
|
429
|
-
continue;
|
|
430
|
-
const fp = extractFingerprint(type, inst);
|
|
431
|
-
if (!fp)
|
|
432
|
-
continue;
|
|
433
|
-
if (newFingerprints.has(fp)) {
|
|
434
|
-
return `${fp} conflicts with agent "${agentName}"`;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
// Check against DefaultAgent
|
|
440
|
-
if (this.defaultAgent) {
|
|
441
|
-
for (const [type, raw] of Object.entries(this.defaultAgent.config.channels || {})) {
|
|
442
|
-
if (type === 'defaultChannel')
|
|
443
|
-
continue;
|
|
444
|
-
const instances = Array.isArray(raw) ? raw : [raw];
|
|
445
|
-
for (const inst of instances) {
|
|
446
|
-
if (!inst || typeof inst !== 'object')
|
|
447
|
-
continue;
|
|
448
|
-
const fp = extractFingerprint(type, inst);
|
|
449
|
-
if (!fp)
|
|
450
|
-
continue;
|
|
451
|
-
if (newFingerprints.has(fp)) {
|
|
452
|
-
return `${fp} conflicts with DefaultAgent`;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
309
|
+
for (const inst of agent.config.channels) {
|
|
310
|
+
const fp = extractFingerprint(inst.type, inst, agent.aid);
|
|
311
|
+
if (fp && newFps.has(fp))
|
|
312
|
+
return `${fp} conflicts with agent "${aid}"`;
|
|
455
313
|
}
|
|
456
314
|
}
|
|
457
315
|
return null;
|
|
458
316
|
}
|
|
459
317
|
toInfo(agent) {
|
|
460
|
-
let baseagent = 'claude';
|
|
461
|
-
let model;
|
|
462
|
-
let effort;
|
|
463
|
-
try {
|
|
464
|
-
baseagent = agent.baseagent;
|
|
465
|
-
model = agent.model;
|
|
466
|
-
effort = agent.effort;
|
|
467
|
-
}
|
|
468
|
-
catch { /* invalid config */ }
|
|
469
318
|
return {
|
|
470
319
|
name: agent.name,
|
|
471
320
|
status: agent.status,
|
|
472
321
|
channels: agent.channelInstanceNames(),
|
|
473
|
-
projectPath: agent.
|
|
474
|
-
baseagent,
|
|
475
|
-
model,
|
|
476
|
-
effort,
|
|
322
|
+
projectPath: agent.projectPath,
|
|
323
|
+
baseagent: agent.baseagent,
|
|
324
|
+
model: agent.model,
|
|
325
|
+
effort: agent.effort,
|
|
477
326
|
lastActivity: agent.lastActivity,
|
|
478
327
|
activeSessions: agent.activeSessions,
|
|
479
328
|
error: agent.error,
|
|
480
|
-
isDefault: agent.isDefault,
|
|
481
329
|
};
|
|
482
330
|
}
|
|
483
331
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
* Returns `{ type, config }` or null if not found.
|
|
487
|
-
*
|
|
488
|
-
* Matches against the effective channel name (with agent prefix for EvolAgents),
|
|
489
|
-
* mirroring `EvolAgent.findChannelInstance`. Only used by `reload()` to detect
|
|
490
|
-
* kept-channel credential changes.
|
|
491
|
-
*/
|
|
492
|
-
function getChannelInstanceConfig(agent, channelName) {
|
|
493
|
-
for (const [type, raw] of Object.entries(agent.config?.channels || {})) {
|
|
494
|
-
if (type === 'defaultChannel')
|
|
495
|
-
continue;
|
|
496
|
-
const instances = Array.isArray(raw) ? raw : [raw];
|
|
497
|
-
for (const inst of instances) {
|
|
498
|
-
if (!inst || typeof inst !== 'object')
|
|
499
|
-
continue;
|
|
500
|
-
const effName = agent.effectiveChannelName(type, inst.name);
|
|
501
|
-
if (effName === channelName)
|
|
502
|
-
return { type, config: inst };
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
return null;
|
|
506
|
-
}
|
|
507
|
-
/**
|
|
508
|
-
* Compare two channel-instance configs by serialized JSON. Channel configs
|
|
509
|
-
* are plain JSON-shaped objects (no functions/Buffers), so this is a sound
|
|
510
|
-
* structural compare for "did anything in this channel block change".
|
|
511
|
-
*/
|
|
512
|
-
function channelConfigChanged(oldConfig, newConfig) {
|
|
513
|
-
return JSON.stringify(oldConfig) !== JSON.stringify(newConfig);
|
|
332
|
+
function findInstanceByKey(raw, agent, channelKey) {
|
|
333
|
+
return raw.channels.find(c => agent.effectiveChannelName(c.type, c.name) === channelKey) ?? null;
|
|
514
334
|
}
|