evolclaw 2.5.4 → 2.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/aun/pyproject.toml +20 -0
- package/data/evolclaw.sample.json +30 -8
- package/dist/agents/claude-runner.js +13 -1
- package/dist/channels/aun.js +2 -1
- package/dist/cli.js +9 -32
- package/dist/config.js +26 -1
- package/dist/core/command-handler.js +76 -14
- package/dist/core/message/message-processor.js +101 -83
- package/dist/core/message/stream-flusher.js +18 -2
- package/dist/core/session/session-manager.js +72 -21
- package/dist/index.js +25 -2
- package/dist/utils/init-channel.js +62 -16
- package/dist/utils/init.js +7 -4
- package/evolclaw-install-aun.md +102 -4
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "aun-cli"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "AUN CLI - Interactive command-line client for AUN protocol"
|
|
5
|
+
requires-python = ">=3.10"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"aunp>=0.2.12",
|
|
8
|
+
"prompt-toolkit>=3.0.0",
|
|
9
|
+
"rich>=13.0.0",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[project.scripts]
|
|
13
|
+
aun = "aun_cli:cli_main"
|
|
14
|
+
|
|
15
|
+
[build-system]
|
|
16
|
+
requires = ["setuptools>=61.0"]
|
|
17
|
+
build-backend = "setuptools.build_meta"
|
|
18
|
+
|
|
19
|
+
[tool.setuptools]
|
|
20
|
+
py-modules = ["aun_cli"]
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"agents": {
|
|
3
3
|
"anthropic": {
|
|
4
|
-
"
|
|
4
|
+
"apiKey": "",
|
|
5
|
+
"baseUrl": "",
|
|
6
|
+
"model": "sonnet",
|
|
7
|
+
"effort": "high",
|
|
8
|
+
"useSettingSources": true,
|
|
9
|
+
"agentProgressSummaries": true
|
|
5
10
|
},
|
|
6
11
|
"openai": {
|
|
7
12
|
"apiKey": "your-openai-api-key",
|
|
@@ -10,7 +15,10 @@
|
|
|
10
15
|
"effort": "medium"
|
|
11
16
|
},
|
|
12
17
|
"google": {
|
|
13
|
-
"
|
|
18
|
+
"apiKey": "",
|
|
19
|
+
"model": "gemini-2.5-flash",
|
|
20
|
+
"cliPath": "",
|
|
21
|
+
"mode": "cli"
|
|
14
22
|
},
|
|
15
23
|
"defaultAgent": "claude"
|
|
16
24
|
},
|
|
@@ -19,17 +27,26 @@
|
|
|
19
27
|
"feishu": {
|
|
20
28
|
"enabled": true,
|
|
21
29
|
"appId": "",
|
|
22
|
-
"appSecret": ""
|
|
30
|
+
"appSecret": "",
|
|
31
|
+
"owner": "",
|
|
32
|
+
"flushDelay": 10,
|
|
33
|
+
"debounce": 2,
|
|
34
|
+
"showActivities": "dm-only"
|
|
23
35
|
},
|
|
24
36
|
"wechat": {
|
|
25
37
|
"enabled": false,
|
|
26
38
|
"baseUrl": "https://ilinkai.weixin.qq.com",
|
|
27
|
-
"token": ""
|
|
39
|
+
"token": "",
|
|
40
|
+
"owner": "",
|
|
41
|
+
"flushDelay": 3,
|
|
42
|
+
"debounce": 2,
|
|
43
|
+
"showActivities": "all"
|
|
28
44
|
},
|
|
29
45
|
"aun": {
|
|
30
46
|
"enabled": false,
|
|
31
47
|
"aid": "your-agent.agentid.pub",
|
|
32
|
-
"
|
|
48
|
+
"owner": "",
|
|
49
|
+
"showActivities": "owner-dm-only"
|
|
33
50
|
}
|
|
34
51
|
},
|
|
35
52
|
"projects": {
|
|
@@ -37,11 +54,16 @@
|
|
|
37
54
|
"autoCreate": true,
|
|
38
55
|
"list": {}
|
|
39
56
|
},
|
|
57
|
+
"flushDelay": 4,
|
|
58
|
+
"debounce": 2,
|
|
59
|
+
"showActivities": "all",
|
|
40
60
|
"idleMonitor": {
|
|
41
61
|
"enabled": true,
|
|
42
62
|
"timeout": 120,
|
|
43
|
-
"safeModeThreshold":
|
|
63
|
+
"safeModeThreshold": 0
|
|
44
64
|
},
|
|
45
|
-
"
|
|
46
|
-
|
|
65
|
+
"debug": {
|
|
66
|
+
"flusherDiag": false,
|
|
67
|
+
"aunTrace": false
|
|
68
|
+
}
|
|
47
69
|
}
|
|
@@ -84,6 +84,7 @@ export class AgentRunner {
|
|
|
84
84
|
sendPromptFn;
|
|
85
85
|
permissionContexts = new Map();
|
|
86
86
|
currentEvolclawSessionId;
|
|
87
|
+
claudeExecutablePath;
|
|
87
88
|
constructor(apiKey, model, onSessionIdUpdate, baseUrl, config) {
|
|
88
89
|
this.apiKey = apiKey;
|
|
89
90
|
this.model = model || 'sonnet';
|
|
@@ -91,6 +92,10 @@ export class AgentRunner {
|
|
|
91
92
|
this.baseUrl = baseUrl;
|
|
92
93
|
this.config = config;
|
|
93
94
|
this.onSessionIdUpdate = onSessionIdUpdate;
|
|
95
|
+
if (config) {
|
|
96
|
+
const anthropic = resolveAnthropicConfig(config);
|
|
97
|
+
this.claudeExecutablePath = anthropic.pathToClaudeCodeExecutable;
|
|
98
|
+
}
|
|
94
99
|
}
|
|
95
100
|
getAgentEnv() {
|
|
96
101
|
return {
|
|
@@ -626,11 +631,18 @@ export class AgentRunner {
|
|
|
626
631
|
const excludeDynamic = this.config?.agents?.anthropic?.excludeDynamicSections === true;
|
|
627
632
|
// 公共 options(新旧模式共用)
|
|
628
633
|
const sdkPermissionMode = this.toSdkPermissionMode();
|
|
629
|
-
logger.info(`[AgentRunner] runQuery model=${this.model} effort=${this.effort ?? 'auto'} permMode=${this.permissionMode} sdkMode=${sdkPermissionMode}
|
|
634
|
+
logger.info(`[AgentRunner] runQuery model=${this.model} effort=${this.effort ?? 'auto'} permMode=${this.permissionMode} sdkMode=${sdkPermissionMode}`);
|
|
635
|
+
if (systemPromptAppend) {
|
|
636
|
+
logger.info(`[AgentRunner] systemPromptAppend (full):\n${systemPromptAppend}`);
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
logger.info(`[AgentRunner] systemPromptAppend: none`);
|
|
640
|
+
}
|
|
630
641
|
const commonOptions = {
|
|
631
642
|
cwd: projectPath,
|
|
632
643
|
model: this.model,
|
|
633
644
|
...(this.effort ? { effort: this.effort } : {}),
|
|
645
|
+
...(this.claudeExecutablePath ? { pathToClaudeCodeExecutable: this.claudeExecutablePath } : {}),
|
|
634
646
|
autoCompactWindow: 200000,
|
|
635
647
|
advisorModel: 'haiku',
|
|
636
648
|
canUseTool: canUseToolCallback,
|
package/dist/channels/aun.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AUNClient, GatewayDiscovery } from '@
|
|
1
|
+
import { AUNClient, GatewayDiscovery } from '@agentunion/aun-node';
|
|
2
2
|
import crypto from 'crypto';
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import path from 'path';
|
|
@@ -1072,6 +1072,7 @@ export class AUNChannelPlugin {
|
|
|
1072
1072
|
const options = {
|
|
1073
1073
|
flushDelay: inst.flushDelay ?? 3,
|
|
1074
1074
|
fileMarkerPattern: /\[SEND_FILE:(?:(\w+):)?([^\]]+)\]/g,
|
|
1075
|
+
sessionMode: inst.sessionMode,
|
|
1075
1076
|
};
|
|
1076
1077
|
result.push({
|
|
1077
1078
|
channelType: 'aun',
|
package/dist/cli.js
CHANGED
|
@@ -1241,33 +1241,6 @@ async function cmdDiagnose() {
|
|
|
1241
1241
|
console.log('\n[diagnose] ✓ 所有检查通过');
|
|
1242
1242
|
}
|
|
1243
1243
|
}
|
|
1244
|
-
async function cmdTui() {
|
|
1245
|
-
const config = loadConfig();
|
|
1246
|
-
// Find the first AUN instance (TUI connects to one AUN instance)
|
|
1247
|
-
const aunResolved = resolveInstanceConfig(config, 'aun');
|
|
1248
|
-
const aun = aunResolved?.type === 'aun' ? aunResolved.config : null;
|
|
1249
|
-
if (!aun?.owner || !aun?.aid) {
|
|
1250
|
-
console.error('[tui] AUN 未配置,请先运行: evolclaw init aun');
|
|
1251
|
-
process.exit(1);
|
|
1252
|
-
}
|
|
1253
|
-
// TUI requires Python + aun_core (independent of init aun which is now pure TS)
|
|
1254
|
-
const pythonCheck = aun.pythonBin || process.env.AUN_PYTHON || 'python3';
|
|
1255
|
-
if (!platform.commandExists(pythonCheck)) {
|
|
1256
|
-
console.error(`[tui] Python 未找到 (${pythonCheck})`);
|
|
1257
|
-
console.error(' → TUI 依赖 Python 和 aun-core (>=0.2.9): pip3 install -U aun-core');
|
|
1258
|
-
process.exit(1);
|
|
1259
|
-
}
|
|
1260
|
-
const pythonBin = aun.pythonBin || process.env.AUN_PYTHON || 'python3';
|
|
1261
|
-
const cliScript = path.join(getPackageRoot(), 'aun', 'aun_cli.py');
|
|
1262
|
-
if (!fs.existsSync(cliScript)) {
|
|
1263
|
-
console.error(`[tui] aun_cli.py 不存在: ${cliScript}`);
|
|
1264
|
-
console.error(' → TUI 需要 AUN CLI 工具,请确认源码目录包含 aun/aun_cli.py');
|
|
1265
|
-
console.error(' → 安装: pip3 install -U aun-core && 从源码仓库获取 aun_cli.py');
|
|
1266
|
-
process.exit(1);
|
|
1267
|
-
}
|
|
1268
|
-
const child = spawn(pythonBin, [cliScript, '-a', aun.owner, '-t', aun.aid], { stdio: 'inherit' });
|
|
1269
|
-
child.on('exit', (code) => process.exit(code ?? 0));
|
|
1270
|
-
}
|
|
1271
1244
|
// ==================== Ctl ====================
|
|
1272
1245
|
async function cmdCtl(args) {
|
|
1273
1246
|
if (args.length === 0) {
|
|
@@ -1275,6 +1248,8 @@ async function cmdCtl(args) {
|
|
|
1275
1248
|
console.error('示例: evolclaw ctl model sonnet');
|
|
1276
1249
|
console.error(' evolclaw ctl status');
|
|
1277
1250
|
console.error(' evolclaw ctl effort high');
|
|
1251
|
+
console.error(' evolclaw ctl send "<消息内容>" # proactive 模式主动发消息');
|
|
1252
|
+
console.error(' evolclaw ctl chatmode proactive # 切换会话模式');
|
|
1278
1253
|
process.exit(1);
|
|
1279
1254
|
}
|
|
1280
1255
|
const sessionId = process.env.EVOLCLAW_SESSION_ID;
|
|
@@ -1332,6 +1307,12 @@ export async function main(args) {
|
|
|
1332
1307
|
else if (args[1] === 'wecom') {
|
|
1333
1308
|
await cmdInitWecom();
|
|
1334
1309
|
}
|
|
1310
|
+
else if (args[1]) {
|
|
1311
|
+
const supported = ['feishu', 'wechat', 'aun', 'dingtalk', 'qqbot', 'wecom'];
|
|
1312
|
+
console.error(`❌ 不支持的渠道: ${args[1]}`);
|
|
1313
|
+
console.error(` 支持的渠道: ${supported.join(', ')}`);
|
|
1314
|
+
process.exit(1);
|
|
1315
|
+
}
|
|
1335
1316
|
else {
|
|
1336
1317
|
const nonInteractive = args.includes('--non-interactive');
|
|
1337
1318
|
if (nonInteractive) {
|
|
@@ -1372,14 +1353,11 @@ export async function main(args) {
|
|
|
1372
1353
|
case 'diagnose':
|
|
1373
1354
|
await cmdDiagnose();
|
|
1374
1355
|
break;
|
|
1375
|
-
case 'tui':
|
|
1376
|
-
await cmdTui();
|
|
1377
|
-
break;
|
|
1378
1356
|
case 'ctl':
|
|
1379
1357
|
await cmdCtl(args.slice(1));
|
|
1380
1358
|
break;
|
|
1381
1359
|
default:
|
|
1382
|
-
console.log(`Usage: evolclaw {init|start|stop|restart|status|logs|
|
|
1360
|
+
console.log(`Usage: evolclaw {init|start|stop|restart|status|logs|ctl|diagnose|mv}
|
|
1383
1361
|
|
|
1384
1362
|
Commands:
|
|
1385
1363
|
init 创建配置文件 (${resolvePaths().config})
|
|
@@ -1397,7 +1375,6 @@ Commands:
|
|
|
1397
1375
|
--level error|warn 只显示指定级别及以上
|
|
1398
1376
|
--module <name> 只显示指定模块(如 feishu、AgentRunner)
|
|
1399
1377
|
--raw 原始输出,不着色
|
|
1400
|
-
tui 启动 AUN TUI 客户端
|
|
1401
1378
|
diagnose 诊断启动环境(配置、数据库、进程)
|
|
1402
1379
|
mv <old> <new> 迁移项目目录(保留 Claude/Codex/EvolClaw 会话)
|
|
1403
1380
|
|
package/dist/config.js
CHANGED
|
@@ -73,7 +73,10 @@ export function resolveAnthropicConfig(config) {
|
|
|
73
73
|
const effort = config.agents?.anthropic?.effort
|
|
74
74
|
|| settings.effortLevel
|
|
75
75
|
|| undefined;
|
|
76
|
-
|
|
76
|
+
const configExecPath = config.agents?.anthropic?.pathToClaudeCodeExecutable;
|
|
77
|
+
const isPlaceholderExec = !configExecPath || configExecPath.includes('your-') || configExecPath.includes('placeholder');
|
|
78
|
+
const pathToClaudeCodeExecutable = isPlaceholderExec ? undefined : configExecPath;
|
|
79
|
+
return { apiKey, baseUrl, model, effort, pathToClaudeCodeExecutable };
|
|
77
80
|
}
|
|
78
81
|
export function resolveOpenaiConfig(config) {
|
|
79
82
|
const codexSettings = loadCodexSettings();
|
|
@@ -305,6 +308,28 @@ export function setChannelShowActivities(config, instanceName, mode) {
|
|
|
305
308
|
}
|
|
306
309
|
}
|
|
307
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* 读取通道实例的 sessionMode 锁定配置
|
|
313
|
+
* 返回 undefined 表示未配置(由 session-manager 按 chatType 默认决定)
|
|
314
|
+
*/
|
|
315
|
+
export function getChannelSessionMode(config, instanceName) {
|
|
316
|
+
for (const type of channelTypes) {
|
|
317
|
+
const raw = config.channels?.[type];
|
|
318
|
+
if (raw === undefined)
|
|
319
|
+
continue;
|
|
320
|
+
if (Array.isArray(raw)) {
|
|
321
|
+
const inst = raw.find((item) => item.name === instanceName);
|
|
322
|
+
if (inst)
|
|
323
|
+
return inst.sessionMode;
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
const effectiveName = raw.name ?? type;
|
|
327
|
+
if (effectiveName === instanceName)
|
|
328
|
+
return raw.sessionMode;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return undefined;
|
|
332
|
+
}
|
|
308
333
|
export function isOwner(config, channelOrType, userId) {
|
|
309
334
|
// 按实例名精确匹配
|
|
310
335
|
if (getOwner(config, channelOrType) === userId)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { hasModelSwitcher, hasPermissionController } from '../agents/claude-runner.js';
|
|
2
|
-
import { saveConfig, resolvePaths, getPackageRoot, getOwner, getChannelShowActivities, setChannelShowActivities } from '../config.js';
|
|
2
|
+
import { saveConfig, resolvePaths, getPackageRoot, getOwner, getChannelShowActivities, setChannelShowActivities, getChannelSessionMode } from '../config.js';
|
|
3
3
|
import { logger } from '../utils/logger.js';
|
|
4
4
|
import crypto from 'crypto';
|
|
5
5
|
import path from 'path';
|
|
@@ -103,7 +103,7 @@ function formatIdleTime(ms) {
|
|
|
103
103
|
return '刚刚';
|
|
104
104
|
}
|
|
105
105
|
// 支持的命令列表
|
|
106
|
-
const commands = ['/new', '/pwd', '/plist', '/project', '/bind', '/help', '/status', '/restart', '/model', '/effort', '/agent', '/slist', '/session', '/rename', '/stop', '/clear', '/compact', '/repair', '/safe', '/fork', '/del', '/perm', '/
|
|
106
|
+
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', '/agentmd', '/chatmode'];
|
|
107
107
|
// 命令别名映射
|
|
108
108
|
const aliases = {
|
|
109
109
|
'/p': '/project',
|
|
@@ -112,7 +112,7 @@ const aliases = {
|
|
|
112
112
|
'/rw': '/rewind'
|
|
113
113
|
};
|
|
114
114
|
// 命令快速路径前缀(所有命令都不进入消息队列)
|
|
115
|
-
const quickCommandPrefixes = ['/new', '/pwd', '/plist', '/project', '/bind', '/help', '/status', '/restart', '/model', '/effort', '/agent', '/slist', '/session', '/rename', '/repair', '/fork', '/stop', '/clear', '/compact', '/safe', '/del', '/perm', '/
|
|
115
|
+
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'];
|
|
116
116
|
export class CommandHandler {
|
|
117
117
|
sessionManager;
|
|
118
118
|
config;
|
|
@@ -406,7 +406,7 @@ export class CommandHandler {
|
|
|
406
406
|
{ cmd: '/restart', label: '重启/重连', desc: '重启服务或重连指定渠道', next: { type: 'select', dynamic: true } },
|
|
407
407
|
] : []),
|
|
408
408
|
...(isOwner ? [
|
|
409
|
-
{ cmd: '/
|
|
409
|
+
{ cmd: '/file', label: '发送项目内文件', desc: '将项目目录内的文件发送给用户' },
|
|
410
410
|
{ cmd: '/agentmd', label: '管理 agent.md', desc: '查看或更新 AUN 网络上的 agent.md 身份文件', next: { type: 'select', items: [
|
|
411
411
|
{ value: 'put', label: '上传当前', desc: '将本地 agent.md 上传到 AUN 网络' },
|
|
412
412
|
{ value: 'set', label: '直接设置', desc: '输入内容直接更新 agent.md', next: { type: 'text' } },
|
|
@@ -529,7 +529,7 @@ export class CommandHandler {
|
|
|
529
529
|
}
|
|
530
530
|
}
|
|
531
531
|
// 空闲检查:某些命令需要等待当前会话空闲
|
|
532
|
-
const requiresIdle = ['/new', '/session', '/clear', '/compact', '/safe', '/repair', '/fork', '/bind', '/project', '/agent', '/rewind'];
|
|
532
|
+
const requiresIdle = ['/new', '/session', '/clear', '/compact', '/safe', '/repair', '/fork', '/bind', '/project', '/agent', '/rewind', '/chatmode'];
|
|
533
533
|
if (requiresIdle.some(cmd => normalizedContent === cmd || normalizedContent.startsWith(cmd + ' '))) {
|
|
534
534
|
if (threadId) {
|
|
535
535
|
// 话题中:检查话题 session 是否在处理(不创建)
|
|
@@ -633,7 +633,7 @@ export class CommandHandler {
|
|
|
633
633
|
] : []),
|
|
634
634
|
...(isOwner ? [
|
|
635
635
|
' /restart - 重启服务',
|
|
636
|
-
' /
|
|
636
|
+
' /file [channel] <path> - 发送项目内文件',
|
|
637
637
|
' /agentmd [put|set <内容>] - 管理 agent.md',
|
|
638
638
|
] : []),
|
|
639
639
|
'',
|
|
@@ -1207,6 +1207,10 @@ export class CommandHandler {
|
|
|
1207
1207
|
if (normalizedContent === '/activity' || normalizedContent.startsWith('/activity ')) {
|
|
1208
1208
|
if (!isAdmin)
|
|
1209
1209
|
return '❌ 无权限:此命令仅限管理员使用';
|
|
1210
|
+
// proactive 模式下流式输出全部静默,activity 配置无意义
|
|
1211
|
+
if (activeSession?.sessionMode === 'proactive') {
|
|
1212
|
+
return '❌ 当前会话为 proactive 模式,不支持 activity 配置(流式输出已全部静默)';
|
|
1213
|
+
}
|
|
1210
1214
|
const activityArg = normalizedContent.slice(9).trim();
|
|
1211
1215
|
const modeMap = {
|
|
1212
1216
|
all: 'all',
|
|
@@ -1284,6 +1288,31 @@ export class CommandHandler {
|
|
|
1284
1288
|
setChannelShowActivities(this.config, channel, newMode);
|
|
1285
1289
|
return `✅ 中间输出模式: ${activityArg}(${label})`;
|
|
1286
1290
|
}
|
|
1291
|
+
// /chatmode 命令:查看/切换 session 会话模式(interactive | proactive)
|
|
1292
|
+
if (normalizedContent === '/chatmode' || normalizedContent.startsWith('/chatmode ')) {
|
|
1293
|
+
if (!isAdmin)
|
|
1294
|
+
return '❌ 无权限:此命令仅限管理员使用';
|
|
1295
|
+
if (!activeSession)
|
|
1296
|
+
return '❌ 当前无活跃会话';
|
|
1297
|
+
const lockedMode = getChannelSessionMode(this.config, channel);
|
|
1298
|
+
const arg = normalizedContent.slice(9).trim();
|
|
1299
|
+
const currentMode = activeSession.sessionMode || 'interactive';
|
|
1300
|
+
if (!arg) {
|
|
1301
|
+
const lockHint = lockedMode ? `(由通道配置锁定为 ${lockedMode})` : '';
|
|
1302
|
+
return `📋 当前会话模式: ${currentMode}${lockHint}\n可选: interactive / proactive\n用法: /chatmode <模式>`;
|
|
1303
|
+
}
|
|
1304
|
+
if (arg !== 'interactive' && arg !== 'proactive') {
|
|
1305
|
+
return `❌ 无效模式: ${arg}\n可选: interactive / proactive`;
|
|
1306
|
+
}
|
|
1307
|
+
if (lockedMode) {
|
|
1308
|
+
return `❌ 会话模式由通道配置锁定为 ${lockedMode},无法切换`;
|
|
1309
|
+
}
|
|
1310
|
+
if (arg === currentMode) {
|
|
1311
|
+
return `📋 当前会话模式已是 ${arg}`;
|
|
1312
|
+
}
|
|
1313
|
+
await this.sessionManager.updateSession(activeSession.id, { sessionMode: arg });
|
|
1314
|
+
return `✅ 会话模式已切换: ${arg}`;
|
|
1315
|
+
}
|
|
1287
1316
|
// /stop 命令:中断当前任务
|
|
1288
1317
|
if (normalizedContent === '/stop') {
|
|
1289
1318
|
const stopResult = await this.ensureSession(channel, channelId, threadId);
|
|
@@ -1633,15 +1662,15 @@ export class CommandHandler {
|
|
|
1633
1662
|
}
|
|
1634
1663
|
return `当前项目: ${session.projectPath}`;
|
|
1635
1664
|
}
|
|
1636
|
-
// /
|
|
1637
|
-
if (normalizedContent.startsWith('/
|
|
1665
|
+
// /file 命令:发送项目内文件,支持 /file path 和 /file channel path(owner only)
|
|
1666
|
+
if (normalizedContent.startsWith('/file')) {
|
|
1638
1667
|
if (!isOwner)
|
|
1639
1668
|
return '❌ 无权限:此命令仅限 owner 使用';
|
|
1640
1669
|
// 飞书会将 .md 等后缀自动转为 Markdown 链接: foo.md → [foo.md](http://foo.md/)
|
|
1641
1670
|
// 还原: 将 [text](url) 替换为 text
|
|
1642
1671
|
const rawArg = normalizedContent.slice(5).trim().replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
|
|
1643
1672
|
if (!rawArg) {
|
|
1644
|
-
return '用法: /
|
|
1673
|
+
return '用法: /file <相对路径> 或 /file <渠道> <相对路径>\n示例: /file src/index.ts\n示例: /file feishu report.md';
|
|
1645
1674
|
}
|
|
1646
1675
|
// 解析目标通道:第一个 token 按实例名匹配,再按 channelType 匹配
|
|
1647
1676
|
const tokens = rawArg.split(/\s+/);
|
|
@@ -1733,7 +1762,7 @@ export class CommandHandler {
|
|
|
1733
1762
|
: `✅ 已发送: ${filePath} (${sizeStr})`;
|
|
1734
1763
|
}
|
|
1735
1764
|
catch (error) {
|
|
1736
|
-
logger.error('[CommandHandler] /
|
|
1765
|
+
logger.error('[CommandHandler] /file failed:', error);
|
|
1737
1766
|
return `❌ 文件发送失败: ${error.message || error}`;
|
|
1738
1767
|
}
|
|
1739
1768
|
}
|
|
@@ -2572,8 +2601,24 @@ export class CommandHandler {
|
|
|
2572
2601
|
static CTL_COMMANDS = [
|
|
2573
2602
|
'/help', '/status', '/check',
|
|
2574
2603
|
'/model', '/effort', '/perm',
|
|
2575
|
-
'/compact', '/activity', '/send', '/restart', '/agentmd',
|
|
2604
|
+
'/compact', '/activity', '/file', '/send', '/chatmode', '/restart', '/agentmd',
|
|
2576
2605
|
];
|
|
2606
|
+
/**
|
|
2607
|
+
* 从 session 恢复 ReplyContext,用于 ctl send 主动发送文本时的路由
|
|
2608
|
+
* - 群聊话题:metadata.replyContext.{threadId,peerId}
|
|
2609
|
+
* - 私聊:metadata.peerId
|
|
2610
|
+
*/
|
|
2611
|
+
buildCtlReplyContext(session) {
|
|
2612
|
+
const ctx = {};
|
|
2613
|
+
const meta = session.metadata;
|
|
2614
|
+
if (meta?.replyContext?.threadId)
|
|
2615
|
+
ctx.threadId = meta.replyContext.threadId;
|
|
2616
|
+
if (meta?.replyContext?.peerId)
|
|
2617
|
+
ctx.peerId = meta.replyContext.peerId;
|
|
2618
|
+
if (!ctx.peerId && meta?.peerId)
|
|
2619
|
+
ctx.peerId = meta.peerId;
|
|
2620
|
+
return Object.keys(ctx).length > 0 ? ctx : undefined;
|
|
2621
|
+
}
|
|
2577
2622
|
/**
|
|
2578
2623
|
* Agent ctl 入口:通过 IPC 接收 Agent 自主管理指令
|
|
2579
2624
|
* 复用现有 slash cmd 逻辑,权限继承 session 用户角色
|
|
@@ -2591,8 +2636,25 @@ export class CommandHandler {
|
|
|
2591
2636
|
}
|
|
2592
2637
|
// 3. 从 session.metadata.peerId 获取 userId(用于权限判断)
|
|
2593
2638
|
const userId = session.metadata?.peerId;
|
|
2594
|
-
// 4. send
|
|
2595
|
-
if (cmd.startsWith('/send')) {
|
|
2639
|
+
// 4. /send 文本消息:直接通过 adapter 主动发送,不走 handle()
|
|
2640
|
+
if (cmd.startsWith('/send ') || cmd === '/send') {
|
|
2641
|
+
const text = cmd.startsWith('/send ') ? cmd.slice(6).trim() : '';
|
|
2642
|
+
if (!text)
|
|
2643
|
+
return { ok: false, error: '消息内容不能为空' };
|
|
2644
|
+
const adapter = this.adapters.get(session.channel);
|
|
2645
|
+
if (!adapter)
|
|
2646
|
+
return { ok: false, error: `adapter 未找到: ${session.channel}` };
|
|
2647
|
+
try {
|
|
2648
|
+
const replyContext = this.buildCtlReplyContext(session);
|
|
2649
|
+
await adapter.sendText(session.channelId, text, replyContext);
|
|
2650
|
+
return { ok: true, result: '已发送' };
|
|
2651
|
+
}
|
|
2652
|
+
catch (err) {
|
|
2653
|
+
return { ok: false, error: err.message || String(err) };
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
// 5. file 路径限制:只允许 projectPath 下的文件
|
|
2657
|
+
if (cmd.startsWith('/file')) {
|
|
2596
2658
|
const sendArgs = cmd.slice(5).trim();
|
|
2597
2659
|
const parts = sendArgs.split(/\s+/);
|
|
2598
2660
|
const filePath = parts[parts.length - 1];
|
|
@@ -2603,7 +2665,7 @@ export class CommandHandler {
|
|
|
2603
2665
|
}
|
|
2604
2666
|
}
|
|
2605
2667
|
}
|
|
2606
|
-
//
|
|
2668
|
+
// 6. 调用现有 handle(),不传 sendMessage 回调(结果直接返回)
|
|
2607
2669
|
try {
|
|
2608
2670
|
const result = await this.handle(cmd, session.channel, session.channelId, undefined, // 不发送消息
|
|
2609
2671
|
userId);
|