evolclaw 2.8.1 → 2.8.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/dist/agents/templates.js +122 -0
- package/dist/channels/aun-ops.js +1 -1
- package/dist/channels/qqbot.js +1 -1
- package/dist/channels/wechat.js +1 -1
- package/dist/cli.js +345 -48
- package/dist/config.js +93 -21
- package/dist/core/agent-registry.js +287 -1
- package/dist/core/command-handler.js +370 -160
- package/dist/core/evolagent-registry.js +503 -0
- package/dist/core/evolagent.js +250 -1
- package/dist/core/message/message-bridge.js +23 -3
- package/dist/core/message/message-processor.js +39 -4
- package/dist/core/message/message-queue.js +59 -4
- package/dist/core/reload-hooks.js +87 -0
- package/dist/index.js +119 -8
- package/dist/ipc.js +47 -0
- package/dist/types.js +2 -0
- package/dist/utils/reload-hooks.js +87 -0
- package/dist/utils/rich-content-renderer.js +33 -0
- package/dist/utils/stats-collector.js +15 -10
- package/package.json +1 -1
|
@@ -130,6 +130,7 @@ export class CommandHandler {
|
|
|
130
130
|
statsCollector;
|
|
131
131
|
agentMap;
|
|
132
132
|
defaultAgentId;
|
|
133
|
+
agentRegistry;
|
|
133
134
|
/** 按 agentId 获取 agent,回退到默认 */
|
|
134
135
|
getAgent(agentId) {
|
|
135
136
|
if (agentId && this.agentMap.has(agentId))
|
|
@@ -150,6 +151,193 @@ export class CommandHandler {
|
|
|
150
151
|
this.defaultAgentId = agentRunnerOrMap.name;
|
|
151
152
|
}
|
|
152
153
|
}
|
|
154
|
+
/** 注入 EvolAgentRegistry,用于判断通道是否被 EvolAgent 管理 */
|
|
155
|
+
setAgentRegistry(registry) {
|
|
156
|
+
this.agentRegistry = registry;
|
|
157
|
+
}
|
|
158
|
+
/** 返回管理当前通道的 EvolAgent(非 default),无则返回 null */
|
|
159
|
+
getOwningAgent(channel) {
|
|
160
|
+
if (!this.agentRegistry)
|
|
161
|
+
return null;
|
|
162
|
+
const agent = this.agentRegistry.resolveByChannel(channel);
|
|
163
|
+
if (!agent || agent.isDefault)
|
|
164
|
+
return null;
|
|
165
|
+
return agent;
|
|
166
|
+
}
|
|
167
|
+
/** 返回当前通道的有效项目路径:agent-owned 用 agent.projectPath;否则用全局 defaultPath。*/
|
|
168
|
+
getEffectiveDefaultPath(channel) {
|
|
169
|
+
const owning = this.getOwningAgent(channel);
|
|
170
|
+
if (owning)
|
|
171
|
+
return owning.projectPath;
|
|
172
|
+
return this.config.projects?.defaultPath || process.cwd();
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* 返回当前通道有效的 projects.list(agent-owned 用 agent.json 的;否则全局 evolclaw.json 的)。
|
|
176
|
+
* 都没配 list 时回退到 defaultPath 单项目。
|
|
177
|
+
*/
|
|
178
|
+
getEffectiveProjects(channel) {
|
|
179
|
+
const owning = this.getOwningAgent(channel);
|
|
180
|
+
if (owning) {
|
|
181
|
+
return owning.getProjects();
|
|
182
|
+
}
|
|
183
|
+
return this.projects;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 添加项目到当前通道范围(agent-owned 写 agent.json;default 写 evolclaw.json)。
|
|
187
|
+
*/
|
|
188
|
+
async addProjectInScope(channel, name, projectPath) {
|
|
189
|
+
const owning = this.getOwningAgent(channel);
|
|
190
|
+
if (owning) {
|
|
191
|
+
try {
|
|
192
|
+
owning.addProject(name, projectPath);
|
|
193
|
+
}
|
|
194
|
+
catch (e) {
|
|
195
|
+
return `⚠️ 写入 agent.json 失败: ${e?.message || e}`;
|
|
196
|
+
}
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
if (!this.config.projects) {
|
|
200
|
+
this.config.projects = { defaultPath: process.cwd(), autoCreate: false, list: {} };
|
|
201
|
+
}
|
|
202
|
+
if (!this.config.projects.list) {
|
|
203
|
+
this.config.projects.list = {};
|
|
204
|
+
}
|
|
205
|
+
this.config.projects.list[name] = projectPath;
|
|
206
|
+
try {
|
|
207
|
+
const { saveConfig } = await import('../config.js');
|
|
208
|
+
saveConfig(this.config);
|
|
209
|
+
}
|
|
210
|
+
catch (e) {
|
|
211
|
+
return `⚠️ 写入 evolclaw.json 失败: ${e?.message || e}`;
|
|
212
|
+
}
|
|
213
|
+
// Refresh in-memory list cache (this.projects getter reads from this.config)
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* 持久化 baseagent.model:agent-owned 写到 agent.json;否则写 evolclaw.json 或 ~/.claude/settings.json。
|
|
218
|
+
* 返回错误信息或 undefined。
|
|
219
|
+
*/
|
|
220
|
+
persistBaseagentModel(channel, baseagentName, newModel) {
|
|
221
|
+
const owning = this.getOwningAgent(channel);
|
|
222
|
+
if (owning) {
|
|
223
|
+
try {
|
|
224
|
+
owning.setBaseagentModel(newModel);
|
|
225
|
+
}
|
|
226
|
+
catch (e) {
|
|
227
|
+
return `⚠️ 写入 agent.json 失败: ${e?.message || e}`;
|
|
228
|
+
}
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
// DefaultAgent / 无 owning agent:保留原"就近原则"
|
|
232
|
+
if (!this.config.agents)
|
|
233
|
+
this.config.agents = {};
|
|
234
|
+
const isCodex = baseagentName === 'codex';
|
|
235
|
+
if (isCodex) {
|
|
236
|
+
if (!this.config.agents.codex)
|
|
237
|
+
this.config.agents.codex = {};
|
|
238
|
+
if (newModel)
|
|
239
|
+
this.config.agents.codex.model = newModel;
|
|
240
|
+
try {
|
|
241
|
+
saveConfig(this.config);
|
|
242
|
+
}
|
|
243
|
+
catch (e) {
|
|
244
|
+
return `⚠️ 写入 evolclaw.json 失败: ${e.message}`;
|
|
245
|
+
}
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
const configuredInEvolclaw = !!(this.config.agents?.claude?.model || this.config.agents?.claude?.effort);
|
|
249
|
+
if (configuredInEvolclaw) {
|
|
250
|
+
if (!this.config.agents.claude)
|
|
251
|
+
this.config.agents.claude = {};
|
|
252
|
+
if (newModel)
|
|
253
|
+
this.config.agents.claude.model = newModel;
|
|
254
|
+
try {
|
|
255
|
+
saveConfig(this.config);
|
|
256
|
+
}
|
|
257
|
+
catch (e) {
|
|
258
|
+
return `⚠️ 写入 evolclaw.json 失败: ${e.message}`;
|
|
259
|
+
}
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
// Fallback: ~/.claude/settings.json
|
|
263
|
+
const updates = {};
|
|
264
|
+
if (newModel)
|
|
265
|
+
updates.model = newModel;
|
|
266
|
+
const writeResult = writeUserSettings(updates);
|
|
267
|
+
if (!writeResult.success) {
|
|
268
|
+
return `⚠️ 写入用户配置失败: ${writeResult.error}`;
|
|
269
|
+
}
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* 持久化 baseagent.effort:agent-owned 写到 agent.json;否则就近原则。
|
|
274
|
+
*/
|
|
275
|
+
persistBaseagentEffort(channel, baseagentName, newEffort) {
|
|
276
|
+
const owning = this.getOwningAgent(channel);
|
|
277
|
+
if (owning) {
|
|
278
|
+
try {
|
|
279
|
+
owning.setBaseagentEffort(newEffort);
|
|
280
|
+
}
|
|
281
|
+
catch (e) {
|
|
282
|
+
return `⚠️ 写入 agent.json 失败: ${e?.message || e}`;
|
|
283
|
+
}
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
if (!this.config.agents)
|
|
287
|
+
this.config.agents = {};
|
|
288
|
+
const isCodex = baseagentName === 'codex';
|
|
289
|
+
if (isCodex) {
|
|
290
|
+
if (newEffort === undefined) {
|
|
291
|
+
if (this.config.agents.codex?.reasoning) {
|
|
292
|
+
delete this.config.agents.codex.reasoning;
|
|
293
|
+
try {
|
|
294
|
+
saveConfig(this.config);
|
|
295
|
+
}
|
|
296
|
+
catch { }
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
if (!this.config.agents.codex)
|
|
301
|
+
this.config.agents.codex = {};
|
|
302
|
+
this.config.agents.codex.reasoning = newEffort;
|
|
303
|
+
try {
|
|
304
|
+
saveConfig(this.config);
|
|
305
|
+
}
|
|
306
|
+
catch (e) {
|
|
307
|
+
return `⚠️ 写入 evolclaw.json 失败: ${e.message}`;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return undefined;
|
|
311
|
+
}
|
|
312
|
+
const configuredInEvolclaw = !!(this.config.agents?.claude?.model || this.config.agents?.claude?.effort);
|
|
313
|
+
if (configuredInEvolclaw) {
|
|
314
|
+
if (newEffort === undefined) {
|
|
315
|
+
delete this.config.agents.claude.effort;
|
|
316
|
+
try {
|
|
317
|
+
saveConfig(this.config);
|
|
318
|
+
}
|
|
319
|
+
catch { }
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
if (!this.config.agents.claude)
|
|
323
|
+
this.config.agents.claude = {};
|
|
324
|
+
this.config.agents.claude.effort = newEffort;
|
|
325
|
+
try {
|
|
326
|
+
saveConfig(this.config);
|
|
327
|
+
}
|
|
328
|
+
catch (e) {
|
|
329
|
+
return `⚠️ 写入 evolclaw.json 失败: ${e.message}`;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return undefined;
|
|
333
|
+
}
|
|
334
|
+
const updates = { effortLevel: newEffort ?? null };
|
|
335
|
+
const writeResult = writeUserSettings(updates);
|
|
336
|
+
if (!writeResult.success) {
|
|
337
|
+
return `⚠️ 写入用户配置失败: ${writeResult.error}`;
|
|
338
|
+
}
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
153
341
|
/** 项目列表快捷访问(list 缺失时用 defaultPath 作为唯一项目) */
|
|
154
342
|
get projects() {
|
|
155
343
|
const list = this.config.projects?.list;
|
|
@@ -491,7 +679,9 @@ export class CommandHandler {
|
|
|
491
679
|
return items;
|
|
492
680
|
}
|
|
493
681
|
if (cmd === '/p') {
|
|
494
|
-
|
|
682
|
+
// Use agent-scoped project list: agent-owned channels see their agent.json's
|
|
683
|
+
// projects.list; default channel sees evolclaw.json's projects.list
|
|
684
|
+
const list = this.getEffectiveProjects(channel);
|
|
495
685
|
return Object.entries(list).map(([name, path]) => ({ value: name, label: name, desc: path }));
|
|
496
686
|
}
|
|
497
687
|
if (cmd === '/agent') {
|
|
@@ -507,8 +697,19 @@ export class CommandHandler {
|
|
|
507
697
|
return null;
|
|
508
698
|
}
|
|
509
699
|
if (cmd === '/restart') {
|
|
700
|
+
// /restart 是服务级操作(重连/重启进程),仅限 default 通道。
|
|
701
|
+
// EvolAgent 通道返回空菜单(用户在 agent-owned 通道上无可选项)
|
|
702
|
+
if (this.getOwningAgent(channel))
|
|
703
|
+
return [];
|
|
510
704
|
const isOwner = userId ? this.sessionManager.resolveIdentity(channel, userId).role === 'owner' : false;
|
|
511
|
-
|
|
705
|
+
// 列出所有 channel type
|
|
706
|
+
const visibleTypes = new Set();
|
|
707
|
+
for (const [name] of this.adapters) {
|
|
708
|
+
const t = this.channelTypeMap.get(name);
|
|
709
|
+
if (t)
|
|
710
|
+
visibleTypes.add(t);
|
|
711
|
+
}
|
|
712
|
+
const channels = [...visibleTypes].map(type => ({ value: type, label: type, desc: '重连此类型所有渠道实例' }));
|
|
512
713
|
if (isOwner)
|
|
513
714
|
channels.unshift({ value: '', label: '重启服务', desc: '重启整个 EvolClaw 服务进程' });
|
|
514
715
|
return channels;
|
|
@@ -599,6 +800,20 @@ export class CommandHandler {
|
|
|
599
800
|
return '⚠️ 话题中不支持此命令';
|
|
600
801
|
}
|
|
601
802
|
}
|
|
803
|
+
// Agent-owned 通道:禁止项目切换和 agent 切换
|
|
804
|
+
const owningAgent = this.getOwningAgent(channel);
|
|
805
|
+
if (owningAgent) {
|
|
806
|
+
const isProjectCmd = normalizedContent === '/project' || normalizedContent.startsWith('/project ') ||
|
|
807
|
+
normalizedContent === '/bind' || normalizedContent.startsWith('/bind ') ||
|
|
808
|
+
normalizedContent === '/plist' ||
|
|
809
|
+
normalizedContent === '/p' || normalizedContent.startsWith('/p ');
|
|
810
|
+
if (isProjectCmd) {
|
|
811
|
+
return `❌ 当前通道由 agent [${owningAgent.name}] 管理,项目已锁定为 ${owningAgent.projectPath}`;
|
|
812
|
+
}
|
|
813
|
+
if (normalizedContent.startsWith('/agent ')) {
|
|
814
|
+
return `❌ 当前通道由 agent [${owningAgent.name}] 管理,baseagent 已锁定为 ${owningAgent.baseagent}`;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
602
817
|
// 权限检查:区分用户级命令和管理级命令
|
|
603
818
|
const isOwner = identity.role === 'owner';
|
|
604
819
|
const isAdmin = identity.role === 'owner' || identity.role === 'admin';
|
|
@@ -732,7 +947,7 @@ export class CommandHandler {
|
|
|
732
947
|
' /check - 检查渠道状态',
|
|
733
948
|
' /activity [all|dm|owner|none] - 查看/控制中间输出显示模式',
|
|
734
949
|
...(isAdmin ? [
|
|
735
|
-
' /restart <
|
|
950
|
+
' /restart <type> - 重连该类型所有渠道实例(服务级,admin+)',
|
|
736
951
|
] : []),
|
|
737
952
|
...(isOwner ? [
|
|
738
953
|
' /restart - 重启服务',
|
|
@@ -1091,68 +1306,16 @@ export class CommandHandler {
|
|
|
1091
1306
|
modelAgent.setEffort?.(newEffort);
|
|
1092
1307
|
changes.push(`推理强度: ${newEffort}`);
|
|
1093
1308
|
}
|
|
1094
|
-
//
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
if (configuredInEvolclaw) {
|
|
1100
|
-
if (!this.config.agents.codex)
|
|
1101
|
-
this.config.agents.codex = {};
|
|
1102
|
-
if (newModel)
|
|
1103
|
-
this.config.agents.codex.model = newModel;
|
|
1104
|
-
if (newEffort)
|
|
1105
|
-
this.config.agents.codex.reasoning = newEffort;
|
|
1106
|
-
try {
|
|
1107
|
-
saveConfig(this.config);
|
|
1108
|
-
}
|
|
1109
|
-
catch (error) {
|
|
1110
|
-
return `⚠️ 写入 evolclaw.json 失败: ${error.message}\n已更新运行时配置,但未持久化`;
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
else {
|
|
1114
|
-
// Codex 全局配置(~/.codex/config.toml)目前不支持写入,回退到 evolclaw.json
|
|
1115
|
-
if (!this.config.agents.codex)
|
|
1116
|
-
this.config.agents.codex = {};
|
|
1117
|
-
if (newModel)
|
|
1118
|
-
this.config.agents.codex.model = newModel;
|
|
1119
|
-
if (newEffort)
|
|
1120
|
-
this.config.agents.codex.reasoning = newEffort;
|
|
1121
|
-
try {
|
|
1122
|
-
saveConfig(this.config);
|
|
1123
|
-
}
|
|
1124
|
-
catch (error) {
|
|
1125
|
-
return `⚠️ 写入 evolclaw.json 失败: ${error.message}\n已更新运行时配置,但未持久化`;
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1309
|
+
// 持久化:agent-owned channel 写到 agent.json;default 走原"就近原则"
|
|
1310
|
+
if (newModel) {
|
|
1311
|
+
const err = this.persistBaseagentModel(channel, modelAgent.name, newModel);
|
|
1312
|
+
if (err)
|
|
1313
|
+
return `${err}\n已更新运行时配置,但未持久化`;
|
|
1128
1314
|
}
|
|
1129
|
-
|
|
1130
|
-
const
|
|
1131
|
-
if (
|
|
1132
|
-
|
|
1133
|
-
this.config.agents.claude = {};
|
|
1134
|
-
if (newModel)
|
|
1135
|
-
this.config.agents.claude.model = newModel;
|
|
1136
|
-
if (newEffort)
|
|
1137
|
-
this.config.agents.claude.effort = newEffort;
|
|
1138
|
-
try {
|
|
1139
|
-
saveConfig(this.config);
|
|
1140
|
-
}
|
|
1141
|
-
catch (error) {
|
|
1142
|
-
return `⚠️ 写入 evolclaw.json 失败: ${error.message}\n已更新运行时配置,但未持久化`;
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
else {
|
|
1146
|
-
const updates = {};
|
|
1147
|
-
if (newModel)
|
|
1148
|
-
updates.model = newModel;
|
|
1149
|
-
if (newEffort)
|
|
1150
|
-
updates.effortLevel = newEffort;
|
|
1151
|
-
const writeResult = writeUserSettings(updates);
|
|
1152
|
-
if (!writeResult.success) {
|
|
1153
|
-
return `⚠️ 写入用户配置失败: ${writeResult.error}\n已更新运行时配置,但未持久化到 ~/.claude/settings.json`;
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1315
|
+
if (newEffort) {
|
|
1316
|
+
const err = this.persistBaseagentEffort(channel, modelAgent.name, newEffort);
|
|
1317
|
+
if (err)
|
|
1318
|
+
return `${err}\n已更新运行时配置,但未持久化`;
|
|
1156
1319
|
}
|
|
1157
1320
|
return `✓ 已切换\n ${changes.join('\n ')}`;
|
|
1158
1321
|
}
|
|
@@ -1231,29 +1394,9 @@ export class CommandHandler {
|
|
|
1231
1394
|
// /effort auto:恢复 SDK 默认
|
|
1232
1395
|
if (args === 'auto') {
|
|
1233
1396
|
effortAgent.setEffort?.(undefined);
|
|
1234
|
-
const
|
|
1235
|
-
if (
|
|
1236
|
-
|
|
1237
|
-
delete this.config.agents.codex.reasoning;
|
|
1238
|
-
try {
|
|
1239
|
-
saveConfig(this.config);
|
|
1240
|
-
}
|
|
1241
|
-
catch { }
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
else {
|
|
1245
|
-
const configuredInEvolclaw = !!this.config.agents?.claude?.effort;
|
|
1246
|
-
if (configuredInEvolclaw) {
|
|
1247
|
-
delete this.config.agents.claude.effort;
|
|
1248
|
-
try {
|
|
1249
|
-
saveConfig(this.config);
|
|
1250
|
-
}
|
|
1251
|
-
catch { }
|
|
1252
|
-
}
|
|
1253
|
-
else {
|
|
1254
|
-
writeUserSettings({ effortLevel: null });
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1397
|
+
const err = this.persistBaseagentEffort(channel, effortAgent.name, undefined);
|
|
1398
|
+
if (err)
|
|
1399
|
+
return `${err}\n已更新运行时配置,但未持久化`;
|
|
1257
1400
|
return '✓ 推理强度已恢复为 auto (SDK默认)';
|
|
1258
1401
|
}
|
|
1259
1402
|
// /effort <level>:切换推理强度
|
|
@@ -1265,34 +1408,9 @@ export class CommandHandler {
|
|
|
1265
1408
|
}
|
|
1266
1409
|
const newEffort = args;
|
|
1267
1410
|
effortAgent.setEffort?.(newEffort);
|
|
1268
|
-
|
|
1269
|
-
if (
|
|
1270
|
-
|
|
1271
|
-
const isCodex = effortAgent.name === 'codex';
|
|
1272
|
-
if (isCodex) {
|
|
1273
|
-
if (!this.config.agents.codex)
|
|
1274
|
-
this.config.agents.codex = {};
|
|
1275
|
-
this.config.agents.codex.reasoning = newEffort;
|
|
1276
|
-
try {
|
|
1277
|
-
saveConfig(this.config);
|
|
1278
|
-
}
|
|
1279
|
-
catch { }
|
|
1280
|
-
}
|
|
1281
|
-
else {
|
|
1282
|
-
const configuredInEvolclaw = !!(this.config.agents?.claude?.model || this.config.agents?.claude?.effort);
|
|
1283
|
-
if (configuredInEvolclaw) {
|
|
1284
|
-
if (!this.config.agents.claude)
|
|
1285
|
-
this.config.agents.claude = {};
|
|
1286
|
-
this.config.agents.claude.effort = newEffort;
|
|
1287
|
-
try {
|
|
1288
|
-
saveConfig(this.config);
|
|
1289
|
-
}
|
|
1290
|
-
catch { }
|
|
1291
|
-
}
|
|
1292
|
-
else {
|
|
1293
|
-
writeUserSettings({ effortLevel: newEffort });
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1411
|
+
const err = this.persistBaseagentEffort(channel, effortAgent.name, newEffort);
|
|
1412
|
+
if (err)
|
|
1413
|
+
return `${err}\n已更新运行时配置,但未持久化`;
|
|
1296
1414
|
return `✓ 推理强度: ${newEffort}`;
|
|
1297
1415
|
}
|
|
1298
1416
|
// /aid 命令:AID 身份管理(list / new)
|
|
@@ -1424,7 +1542,7 @@ export class CommandHandler {
|
|
|
1424
1542
|
owner: 'owner-dm-only',
|
|
1425
1543
|
none: 'none',
|
|
1426
1544
|
};
|
|
1427
|
-
const currentMode = getChannelShowActivities(this.config, channel);
|
|
1545
|
+
const currentMode = this.agentRegistry?.getShowActivities?.(channel) ?? getChannelShowActivities(this.config, channel);
|
|
1428
1546
|
// 模式描述列表(用于 body 和文本降级)
|
|
1429
1547
|
const modeDescriptions = [
|
|
1430
1548
|
{ key: 'all', configVal: 'all', label: '全部显示' },
|
|
@@ -1495,7 +1613,12 @@ export class CommandHandler {
|
|
|
1495
1613
|
// 切换操作仅 owner
|
|
1496
1614
|
if (!isOwner)
|
|
1497
1615
|
return '❌ 中间输出模式切换仅限 owner';
|
|
1498
|
-
|
|
1616
|
+
if (this.agentRegistry?.setShowActivities) {
|
|
1617
|
+
this.agentRegistry.setShowActivities(channel, newMode);
|
|
1618
|
+
}
|
|
1619
|
+
else {
|
|
1620
|
+
setChannelShowActivities(this.config, channel, newMode);
|
|
1621
|
+
}
|
|
1499
1622
|
return `✅ 中间输出模式: ${activityArg}(${label})`;
|
|
1500
1623
|
}
|
|
1501
1624
|
// /chatmode 命令:查看/切换 session 会话模式(interactive | proactive)
|
|
@@ -1553,7 +1676,12 @@ export class CommandHandler {
|
|
|
1553
1676
|
}
|
|
1554
1677
|
await stopAgent.interrupt(sessionKey);
|
|
1555
1678
|
// 发布中断事件,让 MessageProcessor 标记为 interrupted(而非 done)
|
|
1556
|
-
this.eventBus.publish({
|
|
1679
|
+
this.eventBus.publish({
|
|
1680
|
+
type: 'message:interrupted',
|
|
1681
|
+
sessionId: sessionKey,
|
|
1682
|
+
reason: 'stop',
|
|
1683
|
+
agentName: this.agentRegistry?.resolveByChannel(channel)?.name ?? '[default]',
|
|
1684
|
+
});
|
|
1557
1685
|
// 强制清除 processing_state
|
|
1558
1686
|
this.sessionManager.clearProcessing(sessionKey);
|
|
1559
1687
|
return '✓ 已发送中断信号,任务将尽快停止';
|
|
@@ -1626,7 +1754,7 @@ export class CommandHandler {
|
|
|
1626
1754
|
// 尝试获取活跃会话(话题时直接查找话题 session)
|
|
1627
1755
|
let session;
|
|
1628
1756
|
if (threadId) {
|
|
1629
|
-
session = await this.sessionManager.getOrCreateSession(channel, channelId, this.
|
|
1757
|
+
session = await this.sessionManager.getOrCreateSession(channel, channelId, this.getEffectiveDefaultPath(channel), threadId);
|
|
1630
1758
|
}
|
|
1631
1759
|
else {
|
|
1632
1760
|
session = await this.sessionManager.getActiveSession(channel, channelId);
|
|
@@ -1638,7 +1766,7 @@ export class CommandHandler {
|
|
|
1638
1766
|
normalizedContent.startsWith('/project') ||
|
|
1639
1767
|
normalizedContent === '/pwd' ||
|
|
1640
1768
|
normalizedContent === '/status')) {
|
|
1641
|
-
session = await this.sessionManager.getOrCreateSession(channel, channelId, this.
|
|
1769
|
+
session = await this.sessionManager.getOrCreateSession(channel, channelId, this.getEffectiveDefaultPath(channel));
|
|
1642
1770
|
}
|
|
1643
1771
|
// /status 命令:显示会话状态
|
|
1644
1772
|
if (normalizedContent === '/status') {
|
|
@@ -1707,7 +1835,7 @@ export class CommandHandler {
|
|
|
1707
1835
|
return `❌ 会话名称 "${sessionName}" 已存在,请使用其他名称`;
|
|
1708
1836
|
}
|
|
1709
1837
|
}
|
|
1710
|
-
const projectPath = session?.projectPath || this.
|
|
1838
|
+
const projectPath = session?.projectPath || this.getEffectiveDefaultPath(channel);
|
|
1711
1839
|
const newSession = await this.sessionManager.createNewSession(channel, channelId, projectPath, sessionName, session?.agentId || this.defaultAgentId);
|
|
1712
1840
|
this.eventBus.publish({
|
|
1713
1841
|
type: 'session:created',
|
|
@@ -1729,11 +1857,30 @@ export class CommandHandler {
|
|
|
1729
1857
|
// /check 命令:检查渠道状态(guest 可用,详情仅 admin)/ 重连指定渠道(admin only)
|
|
1730
1858
|
if (normalizedContent === '/check' || normalizedContent.startsWith('/check ')) {
|
|
1731
1859
|
const subCmd = normalizedContent.slice('/check'.length).trim();
|
|
1860
|
+
// 限定可见渠道:agent-owned 通道仅显示该 agent 名下的渠道;
|
|
1861
|
+
// default 通道也仅显示 default 的渠道(不再展示 evolagents 的渠道)
|
|
1862
|
+
const checkOwningAgent = this.getOwningAgent(channel);
|
|
1863
|
+
let allowedChannels;
|
|
1864
|
+
if (checkOwningAgent) {
|
|
1865
|
+
allowedChannels = new Set(checkOwningAgent.channelInstanceNames());
|
|
1866
|
+
}
|
|
1867
|
+
else {
|
|
1868
|
+
// default 范围:所有 channel 中,不属于任何 evolagent 的
|
|
1869
|
+
const defaultNames = [];
|
|
1870
|
+
for (const [name] of this.adapters) {
|
|
1871
|
+
const owner = this.agentRegistry?.resolveByChannel(name);
|
|
1872
|
+
if (!owner || owner.isDefault)
|
|
1873
|
+
defaultNames.push(name);
|
|
1874
|
+
}
|
|
1875
|
+
allowedChannels = new Set(defaultNames);
|
|
1876
|
+
}
|
|
1732
1877
|
// Default: show system health check (non-admin 仅看摘要)
|
|
1733
1878
|
const lines = ['📡 渠道状态:'];
|
|
1734
1879
|
// Group by channelType
|
|
1735
1880
|
const groups = new Map();
|
|
1736
1881
|
for (const [name] of this.adapters) {
|
|
1882
|
+
if (!allowedChannels.has(name))
|
|
1883
|
+
continue;
|
|
1737
1884
|
const type = this.channelTypeMap.get(name) || name;
|
|
1738
1885
|
const ch = this.channelObjects.get(name);
|
|
1739
1886
|
let status;
|
|
@@ -1764,19 +1911,21 @@ export class CommandHandler {
|
|
|
1764
1911
|
lines.push(` ${type}: [${parts.join(', ')}]`);
|
|
1765
1912
|
}
|
|
1766
1913
|
}
|
|
1767
|
-
//
|
|
1914
|
+
// 当前 agent 名(用于 agent 维度 stats / queue 查询)
|
|
1915
|
+
const currentAgentName = checkOwningAgent?.name ?? '[default]';
|
|
1916
|
+
// 队列状态(按当前 agent 维度)
|
|
1768
1917
|
lines.push('', '📬 队列状态:');
|
|
1769
|
-
lines.push(` 待处理消息: ${this.messageQueue.
|
|
1770
|
-
lines.push(` 处理中队列: ${this.messageQueue.
|
|
1771
|
-
//
|
|
1918
|
+
lines.push(` 待处理消息: ${this.messageQueue.getQueueLengthByAgent(currentAgentName)}`);
|
|
1919
|
+
lines.push(` 处理中队列: ${this.messageQueue.getProcessingCountByAgent(currentAgentName)}`);
|
|
1920
|
+
// 运行概况(全局,进程级)
|
|
1772
1921
|
lines.push('', '🖥️ 运行概况:');
|
|
1773
1922
|
const uptimeMs = this.statsCollector
|
|
1774
1923
|
? this.statsCollector.getSnapshot().uptimeMs
|
|
1775
1924
|
: process.uptime() * 1000;
|
|
1776
1925
|
lines.push(` 运行时间: ${this.formatUptime(uptimeMs)}`);
|
|
1777
|
-
// 近 1
|
|
1926
|
+
// 近 1 小时统计(按当前 agent 维度)
|
|
1778
1927
|
if (this.statsCollector) {
|
|
1779
|
-
const snap = this.statsCollector.getSnapshot();
|
|
1928
|
+
const snap = this.statsCollector.getSnapshot(currentAgentName);
|
|
1780
1929
|
const h = snap.lastHour;
|
|
1781
1930
|
lines.push('', '📊 近 1 小时统计:');
|
|
1782
1931
|
lines.push(` 收到消息: ${h.received}`);
|
|
@@ -1802,23 +1951,50 @@ export class CommandHandler {
|
|
|
1802
1951
|
// /restart 命令:重启服务(owner only) / 重连指定渠道(admin+)
|
|
1803
1952
|
if (normalizedContent === '/restart' || normalizedContent.startsWith('/restart ')) {
|
|
1804
1953
|
const restartArg = normalizedContent.slice('/restart'.length).trim();
|
|
1805
|
-
// /restart <
|
|
1954
|
+
// /restart <type> — 重连指定类型的所有渠道(admin only,evolclaw 服务级操作)
|
|
1955
|
+
// 服务级操作仅可从 default 通道发起,避免 evolagent owner/admin 越权
|
|
1806
1956
|
if (restartArg) {
|
|
1957
|
+
if (this.getOwningAgent(channel)) {
|
|
1958
|
+
return '❌ 渠道重连只能从 DefaultAgent 通道发起(服务级操作)';
|
|
1959
|
+
}
|
|
1807
1960
|
if (!isAdmin)
|
|
1808
1961
|
return '❌ 无权限:渠道重连仅限管理员使用';
|
|
1809
|
-
const
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1962
|
+
const type = restartArg;
|
|
1963
|
+
// /restart 是服务级操作:重连该 type 下的所有实例(不分 agent)
|
|
1964
|
+
const scopedNames = [];
|
|
1965
|
+
for (const [name] of this.adapters) {
|
|
1966
|
+
if (this.channelTypeMap.get(name) === type)
|
|
1967
|
+
scopedNames.push(name);
|
|
1968
|
+
}
|
|
1969
|
+
if (scopedNames.length === 0) {
|
|
1970
|
+
return `❌ 没有类型为 "${type}" 的渠道`;
|
|
1971
|
+
}
|
|
1972
|
+
const results = [];
|
|
1973
|
+
for (const name of scopedNames) {
|
|
1974
|
+
const ch = this.channelObjects.get(name);
|
|
1975
|
+
if (!ch) {
|
|
1976
|
+
results.push(`${name}: 未找到渠道对象`);
|
|
1977
|
+
continue;
|
|
1978
|
+
}
|
|
1979
|
+
if (!ch.reconnect) {
|
|
1980
|
+
results.push(`${name}: 不支持重连`);
|
|
1981
|
+
continue;
|
|
1982
|
+
}
|
|
1983
|
+
try {
|
|
1984
|
+
const result = await ch.reconnect();
|
|
1985
|
+
results.push(`${name}: ${result}`);
|
|
1986
|
+
}
|
|
1987
|
+
catch (e) {
|
|
1988
|
+
results.push(`${name}: 重连失败 - ${e?.message || e}`);
|
|
1989
|
+
}
|
|
1817
1990
|
}
|
|
1818
|
-
|
|
1819
|
-
|
|
1991
|
+
return `🔄 重连 ${type}:\n ${results.join('\n ')}`;
|
|
1992
|
+
}
|
|
1993
|
+
// /restart(无参数)— 重启整个服务(owner only,且仅可从 default 通道触发)
|
|
1994
|
+
// 防止 evolagent 通道的 owner 越权杀整个 evolclaw 进程(影响所有租户)
|
|
1995
|
+
if (this.getOwningAgent(channel)) {
|
|
1996
|
+
return '❌ 服务重启只能从 DefaultAgent 通道发起。EvolAgent 通道仅可执行 /restart <type> 重连特定类型渠道';
|
|
1820
1997
|
}
|
|
1821
|
-
// /restart(无参数)— 重启整个服务(owner only)
|
|
1822
1998
|
if (!isOwner)
|
|
1823
1999
|
return '❌ 无权限:服务重启仅限 owner 使用';
|
|
1824
2000
|
const allSessions = await this.sessionManager.listSessions(channel, channelId);
|
|
@@ -1832,7 +2008,7 @@ export class CommandHandler {
|
|
|
1832
2008
|
const executeRestart = async () => {
|
|
1833
2009
|
let replyContext;
|
|
1834
2010
|
if (threadId) {
|
|
1835
|
-
const threadSession = await this.sessionManager.getOrCreateSession(channel, channelId, this.
|
|
2011
|
+
const threadSession = await this.sessionManager.getOrCreateSession(channel, channelId, this.getEffectiveDefaultPath(channel), threadId);
|
|
1836
2012
|
replyContext = this.getReplyContext(threadSession);
|
|
1837
2013
|
}
|
|
1838
2014
|
const restartInfo = {
|
|
@@ -1971,7 +2147,7 @@ export class CommandHandler {
|
|
|
1971
2147
|
// 找目标 channelId
|
|
1972
2148
|
let targetChannelId = channelId;
|
|
1973
2149
|
if (isCrossChannel) {
|
|
1974
|
-
const ownerPeerId = getOwner(this.config, targetChannel);
|
|
2150
|
+
const ownerPeerId = this.agentRegistry?.getOwner?.(targetChannel) ?? getOwner(this.config, targetChannel);
|
|
1975
2151
|
targetChannelId = ownerPeerId ? (this.sessionManager.getOwnerChatId(targetChannel, ownerPeerId) ?? '') : '';
|
|
1976
2152
|
if (!targetChannelId) {
|
|
1977
2153
|
return `❌ 未找到 ${targetLabel} 的私聊会话,请先在该通道发送一条消息`;
|
|
@@ -2226,27 +2402,19 @@ export class CommandHandler {
|
|
|
2226
2402
|
}
|
|
2227
2403
|
// 生成项目名称(使用目录名)
|
|
2228
2404
|
const projectName = path.basename(projectPath);
|
|
2229
|
-
//
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2405
|
+
// 检查在当前 scope 内是否已存在
|
|
2406
|
+
const scopeProjects = this.getEffectiveProjects(channel);
|
|
2407
|
+
const existing = scopeProjects[projectName];
|
|
2408
|
+
if (existing) {
|
|
2409
|
+
if (existing === projectPath) {
|
|
2233
2410
|
return `项目 "${projectName}" 已存在\n 路径: ${projectPath}\n\n使用 /p ${projectName} 切换到该项目`;
|
|
2234
2411
|
}
|
|
2235
|
-
return `❌ 项目名称 "${projectName}" 已被占用\n 现有路径: ${
|
|
2412
|
+
return `❌ 项目名称 "${projectName}" 已被占用\n 现有路径: ${existing}\n 新路径: ${projectPath}\n\n请重命名目录或手动编辑配置文件`;
|
|
2236
2413
|
}
|
|
2237
|
-
//
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
if (!this.config.projects.list) {
|
|
2242
|
-
this.config.projects.list = {};
|
|
2243
|
-
}
|
|
2244
|
-
this.config.projects.list[projectName] = projectPath;
|
|
2245
|
-
// 保存配置
|
|
2246
|
-
const { saveConfig } = await import('../config.js');
|
|
2247
|
-
saveConfig(this.config);
|
|
2248
|
-
// 更新内存中的项目列表
|
|
2249
|
-
this.projects[projectName] = projectPath;
|
|
2414
|
+
// 写入:agent-owned channel → agent.json;default → evolclaw.json
|
|
2415
|
+
const err = await this.addProjectInScope(channel, projectName, projectPath);
|
|
2416
|
+
if (err)
|
|
2417
|
+
return err;
|
|
2250
2418
|
return `✓ 已添加项目: ${projectName}\n 路径: ${projectPath}\n\n使用 /p ${projectName} 切换到该项目`;
|
|
2251
2419
|
}
|
|
2252
2420
|
// /slist 命令:列出当前项目的会话
|
|
@@ -2838,7 +3006,7 @@ export class CommandHandler {
|
|
|
2838
3006
|
'/help', '/status', '/check', '/pwd',
|
|
2839
3007
|
'/model', '/effort', '/perm', '/agent',
|
|
2840
3008
|
'/compact', '/activity', '/file', '/send', '/chatmode', '/restart', '/agentmd', '/bind', '/aid',
|
|
2841
|
-
'/rename', '/name',
|
|
3009
|
+
'/rename', '/name', '/evolagent',
|
|
2842
3010
|
];
|
|
2843
3011
|
/** ctl 中仅允许查询形态的指令;写形态(带参)一律拒绝 */
|
|
2844
3012
|
static CTL_READONLY = new Set(['/agent']);
|
|
@@ -2892,6 +3060,48 @@ export class CommandHandler {
|
|
|
2892
3060
|
}
|
|
2893
3061
|
// 3. 从 session.metadata.peerId 获取 userId(用于权限判断)
|
|
2894
3062
|
const userId = session.metadata?.peerId;
|
|
3063
|
+
// 3.1 /evolagent: EvolAgent 管理(show identity / reload)
|
|
3064
|
+
if (cmd === '/evolagent' || cmd.startsWith('/evolagent ')) {
|
|
3065
|
+
const arg = cmd.slice('/evolagent'.length).trim();
|
|
3066
|
+
if (!arg) {
|
|
3067
|
+
const owning = this.getOwningAgent(session.channel);
|
|
3068
|
+
if (owning) {
|
|
3069
|
+
return { ok: true, result: `当前 EvolAgent: ${owning.name} (${owning.baseagent})` };
|
|
3070
|
+
}
|
|
3071
|
+
return { ok: true, result: '当前为 DefaultAgent 模式' };
|
|
3072
|
+
}
|
|
3073
|
+
if (arg.startsWith('reload ') || arg === 'reload') {
|
|
3074
|
+
const name = arg === 'reload' ? '' : arg.slice('reload '.length).trim();
|
|
3075
|
+
if (!name)
|
|
3076
|
+
return { ok: false, error: '用法: evolclaw ctl evolagent reload <name>' };
|
|
3077
|
+
// I8: reload is a structural op, require admin or owner
|
|
3078
|
+
if (!userId) {
|
|
3079
|
+
return { ok: false, error: '权限不足:evolagent reload 仅 owner/admin 可用' };
|
|
3080
|
+
}
|
|
3081
|
+
const identity = this.sessionManager.resolveIdentity(session.channel, userId);
|
|
3082
|
+
if (identity.role !== 'owner' && identity.role !== 'admin') {
|
|
3083
|
+
return { ok: false, error: '权限不足:evolagent reload 仅 owner/admin 可用' };
|
|
3084
|
+
}
|
|
3085
|
+
if (!this.agentRegistry)
|
|
3086
|
+
return { ok: false, error: 'EvolAgentRegistry not available' };
|
|
3087
|
+
const a = this.agentRegistry.get(name);
|
|
3088
|
+
if (!a)
|
|
3089
|
+
return { ok: false, error: `Agent "${name}" not found` };
|
|
3090
|
+
const hooks = globalThis.__evolclaw_reloadHooks;
|
|
3091
|
+
if (!hooks)
|
|
3092
|
+
return { ok: false, error: 'Reload hooks not initialized' };
|
|
3093
|
+
if (!this.agentRegistry.reload)
|
|
3094
|
+
return { ok: false, error: 'EvolAgentRegistry.reload not available' };
|
|
3095
|
+
try {
|
|
3096
|
+
await this.agentRegistry.reload(name, hooks);
|
|
3097
|
+
return { ok: true, result: `Agent "${name}" reloaded` };
|
|
3098
|
+
}
|
|
3099
|
+
catch (e) {
|
|
3100
|
+
return { ok: false, error: `Reload failed: ${e?.message || e}` };
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
return { ok: false, error: '用法: evolclaw ctl evolagent [reload <name>]' };
|
|
3104
|
+
}
|
|
2895
3105
|
// 4. /send 文本消息:直接通过 adapter 主动发送,不走 handle()
|
|
2896
3106
|
if (cmd.startsWith('/send ') || cmd === '/send') {
|
|
2897
3107
|
const text = cmd.startsWith('/send ') ? cmd.slice(6).trim() : '';
|