evolclaw 2.8.0 → 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 +275 -0
- package/dist/channels/aun.js +206 -103
- package/dist/channels/qqbot.js +1 -1
- package/dist/channels/wechat.js +1 -1
- package/dist/cli.js +676 -20
- package/dist/config.js +94 -22
- package/dist/core/agent-registry.js +450 -0
- package/dist/core/command-handler.js +422 -255
- package/dist/core/evolagent-registry.js +503 -0
- package/dist/core/evolagent-schema.js +72 -0
- package/dist/core/evolagent.js +315 -0
- package/dist/core/message/message-bridge.js +23 -3
- package/dist/core/message/message-processor.js +56 -11
- package/dist/core/message/message-queue.js +59 -4
- package/dist/core/reload-hooks.js +87 -0
- package/dist/index.js +119 -20
- package/dist/ipc.js +47 -0
- package/dist/paths.js +2 -0
- package/dist/types.js +2 -0
- package/dist/utils/init-channel.js +91 -221
- package/dist/utils/init.js +18 -42
- package/dist/utils/logger.js +58 -2
- 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/evolclaw-install-aun.md +48 -7
- package/package.json +1 -1
|
@@ -128,9 +128,9 @@ export class CommandHandler {
|
|
|
128
128
|
permissionGateway;
|
|
129
129
|
interactionRouter;
|
|
130
130
|
statsCollector;
|
|
131
|
-
hotLoadChannel;
|
|
132
131
|
agentMap;
|
|
133
132
|
defaultAgentId;
|
|
133
|
+
agentRegistry;
|
|
134
134
|
/** 按 agentId 获取 agent,回退到默认 */
|
|
135
135
|
getAgent(agentId) {
|
|
136
136
|
if (agentId && this.agentMap.has(agentId))
|
|
@@ -151,6 +151,193 @@ export class CommandHandler {
|
|
|
151
151
|
this.defaultAgentId = agentRunnerOrMap.name;
|
|
152
152
|
}
|
|
153
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
|
+
}
|
|
154
341
|
/** 项目列表快捷访问(list 缺失时用 defaultPath 作为唯一项目) */
|
|
155
342
|
get projects() {
|
|
156
343
|
const list = this.config.projects?.list;
|
|
@@ -294,9 +481,6 @@ export class CommandHandler {
|
|
|
294
481
|
setMessageQueue(messageQueue) {
|
|
295
482
|
this.messageQueue = messageQueue;
|
|
296
483
|
}
|
|
297
|
-
setHotLoadChannel(fn) {
|
|
298
|
-
this.hotLoadChannel = fn;
|
|
299
|
-
}
|
|
300
484
|
setPermissionGateway(gateway) {
|
|
301
485
|
this.permissionGateway = gateway;
|
|
302
486
|
}
|
|
@@ -442,9 +626,9 @@ export class CommandHandler {
|
|
|
442
626
|
] : []),
|
|
443
627
|
...(isOwner ? [
|
|
444
628
|
{ cmd: '/file', label: '发送项目内文件', desc: '将项目目录内的文件发送给用户' },
|
|
445
|
-
{ cmd: '/aid', label: 'AID
|
|
446
|
-
{ value: 'list', label: '列表', desc: '
|
|
447
|
-
{ value: 'new', label: '创建', desc: '创建新 AID
|
|
629
|
+
{ cmd: '/aid', label: 'AID 身份管理', desc: '管理本地 AID 身份(创建/列表)', next: { type: 'select', items: [
|
|
630
|
+
{ value: 'list', label: '列表', desc: '列出本地所有 AID' },
|
|
631
|
+
{ value: 'new', label: '创建', desc: '创建新 AID 身份', next: { type: 'text' } },
|
|
448
632
|
] } },
|
|
449
633
|
{ cmd: '/agentmd', label: '管理 agent.md', desc: '查看或更新 AUN 网络上的 agent.md 身份文件', next: { type: 'select', items: [
|
|
450
634
|
{ value: 'put', label: '上传当前', desc: '将本地 agent.md 上传到 AUN 网络' },
|
|
@@ -495,7 +679,9 @@ export class CommandHandler {
|
|
|
495
679
|
return items;
|
|
496
680
|
}
|
|
497
681
|
if (cmd === '/p') {
|
|
498
|
-
|
|
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);
|
|
499
685
|
return Object.entries(list).map(([name, path]) => ({ value: name, label: name, desc: path }));
|
|
500
686
|
}
|
|
501
687
|
if (cmd === '/agent') {
|
|
@@ -511,8 +697,19 @@ export class CommandHandler {
|
|
|
511
697
|
return null;
|
|
512
698
|
}
|
|
513
699
|
if (cmd === '/restart') {
|
|
700
|
+
// /restart 是服务级操作(重连/重启进程),仅限 default 通道。
|
|
701
|
+
// EvolAgent 通道返回空菜单(用户在 agent-owned 通道上无可选项)
|
|
702
|
+
if (this.getOwningAgent(channel))
|
|
703
|
+
return [];
|
|
514
704
|
const isOwner = userId ? this.sessionManager.resolveIdentity(channel, userId).role === 'owner' : false;
|
|
515
|
-
|
|
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: '重连此类型所有渠道实例' }));
|
|
516
713
|
if (isOwner)
|
|
517
714
|
channels.unshift({ value: '', label: '重启服务', desc: '重启整个 EvolClaw 服务进程' });
|
|
518
715
|
return channels;
|
|
@@ -603,6 +800,20 @@ export class CommandHandler {
|
|
|
603
800
|
return '⚠️ 话题中不支持此命令';
|
|
604
801
|
}
|
|
605
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
|
+
}
|
|
606
817
|
// 权限检查:区分用户级命令和管理级命令
|
|
607
818
|
const isOwner = identity.role === 'owner';
|
|
608
819
|
const isAdmin = identity.role === 'owner' || identity.role === 'admin';
|
|
@@ -736,12 +947,12 @@ export class CommandHandler {
|
|
|
736
947
|
' /check - 检查渠道状态',
|
|
737
948
|
' /activity [all|dm|owner|none] - 查看/控制中间输出显示模式',
|
|
738
949
|
...(isAdmin ? [
|
|
739
|
-
' /restart <
|
|
950
|
+
' /restart <type> - 重连该类型所有渠道实例(服务级,admin+)',
|
|
740
951
|
] : []),
|
|
741
952
|
...(isOwner ? [
|
|
742
953
|
' /restart - 重启服务',
|
|
743
954
|
' /file [channel] <path> - 发送项目内文件',
|
|
744
|
-
' /aid [list|new <aid>] - AID
|
|
955
|
+
' /aid [list|new <aid>] - AID 身份管理',
|
|
745
956
|
' /agentmd [put|set <内容>] - 管理 agent.md',
|
|
746
957
|
] : []),
|
|
747
958
|
'',
|
|
@@ -1095,68 +1306,16 @@ export class CommandHandler {
|
|
|
1095
1306
|
modelAgent.setEffort?.(newEffort);
|
|
1096
1307
|
changes.push(`推理强度: ${newEffort}`);
|
|
1097
1308
|
}
|
|
1098
|
-
//
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
if (configuredInEvolclaw) {
|
|
1104
|
-
if (!this.config.agents.codex)
|
|
1105
|
-
this.config.agents.codex = {};
|
|
1106
|
-
if (newModel)
|
|
1107
|
-
this.config.agents.codex.model = newModel;
|
|
1108
|
-
if (newEffort)
|
|
1109
|
-
this.config.agents.codex.reasoning = newEffort;
|
|
1110
|
-
try {
|
|
1111
|
-
saveConfig(this.config);
|
|
1112
|
-
}
|
|
1113
|
-
catch (error) {
|
|
1114
|
-
return `⚠️ 写入 evolclaw.json 失败: ${error.message}\n已更新运行时配置,但未持久化`;
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
else {
|
|
1118
|
-
// Codex 全局配置(~/.codex/config.toml)目前不支持写入,回退到 evolclaw.json
|
|
1119
|
-
if (!this.config.agents.codex)
|
|
1120
|
-
this.config.agents.codex = {};
|
|
1121
|
-
if (newModel)
|
|
1122
|
-
this.config.agents.codex.model = newModel;
|
|
1123
|
-
if (newEffort)
|
|
1124
|
-
this.config.agents.codex.reasoning = newEffort;
|
|
1125
|
-
try {
|
|
1126
|
-
saveConfig(this.config);
|
|
1127
|
-
}
|
|
1128
|
-
catch (error) {
|
|
1129
|
-
return `⚠️ 写入 evolclaw.json 失败: ${error.message}\n已更新运行时配置,但未持久化`;
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
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已更新运行时配置,但未持久化`;
|
|
1132
1314
|
}
|
|
1133
|
-
|
|
1134
|
-
const
|
|
1135
|
-
if (
|
|
1136
|
-
|
|
1137
|
-
this.config.agents.claude = {};
|
|
1138
|
-
if (newModel)
|
|
1139
|
-
this.config.agents.claude.model = newModel;
|
|
1140
|
-
if (newEffort)
|
|
1141
|
-
this.config.agents.claude.effort = newEffort;
|
|
1142
|
-
try {
|
|
1143
|
-
saveConfig(this.config);
|
|
1144
|
-
}
|
|
1145
|
-
catch (error) {
|
|
1146
|
-
return `⚠️ 写入 evolclaw.json 失败: ${error.message}\n已更新运行时配置,但未持久化`;
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
else {
|
|
1150
|
-
const updates = {};
|
|
1151
|
-
if (newModel)
|
|
1152
|
-
updates.model = newModel;
|
|
1153
|
-
if (newEffort)
|
|
1154
|
-
updates.effortLevel = newEffort;
|
|
1155
|
-
const writeResult = writeUserSettings(updates);
|
|
1156
|
-
if (!writeResult.success) {
|
|
1157
|
-
return `⚠️ 写入用户配置失败: ${writeResult.error}\n已更新运行时配置,但未持久化到 ~/.claude/settings.json`;
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1315
|
+
if (newEffort) {
|
|
1316
|
+
const err = this.persistBaseagentEffort(channel, modelAgent.name, newEffort);
|
|
1317
|
+
if (err)
|
|
1318
|
+
return `${err}\n已更新运行时配置,但未持久化`;
|
|
1160
1319
|
}
|
|
1161
1320
|
return `✓ 已切换\n ${changes.join('\n ')}`;
|
|
1162
1321
|
}
|
|
@@ -1235,29 +1394,9 @@ export class CommandHandler {
|
|
|
1235
1394
|
// /effort auto:恢复 SDK 默认
|
|
1236
1395
|
if (args === 'auto') {
|
|
1237
1396
|
effortAgent.setEffort?.(undefined);
|
|
1238
|
-
const
|
|
1239
|
-
if (
|
|
1240
|
-
|
|
1241
|
-
delete this.config.agents.codex.reasoning;
|
|
1242
|
-
try {
|
|
1243
|
-
saveConfig(this.config);
|
|
1244
|
-
}
|
|
1245
|
-
catch { }
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
else {
|
|
1249
|
-
const configuredInEvolclaw = !!this.config.agents?.claude?.effort;
|
|
1250
|
-
if (configuredInEvolclaw) {
|
|
1251
|
-
delete this.config.agents.claude.effort;
|
|
1252
|
-
try {
|
|
1253
|
-
saveConfig(this.config);
|
|
1254
|
-
}
|
|
1255
|
-
catch { }
|
|
1256
|
-
}
|
|
1257
|
-
else {
|
|
1258
|
-
writeUserSettings({ effortLevel: null });
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1397
|
+
const err = this.persistBaseagentEffort(channel, effortAgent.name, undefined);
|
|
1398
|
+
if (err)
|
|
1399
|
+
return `${err}\n已更新运行时配置,但未持久化`;
|
|
1261
1400
|
return '✓ 推理强度已恢复为 auto (SDK默认)';
|
|
1262
1401
|
}
|
|
1263
1402
|
// /effort <level>:切换推理强度
|
|
@@ -1269,117 +1408,56 @@ export class CommandHandler {
|
|
|
1269
1408
|
}
|
|
1270
1409
|
const newEffort = args;
|
|
1271
1410
|
effortAgent.setEffort?.(newEffort);
|
|
1272
|
-
|
|
1273
|
-
if (
|
|
1274
|
-
|
|
1275
|
-
const isCodex = effortAgent.name === 'codex';
|
|
1276
|
-
if (isCodex) {
|
|
1277
|
-
if (!this.config.agents.codex)
|
|
1278
|
-
this.config.agents.codex = {};
|
|
1279
|
-
this.config.agents.codex.reasoning = newEffort;
|
|
1280
|
-
try {
|
|
1281
|
-
saveConfig(this.config);
|
|
1282
|
-
}
|
|
1283
|
-
catch { }
|
|
1284
|
-
}
|
|
1285
|
-
else {
|
|
1286
|
-
const configuredInEvolclaw = !!(this.config.agents?.claude?.model || this.config.agents?.claude?.effort);
|
|
1287
|
-
if (configuredInEvolclaw) {
|
|
1288
|
-
if (!this.config.agents.claude)
|
|
1289
|
-
this.config.agents.claude = {};
|
|
1290
|
-
this.config.agents.claude.effort = newEffort;
|
|
1291
|
-
try {
|
|
1292
|
-
saveConfig(this.config);
|
|
1293
|
-
}
|
|
1294
|
-
catch { }
|
|
1295
|
-
}
|
|
1296
|
-
else {
|
|
1297
|
-
writeUserSettings({ effortLevel: newEffort });
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1411
|
+
const err = this.persistBaseagentEffort(channel, effortAgent.name, newEffort);
|
|
1412
|
+
if (err)
|
|
1413
|
+
return `${err}\n已更新运行时配置,但未持久化`;
|
|
1300
1414
|
return `✓ 推理强度: ${newEffort}`;
|
|
1301
1415
|
}
|
|
1302
|
-
// /aid 命令:AID
|
|
1416
|
+
// /aid 命令:AID 身份管理(list / new)
|
|
1303
1417
|
if (normalizedContent === '/aid' || normalizedContent === '/aid list' || normalizedContent.startsWith('/aid ')) {
|
|
1304
1418
|
if (!isOwner)
|
|
1305
1419
|
return '❌ 无权限:此命令仅限 owner 使用';
|
|
1306
|
-
const adapter = this.adapters.get(channel);
|
|
1307
|
-
const channelType = this.channelTypeMap.get(channel);
|
|
1308
|
-
if (channelType !== 'aun')
|
|
1309
|
-
return '❌ 此命令仅在 AUN 通道中可用';
|
|
1310
1420
|
const arg = normalizedContent.slice(4).trim();
|
|
1311
|
-
|
|
1421
|
+
const { aidList, aidCreate, agentmdPut, buildInitialAgentMd, isValidAid } = await import('../channels/aun-ops.js');
|
|
1422
|
+
// /aid 或 /aid list — 列出本地所有 AID
|
|
1312
1423
|
if (!arg || arg === 'list') {
|
|
1313
|
-
const
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
const
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
const state = connected ? '已连接' : '未连接';
|
|
1326
|
-
lines.push(` ${icon} ${inst.name} ${inst.aid} ${state}`);
|
|
1327
|
-
}
|
|
1424
|
+
const aids = aidList();
|
|
1425
|
+
if (aids.length === 0)
|
|
1426
|
+
return '本地无 AID';
|
|
1427
|
+
const lines = ['本地 AID:'];
|
|
1428
|
+
for (const a of aids) {
|
|
1429
|
+
const icons = [
|
|
1430
|
+
a.hasPrivateKey ? '🔑' : ' ',
|
|
1431
|
+
a.hasAgentMd ? '📄' : ' ',
|
|
1432
|
+
].join('');
|
|
1433
|
+
lines.push(` ${icons} ${a.aid}`);
|
|
1434
|
+
}
|
|
1435
|
+
lines.push('\n🔑=私钥 📄=agent.md');
|
|
1328
1436
|
return lines.join('\n');
|
|
1329
1437
|
}
|
|
1330
|
-
// /aid new <aid> —
|
|
1438
|
+
// /aid new <aid> — 创建 AID(纯身份,不动 config)
|
|
1331
1439
|
if (arg.startsWith('new ')) {
|
|
1332
|
-
const
|
|
1333
|
-
if (!
|
|
1334
|
-
return '用法: /aid new
|
|
1335
|
-
if (!
|
|
1336
|
-
return
|
|
1337
|
-
// Derive full AID: if no dots, append domain from current AID
|
|
1338
|
-
const selfAid = typeof adapter._selfAid === 'function' ? adapter._selfAid() : '';
|
|
1339
|
-
let fullAid = rawName;
|
|
1340
|
-
if (!rawName.includes('.')) {
|
|
1341
|
-
const domain = selfAid.split('.').slice(1).join('.');
|
|
1342
|
-
if (!domain)
|
|
1343
|
-
return '❌ 无法推导 AID 域(当前实例未连接)';
|
|
1344
|
-
fullAid = `${rawName}.${domain}`;
|
|
1345
|
-
}
|
|
1346
|
-
// Validate AID format
|
|
1347
|
-
const { isValidAid } = await import('../utils/init-channel.js');
|
|
1348
|
-
if (!isValidAid(fullAid))
|
|
1349
|
-
return `❌ 无效 AID 格式: ${fullAid}`;
|
|
1350
|
-
// Check instance name conflict
|
|
1351
|
-
const instName = rawName.includes('.') ? rawName.split('.')[0] : rawName;
|
|
1352
|
-
const { normalizeChannelInstances } = await import('../config.js');
|
|
1353
|
-
const existing = normalizeChannelInstances(this.config.channels?.aun, 'aun');
|
|
1354
|
-
if (existing.some(e => e.name === instName)) {
|
|
1355
|
-
return `❌ 实例名 "${instName}" 已存在`;
|
|
1356
|
-
}
|
|
1357
|
-
if (existing.some(e => e.aid === fullAid)) {
|
|
1358
|
-
return `❌ AID ${fullAid} 已在配置中`;
|
|
1359
|
-
}
|
|
1360
|
-
// Create AID (reuse init-channel.ts silent logic)
|
|
1440
|
+
const rawAid = arg.slice(4).trim();
|
|
1441
|
+
if (!rawAid)
|
|
1442
|
+
return '用法: /aid new <完整AID>\n例: /aid new reviewer.agentid.pub';
|
|
1443
|
+
if (!isValidAid(rawAid))
|
|
1444
|
+
return `❌ 无效 AID 格式: ${rawAid}`;
|
|
1361
1445
|
try {
|
|
1362
|
-
const
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
return '❌ 通道实例创建失败';
|
|
1378
|
-
await this.hotLoadChannel(newInstances[0]);
|
|
1379
|
-
// Write config only after successful hot-load
|
|
1380
|
-
appendAunInstance(this.config, { name: instName, aid: fullAid, owner });
|
|
1381
|
-
const verb = createResult.alreadyExisted ? '已存在,现已上线' : '已创建并上线';
|
|
1382
|
-
return `✓ ${fullAid} ${verb}\n 实例名: ${instName}\n 可在 AUN 中搜索该 AID 开始对话`;
|
|
1446
|
+
const result = await aidCreate(rawAid);
|
|
1447
|
+
if (!result.alreadyExisted) {
|
|
1448
|
+
const content = buildInitialAgentMd({ aid: rawAid });
|
|
1449
|
+
try {
|
|
1450
|
+
await agentmdPut(content, { aid: rawAid, client: result.client });
|
|
1451
|
+
}
|
|
1452
|
+
catch { /* non-fatal */ }
|
|
1453
|
+
}
|
|
1454
|
+
try {
|
|
1455
|
+
await result.client.close();
|
|
1456
|
+
}
|
|
1457
|
+
catch { /* ignore */ }
|
|
1458
|
+
const verb = result.alreadyExisted ? '已存在' : '已创建';
|
|
1459
|
+
return `✓ ${rawAid} ${verb}
|
|
1460
|
+
如需上线 AUN 通道,运行 evolclaw init aun`;
|
|
1383
1461
|
}
|
|
1384
1462
|
catch (e) {
|
|
1385
1463
|
return `❌ 创建失败: ${String(e.message || e).slice(0, 200)}`;
|
|
@@ -1387,7 +1465,7 @@ export class CommandHandler {
|
|
|
1387
1465
|
}
|
|
1388
1466
|
return '用法: /aid [list|new <aid>]';
|
|
1389
1467
|
}
|
|
1390
|
-
// /
|
|
1468
|
+
// /agentmd 命令:管理 agent.md 身份文件
|
|
1391
1469
|
if (normalizedContent === '/agentmd' || normalizedContent.startsWith('/agentmd ')) {
|
|
1392
1470
|
if (!isOwner)
|
|
1393
1471
|
return '❌ 无权限:此命令仅限 owner 使用';
|
|
@@ -1396,7 +1474,8 @@ export class CommandHandler {
|
|
|
1396
1474
|
return '❌ 当前通道不支持 agent.md 操作';
|
|
1397
1475
|
const selfAid = typeof adapter._selfAid === 'function' ? adapter._selfAid() : '';
|
|
1398
1476
|
const arg = normalizedContent.slice(9).trim();
|
|
1399
|
-
|
|
1477
|
+
const { agentmdGet, agentmdPut } = await import('../channels/aun-ops.js');
|
|
1478
|
+
// put — read local agent.md and upload to network
|
|
1400
1479
|
if (arg === 'put') {
|
|
1401
1480
|
if (!selfAid)
|
|
1402
1481
|
return '❌ 未连接,无法确定本地 AID';
|
|
@@ -1405,15 +1484,17 @@ export class CommandHandler {
|
|
|
1405
1484
|
const { join } = await import('node:path');
|
|
1406
1485
|
const { homedir } = await import('node:os');
|
|
1407
1486
|
const localPath = join(homedir(), '.aun', 'AIDs', selfAid, 'agent.md');
|
|
1487
|
+
if (!readFileSync)
|
|
1488
|
+
return '❌ 读取失败';
|
|
1408
1489
|
const content = readFileSync(localPath, 'utf-8');
|
|
1409
|
-
await
|
|
1490
|
+
await agentmdPut(content, { aid: selfAid });
|
|
1410
1491
|
return '✅ agent.md 已发布';
|
|
1411
1492
|
}
|
|
1412
1493
|
catch (e) {
|
|
1413
1494
|
return `❌ 发布失败: ${String(e.message || e).slice(0, 100)}`;
|
|
1414
1495
|
}
|
|
1415
1496
|
}
|
|
1416
|
-
// set <content> — upload inline content
|
|
1497
|
+
// set <content> — upload inline content
|
|
1417
1498
|
if (arg.startsWith('set ')) {
|
|
1418
1499
|
const content = arg.slice(4).trim();
|
|
1419
1500
|
if (!content)
|
|
@@ -1421,13 +1502,7 @@ export class CommandHandler {
|
|
|
1421
1502
|
if (!selfAid)
|
|
1422
1503
|
return '❌ 未连接,无法确定本地 AID';
|
|
1423
1504
|
try {
|
|
1424
|
-
await
|
|
1425
|
-
const { writeFileSync, mkdirSync } = await import('node:fs');
|
|
1426
|
-
const { join } = await import('node:path');
|
|
1427
|
-
const { homedir } = await import('node:os');
|
|
1428
|
-
const localDir = join(homedir(), '.aun', 'AIDs', selfAid);
|
|
1429
|
-
mkdirSync(localDir, { recursive: true });
|
|
1430
|
-
writeFileSync(join(localDir, 'agent.md'), content, 'utf-8');
|
|
1505
|
+
await agentmdPut(content, { aid: selfAid });
|
|
1431
1506
|
return '✅ agent.md 已更新并发布到AUN网络';
|
|
1432
1507
|
}
|
|
1433
1508
|
catch (e) {
|
|
@@ -1439,7 +1514,7 @@ export class CommandHandler {
|
|
|
1439
1514
|
if (!aidToView)
|
|
1440
1515
|
return '用法:/agentmd [<aid>] | put | set <内容>';
|
|
1441
1516
|
try {
|
|
1442
|
-
const md = await
|
|
1517
|
+
const md = await agentmdGet(aidToView);
|
|
1443
1518
|
if (!md || !md.trim())
|
|
1444
1519
|
return `ℹ️ ${aidToView} 尚未设置 agent.md`;
|
|
1445
1520
|
return `\`\`\`\n${md.slice(0, 1500)}\n\`\`\``;
|
|
@@ -1467,7 +1542,7 @@ export class CommandHandler {
|
|
|
1467
1542
|
owner: 'owner-dm-only',
|
|
1468
1543
|
none: 'none',
|
|
1469
1544
|
};
|
|
1470
|
-
const currentMode = getChannelShowActivities(this.config, channel);
|
|
1545
|
+
const currentMode = this.agentRegistry?.getShowActivities?.(channel) ?? getChannelShowActivities(this.config, channel);
|
|
1471
1546
|
// 模式描述列表(用于 body 和文本降级)
|
|
1472
1547
|
const modeDescriptions = [
|
|
1473
1548
|
{ key: 'all', configVal: 'all', label: '全部显示' },
|
|
@@ -1538,7 +1613,12 @@ export class CommandHandler {
|
|
|
1538
1613
|
// 切换操作仅 owner
|
|
1539
1614
|
if (!isOwner)
|
|
1540
1615
|
return '❌ 中间输出模式切换仅限 owner';
|
|
1541
|
-
|
|
1616
|
+
if (this.agentRegistry?.setShowActivities) {
|
|
1617
|
+
this.agentRegistry.setShowActivities(channel, newMode);
|
|
1618
|
+
}
|
|
1619
|
+
else {
|
|
1620
|
+
setChannelShowActivities(this.config, channel, newMode);
|
|
1621
|
+
}
|
|
1542
1622
|
return `✅ 中间输出模式: ${activityArg}(${label})`;
|
|
1543
1623
|
}
|
|
1544
1624
|
// /chatmode 命令:查看/切换 session 会话模式(interactive | proactive)
|
|
@@ -1596,7 +1676,12 @@ export class CommandHandler {
|
|
|
1596
1676
|
}
|
|
1597
1677
|
await stopAgent.interrupt(sessionKey);
|
|
1598
1678
|
// 发布中断事件,让 MessageProcessor 标记为 interrupted(而非 done)
|
|
1599
|
-
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
|
+
});
|
|
1600
1685
|
// 强制清除 processing_state
|
|
1601
1686
|
this.sessionManager.clearProcessing(sessionKey);
|
|
1602
1687
|
return '✓ 已发送中断信号,任务将尽快停止';
|
|
@@ -1669,7 +1754,7 @@ export class CommandHandler {
|
|
|
1669
1754
|
// 尝试获取活跃会话(话题时直接查找话题 session)
|
|
1670
1755
|
let session;
|
|
1671
1756
|
if (threadId) {
|
|
1672
|
-
session = await this.sessionManager.getOrCreateSession(channel, channelId, this.
|
|
1757
|
+
session = await this.sessionManager.getOrCreateSession(channel, channelId, this.getEffectiveDefaultPath(channel), threadId);
|
|
1673
1758
|
}
|
|
1674
1759
|
else {
|
|
1675
1760
|
session = await this.sessionManager.getActiveSession(channel, channelId);
|
|
@@ -1681,7 +1766,7 @@ export class CommandHandler {
|
|
|
1681
1766
|
normalizedContent.startsWith('/project') ||
|
|
1682
1767
|
normalizedContent === '/pwd' ||
|
|
1683
1768
|
normalizedContent === '/status')) {
|
|
1684
|
-
session = await this.sessionManager.getOrCreateSession(channel, channelId, this.
|
|
1769
|
+
session = await this.sessionManager.getOrCreateSession(channel, channelId, this.getEffectiveDefaultPath(channel));
|
|
1685
1770
|
}
|
|
1686
1771
|
// /status 命令:显示会话状态
|
|
1687
1772
|
if (normalizedContent === '/status') {
|
|
@@ -1750,7 +1835,7 @@ export class CommandHandler {
|
|
|
1750
1835
|
return `❌ 会话名称 "${sessionName}" 已存在,请使用其他名称`;
|
|
1751
1836
|
}
|
|
1752
1837
|
}
|
|
1753
|
-
const projectPath = session?.projectPath || this.
|
|
1838
|
+
const projectPath = session?.projectPath || this.getEffectiveDefaultPath(channel);
|
|
1754
1839
|
const newSession = await this.sessionManager.createNewSession(channel, channelId, projectPath, sessionName, session?.agentId || this.defaultAgentId);
|
|
1755
1840
|
this.eventBus.publish({
|
|
1756
1841
|
type: 'session:created',
|
|
@@ -1772,11 +1857,30 @@ export class CommandHandler {
|
|
|
1772
1857
|
// /check 命令:检查渠道状态(guest 可用,详情仅 admin)/ 重连指定渠道(admin only)
|
|
1773
1858
|
if (normalizedContent === '/check' || normalizedContent.startsWith('/check ')) {
|
|
1774
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
|
+
}
|
|
1775
1877
|
// Default: show system health check (non-admin 仅看摘要)
|
|
1776
1878
|
const lines = ['📡 渠道状态:'];
|
|
1777
1879
|
// Group by channelType
|
|
1778
1880
|
const groups = new Map();
|
|
1779
1881
|
for (const [name] of this.adapters) {
|
|
1882
|
+
if (!allowedChannels.has(name))
|
|
1883
|
+
continue;
|
|
1780
1884
|
const type = this.channelTypeMap.get(name) || name;
|
|
1781
1885
|
const ch = this.channelObjects.get(name);
|
|
1782
1886
|
let status;
|
|
@@ -1807,19 +1911,21 @@ export class CommandHandler {
|
|
|
1807
1911
|
lines.push(` ${type}: [${parts.join(', ')}]`);
|
|
1808
1912
|
}
|
|
1809
1913
|
}
|
|
1810
|
-
//
|
|
1914
|
+
// 当前 agent 名(用于 agent 维度 stats / queue 查询)
|
|
1915
|
+
const currentAgentName = checkOwningAgent?.name ?? '[default]';
|
|
1916
|
+
// 队列状态(按当前 agent 维度)
|
|
1811
1917
|
lines.push('', '📬 队列状态:');
|
|
1812
|
-
lines.push(` 待处理消息: ${this.messageQueue.
|
|
1813
|
-
lines.push(` 处理中队列: ${this.messageQueue.
|
|
1814
|
-
//
|
|
1918
|
+
lines.push(` 待处理消息: ${this.messageQueue.getQueueLengthByAgent(currentAgentName)}`);
|
|
1919
|
+
lines.push(` 处理中队列: ${this.messageQueue.getProcessingCountByAgent(currentAgentName)}`);
|
|
1920
|
+
// 运行概况(全局,进程级)
|
|
1815
1921
|
lines.push('', '🖥️ 运行概况:');
|
|
1816
1922
|
const uptimeMs = this.statsCollector
|
|
1817
1923
|
? this.statsCollector.getSnapshot().uptimeMs
|
|
1818
1924
|
: process.uptime() * 1000;
|
|
1819
1925
|
lines.push(` 运行时间: ${this.formatUptime(uptimeMs)}`);
|
|
1820
|
-
// 近 1
|
|
1926
|
+
// 近 1 小时统计(按当前 agent 维度)
|
|
1821
1927
|
if (this.statsCollector) {
|
|
1822
|
-
const snap = this.statsCollector.getSnapshot();
|
|
1928
|
+
const snap = this.statsCollector.getSnapshot(currentAgentName);
|
|
1823
1929
|
const h = snap.lastHour;
|
|
1824
1930
|
lines.push('', '📊 近 1 小时统计:');
|
|
1825
1931
|
lines.push(` 收到消息: ${h.received}`);
|
|
@@ -1845,23 +1951,50 @@ export class CommandHandler {
|
|
|
1845
1951
|
// /restart 命令:重启服务(owner only) / 重连指定渠道(admin+)
|
|
1846
1952
|
if (normalizedContent === '/restart' || normalizedContent.startsWith('/restart ')) {
|
|
1847
1953
|
const restartArg = normalizedContent.slice('/restart'.length).trim();
|
|
1848
|
-
// /restart <
|
|
1954
|
+
// /restart <type> — 重连指定类型的所有渠道(admin only,evolclaw 服务级操作)
|
|
1955
|
+
// 服务级操作仅可从 default 通道发起,避免 evolagent owner/admin 越权
|
|
1849
1956
|
if (restartArg) {
|
|
1957
|
+
if (this.getOwningAgent(channel)) {
|
|
1958
|
+
return '❌ 渠道重连只能从 DefaultAgent 通道发起(服务级操作)';
|
|
1959
|
+
}
|
|
1850
1960
|
if (!isAdmin)
|
|
1851
1961
|
return '❌ 无权限:渠道重连仅限管理员使用';
|
|
1852
|
-
const
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
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
|
+
}
|
|
1860
1990
|
}
|
|
1861
|
-
|
|
1862
|
-
|
|
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> 重连特定类型渠道';
|
|
1863
1997
|
}
|
|
1864
|
-
// /restart(无参数)— 重启整个服务(owner only)
|
|
1865
1998
|
if (!isOwner)
|
|
1866
1999
|
return '❌ 无权限:服务重启仅限 owner 使用';
|
|
1867
2000
|
const allSessions = await this.sessionManager.listSessions(channel, channelId);
|
|
@@ -1875,7 +2008,7 @@ export class CommandHandler {
|
|
|
1875
2008
|
const executeRestart = async () => {
|
|
1876
2009
|
let replyContext;
|
|
1877
2010
|
if (threadId) {
|
|
1878
|
-
const threadSession = await this.sessionManager.getOrCreateSession(channel, channelId, this.
|
|
2011
|
+
const threadSession = await this.sessionManager.getOrCreateSession(channel, channelId, this.getEffectiveDefaultPath(channel), threadId);
|
|
1879
2012
|
replyContext = this.getReplyContext(threadSession);
|
|
1880
2013
|
}
|
|
1881
2014
|
const restartInfo = {
|
|
@@ -2014,7 +2147,7 @@ export class CommandHandler {
|
|
|
2014
2147
|
// 找目标 channelId
|
|
2015
2148
|
let targetChannelId = channelId;
|
|
2016
2149
|
if (isCrossChannel) {
|
|
2017
|
-
const ownerPeerId = getOwner(this.config, targetChannel);
|
|
2150
|
+
const ownerPeerId = this.agentRegistry?.getOwner?.(targetChannel) ?? getOwner(this.config, targetChannel);
|
|
2018
2151
|
targetChannelId = ownerPeerId ? (this.sessionManager.getOwnerChatId(targetChannel, ownerPeerId) ?? '') : '';
|
|
2019
2152
|
if (!targetChannelId) {
|
|
2020
2153
|
return `❌ 未找到 ${targetLabel} 的私聊会话,请先在该通道发送一条消息`;
|
|
@@ -2269,27 +2402,19 @@ export class CommandHandler {
|
|
|
2269
2402
|
}
|
|
2270
2403
|
// 生成项目名称(使用目录名)
|
|
2271
2404
|
const projectName = path.basename(projectPath);
|
|
2272
|
-
//
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2405
|
+
// 检查在当前 scope 内是否已存在
|
|
2406
|
+
const scopeProjects = this.getEffectiveProjects(channel);
|
|
2407
|
+
const existing = scopeProjects[projectName];
|
|
2408
|
+
if (existing) {
|
|
2409
|
+
if (existing === projectPath) {
|
|
2276
2410
|
return `项目 "${projectName}" 已存在\n 路径: ${projectPath}\n\n使用 /p ${projectName} 切换到该项目`;
|
|
2277
2411
|
}
|
|
2278
|
-
return `❌ 项目名称 "${projectName}" 已被占用\n 现有路径: ${
|
|
2279
|
-
}
|
|
2280
|
-
// 添加到配置
|
|
2281
|
-
if (!this.config.projects) {
|
|
2282
|
-
this.config.projects = { defaultPath: process.cwd(), autoCreate: false, list: {} };
|
|
2283
|
-
}
|
|
2284
|
-
if (!this.config.projects.list) {
|
|
2285
|
-
this.config.projects.list = {};
|
|
2412
|
+
return `❌ 项目名称 "${projectName}" 已被占用\n 现有路径: ${existing}\n 新路径: ${projectPath}\n\n请重命名目录或手动编辑配置文件`;
|
|
2286
2413
|
}
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
// 更新内存中的项目列表
|
|
2292
|
-
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;
|
|
2293
2418
|
return `✓ 已添加项目: ${projectName}\n 路径: ${projectPath}\n\n使用 /p ${projectName} 切换到该项目`;
|
|
2294
2419
|
}
|
|
2295
2420
|
// /slist 命令:列出当前项目的会话
|
|
@@ -2530,11 +2655,11 @@ export class CommandHandler {
|
|
|
2530
2655
|
return `❌ 序号超出范围 (1-${visibleSessions.length})\n使用 /s 查看可用会话`;
|
|
2531
2656
|
}
|
|
2532
2657
|
}
|
|
2533
|
-
if (!targetSession && sessionName.length
|
|
2658
|
+
if (!targetSession && sessionName.length >= 8) {
|
|
2534
2659
|
targetSession = await this.sessionManager.getSessionByUuidPrefix(channel, channelId, sessionName);
|
|
2535
2660
|
}
|
|
2536
2661
|
const canImport = policy.canImportCliSession(session?.chatType || 'private', identity.role);
|
|
2537
|
-
if (!targetSession && sessionName.length
|
|
2662
|
+
if (!targetSession && sessionName.length >= 8 && canImport) {
|
|
2538
2663
|
const projectPaths = Object.values(this.projects);
|
|
2539
2664
|
if (session) {
|
|
2540
2665
|
projectPaths.unshift(session.projectPath);
|
|
@@ -2637,7 +2762,7 @@ export class CommandHandler {
|
|
|
2637
2762
|
return `❌ 序号超出范围 (1-${visibleSessions.length})\n使用 /s 查看可用会话`;
|
|
2638
2763
|
}
|
|
2639
2764
|
}
|
|
2640
|
-
if (!targetSession && sessionName.length
|
|
2765
|
+
if (!targetSession && sessionName.length >= 8) {
|
|
2641
2766
|
targetSession = await this.sessionManager.getSessionByUuidPrefix(channel, channelId, sessionName);
|
|
2642
2767
|
}
|
|
2643
2768
|
if (!targetSession) {
|
|
@@ -2881,7 +3006,7 @@ export class CommandHandler {
|
|
|
2881
3006
|
'/help', '/status', '/check', '/pwd',
|
|
2882
3007
|
'/model', '/effort', '/perm', '/agent',
|
|
2883
3008
|
'/compact', '/activity', '/file', '/send', '/chatmode', '/restart', '/agentmd', '/bind', '/aid',
|
|
2884
|
-
'/rename', '/name',
|
|
3009
|
+
'/rename', '/name', '/evolagent',
|
|
2885
3010
|
];
|
|
2886
3011
|
/** ctl 中仅允许查询形态的指令;写形态(带参)一律拒绝 */
|
|
2887
3012
|
static CTL_READONLY = new Set(['/agent']);
|
|
@@ -2935,6 +3060,48 @@ export class CommandHandler {
|
|
|
2935
3060
|
}
|
|
2936
3061
|
// 3. 从 session.metadata.peerId 获取 userId(用于权限判断)
|
|
2937
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
|
+
}
|
|
2938
3105
|
// 4. /send 文本消息:直接通过 adapter 主动发送,不走 handle()
|
|
2939
3106
|
if (cmd.startsWith('/send ') || cmd === '/send') {
|
|
2940
3107
|
const text = cmd.startsWith('/send ') ? cmd.slice(6).trim() : '';
|