evolclaw 2.8.1 → 2.8.3
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/claude-runner.js +18 -7
- package/dist/agents/codex-runner.js +16 -5
- package/dist/agents/gemini-runner.js +15 -4
- package/dist/agents/templates.js +122 -0
- package/dist/channels/aun-ops.js +1 -1
- package/dist/channels/aun.js +22 -0
- package/dist/channels/qqbot.js +1 -1
- package/dist/channels/wechat.js +1 -1
- package/dist/cli.js +345 -48
- package/dist/config.js +152 -65
- package/dist/core/agent-loader.js +34 -19
- package/dist/core/agent-registry.js +287 -1
- package/dist/core/command-handler.js +643 -192
- package/dist/core/evolagent-registry.js +514 -0
- package/dist/core/evolagent.js +250 -1
- package/dist/core/message/message-bridge.js +23 -3
- package/dist/core/message/message-processor.js +64 -15
- package/dist/core/message/message-queue.js +61 -6
- package/dist/core/reload-hooks.js +87 -0
- package/dist/index.js +140 -21
- 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
|
@@ -104,7 +104,7 @@ function formatIdleTime(ms) {
|
|
|
104
104
|
return '刚刚';
|
|
105
105
|
}
|
|
106
106
|
// 支持的命令列表
|
|
107
|
-
const commands = ['/new', '/pwd', '/plist', '/project', '/bind', '/help', '/status', '/restart', '/model', '/effort', '/agent', '/slist', '/session', '/rename', '/stop', '/clear', '/compact', '/repair', '/safe', '/fork', '/del', '/perm', '/file', '/check', '/rewind', '/activity', '/aid', '/agentmd', '/chatmode', '/ask'];
|
|
107
|
+
const commands = ['/new', '/pwd', '/plist', '/project', '/bind', '/help', '/evolhelp', '/status', '/restart', '/model', '/setmodel', '/effort', '/agent', '/slist', '/session', '/rename', '/stop', '/clear', '/compact', '/repair', '/safe', '/fork', '/del', '/perm', '/file', '/check', '/rewind', '/activity', '/aid', '/agentmd', '/chatmode', '/ask', '/resume'];
|
|
108
108
|
// 命令别名映射
|
|
109
109
|
const aliases = {
|
|
110
110
|
'/p': '/project',
|
|
@@ -113,7 +113,7 @@ const aliases = {
|
|
|
113
113
|
'/rw': '/rewind'
|
|
114
114
|
};
|
|
115
115
|
// 命令快速路径前缀(所有命令都不进入消息队列)
|
|
116
|
-
const quickCommandPrefixes = ['/new', '/pwd', '/plist', '/project', '/bind', '/help', '/status', '/restart', '/model', '/effort', '/agent', '/slist', '/session', '/rename', '/repair', '/fork', '/stop', '/clear', '/compact', '/safe', '/del', '/perm', '/file', '/check', '/p ', '/s ', '/name', '/rewind', '/rw', '/rw ', '/activity', '/chatmode', '/aid', '/agentmd', '/ask'];
|
|
116
|
+
const quickCommandPrefixes = ['/new', '/pwd', '/plist', '/project', '/bind', '/help', '/evolhelp', '/status', '/restart', '/model', '/setmodel', '/effort', '/agent', '/slist', '/session', '/rename', '/repair', '/fork', '/stop', '/clear', '/compact', '/safe', '/del', '/perm', '/file', '/check', '/p ', '/s ', '/name', '/rewind', '/rw', '/rw ', '/activity', '/chatmode', '/aid', '/agentmd', '/ask', '/resume'];
|
|
117
117
|
export class CommandHandler {
|
|
118
118
|
sessionManager;
|
|
119
119
|
config;
|
|
@@ -130,11 +130,39 @@ export class CommandHandler {
|
|
|
130
130
|
statsCollector;
|
|
131
131
|
agentMap;
|
|
132
132
|
defaultAgentId;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
133
|
+
agentRegistry;
|
|
134
|
+
/**
|
|
135
|
+
* Get the runner for a (channel, baseagent) pair.
|
|
136
|
+
*
|
|
137
|
+
* Resolves the owning EvolAgent via the registry; falls back to default key.
|
|
138
|
+
* `baseagent` typically comes from `session.agentId` (e.g. 'claude').
|
|
139
|
+
*/
|
|
140
|
+
getAgent(channel, baseagent) {
|
|
141
|
+
if (channel && baseagent) {
|
|
142
|
+
const evolName = this.agentRegistry?.resolveByChannel(channel)?.name || '[default]';
|
|
143
|
+
const key = `${evolName}::${baseagent}`;
|
|
144
|
+
if (this.agentMap.has(key))
|
|
145
|
+
return this.agentMap.get(key);
|
|
146
|
+
}
|
|
147
|
+
if (this.agentMap.has(this.defaultAgentId))
|
|
148
|
+
return this.agentMap.get(this.defaultAgentId);
|
|
149
|
+
return this.agentMap.values().next().value;
|
|
150
|
+
}
|
|
151
|
+
/** Return the list of baseagents available to a given channel (per-EvolAgent isolation). */
|
|
152
|
+
getAvailableBaseagents(channel) {
|
|
153
|
+
const evolName = this.agentRegistry?.resolveByChannel(channel)?.name || '[default]';
|
|
154
|
+
const prefix = `${evolName}::`;
|
|
155
|
+
const result = [];
|
|
156
|
+
for (const key of this.agentMap.keys()) {
|
|
157
|
+
if (key.startsWith(prefix))
|
|
158
|
+
result.push(key.slice(prefix.length));
|
|
159
|
+
}
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
/** Extract the baseagent component from `defaultAgentId` (e.g. `[default]::claude` → `claude`). */
|
|
163
|
+
parseDefaultBaseagent() {
|
|
164
|
+
const idx = this.defaultAgentId.indexOf('::');
|
|
165
|
+
return idx >= 0 ? this.defaultAgentId.slice(idx + 2) : this.defaultAgentId;
|
|
138
166
|
}
|
|
139
167
|
constructor(sessionManager, agentRunnerOrMap, config, messageCache, eventBus, defaultAgentId) {
|
|
140
168
|
this.sessionManager = sessionManager;
|
|
@@ -143,12 +171,200 @@ export class CommandHandler {
|
|
|
143
171
|
this.eventBus = eventBus;
|
|
144
172
|
if (agentRunnerOrMap instanceof Map) {
|
|
145
173
|
this.agentMap = agentRunnerOrMap;
|
|
146
|
-
this.defaultAgentId = defaultAgentId || 'claude';
|
|
174
|
+
this.defaultAgentId = defaultAgentId || '[default]::claude';
|
|
147
175
|
}
|
|
148
176
|
else {
|
|
149
|
-
|
|
150
|
-
this.
|
|
177
|
+
// Backward-compat single-runner path: treat as DefaultAgent's claude.
|
|
178
|
+
this.agentMap = new Map([[`[default]::${agentRunnerOrMap.name}`, agentRunnerOrMap]]);
|
|
179
|
+
this.defaultAgentId = `[default]::${agentRunnerOrMap.name}`;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/** 注入 EvolAgentRegistry,用于判断通道是否被 EvolAgent 管理 */
|
|
183
|
+
setAgentRegistry(registry) {
|
|
184
|
+
this.agentRegistry = registry;
|
|
185
|
+
}
|
|
186
|
+
/** 返回管理当前通道的 EvolAgent(非 default),无则返回 null */
|
|
187
|
+
getOwningAgent(channel) {
|
|
188
|
+
if (!this.agentRegistry)
|
|
189
|
+
return null;
|
|
190
|
+
const agent = this.agentRegistry.resolveByChannel(channel);
|
|
191
|
+
if (!agent || agent.isDefault)
|
|
192
|
+
return null;
|
|
193
|
+
return agent;
|
|
194
|
+
}
|
|
195
|
+
/** 返回当前通道的有效项目路径:agent-owned 用 agent.projectPath;否则用全局 defaultPath。*/
|
|
196
|
+
getEffectiveDefaultPath(channel) {
|
|
197
|
+
const owning = this.getOwningAgent(channel);
|
|
198
|
+
if (owning)
|
|
199
|
+
return owning.projectPath;
|
|
200
|
+
return this.config.projects?.defaultPath || process.cwd();
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* 返回当前通道有效的 projects.list(agent-owned 用 agent.json 的;否则全局 evolclaw.json 的)。
|
|
204
|
+
* 都没配 list 时回退到 defaultPath 单项目。
|
|
205
|
+
*/
|
|
206
|
+
getEffectiveProjects(channel) {
|
|
207
|
+
const owning = this.getOwningAgent(channel);
|
|
208
|
+
if (owning) {
|
|
209
|
+
return owning.getProjects();
|
|
210
|
+
}
|
|
211
|
+
return this.projects;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* 添加项目到当前通道范围(agent-owned 写 agent.json;default 写 evolclaw.json)。
|
|
215
|
+
*/
|
|
216
|
+
async addProjectInScope(channel, name, projectPath) {
|
|
217
|
+
const owning = this.getOwningAgent(channel);
|
|
218
|
+
if (owning) {
|
|
219
|
+
try {
|
|
220
|
+
owning.addProject(name, projectPath);
|
|
221
|
+
}
|
|
222
|
+
catch (e) {
|
|
223
|
+
return `⚠️ 写入 agent.json 失败: ${e?.message || e}`;
|
|
224
|
+
}
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
if (!this.config.projects) {
|
|
228
|
+
this.config.projects = { defaultPath: process.cwd(), autoCreate: false, list: {} };
|
|
229
|
+
}
|
|
230
|
+
if (!this.config.projects.list) {
|
|
231
|
+
this.config.projects.list = {};
|
|
232
|
+
}
|
|
233
|
+
this.config.projects.list[name] = projectPath;
|
|
234
|
+
try {
|
|
235
|
+
const { saveConfig } = await import('../config.js');
|
|
236
|
+
saveConfig(this.config);
|
|
237
|
+
}
|
|
238
|
+
catch (e) {
|
|
239
|
+
return `⚠️ 写入 evolclaw.json 失败: ${e?.message || e}`;
|
|
240
|
+
}
|
|
241
|
+
// Refresh in-memory list cache (this.projects getter reads from this.config)
|
|
242
|
+
return undefined;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* 持久化 baseagent.model:agent-owned 写到 agent.json;否则写 evolclaw.json 或 ~/.claude/settings.json。
|
|
246
|
+
* 返回错误信息或 undefined。
|
|
247
|
+
*/
|
|
248
|
+
persistBaseagentModel(channel, baseagentName, newModel) {
|
|
249
|
+
const owning = this.getOwningAgent(channel);
|
|
250
|
+
if (owning) {
|
|
251
|
+
try {
|
|
252
|
+
owning.setBaseagentModel(newModel);
|
|
253
|
+
}
|
|
254
|
+
catch (e) {
|
|
255
|
+
return `⚠️ 写入 agent.json 失败: ${e?.message || e}`;
|
|
256
|
+
}
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
// DefaultAgent / 无 owning agent:保留原"就近原则"
|
|
260
|
+
if (!this.config.agents)
|
|
261
|
+
this.config.agents = {};
|
|
262
|
+
const isCodex = baseagentName === 'codex';
|
|
263
|
+
if (isCodex) {
|
|
264
|
+
if (!this.config.agents.codex)
|
|
265
|
+
this.config.agents.codex = {};
|
|
266
|
+
if (newModel)
|
|
267
|
+
this.config.agents.codex.model = newModel;
|
|
268
|
+
try {
|
|
269
|
+
saveConfig(this.config);
|
|
270
|
+
}
|
|
271
|
+
catch (e) {
|
|
272
|
+
return `⚠️ 写入 evolclaw.json 失败: ${e.message}`;
|
|
273
|
+
}
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
276
|
+
const configuredInEvolclaw = !!(this.config.agents?.claude?.model || this.config.agents?.claude?.effort);
|
|
277
|
+
if (configuredInEvolclaw) {
|
|
278
|
+
if (!this.config.agents.claude)
|
|
279
|
+
this.config.agents.claude = {};
|
|
280
|
+
if (newModel)
|
|
281
|
+
this.config.agents.claude.model = newModel;
|
|
282
|
+
try {
|
|
283
|
+
saveConfig(this.config);
|
|
284
|
+
}
|
|
285
|
+
catch (e) {
|
|
286
|
+
return `⚠️ 写入 evolclaw.json 失败: ${e.message}`;
|
|
287
|
+
}
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
// Fallback: ~/.claude/settings.json
|
|
291
|
+
const updates = {};
|
|
292
|
+
if (newModel)
|
|
293
|
+
updates.model = newModel;
|
|
294
|
+
const writeResult = writeUserSettings(updates);
|
|
295
|
+
if (!writeResult.success) {
|
|
296
|
+
return `⚠️ 写入用户配置失败: ${writeResult.error}`;
|
|
297
|
+
}
|
|
298
|
+
return undefined;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* 持久化 baseagent.effort:agent-owned 写到 agent.json;否则就近原则。
|
|
302
|
+
*/
|
|
303
|
+
persistBaseagentEffort(channel, baseagentName, newEffort) {
|
|
304
|
+
const owning = this.getOwningAgent(channel);
|
|
305
|
+
if (owning) {
|
|
306
|
+
try {
|
|
307
|
+
owning.setBaseagentEffort(newEffort);
|
|
308
|
+
}
|
|
309
|
+
catch (e) {
|
|
310
|
+
return `⚠️ 写入 agent.json 失败: ${e?.message || e}`;
|
|
311
|
+
}
|
|
312
|
+
return undefined;
|
|
313
|
+
}
|
|
314
|
+
if (!this.config.agents)
|
|
315
|
+
this.config.agents = {};
|
|
316
|
+
const isCodex = baseagentName === 'codex';
|
|
317
|
+
if (isCodex) {
|
|
318
|
+
if (newEffort === undefined) {
|
|
319
|
+
if (this.config.agents.codex?.reasoning) {
|
|
320
|
+
delete this.config.agents.codex.reasoning;
|
|
321
|
+
try {
|
|
322
|
+
saveConfig(this.config);
|
|
323
|
+
}
|
|
324
|
+
catch { }
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
if (!this.config.agents.codex)
|
|
329
|
+
this.config.agents.codex = {};
|
|
330
|
+
this.config.agents.codex.reasoning = newEffort;
|
|
331
|
+
try {
|
|
332
|
+
saveConfig(this.config);
|
|
333
|
+
}
|
|
334
|
+
catch (e) {
|
|
335
|
+
return `⚠️ 写入 evolclaw.json 失败: ${e.message}`;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return undefined;
|
|
339
|
+
}
|
|
340
|
+
const configuredInEvolclaw = !!(this.config.agents?.claude?.model || this.config.agents?.claude?.effort);
|
|
341
|
+
if (configuredInEvolclaw) {
|
|
342
|
+
if (newEffort === undefined) {
|
|
343
|
+
delete this.config.agents.claude.effort;
|
|
344
|
+
try {
|
|
345
|
+
saveConfig(this.config);
|
|
346
|
+
}
|
|
347
|
+
catch { }
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
if (!this.config.agents.claude)
|
|
351
|
+
this.config.agents.claude = {};
|
|
352
|
+
this.config.agents.claude.effort = newEffort;
|
|
353
|
+
try {
|
|
354
|
+
saveConfig(this.config);
|
|
355
|
+
}
|
|
356
|
+
catch (e) {
|
|
357
|
+
return `⚠️ 写入 evolclaw.json 失败: ${e.message}`;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return undefined;
|
|
151
361
|
}
|
|
362
|
+
const updates = { effortLevel: newEffort ?? null };
|
|
363
|
+
const writeResult = writeUserSettings(updates);
|
|
364
|
+
if (!writeResult.success) {
|
|
365
|
+
return `⚠️ 写入用户配置失败: ${writeResult.error}`;
|
|
366
|
+
}
|
|
367
|
+
return undefined;
|
|
152
368
|
}
|
|
153
369
|
/** 项目列表快捷访问(list 缺失时用 defaultPath 作为唯一项目) */
|
|
154
370
|
get projects() {
|
|
@@ -491,14 +707,16 @@ export class CommandHandler {
|
|
|
491
707
|
return items;
|
|
492
708
|
}
|
|
493
709
|
if (cmd === '/p') {
|
|
494
|
-
|
|
710
|
+
// Use agent-scoped project list: agent-owned channels see their agent.json's
|
|
711
|
+
// projects.list; default channel sees evolclaw.json's projects.list
|
|
712
|
+
const list = this.getEffectiveProjects(channel);
|
|
495
713
|
return Object.entries(list).map(([name, path]) => ({ value: name, label: name, desc: path }));
|
|
496
714
|
}
|
|
497
715
|
if (cmd === '/agent') {
|
|
498
|
-
return
|
|
716
|
+
return this.getAvailableBaseagents(channel).map(name => ({ value: name, label: name }));
|
|
499
717
|
}
|
|
500
718
|
if (cmd === '/model') {
|
|
501
|
-
const agent = this.getAgent(session?.agentId);
|
|
719
|
+
const agent = this.getAgent(channel, session?.agentId);
|
|
502
720
|
if (hasModelSwitcher(agent) && agent.listModels) {
|
|
503
721
|
const models = await agent.listModels() ?? [];
|
|
504
722
|
if (models.length > 0)
|
|
@@ -507,8 +725,19 @@ export class CommandHandler {
|
|
|
507
725
|
return null;
|
|
508
726
|
}
|
|
509
727
|
if (cmd === '/restart') {
|
|
728
|
+
// /restart 是服务级操作(重连/重启进程),仅限 default 通道。
|
|
729
|
+
// EvolAgent 通道返回空菜单(用户在 agent-owned 通道上无可选项)
|
|
730
|
+
if (this.getOwningAgent(channel))
|
|
731
|
+
return [];
|
|
510
732
|
const isOwner = userId ? this.sessionManager.resolveIdentity(channel, userId).role === 'owner' : false;
|
|
511
|
-
|
|
733
|
+
// 列出所有 channel type
|
|
734
|
+
const visibleTypes = new Set();
|
|
735
|
+
for (const [name] of this.adapters) {
|
|
736
|
+
const t = this.channelTypeMap.get(name);
|
|
737
|
+
if (t)
|
|
738
|
+
visibleTypes.add(t);
|
|
739
|
+
}
|
|
740
|
+
const channels = [...visibleTypes].map(type => ({ value: type, label: type, desc: '重连此类型所有渠道实例' }));
|
|
512
741
|
if (isOwner)
|
|
513
742
|
channels.unshift({ value: '', label: '重启服务', desc: '重启整个 EvolClaw 服务进程' });
|
|
514
743
|
return channels;
|
|
@@ -536,7 +765,7 @@ export class CommandHandler {
|
|
|
536
765
|
const identity = this.sessionManager.resolveIdentity(channel, userId);
|
|
537
766
|
if (identity.role !== 'owner')
|
|
538
767
|
return { error: '无权限' };
|
|
539
|
-
const permAgent = this.getAgent(session.agentId);
|
|
768
|
+
const permAgent = this.getAgent(channel, session.agentId);
|
|
540
769
|
const validModes = hasPermissionController(permAgent)
|
|
541
770
|
? permAgent.listModes().filter(m => m.available).map(m => m.key)
|
|
542
771
|
: ['auto', 'bypass', 'plan', 'edit', 'request', 'noask'];
|
|
@@ -578,7 +807,7 @@ export class CommandHandler {
|
|
|
578
807
|
const policy = this.getPolicy(channel);
|
|
579
808
|
// 按当前会话选择 agent 后端
|
|
580
809
|
const activeSession = await this.sessionManager.getActiveSession(channel, channelId);
|
|
581
|
-
const agent = this.getAgent(activeSession?.agentId);
|
|
810
|
+
const agent = this.getAgent(channel, activeSession?.agentId);
|
|
582
811
|
// 规范化命令(将别名转换为完整命令)
|
|
583
812
|
let normalizedContent = content;
|
|
584
813
|
for (const [alias, full] of Object.entries(aliases)) {
|
|
@@ -599,6 +828,20 @@ export class CommandHandler {
|
|
|
599
828
|
return '⚠️ 话题中不支持此命令';
|
|
600
829
|
}
|
|
601
830
|
}
|
|
831
|
+
// Agent-owned 通道:禁止项目切换和 agent 切换
|
|
832
|
+
const owningAgent = this.getOwningAgent(channel);
|
|
833
|
+
if (owningAgent) {
|
|
834
|
+
const isProjectCmd = normalizedContent === '/project' || normalizedContent.startsWith('/project ') ||
|
|
835
|
+
normalizedContent === '/bind' || normalizedContent.startsWith('/bind ') ||
|
|
836
|
+
normalizedContent === '/plist' ||
|
|
837
|
+
normalizedContent === '/p' || normalizedContent.startsWith('/p ');
|
|
838
|
+
if (isProjectCmd) {
|
|
839
|
+
return `❌ 当前通道由 agent [${owningAgent.name}] 管理,项目已锁定为 ${owningAgent.projectPath}`;
|
|
840
|
+
}
|
|
841
|
+
if (normalizedContent.startsWith('/agent ')) {
|
|
842
|
+
return `❌ 当前通道由 agent [${owningAgent.name}] 管理,baseagent 已锁定为 ${owningAgent.baseagent}`;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
602
845
|
// 权限检查:区分用户级命令和管理级命令
|
|
603
846
|
const isOwner = identity.role === 'owner';
|
|
604
847
|
const isAdmin = identity.role === 'owner' || identity.role === 'admin';
|
|
@@ -606,8 +849,9 @@ export class CommandHandler {
|
|
|
606
849
|
if (normalizedContent.startsWith('/')) {
|
|
607
850
|
// guest 在群聊和私聊中均可访问的只读命令:纯查询形态(带参写操作由各 handler 内部守卫拦截)
|
|
608
851
|
const guestGroupCommands = [
|
|
609
|
-
'/status', '/help', '/check', '/chatmode',
|
|
610
|
-
'/model', '/effort', '/agent', '/perm', '/activity', '/safe', '/stop',
|
|
852
|
+
'/status', '/help', '/evolhelp', '/check', '/chatmode',
|
|
853
|
+
'/model', '/setmodel', '/effort', '/agent', '/perm', '/activity', '/safe', '/stop',
|
|
854
|
+
'/resume',
|
|
611
855
|
];
|
|
612
856
|
const userCommands = activeChatType === 'group' && !isAdmin
|
|
613
857
|
? guestGroupCommands
|
|
@@ -638,7 +882,7 @@ export class CommandHandler {
|
|
|
638
882
|
// 话题中:检查话题 session 是否在处理(不创建)
|
|
639
883
|
const threadSession = await this.sessionManager.getThreadSession(channel, channelId, threadId);
|
|
640
884
|
if (threadSession) {
|
|
641
|
-
const threadAgent = this.getAgent(threadSession.agentId);
|
|
885
|
+
const threadAgent = this.getAgent(channel, threadSession.agentId);
|
|
642
886
|
if (threadAgent.hasActiveStream(threadSession.id)) {
|
|
643
887
|
return '⚠️ 当前正在处理消息,请稍后再试\n使用 /stop 中断当前任务后重试';
|
|
644
888
|
}
|
|
@@ -732,7 +976,7 @@ export class CommandHandler {
|
|
|
732
976
|
' /check - 检查渠道状态',
|
|
733
977
|
' /activity [all|dm|owner|none] - 查看/控制中间输出显示模式',
|
|
734
978
|
...(isAdmin ? [
|
|
735
|
-
' /restart <
|
|
979
|
+
' /restart <type> - 重连该类型所有渠道实例(服务级,admin+)',
|
|
736
980
|
] : []),
|
|
737
981
|
...(isOwner ? [
|
|
738
982
|
' /restart - 重启服务',
|
|
@@ -746,6 +990,64 @@ export class CommandHandler {
|
|
|
746
990
|
];
|
|
747
991
|
return lines.join('\n');
|
|
748
992
|
}
|
|
993
|
+
// /evolhelp 命令:返回 JSON 格式的命令列表(供程序解析)
|
|
994
|
+
if (normalizedContent === '/evolhelp') {
|
|
995
|
+
const cmds = [];
|
|
996
|
+
// 项目管理
|
|
997
|
+
cmds.push({ command: '/pwd', description: '显示当前项目路径', category: '项目管理', roles: ['admin', 'owner'] });
|
|
998
|
+
cmds.push({ command: '/p', aliases: ['/project', '/plist'], args: '[name|path]', description: '列出或切换项目', category: '项目管理', roles: ['admin', 'owner'] });
|
|
999
|
+
if (isOwner) {
|
|
1000
|
+
cmds.push({ command: '/bind', args: '<path>', description: '绑定新项目目录', category: '项目管理', roles: ['owner'] });
|
|
1001
|
+
}
|
|
1002
|
+
// 会话管理
|
|
1003
|
+
cmds.push({ command: '/new', args: '[名称]', description: '创建新会话(清空历史请用此命令,可选命名)', category: '会话管理', roles: ['guest', 'admin', 'owner'] });
|
|
1004
|
+
cmds.push({ command: '/s', aliases: ['/session', '/slist'], args: '[cli|名称|序号|uuid]', description: '列出或切换会话', category: '会话管理', roles: ['guest', 'admin', 'owner'] });
|
|
1005
|
+
cmds.push({ command: '/name', aliases: ['/rename'], args: '<新名称>', description: '重命名当前会话', category: '会话管理', roles: ['guest', 'admin', 'owner'] });
|
|
1006
|
+
cmds.push({ command: '/del', args: '<名称>', description: '删除指定会话(仅解绑,不删除文件)', category: '会话管理', roles: ['guest', 'admin', 'owner'] });
|
|
1007
|
+
if (isAdmin) {
|
|
1008
|
+
cmds.push({ command: '/fork', args: '[名称]', description: '分支当前会话(从当前对话点创建分支)', category: '会话管理', roles: ['admin', 'owner'] });
|
|
1009
|
+
cmds.push({ command: '/rewind', aliases: ['/rw'], args: '[N] [chat|file|all]', description: '查看历史/撤销指定轮次', category: '会话管理', roles: ['admin', 'owner'] });
|
|
1010
|
+
cmds.push({ command: '/compact', description: '压缩会话上下文(减少 token 用量)', category: '会话管理', roles: ['admin', 'owner'] });
|
|
1011
|
+
}
|
|
1012
|
+
// Agent 与模型
|
|
1013
|
+
if (isAdmin) {
|
|
1014
|
+
cmds.push({ command: '/agent', args: '[name]', description: '查看或切换 Agent 后端', category: 'Agent 与模型', roles: ['admin', 'owner'] });
|
|
1015
|
+
cmds.push({ command: '/model', args: '[model]', description: '查看或切换模型', category: 'Agent 与模型', roles: ['admin', 'owner'] });
|
|
1016
|
+
cmds.push({ command: '/setmodel', description: '返回 JSON 格式的模型列表(供程序解析)', category: 'Agent 与模型', roles: ['admin', 'owner'] });
|
|
1017
|
+
cmds.push({ command: '/effort', args: '[level]', description: '查看或切换推理强度', category: 'Agent 与模型', roles: ['admin', 'owner'] });
|
|
1018
|
+
}
|
|
1019
|
+
// 权限管理
|
|
1020
|
+
if (isAdmin) {
|
|
1021
|
+
cmds.push({ command: '/perm', args: isOwner ? '<auto|bypass|request|edit|plan|noask>' : undefined, description: '查看当前权限模式', category: '权限管理', roles: ['admin', 'owner'] });
|
|
1022
|
+
cmds.push({ command: '/perm', args: 'allow|always|deny', description: '审批权限请求', category: '权限管理', roles: ['admin', 'owner'] });
|
|
1023
|
+
}
|
|
1024
|
+
// 运维
|
|
1025
|
+
cmds.push({ command: '/status', description: '显示会话状态', category: '运维', roles: ['guest', 'admin', 'owner'] });
|
|
1026
|
+
cmds.push({ command: '/stop', description: '中断当前任务', category: '运维', roles: ['admin', 'owner'] });
|
|
1027
|
+
cmds.push({ command: '/check', description: '检查渠道状态', category: '运维', roles: ['guest', 'admin', 'owner'] });
|
|
1028
|
+
if (isAdmin) {
|
|
1029
|
+
cmds.push({ command: '/activity', args: '[all|dm|owner|none]', description: '查看/控制中间输出显示模式', category: '运维', roles: ['admin', 'owner'] });
|
|
1030
|
+
cmds.push({ command: '/restart', args: '<channel>', description: '重连指定渠道', category: '运维', roles: ['admin', 'owner'] });
|
|
1031
|
+
}
|
|
1032
|
+
if (isOwner) {
|
|
1033
|
+
cmds.push({ command: '/restart', description: '重启服务', category: '运维', roles: ['owner'] });
|
|
1034
|
+
cmds.push({ command: '/file', args: '[channel] <path>', description: '发送项目内文件', category: '运维', roles: ['owner'] });
|
|
1035
|
+
cmds.push({ command: '/aid', args: '[list|new <aid>]', description: 'AID 管理', category: '运维', roles: ['owner'] });
|
|
1036
|
+
cmds.push({ command: '/agentmd', args: '[put|set <内容>]', description: '管理 agent.md', category: '运维', roles: ['owner'] });
|
|
1037
|
+
}
|
|
1038
|
+
// 会话模式
|
|
1039
|
+
if (isAdmin) {
|
|
1040
|
+
cmds.push({ command: '/chatmode', args: '[interactive|proactive]', description: '查看/切换会话模式(被动响应或主动推进)', category: '会话管理', roles: ['admin', 'owner'] });
|
|
1041
|
+
}
|
|
1042
|
+
// 交互
|
|
1043
|
+
cmds.push({ command: '/ask', args: '<选项>', description: '回答 Agent 的交互式问题', category: '运维', roles: ['guest', 'admin', 'owner'] });
|
|
1044
|
+
cmds.push({ command: '/resume', description: '查看当前项目的 Claude 会话记录', category: '会话管理', roles: ['guest', 'admin', 'owner'] });
|
|
1045
|
+
// 帮助
|
|
1046
|
+
cmds.push({ command: '/help', description: '显示帮助信息', category: '帮助', roles: ['guest', 'admin', 'owner'] });
|
|
1047
|
+
cmds.push({ command: '/evolhelp', description: '返回 JSON 格式命令列表', category: '帮助', roles: ['guest', 'admin', 'owner'] });
|
|
1048
|
+
const categories = [...new Set(cmds.map(c => c.category))];
|
|
1049
|
+
return JSON.stringify({ commands: cmds, categories });
|
|
1050
|
+
}
|
|
749
1051
|
// /perm 命令:权限模式切换 + 权限审批(快速路径,不进入消息队列)
|
|
750
1052
|
if (normalizedContent.startsWith('/perm')) {
|
|
751
1053
|
const args = normalizedContent.slice(5).trim();
|
|
@@ -754,7 +1056,7 @@ export class CommandHandler {
|
|
|
754
1056
|
if ('error' in permResult)
|
|
755
1057
|
return permResult.error;
|
|
756
1058
|
const { session: permSession } = permResult;
|
|
757
|
-
const permAgent = this.getAgent(permSession.agentId);
|
|
1059
|
+
const permAgent = this.getAgent(channel, permSession.agentId);
|
|
758
1060
|
// /perm(无参数):显示当前模式和可选模式
|
|
759
1061
|
if (!args) {
|
|
760
1062
|
if (!hasPermissionController(permAgent)) {
|
|
@@ -893,6 +1195,89 @@ export class CommandHandler {
|
|
|
893
1195
|
this.interactionRouter.handle({ type: 'interaction.response', id: targetId, action: args, operatorId: userId });
|
|
894
1196
|
return `✓ 已回答`;
|
|
895
1197
|
}
|
|
1198
|
+
// /resume 命令:返回当前项目的 Claude 会话记录(JSON)
|
|
1199
|
+
if (normalizedContent === '/resume' || normalizedContent.startsWith('/resume ')) {
|
|
1200
|
+
const resumeResult = await this.ensureSession(channel, channelId, threadId);
|
|
1201
|
+
if ('error' in resumeResult)
|
|
1202
|
+
return resumeResult.error;
|
|
1203
|
+
const { session: resumeSession } = resumeResult;
|
|
1204
|
+
try {
|
|
1205
|
+
const { encodePath } = await import('../utils/cross-platform.js');
|
|
1206
|
+
const homeDir = os.homedir();
|
|
1207
|
+
const encodedPath = encodePath(resumeSession.projectPath);
|
|
1208
|
+
const projectDir = path.join(homeDir, '.claude', 'projects', encodedPath);
|
|
1209
|
+
if (!fs.existsSync(projectDir)) {
|
|
1210
|
+
return '❌ 未找到 Claude 会话记录目录';
|
|
1211
|
+
}
|
|
1212
|
+
const jsonlFiles = fs.readdirSync(projectDir).filter(f => f.endsWith('.jsonl'));
|
|
1213
|
+
if (jsonlFiles.length === 0) {
|
|
1214
|
+
return '❌ 当前项目没有 Claude 会话记录';
|
|
1215
|
+
}
|
|
1216
|
+
const sessions = [];
|
|
1217
|
+
for (const file of jsonlFiles) {
|
|
1218
|
+
const filePath = path.join(projectDir, file);
|
|
1219
|
+
const sessionId = file.replace('.jsonl', '');
|
|
1220
|
+
let lastTimestamp = '';
|
|
1221
|
+
let firstUserMessage = '';
|
|
1222
|
+
let model = '';
|
|
1223
|
+
let branch = '';
|
|
1224
|
+
let turns = 0;
|
|
1225
|
+
try {
|
|
1226
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
1227
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
1228
|
+
for (const line of lines) {
|
|
1229
|
+
const event = JSON.parse(line);
|
|
1230
|
+
if (event.timestamp && event.timestamp > lastTimestamp) {
|
|
1231
|
+
lastTimestamp = event.timestamp;
|
|
1232
|
+
}
|
|
1233
|
+
if (event.gitBranch && !branch) {
|
|
1234
|
+
branch = event.gitBranch;
|
|
1235
|
+
}
|
|
1236
|
+
if (event.type === 'user' && event.message?.role === 'user') {
|
|
1237
|
+
const msgContent = event.message.content;
|
|
1238
|
+
const isToolResult = Array.isArray(msgContent) && msgContent.every((c) => c.type === 'tool_result');
|
|
1239
|
+
if (!isToolResult) {
|
|
1240
|
+
turns++;
|
|
1241
|
+
if (!firstUserMessage) {
|
|
1242
|
+
if (typeof msgContent === 'string') {
|
|
1243
|
+
firstUserMessage = msgContent.slice(0, 100);
|
|
1244
|
+
}
|
|
1245
|
+
else if (Array.isArray(msgContent)) {
|
|
1246
|
+
const textBlock = msgContent.find((c) => c.type === 'text');
|
|
1247
|
+
if (textBlock?.text) {
|
|
1248
|
+
firstUserMessage = textBlock.text.slice(0, 100);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
if (event.type === 'assistant' && event.message?.model && !model) {
|
|
1255
|
+
model = event.message.model;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
catch {
|
|
1260
|
+
continue;
|
|
1261
|
+
}
|
|
1262
|
+
if (!lastTimestamp)
|
|
1263
|
+
continue;
|
|
1264
|
+
sessions.push({
|
|
1265
|
+
sessionId,
|
|
1266
|
+
lastMessageTime: lastTimestamp,
|
|
1267
|
+
firstUserMessage: firstUserMessage || '(无消息)',
|
|
1268
|
+
model: model || 'unknown',
|
|
1269
|
+
turns,
|
|
1270
|
+
branch: branch || 'unknown',
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
sessions.sort((a, b) => b.lastMessageTime.localeCompare(a.lastMessageTime));
|
|
1274
|
+
return JSON.stringify(sessions, null, 2);
|
|
1275
|
+
}
|
|
1276
|
+
catch (error) {
|
|
1277
|
+
logger.error('[CommandHandler] /resume failed:', error);
|
|
1278
|
+
return `❌ 读取会话记录失败: ${error instanceof Error ? error.message : '未知错误'}`;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
896
1281
|
// /agent 命令:查看或切换 Agent 后端
|
|
897
1282
|
if (normalizedContent === '/agent' || normalizedContent.startsWith('/agent ')) {
|
|
898
1283
|
const args = normalizedContent.slice(6).trim();
|
|
@@ -900,9 +1285,12 @@ export class CommandHandler {
|
|
|
900
1285
|
if (args && (activeChatType === 'group' ? !isOwner : !isAdmin)) {
|
|
901
1286
|
return '❌ 无权限:此命令仅限管理员使用';
|
|
902
1287
|
}
|
|
903
|
-
const available =
|
|
1288
|
+
const available = this.getAvailableBaseagents(channel);
|
|
904
1289
|
if (!args) {
|
|
905
|
-
|
|
1290
|
+
// currentAgent: 当前 session 的 baseagent,或该 channel 所属 evolagent 的 baseagent
|
|
1291
|
+
const currentAgent = activeSession?.agentId
|
|
1292
|
+
|| this.agentRegistry?.resolveByChannel(channel)?.baseagent
|
|
1293
|
+
|| this.parseDefaultBaseagent();
|
|
906
1294
|
// 尝试发送交互卡片
|
|
907
1295
|
if (this.interactionRouter && available.length > 1) {
|
|
908
1296
|
const requestId = `agent-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`;
|
|
@@ -948,7 +1336,7 @@ export class CommandHandler {
|
|
|
948
1336
|
}
|
|
949
1337
|
return `当前 Agent: ${currentAgent}`;
|
|
950
1338
|
}
|
|
951
|
-
if (!
|
|
1339
|
+
if (!available.includes(args)) {
|
|
952
1340
|
return `❌ 未知 Agent: ${args}\n可用: ${available.join(', ')}`;
|
|
953
1341
|
}
|
|
954
1342
|
const result = await this.ensureSession(channel, channelId, threadId);
|
|
@@ -969,6 +1357,74 @@ export class CommandHandler {
|
|
|
969
1357
|
let agentSwitchResponse = `✓ 已切换 Agent: ${args}\n 项目: ${projectName}\n 会话: ${newSession.name || '(未命名)'}\n ${hasExistingSession}`;
|
|
970
1358
|
return agentSwitchResponse;
|
|
971
1359
|
}
|
|
1360
|
+
// /setmodel 命令:返回 JSON 格式的模型列表(供程序解析)
|
|
1361
|
+
if (normalizedContent === '/setmodel' || normalizedContent.startsWith('/setmodel ')) {
|
|
1362
|
+
const setmodelResult = await this.ensureSession(channel, channelId, threadId);
|
|
1363
|
+
if ('error' in setmodelResult)
|
|
1364
|
+
return setmodelResult.error;
|
|
1365
|
+
const { session: setmodelSession } = setmodelResult;
|
|
1366
|
+
const setmodelAgent = this.getAgent(channel, setmodelSession.agentId);
|
|
1367
|
+
const currentModel = hasModelSwitcher(setmodelAgent) ? setmodelAgent.getModel() : setmodelAgent.name;
|
|
1368
|
+
const efforts = getAvailableEfforts(setmodelAgent, currentModel);
|
|
1369
|
+
const currentEffort = setmodelAgent.getEffort?.() || 'auto';
|
|
1370
|
+
// 获取 API URL 用于请求 /models
|
|
1371
|
+
let apiBaseUrl;
|
|
1372
|
+
try {
|
|
1373
|
+
const configBaseUrl = this.config.agents?.claude?.baseUrl;
|
|
1374
|
+
const isPlaceholderUrl = configBaseUrl?.includes('api.anthropic.com');
|
|
1375
|
+
if (configBaseUrl && !isPlaceholderUrl) {
|
|
1376
|
+
apiBaseUrl = configBaseUrl;
|
|
1377
|
+
}
|
|
1378
|
+
else if (process.env.ANTHROPIC_BASE_URL) {
|
|
1379
|
+
apiBaseUrl = process.env.ANTHROPIC_BASE_URL;
|
|
1380
|
+
}
|
|
1381
|
+
else {
|
|
1382
|
+
const claudeSettingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
1383
|
+
if (fs.existsSync(claudeSettingsPath)) {
|
|
1384
|
+
const claudeSettings = JSON.parse(fs.readFileSync(claudeSettingsPath, 'utf-8'));
|
|
1385
|
+
if (claudeSettings.env?.ANTHROPIC_BASE_URL) {
|
|
1386
|
+
apiBaseUrl = claudeSettings.env.ANTHROPIC_BASE_URL;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
catch { }
|
|
1392
|
+
let modelListData = null;
|
|
1393
|
+
if (apiBaseUrl) {
|
|
1394
|
+
try {
|
|
1395
|
+
const modelsUrl = apiBaseUrl.replace(/\/+$/, '') + '/v1/models';
|
|
1396
|
+
const controller = new AbortController();
|
|
1397
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
1398
|
+
const resp = await fetch(modelsUrl, {
|
|
1399
|
+
signal: controller.signal,
|
|
1400
|
+
headers: { 'Authorization': `Bearer ${this.config.agents?.claude?.apiKey || process.env.ANTHROPIC_AUTH_TOKEN || ''}` },
|
|
1401
|
+
});
|
|
1402
|
+
clearTimeout(timeout);
|
|
1403
|
+
if (resp.ok) {
|
|
1404
|
+
modelListData = await resp.json();
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
catch { }
|
|
1408
|
+
}
|
|
1409
|
+
// 兜底模型列表
|
|
1410
|
+
if (!modelListData || !modelListData.data || modelListData.data.length === 0) {
|
|
1411
|
+
const now = Math.floor(Date.now() / 1000);
|
|
1412
|
+
modelListData = {
|
|
1413
|
+
object: 'list',
|
|
1414
|
+
data: [
|
|
1415
|
+
{ id: 'claude-opus-4-7', object: 'model', created: now, owned_by: 'anthropic' },
|
|
1416
|
+
{ id: 'claude-opus-4-6', object: 'model', created: now, owned_by: 'anthropic' },
|
|
1417
|
+
{ id: 'claude-sonnet-4-6', object: 'model', created: now, owned_by: 'anthropic' },
|
|
1418
|
+
],
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1421
|
+
return JSON.stringify({
|
|
1422
|
+
current_model: currentModel,
|
|
1423
|
+
current_effort: currentEffort,
|
|
1424
|
+
available_efforts: efforts,
|
|
1425
|
+
models: modelListData,
|
|
1426
|
+
}, null, 2);
|
|
1427
|
+
}
|
|
972
1428
|
// /model 命令:查看或切换模型/推理强度
|
|
973
1429
|
if (normalizedContent.startsWith('/model')) {
|
|
974
1430
|
const args = normalizedContent.slice(6).trim();
|
|
@@ -977,7 +1433,7 @@ export class CommandHandler {
|
|
|
977
1433
|
if ('error' in modelResult)
|
|
978
1434
|
return modelResult.error;
|
|
979
1435
|
const { session: modelSession } = modelResult;
|
|
980
|
-
const modelAgent = this.getAgent(modelSession.agentId);
|
|
1436
|
+
const modelAgent = this.getAgent(channel, modelSession.agentId);
|
|
981
1437
|
const models = hasModelSwitcher(modelAgent) ? modelAgent.listModels() : [];
|
|
982
1438
|
if (!args) {
|
|
983
1439
|
const currentModel = hasModelSwitcher(modelAgent) ? modelAgent.getModel() : modelAgent.name;
|
|
@@ -1091,68 +1547,16 @@ export class CommandHandler {
|
|
|
1091
1547
|
modelAgent.setEffort?.(newEffort);
|
|
1092
1548
|
changes.push(`推理强度: ${newEffort}`);
|
|
1093
1549
|
}
|
|
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
|
-
}
|
|
1550
|
+
// 持久化:agent-owned channel 写到 agent.json;default 走原"就近原则"
|
|
1551
|
+
if (newModel) {
|
|
1552
|
+
const err = this.persistBaseagentModel(channel, modelAgent.name, newModel);
|
|
1553
|
+
if (err)
|
|
1554
|
+
return `${err}\n已更新运行时配置,但未持久化`;
|
|
1128
1555
|
}
|
|
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
|
-
}
|
|
1556
|
+
if (newEffort) {
|
|
1557
|
+
const err = this.persistBaseagentEffort(channel, modelAgent.name, newEffort);
|
|
1558
|
+
if (err)
|
|
1559
|
+
return `${err}\n已更新运行时配置,但未持久化`;
|
|
1156
1560
|
}
|
|
1157
1561
|
return `✓ 已切换\n ${changes.join('\n ')}`;
|
|
1158
1562
|
}
|
|
@@ -1163,7 +1567,7 @@ export class CommandHandler {
|
|
|
1163
1567
|
if ('error' in effortResult)
|
|
1164
1568
|
return effortResult.error;
|
|
1165
1569
|
const { session: effortSession } = effortResult;
|
|
1166
|
-
const effortAgent = this.getAgent(effortSession.agentId);
|
|
1570
|
+
const effortAgent = this.getAgent(channel, effortSession.agentId);
|
|
1167
1571
|
const currentModel = hasModelSwitcher(effortAgent) ? effortAgent.getModel() : effortAgent.name;
|
|
1168
1572
|
const efforts = getAvailableEfforts(effortAgent, currentModel);
|
|
1169
1573
|
const currentEffort = effortAgent.getEffort?.() || 'auto';
|
|
@@ -1231,29 +1635,9 @@ export class CommandHandler {
|
|
|
1231
1635
|
// /effort auto:恢复 SDK 默认
|
|
1232
1636
|
if (args === 'auto') {
|
|
1233
1637
|
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
|
-
}
|
|
1638
|
+
const err = this.persistBaseagentEffort(channel, effortAgent.name, undefined);
|
|
1639
|
+
if (err)
|
|
1640
|
+
return `${err}\n已更新运行时配置,但未持久化`;
|
|
1257
1641
|
return '✓ 推理强度已恢复为 auto (SDK默认)';
|
|
1258
1642
|
}
|
|
1259
1643
|
// /effort <level>:切换推理强度
|
|
@@ -1265,34 +1649,9 @@ export class CommandHandler {
|
|
|
1265
1649
|
}
|
|
1266
1650
|
const newEffort = args;
|
|
1267
1651
|
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
|
-
}
|
|
1652
|
+
const err = this.persistBaseagentEffort(channel, effortAgent.name, newEffort);
|
|
1653
|
+
if (err)
|
|
1654
|
+
return `${err}\n已更新运行时配置,但未持久化`;
|
|
1296
1655
|
return `✓ 推理强度: ${newEffort}`;
|
|
1297
1656
|
}
|
|
1298
1657
|
// /aid 命令:AID 身份管理(list / new)
|
|
@@ -1424,7 +1783,7 @@ export class CommandHandler {
|
|
|
1424
1783
|
owner: 'owner-dm-only',
|
|
1425
1784
|
none: 'none',
|
|
1426
1785
|
};
|
|
1427
|
-
const currentMode = getChannelShowActivities(this.config, channel);
|
|
1786
|
+
const currentMode = this.agentRegistry?.getShowActivities?.(channel) ?? getChannelShowActivities(this.config, channel);
|
|
1428
1787
|
// 模式描述列表(用于 body 和文本降级)
|
|
1429
1788
|
const modeDescriptions = [
|
|
1430
1789
|
{ key: 'all', configVal: 'all', label: '全部显示' },
|
|
@@ -1495,7 +1854,12 @@ export class CommandHandler {
|
|
|
1495
1854
|
// 切换操作仅 owner
|
|
1496
1855
|
if (!isOwner)
|
|
1497
1856
|
return '❌ 中间输出模式切换仅限 owner';
|
|
1498
|
-
|
|
1857
|
+
if (this.agentRegistry?.setShowActivities) {
|
|
1858
|
+
this.agentRegistry.setShowActivities(channel, newMode);
|
|
1859
|
+
}
|
|
1860
|
+
else {
|
|
1861
|
+
setChannelShowActivities(this.config, channel, newMode);
|
|
1862
|
+
}
|
|
1499
1863
|
return `✅ 中间输出模式: ${activityArg}(${label})`;
|
|
1500
1864
|
}
|
|
1501
1865
|
// /chatmode 命令:查看/切换 session 会话模式(interactive | proactive)
|
|
@@ -1526,7 +1890,7 @@ export class CommandHandler {
|
|
|
1526
1890
|
if (threadId) {
|
|
1527
1891
|
const threadSession = await this.sessionManager.getThreadSession(channel, channelId, threadId);
|
|
1528
1892
|
if (threadSession) {
|
|
1529
|
-
const threadAgent = this.getAgent(threadSession.agentId);
|
|
1893
|
+
const threadAgent = this.getAgent(channel, threadSession.agentId);
|
|
1530
1894
|
if (threadAgent.hasActiveStream(threadSession.id)) {
|
|
1531
1895
|
return '⚠️ 当前正在处理消息,请稍后再试\n使用 /stop 中断当前任务后重试';
|
|
1532
1896
|
}
|
|
@@ -1544,7 +1908,7 @@ export class CommandHandler {
|
|
|
1544
1908
|
if ('error' in stopResult)
|
|
1545
1909
|
return '当前没有正在处理的任务';
|
|
1546
1910
|
const { session: stopSession } = stopResult;
|
|
1547
|
-
const stopAgent = this.getAgent(stopSession.agentId);
|
|
1911
|
+
const stopAgent = this.getAgent(channel, stopSession.agentId);
|
|
1548
1912
|
const sessionKey = stopSession.id;
|
|
1549
1913
|
const queueLength = this.messageQueue.getQueueLength(sessionKey);
|
|
1550
1914
|
const hasActive = stopAgent.hasActiveStream(sessionKey);
|
|
@@ -1553,7 +1917,12 @@ export class CommandHandler {
|
|
|
1553
1917
|
}
|
|
1554
1918
|
await stopAgent.interrupt(sessionKey);
|
|
1555
1919
|
// 发布中断事件,让 MessageProcessor 标记为 interrupted(而非 done)
|
|
1556
|
-
this.eventBus.publish({
|
|
1920
|
+
this.eventBus.publish({
|
|
1921
|
+
type: 'message:interrupted',
|
|
1922
|
+
sessionId: sessionKey,
|
|
1923
|
+
reason: 'stop',
|
|
1924
|
+
agentName: this.agentRegistry?.resolveByChannel(channel)?.name ?? '[default]',
|
|
1925
|
+
});
|
|
1557
1926
|
// 强制清除 processing_state
|
|
1558
1927
|
this.sessionManager.clearProcessing(sessionKey);
|
|
1559
1928
|
return '✓ 已发送中断信号,任务将尽快停止';
|
|
@@ -1564,7 +1933,7 @@ export class CommandHandler {
|
|
|
1564
1933
|
if ('error' in result)
|
|
1565
1934
|
return result.error;
|
|
1566
1935
|
const { session } = result;
|
|
1567
|
-
const sessionAgent = this.getAgent(session.agentId);
|
|
1936
|
+
const sessionAgent = this.getAgent(channel, session.agentId);
|
|
1568
1937
|
if (!sessionAgent.capabilities?.clear) {
|
|
1569
1938
|
return `❌ 当前 Agent (${sessionAgent.name}) 不支持 /clear\n\n可使用 /new 创建新会话替代`;
|
|
1570
1939
|
}
|
|
@@ -1596,7 +1965,7 @@ export class CommandHandler {
|
|
|
1596
1965
|
if ('error' in result)
|
|
1597
1966
|
return result.error;
|
|
1598
1967
|
const { session } = result;
|
|
1599
|
-
const sessionAgent = this.getAgent(session.agentId);
|
|
1968
|
+
const sessionAgent = this.getAgent(channel, session.agentId);
|
|
1600
1969
|
if (!sessionAgent.capabilities?.compact) {
|
|
1601
1970
|
return `❌ 当前 Agent (${sessionAgent.name}) 不支持 /compact`;
|
|
1602
1971
|
}
|
|
@@ -1626,7 +1995,7 @@ export class CommandHandler {
|
|
|
1626
1995
|
// 尝试获取活跃会话(话题时直接查找话题 session)
|
|
1627
1996
|
let session;
|
|
1628
1997
|
if (threadId) {
|
|
1629
|
-
session = await this.sessionManager.getOrCreateSession(channel, channelId, this.
|
|
1998
|
+
session = await this.sessionManager.getOrCreateSession(channel, channelId, this.getEffectiveDefaultPath(channel), threadId);
|
|
1630
1999
|
}
|
|
1631
2000
|
else {
|
|
1632
2001
|
session = await this.sessionManager.getActiveSession(channel, channelId);
|
|
@@ -1638,7 +2007,7 @@ export class CommandHandler {
|
|
|
1638
2007
|
normalizedContent.startsWith('/project') ||
|
|
1639
2008
|
normalizedContent === '/pwd' ||
|
|
1640
2009
|
normalizedContent === '/status')) {
|
|
1641
|
-
session = await this.sessionManager.getOrCreateSession(channel, channelId, this.
|
|
2010
|
+
session = await this.sessionManager.getOrCreateSession(channel, channelId, this.getEffectiveDefaultPath(channel));
|
|
1642
2011
|
}
|
|
1643
2012
|
// /status 命令:显示会话状态
|
|
1644
2013
|
if (normalizedContent === '/status') {
|
|
@@ -1647,7 +2016,7 @@ export class CommandHandler {
|
|
|
1647
2016
|
return `❌ 无法创建会话,请检查配置`;
|
|
1648
2017
|
}
|
|
1649
2018
|
const sessionKey = this.getQueueKey(session, channel, channelId);
|
|
1650
|
-
const sessionAgent = this.getAgent(session.agentId);
|
|
2019
|
+
const sessionAgent = this.getAgent(channel, session.agentId);
|
|
1651
2020
|
const isCurrentlyProcessing = this.messageQueue.isProcessing(sessionKey) || sessionAgent.hasActiveStream(sessionKey);
|
|
1652
2021
|
const queueLength = this.messageQueue.getQueueLength(sessionKey);
|
|
1653
2022
|
const isThread = !!session.threadId;
|
|
@@ -1707,7 +2076,7 @@ export class CommandHandler {
|
|
|
1707
2076
|
return `❌ 会话名称 "${sessionName}" 已存在,请使用其他名称`;
|
|
1708
2077
|
}
|
|
1709
2078
|
}
|
|
1710
|
-
const projectPath = session?.projectPath || this.
|
|
2079
|
+
const projectPath = session?.projectPath || this.getEffectiveDefaultPath(channel);
|
|
1711
2080
|
const newSession = await this.sessionManager.createNewSession(channel, channelId, projectPath, sessionName, session?.agentId || this.defaultAgentId);
|
|
1712
2081
|
this.eventBus.publish({
|
|
1713
2082
|
type: 'session:created',
|
|
@@ -1729,11 +2098,30 @@ export class CommandHandler {
|
|
|
1729
2098
|
// /check 命令:检查渠道状态(guest 可用,详情仅 admin)/ 重连指定渠道(admin only)
|
|
1730
2099
|
if (normalizedContent === '/check' || normalizedContent.startsWith('/check ')) {
|
|
1731
2100
|
const subCmd = normalizedContent.slice('/check'.length).trim();
|
|
2101
|
+
// 限定可见渠道:agent-owned 通道仅显示该 agent 名下的渠道;
|
|
2102
|
+
// default 通道也仅显示 default 的渠道(不再展示 evolagents 的渠道)
|
|
2103
|
+
const checkOwningAgent = this.getOwningAgent(channel);
|
|
2104
|
+
let allowedChannels;
|
|
2105
|
+
if (checkOwningAgent) {
|
|
2106
|
+
allowedChannels = new Set(checkOwningAgent.channelInstanceNames());
|
|
2107
|
+
}
|
|
2108
|
+
else {
|
|
2109
|
+
// default 范围:所有 channel 中,不属于任何 evolagent 的
|
|
2110
|
+
const defaultNames = [];
|
|
2111
|
+
for (const [name] of this.adapters) {
|
|
2112
|
+
const owner = this.agentRegistry?.resolveByChannel(name);
|
|
2113
|
+
if (!owner || owner.isDefault)
|
|
2114
|
+
defaultNames.push(name);
|
|
2115
|
+
}
|
|
2116
|
+
allowedChannels = new Set(defaultNames);
|
|
2117
|
+
}
|
|
1732
2118
|
// Default: show system health check (non-admin 仅看摘要)
|
|
1733
2119
|
const lines = ['📡 渠道状态:'];
|
|
1734
2120
|
// Group by channelType
|
|
1735
2121
|
const groups = new Map();
|
|
1736
2122
|
for (const [name] of this.adapters) {
|
|
2123
|
+
if (!allowedChannels.has(name))
|
|
2124
|
+
continue;
|
|
1737
2125
|
const type = this.channelTypeMap.get(name) || name;
|
|
1738
2126
|
const ch = this.channelObjects.get(name);
|
|
1739
2127
|
let status;
|
|
@@ -1764,19 +2152,21 @@ export class CommandHandler {
|
|
|
1764
2152
|
lines.push(` ${type}: [${parts.join(', ')}]`);
|
|
1765
2153
|
}
|
|
1766
2154
|
}
|
|
1767
|
-
//
|
|
2155
|
+
// 当前 agent 名(用于 agent 维度 stats / queue 查询)
|
|
2156
|
+
const currentAgentName = checkOwningAgent?.name ?? '[default]';
|
|
2157
|
+
// 队列状态(按当前 agent 维度)
|
|
1768
2158
|
lines.push('', '📬 队列状态:');
|
|
1769
|
-
lines.push(` 待处理消息: ${this.messageQueue.
|
|
1770
|
-
lines.push(` 处理中队列: ${this.messageQueue.
|
|
1771
|
-
//
|
|
2159
|
+
lines.push(` 待处理消息: ${this.messageQueue.getQueueLengthByAgent(currentAgentName)}`);
|
|
2160
|
+
lines.push(` 处理中队列: ${this.messageQueue.getProcessingCountByAgent(currentAgentName)}`);
|
|
2161
|
+
// 运行概况(全局,进程级)
|
|
1772
2162
|
lines.push('', '🖥️ 运行概况:');
|
|
1773
2163
|
const uptimeMs = this.statsCollector
|
|
1774
2164
|
? this.statsCollector.getSnapshot().uptimeMs
|
|
1775
2165
|
: process.uptime() * 1000;
|
|
1776
2166
|
lines.push(` 运行时间: ${this.formatUptime(uptimeMs)}`);
|
|
1777
|
-
// 近 1
|
|
2167
|
+
// 近 1 小时统计(按当前 agent 维度)
|
|
1778
2168
|
if (this.statsCollector) {
|
|
1779
|
-
const snap = this.statsCollector.getSnapshot();
|
|
2169
|
+
const snap = this.statsCollector.getSnapshot(currentAgentName);
|
|
1780
2170
|
const h = snap.lastHour;
|
|
1781
2171
|
lines.push('', '📊 近 1 小时统计:');
|
|
1782
2172
|
lines.push(` 收到消息: ${h.received}`);
|
|
@@ -1802,23 +2192,50 @@ export class CommandHandler {
|
|
|
1802
2192
|
// /restart 命令:重启服务(owner only) / 重连指定渠道(admin+)
|
|
1803
2193
|
if (normalizedContent === '/restart' || normalizedContent.startsWith('/restart ')) {
|
|
1804
2194
|
const restartArg = normalizedContent.slice('/restart'.length).trim();
|
|
1805
|
-
// /restart <
|
|
2195
|
+
// /restart <type> — 重连指定类型的所有渠道(admin only,evolclaw 服务级操作)
|
|
2196
|
+
// 服务级操作仅可从 default 通道发起,避免 evolagent owner/admin 越权
|
|
1806
2197
|
if (restartArg) {
|
|
2198
|
+
if (this.getOwningAgent(channel)) {
|
|
2199
|
+
return '❌ 渠道重连只能从 DefaultAgent 通道发起(服务级操作)';
|
|
2200
|
+
}
|
|
1807
2201
|
if (!isAdmin)
|
|
1808
2202
|
return '❌ 无权限:渠道重连仅限管理员使用';
|
|
1809
|
-
const
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
2203
|
+
const type = restartArg;
|
|
2204
|
+
// /restart 是服务级操作:重连该 type 下的所有实例(不分 agent)
|
|
2205
|
+
const scopedNames = [];
|
|
2206
|
+
for (const [name] of this.adapters) {
|
|
2207
|
+
if (this.channelTypeMap.get(name) === type)
|
|
2208
|
+
scopedNames.push(name);
|
|
2209
|
+
}
|
|
2210
|
+
if (scopedNames.length === 0) {
|
|
2211
|
+
return `❌ 没有类型为 "${type}" 的渠道`;
|
|
2212
|
+
}
|
|
2213
|
+
const results = [];
|
|
2214
|
+
for (const name of scopedNames) {
|
|
2215
|
+
const ch = this.channelObjects.get(name);
|
|
2216
|
+
if (!ch) {
|
|
2217
|
+
results.push(`${name}: 未找到渠道对象`);
|
|
2218
|
+
continue;
|
|
2219
|
+
}
|
|
2220
|
+
if (!ch.reconnect) {
|
|
2221
|
+
results.push(`${name}: 不支持重连`);
|
|
2222
|
+
continue;
|
|
2223
|
+
}
|
|
2224
|
+
try {
|
|
2225
|
+
const result = await ch.reconnect();
|
|
2226
|
+
results.push(`${name}: ${result}`);
|
|
2227
|
+
}
|
|
2228
|
+
catch (e) {
|
|
2229
|
+
results.push(`${name}: 重连失败 - ${e?.message || e}`);
|
|
2230
|
+
}
|
|
1817
2231
|
}
|
|
1818
|
-
|
|
1819
|
-
|
|
2232
|
+
return `🔄 重连 ${type}:\n ${results.join('\n ')}`;
|
|
2233
|
+
}
|
|
2234
|
+
// /restart(无参数)— 重启整个服务(owner only,且仅可从 default 通道触发)
|
|
2235
|
+
// 防止 evolagent 通道的 owner 越权杀整个 evolclaw 进程(影响所有租户)
|
|
2236
|
+
if (this.getOwningAgent(channel)) {
|
|
2237
|
+
return '❌ 服务重启只能从 DefaultAgent 通道发起。EvolAgent 通道仅可执行 /restart <type> 重连特定类型渠道';
|
|
1820
2238
|
}
|
|
1821
|
-
// /restart(无参数)— 重启整个服务(owner only)
|
|
1822
2239
|
if (!isOwner)
|
|
1823
2240
|
return '❌ 无权限:服务重启仅限 owner 使用';
|
|
1824
2241
|
const allSessions = await this.sessionManager.listSessions(channel, channelId);
|
|
@@ -1832,7 +2249,7 @@ export class CommandHandler {
|
|
|
1832
2249
|
const executeRestart = async () => {
|
|
1833
2250
|
let replyContext;
|
|
1834
2251
|
if (threadId) {
|
|
1835
|
-
const threadSession = await this.sessionManager.getOrCreateSession(channel, channelId, this.
|
|
2252
|
+
const threadSession = await this.sessionManager.getOrCreateSession(channel, channelId, this.getEffectiveDefaultPath(channel), threadId);
|
|
1836
2253
|
replyContext = this.getReplyContext(threadSession);
|
|
1837
2254
|
}
|
|
1838
2255
|
const restartInfo = {
|
|
@@ -1971,7 +2388,7 @@ export class CommandHandler {
|
|
|
1971
2388
|
// 找目标 channelId
|
|
1972
2389
|
let targetChannelId = channelId;
|
|
1973
2390
|
if (isCrossChannel) {
|
|
1974
|
-
const ownerPeerId = getOwner(this.config, targetChannel);
|
|
2391
|
+
const ownerPeerId = this.agentRegistry?.getOwner?.(targetChannel) ?? getOwner(this.config, targetChannel);
|
|
1975
2392
|
targetChannelId = ownerPeerId ? (this.sessionManager.getOwnerChatId(targetChannel, ownerPeerId) ?? '') : '';
|
|
1976
2393
|
if (!targetChannelId) {
|
|
1977
2394
|
return `❌ 未找到 ${targetLabel} 的私聊会话,请先在该通道发送一条消息`;
|
|
@@ -2226,27 +2643,19 @@ export class CommandHandler {
|
|
|
2226
2643
|
}
|
|
2227
2644
|
// 生成项目名称(使用目录名)
|
|
2228
2645
|
const projectName = path.basename(projectPath);
|
|
2229
|
-
//
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2646
|
+
// 检查在当前 scope 内是否已存在
|
|
2647
|
+
const scopeProjects = this.getEffectiveProjects(channel);
|
|
2648
|
+
const existing = scopeProjects[projectName];
|
|
2649
|
+
if (existing) {
|
|
2650
|
+
if (existing === projectPath) {
|
|
2233
2651
|
return `项目 "${projectName}" 已存在\n 路径: ${projectPath}\n\n使用 /p ${projectName} 切换到该项目`;
|
|
2234
2652
|
}
|
|
2235
|
-
return `❌ 项目名称 "${projectName}" 已被占用\n 现有路径: ${
|
|
2236
|
-
}
|
|
2237
|
-
// 添加到配置
|
|
2238
|
-
if (!this.config.projects) {
|
|
2239
|
-
this.config.projects = { defaultPath: process.cwd(), autoCreate: false, list: {} };
|
|
2653
|
+
return `❌ 项目名称 "${projectName}" 已被占用\n 现有路径: ${existing}\n 新路径: ${projectPath}\n\n请重命名目录或手动编辑配置文件`;
|
|
2240
2654
|
}
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
// 保存配置
|
|
2246
|
-
const { saveConfig } = await import('../config.js');
|
|
2247
|
-
saveConfig(this.config);
|
|
2248
|
-
// 更新内存中的项目列表
|
|
2249
|
-
this.projects[projectName] = projectPath;
|
|
2655
|
+
// 写入:agent-owned channel → agent.json;default → evolclaw.json
|
|
2656
|
+
const err = await this.addProjectInScope(channel, projectName, projectPath);
|
|
2657
|
+
if (err)
|
|
2658
|
+
return err;
|
|
2250
2659
|
return `✓ 已添加项目: ${projectName}\n 路径: ${projectPath}\n\n使用 /p ${projectName} 切换到该项目`;
|
|
2251
2660
|
}
|
|
2252
2661
|
// /slist 命令:列出当前项目的会话
|
|
@@ -2608,7 +3017,7 @@ export class CommandHandler {
|
|
|
2608
3017
|
return `❌ 删除失败`;
|
|
2609
3018
|
}
|
|
2610
3019
|
this.eventBus.publish({ type: 'session:deleted', sessionId: targetSession.id });
|
|
2611
|
-
const targetAgent = this.getAgent(targetSession.agentId);
|
|
3020
|
+
const targetAgent = this.getAgent(channel, targetSession.agentId);
|
|
2612
3021
|
await targetAgent.closeSession(targetSession.id);
|
|
2613
3022
|
return `✓ 已删除会话: ${targetSession.name || sessionName}\n会话文件已保留,可通过 CLI 访问`;
|
|
2614
3023
|
}
|
|
@@ -2621,7 +3030,7 @@ export class CommandHandler {
|
|
|
2621
3030
|
if (!session.agentSessionId) {
|
|
2622
3031
|
return `❌ 当前会话尚未初始化对话,无法分支\n\n请先发送一条消息,然后再使用 /fork`;
|
|
2623
3032
|
}
|
|
2624
|
-
const forkAgent = this.getAgent(session.agentId);
|
|
3033
|
+
const forkAgent = this.getAgent(channel, session.agentId);
|
|
2625
3034
|
if (!forkAgent.capabilities?.fork) {
|
|
2626
3035
|
return `❌ 当前 Agent (${forkAgent.name}) 不支持 /fork\n\n可使用 /new 创建新会话替代`;
|
|
2627
3036
|
}
|
|
@@ -2642,7 +3051,7 @@ export class CommandHandler {
|
|
|
2642
3051
|
if ('error' in result)
|
|
2643
3052
|
return result.error;
|
|
2644
3053
|
const { session } = result;
|
|
2645
|
-
const rewindAgent = this.getAgent(session.agentId);
|
|
3054
|
+
const rewindAgent = this.getAgent(channel, session.agentId);
|
|
2646
3055
|
if (rewindAgent.name !== 'claude') {
|
|
2647
3056
|
return '❌ /rewind 仅支持 Claude 后端';
|
|
2648
3057
|
}
|
|
@@ -2679,7 +3088,7 @@ export class CommandHandler {
|
|
|
2679
3088
|
if ('error' in repairResult)
|
|
2680
3089
|
return repairResult.error;
|
|
2681
3090
|
const { session: repairSession } = repairResult;
|
|
2682
|
-
const repairAgent = this.getAgent(repairSession.agentId);
|
|
3091
|
+
const repairAgent = this.getAgent(channel, repairSession.agentId);
|
|
2683
3092
|
const { checkSessionFile, backupSessionFile } = await import('./session/session-file-health.js');
|
|
2684
3093
|
try {
|
|
2685
3094
|
if (!repairSession.agentSessionId) {
|
|
@@ -2838,7 +3247,7 @@ export class CommandHandler {
|
|
|
2838
3247
|
'/help', '/status', '/check', '/pwd',
|
|
2839
3248
|
'/model', '/effort', '/perm', '/agent',
|
|
2840
3249
|
'/compact', '/activity', '/file', '/send', '/chatmode', '/restart', '/agentmd', '/bind', '/aid',
|
|
2841
|
-
'/rename', '/name',
|
|
3250
|
+
'/rename', '/name', '/evolagent',
|
|
2842
3251
|
];
|
|
2843
3252
|
/** ctl 中仅允许查询形态的指令;写形态(带参)一律拒绝 */
|
|
2844
3253
|
static CTL_READONLY = new Set(['/agent']);
|
|
@@ -2892,6 +3301,48 @@ export class CommandHandler {
|
|
|
2892
3301
|
}
|
|
2893
3302
|
// 3. 从 session.metadata.peerId 获取 userId(用于权限判断)
|
|
2894
3303
|
const userId = session.metadata?.peerId;
|
|
3304
|
+
// 3.1 /evolagent: EvolAgent 管理(show identity / reload)
|
|
3305
|
+
if (cmd === '/evolagent' || cmd.startsWith('/evolagent ')) {
|
|
3306
|
+
const arg = cmd.slice('/evolagent'.length).trim();
|
|
3307
|
+
if (!arg) {
|
|
3308
|
+
const owning = this.getOwningAgent(session.channel);
|
|
3309
|
+
if (owning) {
|
|
3310
|
+
return { ok: true, result: `当前 EvolAgent: ${owning.name} (${owning.baseagent})` };
|
|
3311
|
+
}
|
|
3312
|
+
return { ok: true, result: '当前为 DefaultAgent 模式' };
|
|
3313
|
+
}
|
|
3314
|
+
if (arg.startsWith('reload ') || arg === 'reload') {
|
|
3315
|
+
const name = arg === 'reload' ? '' : arg.slice('reload '.length).trim();
|
|
3316
|
+
if (!name)
|
|
3317
|
+
return { ok: false, error: '用法: evolclaw ctl evolagent reload <name>' };
|
|
3318
|
+
// I8: reload is a structural op, require admin or owner
|
|
3319
|
+
if (!userId) {
|
|
3320
|
+
return { ok: false, error: '权限不足:evolagent reload 仅 owner/admin 可用' };
|
|
3321
|
+
}
|
|
3322
|
+
const identity = this.sessionManager.resolveIdentity(session.channel, userId);
|
|
3323
|
+
if (identity.role !== 'owner' && identity.role !== 'admin') {
|
|
3324
|
+
return { ok: false, error: '权限不足:evolagent reload 仅 owner/admin 可用' };
|
|
3325
|
+
}
|
|
3326
|
+
if (!this.agentRegistry)
|
|
3327
|
+
return { ok: false, error: 'EvolAgentRegistry not available' };
|
|
3328
|
+
const a = this.agentRegistry.get(name);
|
|
3329
|
+
if (!a)
|
|
3330
|
+
return { ok: false, error: `Agent "${name}" not found` };
|
|
3331
|
+
const hooks = globalThis.__evolclaw_reloadHooks;
|
|
3332
|
+
if (!hooks)
|
|
3333
|
+
return { ok: false, error: 'Reload hooks not initialized' };
|
|
3334
|
+
if (!this.agentRegistry.reload)
|
|
3335
|
+
return { ok: false, error: 'EvolAgentRegistry.reload not available' };
|
|
3336
|
+
try {
|
|
3337
|
+
await this.agentRegistry.reload(name, hooks);
|
|
3338
|
+
return { ok: true, result: `Agent "${name}" reloaded` };
|
|
3339
|
+
}
|
|
3340
|
+
catch (e) {
|
|
3341
|
+
return { ok: false, error: `Reload failed: ${e?.message || e}` };
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
return { ok: false, error: '用法: evolclaw ctl evolagent [reload <name>]' };
|
|
3345
|
+
}
|
|
2895
3346
|
// 4. /send 文本消息:直接通过 adapter 主动发送,不走 handle()
|
|
2896
3347
|
if (cmd.startsWith('/send ') || cmd === '/send') {
|
|
2897
3348
|
const text = cmd.startsWith('/send ') ? cmd.slice(6).trim() : '';
|