evolclaw 2.8.3 → 3.0.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/dist/agents/claude-runner.js +102 -38
- package/dist/agents/codex-runner.js +11 -14
- package/dist/agents/gemini-runner.js +10 -12
- package/dist/agents/resolve.js +134 -0
- package/dist/agents/templates.js +3 -3
- package/dist/aun/aid/agentmd.js +186 -0
- package/dist/aun/aid/client.js +134 -0
- package/dist/aun/aid/identity.js +131 -0
- package/dist/aun/aid/index.js +3 -0
- package/dist/aun/aid/types.js +1 -0
- package/dist/aun/aid/validation.js +21 -0
- package/dist/aun/msg/group.js +291 -0
- package/dist/aun/msg/index.js +4 -0
- package/dist/aun/msg/p2p.js +144 -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 +1051 -288
- package/dist/channels/dingtalk.js +58 -5
- package/dist/channels/feishu.js +266 -30
- package/dist/channels/qqbot.js +67 -12
- package/dist/channels/wechat.js +61 -4
- package/dist/channels/wecom.js +58 -5
- package/dist/cli/agent.js +800 -0
- package/dist/cli/index.js +4253 -0
- package/dist/{utils → cli}/init-channel.js +211 -621
- package/dist/cli/init.js +178 -0
- package/dist/config-store.js +613 -0
- package/dist/core/{agent-loader.js → baseagent-loader.js} +6 -12
- package/dist/core/channel-loader.js +162 -11
- package/dist/core/command-handler.js +858 -847
- package/dist/core/evolagent-registry.js +191 -371
- package/dist/core/evolagent.js +203 -234
- package/dist/core/interaction-router.js +52 -5
- package/dist/core/message/im-renderer.js +480 -0
- package/dist/core/message/items-formatter.js +61 -0
- package/dist/core/message/message-bridge.js +104 -56
- package/dist/core/message/message-log.js +91 -0
- package/dist/core/message/message-processor.js +309 -142
- package/dist/core/message/message-queue.js +3 -3
- package/dist/core/permission.js +21 -8
- 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 +704 -775
- 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/{templates → data}/prompts.md +34 -1
- package/dist/index.js +431 -275
- package/dist/ipc.js +49 -0
- package/dist/paths.js +82 -9
- package/dist/types.js +8 -2
- package/dist/utils/atomic-write.js +79 -0
- package/dist/utils/channel-helpers.js +46 -0
- package/dist/utils/cross-platform.js +0 -18
- package/dist/utils/instance-registry.js +433 -0
- package/dist/utils/log-writer.js +216 -0
- package/dist/utils/logger.js +24 -77
- package/dist/utils/media-cache.js +23 -0
- package/dist/utils/{upgrade.js → npm-ops.js} +52 -21
- package/dist/utils/process-introspect.js +144 -0
- package/dist/utils/stats.js +192 -0
- package/dist/watch-msg.js +529 -0
- package/evolclaw-install-aun.md +114 -46
- package/kits/aun/meta.md +25 -0
- package/kits/aun/role.md +25 -0
- package/kits/channels/aun.md +25 -0
- package/kits/evolclaw/commands.md +31 -0
- package/kits/evolclaw/identity-tools.md +26 -0
- package/kits/evolclaw/self-summary.md +29 -0
- package/kits/evolclaw/tools.md +25 -0
- package/kits/templates/group.md +20 -0
- package/kits/templates/private.md +9 -0
- package/kits/templates/system-fragments/personal-context.md +3 -0
- package/kits/templates/system-fragments/self-intro.md +5 -0
- package/kits/templates/system-fragments/speaker-intro.md +5 -0
- package/kits/templates/system-fragments/venue-intro.md +5 -0
- package/package.json +7 -5
- package/data/evolclaw.sample.json +0 -60
- 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/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/core/evolagent.js
CHANGED
|
@@ -1,299 +1,251 @@
|
|
|
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
|
-
const inst = this.findChannelInstance(
|
|
208
|
-
return inst?.showActivities ?? 'all';
|
|
152
|
+
// ── ShowActivities ────────────────────────────────────────────────────
|
|
153
|
+
getShowActivities(channelKey) {
|
|
154
|
+
const inst = this.findChannelInstance(channelKey);
|
|
155
|
+
return inst?.showActivities ?? this.merged.show_activities ?? 'all';
|
|
209
156
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
* Throws when called on DefaultAgent — callers must use the global setter.
|
|
213
|
-
*/
|
|
214
|
-
setShowActivities(channelName, mode) {
|
|
215
|
-
const inst = this.findChannelInstance(channelName);
|
|
157
|
+
setShowActivities(channelKey, mode) {
|
|
158
|
+
const inst = this.findRawChannelInstance(channelKey);
|
|
216
159
|
if (!inst) {
|
|
217
|
-
logger.warn(`[EvolAgent] setShowActivities: channel "${
|
|
160
|
+
logger.warn(`[EvolAgent ${this.aid}] setShowActivities: channel "${channelKey}" not found`);
|
|
218
161
|
return;
|
|
219
162
|
}
|
|
220
163
|
inst.showActivities = mode;
|
|
221
164
|
this.persist();
|
|
222
165
|
}
|
|
223
|
-
|
|
224
|
-
* Set this agent's baseagent.model and persist to agent.json.
|
|
225
|
-
* Refuses for DefaultAgent. Writes to config.agents[baseagent].model.
|
|
226
|
-
*/
|
|
166
|
+
// ── Baseagent 字段写入 ────────────────────────────────────────────────
|
|
227
167
|
setBaseagentModel(value) {
|
|
228
168
|
const ba = this.baseagent;
|
|
229
|
-
if (!this.
|
|
230
|
-
this.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
else
|
|
235
|
-
|
|
236
|
-
}
|
|
169
|
+
if (!this.rawAgent.baseagents)
|
|
170
|
+
this.rawAgent.baseagents = {};
|
|
171
|
+
const block = (this.rawAgent.baseagents[ba] ??= {});
|
|
172
|
+
if (value === undefined)
|
|
173
|
+
delete block.model;
|
|
174
|
+
else
|
|
175
|
+
block.model = value;
|
|
237
176
|
this.persist();
|
|
238
177
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
178
|
+
setBaseagentEffort(value) {
|
|
179
|
+
const ba = this.baseagent;
|
|
180
|
+
if (!this.rawAgent.baseagents)
|
|
181
|
+
this.rawAgent.baseagents = {};
|
|
182
|
+
const block = (this.rawAgent.baseagents[ba] ??= {});
|
|
183
|
+
const fieldName = ba === 'codex' ? 'reasoning' : 'effort';
|
|
184
|
+
if (value === undefined)
|
|
185
|
+
delete block[fieldName];
|
|
186
|
+
else
|
|
187
|
+
block[fieldName] = value;
|
|
188
|
+
this.persist();
|
|
189
|
+
}
|
|
190
|
+
// ── Projects ──────────────────────────────────────────────────────────
|
|
243
191
|
getProjects() {
|
|
244
|
-
const list = this.
|
|
192
|
+
const list = this.merged.projects?.list;
|
|
245
193
|
if (list && Object.keys(list).length > 0)
|
|
246
194
|
return { ...list };
|
|
247
|
-
const dp = this.
|
|
195
|
+
const dp = this.merged.projects?.defaultPath;
|
|
248
196
|
if (dp)
|
|
249
197
|
return { [path.basename(dp)]: dp };
|
|
250
198
|
return {};
|
|
251
199
|
}
|
|
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
200
|
addProject(name, projectPath) {
|
|
257
|
-
if (!this.
|
|
258
|
-
this.
|
|
259
|
-
if (!this.
|
|
260
|
-
this.
|
|
261
|
-
this.
|
|
201
|
+
if (!this.rawAgent.projects)
|
|
202
|
+
this.rawAgent.projects = { defaultPath: projectPath, list: {} };
|
|
203
|
+
if (!this.rawAgent.projects.list)
|
|
204
|
+
this.rawAgent.projects.list = {};
|
|
205
|
+
this.rawAgent.projects.list[name] = projectPath;
|
|
262
206
|
this.persist();
|
|
263
207
|
}
|
|
208
|
+
// ── Personal layer ────────────────────────────────────────────────────
|
|
209
|
+
_personaCache = undefined;
|
|
264
210
|
/**
|
|
265
|
-
*
|
|
266
|
-
*
|
|
211
|
+
* 读取 personal/persona.md 内容(缓存,首次调用时从磁盘读)。
|
|
212
|
+
* 文件不存在返回 null。
|
|
267
213
|
*/
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
delete this.config.agents[ba][fieldName];
|
|
214
|
+
getPersona() {
|
|
215
|
+
if (this._personaCache !== undefined)
|
|
216
|
+
return this._personaCache;
|
|
217
|
+
const personaPath = path.join(agentPersonalDir(this.aid), 'persona.md');
|
|
218
|
+
try {
|
|
219
|
+
this._personaCache = fs.readFileSync(personaPath, 'utf-8').trim() || null;
|
|
275
220
|
}
|
|
276
|
-
|
|
277
|
-
this.
|
|
221
|
+
catch {
|
|
222
|
+
this._personaCache = null;
|
|
278
223
|
}
|
|
279
|
-
this.
|
|
224
|
+
return this._personaCache;
|
|
280
225
|
}
|
|
281
226
|
/**
|
|
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.
|
|
227
|
+
* 读取 personal/memory/working.md 内容(不缓存,每次会话开始时读)。
|
|
285
228
|
*/
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
229
|
+
getWorkingMemory() {
|
|
230
|
+
const workingPath = path.join(agentPersonalDir(this.aid), 'memory', 'working.md');
|
|
231
|
+
try {
|
|
232
|
+
const content = fs.readFileSync(workingPath, 'utf-8').trim();
|
|
233
|
+
return content || null;
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
return null;
|
|
289
237
|
}
|
|
290
|
-
fs.writeFileSync(this.configPath, JSON.stringify(this.config, null, 2) + '\n', 'utf-8');
|
|
291
238
|
}
|
|
292
|
-
|
|
239
|
+
/** 清除 persona 缓存(reload 后重新读取) */
|
|
240
|
+
invalidatePersonaCache() {
|
|
241
|
+
this._personaCache = undefined;
|
|
242
|
+
}
|
|
243
|
+
// ── Context(喂给 message-processor / command-handler) ──────────────
|
|
244
|
+
getContext(_channelKey, chatType, globalChatmode) {
|
|
293
245
|
const chatMode = this.resolveChatMode(chatType, globalChatmode);
|
|
294
246
|
return {
|
|
295
247
|
name: this.name,
|
|
296
|
-
isOwned:
|
|
248
|
+
isOwned: true,
|
|
297
249
|
baseagent: this.baseagent,
|
|
298
250
|
model: this.model,
|
|
299
251
|
effort: this.effort,
|
|
@@ -302,14 +254,31 @@ export class EvolAgent {
|
|
|
302
254
|
};
|
|
303
255
|
}
|
|
304
256
|
resolveChatMode(chatType, globalChatmode) {
|
|
305
|
-
const agentCm = this.config.chatmode;
|
|
306
257
|
const key = chatType === 'group' ? 'group' : 'private';
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
258
|
+
return (this.merged.chatmode?.[key]
|
|
259
|
+
?? globalChatmode?.[key]
|
|
260
|
+
?? (key === 'group' ? 'proactive' : 'interactive'));
|
|
261
|
+
}
|
|
262
|
+
// ── Reload 支持:替换 in-memory config 并复用 channels Map ───────────
|
|
263
|
+
/** 用新的 raw + merged 替换 in-memory 状态。channels 由调用方决定如何 reconcile。 */
|
|
264
|
+
swapConfig(rawAgent, merged) {
|
|
265
|
+
if (rawAgent.aid !== this.aid) {
|
|
266
|
+
throw new Error(`EvolAgent.swapConfig: aid mismatch (${rawAgent.aid} vs ${this.aid})`);
|
|
312
267
|
}
|
|
313
|
-
|
|
268
|
+
this.rawAgent = rawAgent;
|
|
269
|
+
this.merged = merged;
|
|
270
|
+
}
|
|
271
|
+
// ── 内部辅助 ─────────────────────────────────────────────────────────
|
|
272
|
+
/**
|
|
273
|
+
* 找 rawAgent.channels 里的可变实例,用于写入。
|
|
274
|
+
*
|
|
275
|
+
* merged.channels 是 deep clone 时 raw 跟 merged 的 channels 引用同一份(mergeForAgent
|
|
276
|
+
* 直接 `agent.channels` 透传),所以 raw 里的实例就等于 merged 里的实例。
|
|
277
|
+
*/
|
|
278
|
+
findRawChannelInstance(channelKey) {
|
|
279
|
+
return this.rawAgent.channels.find(c => this.effectiveChannelName(c.type, c.name) === channelKey) ?? null;
|
|
280
|
+
}
|
|
281
|
+
persist() {
|
|
282
|
+
saveAgent(this.rawAgent);
|
|
314
283
|
}
|
|
315
284
|
}
|
|
@@ -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
|
}
|