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
package/dist/core/evolagent.js
CHANGED
|
@@ -1,299 +1,246 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
1
|
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
3
|
import { logger } from '../utils/logger.js';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (raw.enabled !== undefined && typeof raw.enabled !== 'boolean') {
|
|
17
|
-
errors.push('enabled must be a boolean if present');
|
|
18
|
-
}
|
|
19
|
-
if (!raw.agents || typeof raw.agents !== 'object') {
|
|
20
|
-
errors.push('agents must be an object with exactly one baseagent block');
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
const keys = Object.keys(raw.agents).filter(k => VALID_BASEAGENTS.has(k));
|
|
24
|
-
const unknownKeys = Object.keys(raw.agents).filter(k => !VALID_BASEAGENTS.has(k));
|
|
25
|
-
if (unknownKeys.length > 0) {
|
|
26
|
-
errors.push(`agents contains unknown baseagent keys: ${unknownKeys.join(', ')}`);
|
|
27
|
-
}
|
|
28
|
-
if (keys.length === 0) {
|
|
29
|
-
errors.push('agents must contain exactly one of: claude | codex | gemini | hermes');
|
|
30
|
-
}
|
|
31
|
-
else if (keys.length > 1) {
|
|
32
|
-
errors.push(`agents must contain exactly one baseagent (single baseagent only), got: ${keys.join(', ')}`);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
if (!raw.channels || typeof raw.channels !== 'object') {
|
|
36
|
-
errors.push('channels is required');
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
const channelKeys = Object.keys(raw.channels).filter(k => k !== 'defaultChannel');
|
|
40
|
-
if (channelKeys.length === 0) {
|
|
41
|
-
errors.push('channels must contain at least one channel type');
|
|
42
|
-
}
|
|
43
|
-
for (const key of channelKeys) {
|
|
44
|
-
if (!VALID_CHANNEL_TYPES.has(key)) {
|
|
45
|
-
errors.push(`unknown channel type: ${key}`);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
// defaultChannel reference validation (same rules as evolclaw.json)
|
|
49
|
-
let totalInstances = 0;
|
|
50
|
-
for (const key of channelKeys) {
|
|
51
|
-
const block = raw.channels[key];
|
|
52
|
-
const insts = Array.isArray(block) ? block : (block ? [block] : []);
|
|
53
|
-
totalInstances += insts.length;
|
|
54
|
-
}
|
|
55
|
-
const dc = raw.channels.defaultChannel;
|
|
56
|
-
if (dc) {
|
|
57
|
-
const err = validateDefaultChannelRef(dc, raw.channels);
|
|
58
|
-
if (err)
|
|
59
|
-
errors.push(err);
|
|
60
|
-
}
|
|
61
|
-
else if (totalInstances > 1) {
|
|
62
|
-
errors.push('channels.defaultChannel is required when multiple channel instances are configured (use "type" or "type/instanceName")');
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
if (!raw.projects || typeof raw.projects !== 'object') {
|
|
66
|
-
errors.push('projects is required');
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
const p = raw.projects.defaultPath;
|
|
70
|
-
if (typeof p !== 'string' || p === '') {
|
|
71
|
-
errors.push('projects.defaultPath is required');
|
|
72
|
-
}
|
|
73
|
-
else if (!path.isAbsolute(p)) {
|
|
74
|
-
errors.push(`projects.defaultPath must be absolute, got: ${p}`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
if (raw.chatmode !== undefined) {
|
|
78
|
-
if (typeof raw.chatmode !== 'object' || raw.chatmode === null) {
|
|
79
|
-
errors.push('chatmode must be an object if present');
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
for (const key of ['private', 'group']) {
|
|
83
|
-
const val = raw.chatmode[key];
|
|
84
|
-
if (val !== undefined && !VALID_CHATMODES.has(val)) {
|
|
85
|
-
errors.push(`chatmode.${key} must be 'interactive' or 'proactive'`);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return { valid: errors.length === 0, errors };
|
|
91
|
-
}
|
|
4
|
+
import { saveAgent } from '../config-store.js';
|
|
5
|
+
import { formatChannelKey, tryParseChannelKey } from './channel-loader.js';
|
|
6
|
+
import { agentPersonalDir } from '../paths.js';
|
|
7
|
+
/**
|
|
8
|
+
* EvolAgent —— 一个 self-agent 的运行时表示。
|
|
9
|
+
*
|
|
10
|
+
* 输入:MergedAgentConfig(defaults + per-agent 合并后的 effective 形态)。
|
|
11
|
+
* 持有该 agent 的 channels Map、活跃状态、生命周期。
|
|
12
|
+
*
|
|
13
|
+
* 写入永远落到 agents/<aid>/config.json(通过 ConfigStore.saveAgent 走双 rename),
|
|
14
|
+
* 不再有 "DefaultAgent" 概念,不再有 globalWriter / configPath null 的路径。
|
|
15
|
+
*/
|
|
92
16
|
export class EvolAgent {
|
|
17
|
+
aid;
|
|
18
|
+
/** 兼容字段:name = aid。channel routing / log / IPC 都用 aid 作 agent 标识。 */
|
|
93
19
|
name;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
20
|
+
/** in-memory effective config(含 defaults 合并结果);写盘时只回写 per-agent 部分。 */
|
|
21
|
+
merged;
|
|
22
|
+
/** per-agent 原始配置(写盘真相源) */
|
|
23
|
+
rawAgent;
|
|
97
24
|
channels = new Map();
|
|
98
25
|
activeSessions = 0;
|
|
99
26
|
lastActivity;
|
|
100
27
|
status;
|
|
101
28
|
error;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
29
|
+
triggerScheduler;
|
|
30
|
+
triggerManager;
|
|
31
|
+
constructor(rawAgent, merged) {
|
|
32
|
+
if (rawAgent.aid !== merged.aid) {
|
|
33
|
+
throw new Error(`EvolAgent: rawAgent.aid (${rawAgent.aid}) != merged.aid (${merged.aid})`);
|
|
34
|
+
}
|
|
35
|
+
this.rawAgent = rawAgent;
|
|
36
|
+
this.merged = merged;
|
|
37
|
+
this.aid = rawAgent.aid;
|
|
38
|
+
this.name = rawAgent.aid;
|
|
39
|
+
this.status = rawAgent.enabled === false ? 'disabled' : 'stopped';
|
|
40
|
+
}
|
|
41
|
+
/** 当前 effective config(合并后的) */
|
|
42
|
+
get config() {
|
|
43
|
+
return this.merged;
|
|
108
44
|
}
|
|
109
45
|
get baseagent() {
|
|
110
|
-
|
|
111
|
-
return keys[0] || 'claude';
|
|
46
|
+
return this.merged.active_baseagent || 'claude';
|
|
112
47
|
}
|
|
113
48
|
get model() {
|
|
114
|
-
|
|
49
|
+
const ba = this.baseagent;
|
|
50
|
+
const block = this.merged.baseagents?.[ba];
|
|
51
|
+
return block?.model;
|
|
115
52
|
}
|
|
116
53
|
get effort() {
|
|
117
|
-
|
|
54
|
+
const ba = this.baseagent;
|
|
55
|
+
const block = this.merged.baseagents?.[ba];
|
|
56
|
+
if (ba === 'codex')
|
|
57
|
+
return block?.effort ?? block?.reasoning;
|
|
58
|
+
return block?.effort;
|
|
118
59
|
}
|
|
119
60
|
get projectPath() {
|
|
120
|
-
return this.
|
|
61
|
+
return this.merged.projects?.defaultPath || process.cwd();
|
|
121
62
|
}
|
|
63
|
+
// ── Channels ──────────────────────────────────────────────────────────
|
|
122
64
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* - DefaultAgent: rawName ?? type (preserves backward-compat with evolclaw.json)
|
|
126
|
-
* - EvolAgent:
|
|
127
|
-
* - rawName present → `${agent.name}-${type}-${rawName}`
|
|
128
|
-
* - rawName absent → `${agent.name}-${type}`
|
|
129
|
-
*
|
|
130
|
-
* The agent-name prefix avoids collisions with DefaultAgent channels, e.g.
|
|
131
|
-
* test-bot's aun → "test-bot-aun" instead of "aun".
|
|
65
|
+
* effective channel key:`<aid>#<type>#<name>`。AUN 实例一个 agent 只有一条;
|
|
66
|
+
* 其它类型靠 name 区分。
|
|
132
67
|
*/
|
|
133
68
|
effectiveChannelName(type, rawName) {
|
|
134
|
-
|
|
135
|
-
return rawName ?? type;
|
|
136
|
-
return rawName ? `${this.name}-${type}-${rawName}` : `${this.name}-${type}`;
|
|
69
|
+
return formatChannelKey({ aid: this.aid, type, name: rawName });
|
|
137
70
|
}
|
|
138
71
|
channelInstanceNames() {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return
|
|
72
|
+
// AUN channel 隐式存在(从 agent.aid 派生),不需要在 channels[] 里声明
|
|
73
|
+
const aunKey = this.effectiveChannelName('aun', 'main');
|
|
74
|
+
const others = this.merged.channels
|
|
75
|
+
.filter(c => c.type !== 'aun')
|
|
76
|
+
.map(c => this.effectiveChannelName(c.type, c.name));
|
|
77
|
+
return [aunKey, ...others];
|
|
78
|
+
}
|
|
79
|
+
/** 列出所有 channel 实例(含 effective key) */
|
|
80
|
+
listChannels() {
|
|
81
|
+
return this.merged.channels.map(inst => ({
|
|
82
|
+
key: this.effectiveChannelName(inst.type, inst.name),
|
|
83
|
+
instance: inst,
|
|
84
|
+
}));
|
|
149
85
|
}
|
|
150
86
|
/**
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
* Returns the raw mutable instance object, or `null` if not found.
|
|
87
|
+
* 按 effective channel key 找到 instance(只读视图)。
|
|
88
|
+
* 找不到返回 null。
|
|
154
89
|
*/
|
|
155
|
-
findChannelInstance(
|
|
156
|
-
|
|
157
|
-
for (const [type, raw] of Object.entries(channels)) {
|
|
158
|
-
if (type === 'defaultChannel')
|
|
159
|
-
continue;
|
|
160
|
-
const instances = Array.isArray(raw) ? raw : [raw];
|
|
161
|
-
for (const inst of instances) {
|
|
162
|
-
if (!inst || typeof inst !== 'object')
|
|
163
|
-
continue;
|
|
164
|
-
const effName = this.effectiveChannelName(type, inst.name);
|
|
165
|
-
if (effName === channelName)
|
|
166
|
-
return inst;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
/** Get owner of a specific channel instance owned by this agent. */
|
|
172
|
-
getOwner(channelName) {
|
|
173
|
-
const inst = this.findChannelInstance(channelName);
|
|
174
|
-
return inst?.owner;
|
|
175
|
-
}
|
|
176
|
-
/** True when `userId` is the owner of `channelName`. */
|
|
177
|
-
isOwner(channelName, userId) {
|
|
178
|
-
return this.getOwner(channelName) === userId;
|
|
90
|
+
findChannelInstance(channelKey) {
|
|
91
|
+
return this.merged.channels.find(c => this.effectiveChannelName(c.type, c.name) === channelKey) ?? null;
|
|
179
92
|
}
|
|
93
|
+
// ── Owner / Admin(per-channel-instance;AUN 走顶层 owners/admins)──────
|
|
180
94
|
/**
|
|
181
|
-
*
|
|
182
|
-
*
|
|
95
|
+
* AUN channel 是隐式的,不在 channels[] 里——其 owner/admin 存于 EvolAgent 顶层
|
|
96
|
+
* `owners`/`admins`(config 加载时由 aunBlock.owner/admins 收集而来)。
|
|
183
97
|
*/
|
|
184
|
-
|
|
185
|
-
|
|
98
|
+
isAunChannelKey(channelKey) {
|
|
99
|
+
const parsed = tryParseChannelKey(channelKey);
|
|
100
|
+
return parsed?.type === 'aun' && parsed.aid === this.aid;
|
|
101
|
+
}
|
|
102
|
+
getOwner(channelKey) {
|
|
103
|
+
if (this.isAunChannelKey(channelKey)) {
|
|
104
|
+
return this.merged.owners?.[0];
|
|
105
|
+
}
|
|
106
|
+
const inst = this.findChannelInstance(channelKey);
|
|
107
|
+
return inst?.owners?.[0];
|
|
108
|
+
}
|
|
109
|
+
isOwner(channelKey, userId) {
|
|
110
|
+
if (this.isAunChannelKey(channelKey)) {
|
|
111
|
+
return this.merged.owners?.includes(userId) ?? false;
|
|
112
|
+
}
|
|
113
|
+
const inst = this.findChannelInstance(channelKey);
|
|
114
|
+
return inst?.owners?.includes(userId) ?? false;
|
|
115
|
+
}
|
|
116
|
+
isAdmin(channelKey, userId) {
|
|
117
|
+
if (this.isOwner(channelKey, userId))
|
|
186
118
|
return true;
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
119
|
+
if (this.isAunChannelKey(channelKey)) {
|
|
120
|
+
return this.merged.admins?.includes(userId) ?? false;
|
|
121
|
+
}
|
|
122
|
+
const inst = this.findChannelInstance(channelKey);
|
|
123
|
+
return inst?.admins?.includes(userId) ?? false;
|
|
190
124
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
125
|
+
setOwner(channelKey, userId) {
|
|
126
|
+
// AUN:写到 rawAgent 顶层 owners(merged 也指向同一份引用)
|
|
127
|
+
if (this.isAunChannelKey(channelKey)) {
|
|
128
|
+
if (!this.rawAgent.owners)
|
|
129
|
+
this.rawAgent.owners = [];
|
|
130
|
+
if (!this.rawAgent.owners.includes(userId))
|
|
131
|
+
this.rawAgent.owners.push(userId);
|
|
132
|
+
// merged.owners 是从 rawAgent.owners 派生的拷贝;同步内存视图避免重新 merge
|
|
133
|
+
if (!this.merged.owners)
|
|
134
|
+
this.merged.owners = [];
|
|
135
|
+
if (!this.merged.owners.includes(userId))
|
|
136
|
+
this.merged.owners.push(userId);
|
|
137
|
+
this.persist();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const inst = this.findRawChannelInstance(channelKey);
|
|
198
141
|
if (!inst) {
|
|
199
|
-
logger.warn(`[EvolAgent] setOwner: channel "${
|
|
142
|
+
logger.warn(`[EvolAgent ${this.aid}] setOwner: channel "${channelKey}" not found`);
|
|
200
143
|
return;
|
|
201
144
|
}
|
|
202
|
-
|
|
145
|
+
// 顶层 owners 是单值列表(首通信者即 owner);channel 实例 owners 也允许多值
|
|
146
|
+
if (!inst.owners)
|
|
147
|
+
inst.owners = [];
|
|
148
|
+
if (!inst.owners.includes(userId))
|
|
149
|
+
inst.owners.push(userId);
|
|
203
150
|
this.persist();
|
|
204
151
|
}
|
|
205
|
-
|
|
206
|
-
getShowActivities(
|
|
207
|
-
|
|
208
|
-
return inst?.showActivities ?? 'all';
|
|
152
|
+
// ── ShowActivities ────────────────────────────────────────────────────
|
|
153
|
+
getShowActivities(_channelKey) {
|
|
154
|
+
return this.merged.show_activities ?? 'all';
|
|
209
155
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
*/
|
|
214
|
-
setShowActivities(channelName, mode) {
|
|
215
|
-
const inst = this.findChannelInstance(channelName);
|
|
216
|
-
if (!inst) {
|
|
217
|
-
logger.warn(`[EvolAgent] setShowActivities: channel "${channelName}" not found in agent "${this.name}"`);
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
inst.showActivities = mode;
|
|
156
|
+
setShowActivities(_channelKey, mode) {
|
|
157
|
+
this.rawAgent.show_activities = mode;
|
|
158
|
+
this.merged.show_activities = mode;
|
|
221
159
|
this.persist();
|
|
222
160
|
}
|
|
223
|
-
|
|
224
|
-
* Set this agent's baseagent.model and persist to agent.json.
|
|
225
|
-
* Refuses for DefaultAgent. Writes to config.agents[baseagent].model.
|
|
226
|
-
*/
|
|
161
|
+
// ── Baseagent 字段写入 ────────────────────────────────────────────────
|
|
227
162
|
setBaseagentModel(value) {
|
|
228
163
|
const ba = this.baseagent;
|
|
229
|
-
if (!this.
|
|
230
|
-
this.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
else
|
|
235
|
-
|
|
236
|
-
}
|
|
164
|
+
if (!this.rawAgent.baseagents)
|
|
165
|
+
this.rawAgent.baseagents = {};
|
|
166
|
+
const block = (this.rawAgent.baseagents[ba] ??= {});
|
|
167
|
+
if (value === undefined)
|
|
168
|
+
delete block.model;
|
|
169
|
+
else
|
|
170
|
+
block.model = value;
|
|
237
171
|
this.persist();
|
|
238
172
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
173
|
+
setBaseagentEffort(value) {
|
|
174
|
+
const ba = this.baseagent;
|
|
175
|
+
if (!this.rawAgent.baseagents)
|
|
176
|
+
this.rawAgent.baseagents = {};
|
|
177
|
+
const block = (this.rawAgent.baseagents[ba] ??= {});
|
|
178
|
+
const fieldName = ba === 'codex' ? 'reasoning' : 'effort';
|
|
179
|
+
if (value === undefined)
|
|
180
|
+
delete block[fieldName];
|
|
181
|
+
else
|
|
182
|
+
block[fieldName] = value;
|
|
183
|
+
this.persist();
|
|
184
|
+
}
|
|
185
|
+
// ── Projects ──────────────────────────────────────────────────────────
|
|
243
186
|
getProjects() {
|
|
244
|
-
const list = this.
|
|
187
|
+
const list = this.merged.projects?.list;
|
|
245
188
|
if (list && Object.keys(list).length > 0)
|
|
246
189
|
return { ...list };
|
|
247
|
-
const dp = this.
|
|
190
|
+
const dp = this.merged.projects?.defaultPath;
|
|
248
191
|
if (dp)
|
|
249
192
|
return { [path.basename(dp)]: dp };
|
|
250
193
|
return {};
|
|
251
194
|
}
|
|
252
|
-
/**
|
|
253
|
-
* Add (or update) a named project in this agent's projects.list and persist.
|
|
254
|
-
* Throws for DefaultAgent (caller should write to evolclaw.json instead).
|
|
255
|
-
*/
|
|
256
195
|
addProject(name, projectPath) {
|
|
257
|
-
if (!this.
|
|
258
|
-
this.
|
|
259
|
-
if (!this.
|
|
260
|
-
this.
|
|
261
|
-
this.
|
|
196
|
+
if (!this.rawAgent.projects)
|
|
197
|
+
this.rawAgent.projects = { defaultPath: projectPath, list: {} };
|
|
198
|
+
if (!this.rawAgent.projects.list)
|
|
199
|
+
this.rawAgent.projects.list = {};
|
|
200
|
+
this.rawAgent.projects.list[name] = projectPath;
|
|
262
201
|
this.persist();
|
|
263
202
|
}
|
|
203
|
+
// ── Personal layer ────────────────────────────────────────────────────
|
|
204
|
+
_personaCache = undefined;
|
|
264
205
|
/**
|
|
265
|
-
*
|
|
266
|
-
*
|
|
206
|
+
* 读取 personal/persona.md 内容(缓存,首次调用时从磁盘读)。
|
|
207
|
+
* 文件不存在返回 null。
|
|
267
208
|
*/
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
delete this.config.agents[ba][fieldName];
|
|
209
|
+
getPersona() {
|
|
210
|
+
if (this._personaCache !== undefined)
|
|
211
|
+
return this._personaCache;
|
|
212
|
+
const personaPath = path.join(agentPersonalDir(this.aid), 'persona.md');
|
|
213
|
+
try {
|
|
214
|
+
this._personaCache = fs.readFileSync(personaPath, 'utf-8').trim() || null;
|
|
275
215
|
}
|
|
276
|
-
|
|
277
|
-
this.
|
|
216
|
+
catch {
|
|
217
|
+
this._personaCache = null;
|
|
278
218
|
}
|
|
279
|
-
this.
|
|
219
|
+
return this._personaCache;
|
|
280
220
|
}
|
|
281
221
|
/**
|
|
282
|
-
*
|
|
283
|
-
* Refuses for DefaultAgent: it is built from evolclaw.json and has no
|
|
284
|
-
* dedicated file — callers must route writes through the global config.
|
|
222
|
+
* 读取 personal/memory/working.md 内容(不缓存,每次会话开始时读)。
|
|
285
223
|
*/
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
224
|
+
getWorkingMemory() {
|
|
225
|
+
const workingPath = path.join(agentPersonalDir(this.aid), 'memory', 'working.md');
|
|
226
|
+
try {
|
|
227
|
+
const content = fs.readFileSync(workingPath, 'utf-8').trim();
|
|
228
|
+
return content || null;
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
return null;
|
|
289
232
|
}
|
|
290
|
-
fs.writeFileSync(this.configPath, JSON.stringify(this.config, null, 2) + '\n', 'utf-8');
|
|
291
233
|
}
|
|
292
|
-
|
|
234
|
+
/** 清除 persona 缓存(reload 后重新读取) */
|
|
235
|
+
invalidatePersonaCache() {
|
|
236
|
+
this._personaCache = undefined;
|
|
237
|
+
}
|
|
238
|
+
// ── Context(喂给 message-processor / command-handler) ──────────────
|
|
239
|
+
getContext(_channelKey, chatType, globalChatmode) {
|
|
293
240
|
const chatMode = this.resolveChatMode(chatType, globalChatmode);
|
|
294
241
|
return {
|
|
295
242
|
name: this.name,
|
|
296
|
-
isOwned:
|
|
243
|
+
isOwned: true,
|
|
297
244
|
baseagent: this.baseagent,
|
|
298
245
|
model: this.model,
|
|
299
246
|
effort: this.effort,
|
|
@@ -302,14 +249,31 @@ export class EvolAgent {
|
|
|
302
249
|
};
|
|
303
250
|
}
|
|
304
251
|
resolveChatMode(chatType, globalChatmode) {
|
|
305
|
-
const agentCm = this.config.chatmode;
|
|
306
252
|
const key = chatType === 'group' ? 'group' : 'private';
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
253
|
+
return (this.merged.chatmode?.[key]
|
|
254
|
+
?? globalChatmode?.[key]
|
|
255
|
+
?? (key === 'group' ? 'proactive' : 'interactive'));
|
|
256
|
+
}
|
|
257
|
+
// ── Reload 支持:替换 in-memory config 并复用 channels Map ───────────
|
|
258
|
+
/** 用新的 raw + merged 替换 in-memory 状态。channels 由调用方决定如何 reconcile。 */
|
|
259
|
+
swapConfig(rawAgent, merged) {
|
|
260
|
+
if (rawAgent.aid !== this.aid) {
|
|
261
|
+
throw new Error(`EvolAgent.swapConfig: aid mismatch (${rawAgent.aid} vs ${this.aid})`);
|
|
312
262
|
}
|
|
313
|
-
|
|
263
|
+
this.rawAgent = rawAgent;
|
|
264
|
+
this.merged = merged;
|
|
265
|
+
}
|
|
266
|
+
// ── 内部辅助 ─────────────────────────────────────────────────────────
|
|
267
|
+
/**
|
|
268
|
+
* 找 rawAgent.channels 里的可变实例,用于写入。
|
|
269
|
+
*
|
|
270
|
+
* merged.channels 是 deep clone 时 raw 跟 merged 的 channels 引用同一份(mergeForAgent
|
|
271
|
+
* 直接 `agent.channels` 透传),所以 raw 里的实例就等于 merged 里的实例。
|
|
272
|
+
*/
|
|
273
|
+
findRawChannelInstance(channelKey) {
|
|
274
|
+
return this.rawAgent.channels.find(c => this.effectiveChannelName(c.type, c.name) === channelKey) ?? null;
|
|
275
|
+
}
|
|
276
|
+
persist() {
|
|
277
|
+
saveAgent(this.rawAgent);
|
|
314
278
|
}
|
|
315
279
|
}
|
|
@@ -2,7 +2,6 @@ import { logger } from '../utils/logger.js';
|
|
|
2
2
|
export class InteractionRouter {
|
|
3
3
|
handlers = new Map();
|
|
4
4
|
register(id, sessionId, callback, opts) {
|
|
5
|
-
// Clear any existing handler for this ID
|
|
6
5
|
const existing = this.handlers.get(id);
|
|
7
6
|
if (existing?.timer)
|
|
8
7
|
clearTimeout(existing.timer);
|
|
@@ -14,7 +13,13 @@ export class InteractionRouter {
|
|
|
14
13
|
opts.onTimeout?.();
|
|
15
14
|
}, opts.timeoutMs);
|
|
16
15
|
}
|
|
17
|
-
this.handlers.set(id, {
|
|
16
|
+
this.handlers.set(id, {
|
|
17
|
+
callback,
|
|
18
|
+
timer,
|
|
19
|
+
sessionId,
|
|
20
|
+
initiatorId: opts?.initiatorId,
|
|
21
|
+
fallbackCommand: opts?.fallbackCommand,
|
|
22
|
+
});
|
|
18
23
|
}
|
|
19
24
|
handle(response) {
|
|
20
25
|
const handler = this.handlers.get(response.id);
|
|
@@ -25,7 +30,6 @@ export class InteractionRouter {
|
|
|
25
30
|
this.handlers.delete(response.id);
|
|
26
31
|
try {
|
|
27
32
|
const result = handler.callback(response.action, response.values, response.operatorId);
|
|
28
|
-
// Catch async callback errors to prevent unhandled rejections
|
|
29
33
|
if (result && typeof result.catch === 'function') {
|
|
30
34
|
result.catch((err) => {
|
|
31
35
|
logger.error(`[InteractionRouter] Async callback error for ${response.id}:`, err);
|
|
@@ -62,7 +66,50 @@ export class InteractionRouter {
|
|
|
62
66
|
}
|
|
63
67
|
return ids;
|
|
64
68
|
}
|
|
65
|
-
|
|
66
|
-
return this.handlers.get(id)?.
|
|
69
|
+
getInitiator(id) {
|
|
70
|
+
return this.handlers.get(id)?.initiatorId;
|
|
67
71
|
}
|
|
72
|
+
findPendingByCommand(sessionId, command) {
|
|
73
|
+
for (const [id, handler] of this.handlers.entries()) {
|
|
74
|
+
if (handler.sessionId === sessionId && handler.fallbackCommand === command) {
|
|
75
|
+
return id;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** 把 CommandCard 渲染为文本提示,channel 不支持卡片时使用 */
|
|
82
|
+
export function renderCommandCardAsText(card) {
|
|
83
|
+
const lines = [card.title];
|
|
84
|
+
if (card.body)
|
|
85
|
+
lines.push(card.body);
|
|
86
|
+
lines.push('', '可用命令:');
|
|
87
|
+
for (const btn of card.buttons) {
|
|
88
|
+
const marker = btn.disabled ? '✓' : ' ';
|
|
89
|
+
lines.push(` ${marker} ${btn.command} ← ${btn.label}`);
|
|
90
|
+
}
|
|
91
|
+
return lines.join('\n');
|
|
92
|
+
}
|
|
93
|
+
/** 把 ActionInteraction 渲染为文本提示,channel 不支持卡片或卡片发送失败时使用 */
|
|
94
|
+
export function renderActionAsText(req) {
|
|
95
|
+
if (req.kind.kind !== 'action') {
|
|
96
|
+
throw new Error('[renderActionAsText] expected ActionInteraction, got ' + req.kind.kind);
|
|
97
|
+
}
|
|
98
|
+
const action = req.kind;
|
|
99
|
+
const fb = req.fallback;
|
|
100
|
+
const lines = [action.title];
|
|
101
|
+
if (action.body)
|
|
102
|
+
lines.push(action.body);
|
|
103
|
+
if (!fb) {
|
|
104
|
+
return lines.join('\n');
|
|
105
|
+
}
|
|
106
|
+
lines.push('', '回复:');
|
|
107
|
+
for (const btn of action.buttons) {
|
|
108
|
+
const arg = fb.buttonArgMap?.[btn.key] ?? btn.key;
|
|
109
|
+
lines.push(` /${fb.command} ${arg} ← ${btn.label}`);
|
|
110
|
+
}
|
|
111
|
+
if (fb.acceptFreeText && fb.freeTextHint) {
|
|
112
|
+
lines.push(` ${fb.freeTextHint}`);
|
|
113
|
+
}
|
|
114
|
+
return lines.join('\n');
|
|
68
115
|
}
|