openclaw-vchat-plugin 0.0.1
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/bin/openclaw-vchat.js +110 -0
- package/dist/commands.d.ts +18 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +509 -0
- package/dist/commands.js.map +1 -0
- package/dist/constants.d.ts +14 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +51 -0
- package/dist/constants.js.map +1 -0
- package/dist/gateway-client.d.ts +43 -0
- package/dist/gateway-client.d.ts.map +1 -0
- package/dist/gateway-client.js +623 -0
- package/dist/gateway-client.js.map +1 -0
- package/dist/group-manager.d.ts +30 -0
- package/dist/group-manager.d.ts.map +1 -0
- package/dist/group-manager.js +107 -0
- package/dist/group-manager.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +382 -0
- package/dist/index.js.map +1 -0
- package/dist/media-handler.d.ts +31 -0
- package/dist/media-handler.d.ts.map +1 -0
- package/dist/media-handler.js +67 -0
- package/dist/media-handler.js.map +1 -0
- package/dist/message-handler.d.ts +52 -0
- package/dist/message-handler.d.ts.map +1 -0
- package/dist/message-handler.js +291 -0
- package/dist/message-handler.js.map +1 -0
- package/dist/relay-server.d.ts +16 -0
- package/dist/relay-server.d.ts.map +1 -0
- package/dist/relay-server.js +877 -0
- package/dist/relay-server.js.map +1 -0
- package/dist/routes/config.routes.d.ts +12 -0
- package/dist/routes/config.routes.d.ts.map +1 -0
- package/dist/routes/config.routes.js +175 -0
- package/dist/routes/config.routes.js.map +1 -0
- package/dist/services/config.service.d.ts +57 -0
- package/dist/services/config.service.d.ts.map +1 -0
- package/dist/services/config.service.js +361 -0
- package/dist/services/config.service.js.map +1 -0
- package/dist/session-key.d.ts +8 -0
- package/dist/session-key.d.ts.map +1 -0
- package/dist/session-key.js +28 -0
- package/dist/session-key.js.map +1 -0
- package/dist/session-manager.d.ts +32 -0
- package/dist/session-manager.d.ts.map +1 -0
- package/dist/session-manager.js +303 -0
- package/dist/session-manager.js.map +1 -0
- package/dist/types.d.ts +81 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/nginx-proxy.conf +24 -0
- package/package.json +51 -0
- package/src/commands.ts +499 -0
- package/src/constants.ts +49 -0
- package/src/gateway-client.ts +648 -0
- package/src/group-manager.ts +119 -0
- package/src/index.ts +443 -0
- package/src/media-handler.ts +70 -0
- package/src/message-handler.ts +419 -0
- package/src/relay-server.ts +979 -0
- package/src/routes/config.routes.ts +144 -0
- package/src/services/config.service.ts +398 -0
- package/src/session-key.ts +30 -0
- package/src/session-manager.ts +374 -0
- package/src/types.ts +96 -0
- package/start.sh +5 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { spawnSync } = require('child_process');
|
|
5
|
+
|
|
6
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
7
|
+
const distEntry = path.join(packageRoot, 'dist', 'index.js');
|
|
8
|
+
const pkg = require(path.join(packageRoot, 'package.json'));
|
|
9
|
+
|
|
10
|
+
function printUsage() {
|
|
11
|
+
console.log(`openclaw-vchat ${pkg.version}
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
openclaw-vchat start
|
|
15
|
+
openclaw-vchat install [--pm2]
|
|
16
|
+
openclaw-vchat doctor
|
|
17
|
+
openclaw-vchat version
|
|
18
|
+
`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function hasCommand(cmd) {
|
|
22
|
+
const res = spawnSync(process.platform === 'win32' ? 'where' : 'which', [cmd], { stdio: 'ignore' });
|
|
23
|
+
return res.status === 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function runNodeEntry() {
|
|
27
|
+
if (!fs.existsSync(distEntry)) {
|
|
28
|
+
console.error('dist/index.js 不存在,请先执行 npm run build');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
const child = spawnSync(process.execPath, [distEntry], { stdio: 'inherit', env: process.env });
|
|
32
|
+
process.exit(child.status || 0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function doctor() {
|
|
36
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
37
|
+
const openclawHome = process.env.OPENCLAW_HOME || path.join(home, '.openclaw');
|
|
38
|
+
const deviceConfig = path.join(openclawHome, 'wechat-device.json');
|
|
39
|
+
const issues = [];
|
|
40
|
+
|
|
41
|
+
if (!hasCommand('openclaw')) issues.push('未找到 openclaw CLI');
|
|
42
|
+
if (!fs.existsSync(deviceConfig)) issues.push('未找到 ~/.openclaw/wechat-device.json,设备尚未配对');
|
|
43
|
+
if (!process.env.WECHAT_BACKEND_URL) issues.push('未设置 WECHAT_BACKEND_URL,将使用内置默认值');
|
|
44
|
+
if (!process.env.PLUGIN_SECRET) issues.push('未设置 PLUGIN_SECRET,内部通信将依赖空密钥');
|
|
45
|
+
|
|
46
|
+
console.log(`OpenClaw VChat Plugin ${pkg.version}`);
|
|
47
|
+
console.log(`packageRoot: ${packageRoot}`);
|
|
48
|
+
console.log(`openclawHome: ${openclawHome}`);
|
|
49
|
+
console.log(`deviceConfig: ${fs.existsSync(deviceConfig) ? 'ok' : 'missing'}`);
|
|
50
|
+
console.log(`openclaw cli: ${hasCommand('openclaw') ? 'ok' : 'missing'}`);
|
|
51
|
+
console.log(`pm2: ${hasCommand('pm2') ? 'ok' : 'missing'}`);
|
|
52
|
+
console.log(`backend: ${process.env.WECHAT_BACKEND_URL || '(default)'}`);
|
|
53
|
+
if (issues.length) {
|
|
54
|
+
console.log('\nIssues:');
|
|
55
|
+
issues.forEach((item) => console.log(`- ${item}`));
|
|
56
|
+
process.exitCode = 1;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
console.log('\nDoctor OK');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function install() {
|
|
63
|
+
const args = new Set(process.argv.slice(3));
|
|
64
|
+
const usePm2 = args.has('--pm2');
|
|
65
|
+
|
|
66
|
+
if (!fs.existsSync(distEntry)) {
|
|
67
|
+
console.error('dist/index.js 不存在,请先执行 npm run build,或使用发布后的 npm 包。');
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log(`Installing OpenClaw VChat Plugin ${pkg.version}`);
|
|
72
|
+
console.log(`packageRoot: ${packageRoot}`);
|
|
73
|
+
|
|
74
|
+
if (usePm2) {
|
|
75
|
+
if (!hasCommand('pm2')) {
|
|
76
|
+
console.error('未找到 pm2,请先安装 pm2:npm i -g pm2');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
const name = 'openclaw-vchat';
|
|
80
|
+
const start = spawnSync('pm2', ['start', process.execPath, '--name', name, '--', distEntry], {
|
|
81
|
+
stdio: 'inherit',
|
|
82
|
+
env: process.env,
|
|
83
|
+
cwd: packageRoot,
|
|
84
|
+
});
|
|
85
|
+
if (start.status !== 0) {
|
|
86
|
+
console.error('pm2 启动失败');
|
|
87
|
+
process.exit(start.status || 1);
|
|
88
|
+
}
|
|
89
|
+
spawnSync('pm2', ['save'], { stdio: 'inherit', env: process.env, cwd: packageRoot });
|
|
90
|
+
console.log('\nInstall OK: pm2 进程已创建');
|
|
91
|
+
console.log(`查看日志: pm2 logs ${name}`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log('\nInstall OK');
|
|
96
|
+
console.log('直接启动: openclaw-vchat start');
|
|
97
|
+
console.log('若需守护进程: openclaw-vchat install --pm2');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const cmd = process.argv[2] || 'start';
|
|
101
|
+
if (cmd === 'start') runNodeEntry();
|
|
102
|
+
else if (cmd === 'install') install();
|
|
103
|
+
else if (cmd === 'doctor') doctor();
|
|
104
|
+
else if (cmd === 'version' || cmd === '--version' || cmd === '-v') console.log(pkg.version);
|
|
105
|
+
else if (cmd === 'help' || cmd === '--help' || cmd === '-h') printUsage();
|
|
106
|
+
else {
|
|
107
|
+
console.error(`未知命令: ${cmd}`);
|
|
108
|
+
printUsage();
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BotCommand, CommandExecutionContext } from './types';
|
|
2
|
+
import { GatewayClient } from './gateway-client';
|
|
3
|
+
export declare function setGatewayClient(client: GatewayClient): void;
|
|
4
|
+
export type CommandRoute = 'local' | 'gateway-rpc' | 'gateway-passthrough' | 'model';
|
|
5
|
+
export interface ParsedSlashCommand {
|
|
6
|
+
rawCommand: string;
|
|
7
|
+
command: string;
|
|
8
|
+
args: string;
|
|
9
|
+
route: CommandRoute;
|
|
10
|
+
}
|
|
11
|
+
export declare const BOT_COMMANDS: BotCommand[];
|
|
12
|
+
export declare function parseCommandInput(content: string): ParsedSlashCommand | null;
|
|
13
|
+
export declare function resolveCommandRoute(command: string): CommandRoute;
|
|
14
|
+
export declare function handleBuiltinCommand(command: string, _args: string): string | null;
|
|
15
|
+
export declare const GATEWAY_API_COMMANDS: string[];
|
|
16
|
+
export declare const GATEWAY_PASSTHROUGH_COMMANDS: string[];
|
|
17
|
+
export declare function handleBuiltinCommandAsync(command: string, args: string, context?: CommandExecutionContext): Promise<string | null>;
|
|
18
|
+
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGjD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAAsB;AAEnF,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,aAAa,GAAG,qBAAqB,GAAG,OAAO,CAAC;AAErF,MAAM,WAAW,kBAAkB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,YAAY,CAAC;CACvB;AAED,eAAO,MAAM,YAAY,EAAE,UAAU,EAyCpC,CAAC;AA6DF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAc5E;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,CAMjE;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQlF;AAED,eAAO,MAAM,oBAAoB,UAA8B,CAAC;AAChE,eAAO,MAAM,4BAA4B,UAAsC,CAAC;AAEhF,wBAAsB,yBAAyB,CAC3C,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,uBAA4B,GACtC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmCxB"}
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GATEWAY_PASSTHROUGH_COMMANDS = exports.GATEWAY_API_COMMANDS = exports.BOT_COMMANDS = void 0;
|
|
4
|
+
exports.setGatewayClient = setGatewayClient;
|
|
5
|
+
exports.parseCommandInput = parseCommandInput;
|
|
6
|
+
exports.resolveCommandRoute = resolveCommandRoute;
|
|
7
|
+
exports.handleBuiltinCommand = handleBuiltinCommand;
|
|
8
|
+
exports.handleBuiltinCommandAsync = handleBuiltinCommandAsync;
|
|
9
|
+
let gateway;
|
|
10
|
+
function setGatewayClient(client) { gateway = client; }
|
|
11
|
+
exports.BOT_COMMANDS = [
|
|
12
|
+
{ command: '/help', description: '显示可用命令', icon: '❓' },
|
|
13
|
+
{ command: '/commands', description: '列出所有斜杠命令', icon: '📋' },
|
|
14
|
+
{ command: '/status', description: '显示当前状态', icon: '📊' },
|
|
15
|
+
{ command: '/whoami', description: '显示你的发送者 ID', icon: '👤' },
|
|
16
|
+
{ command: '/skill', description: '按名称运行一个技能', icon: '🛠️' },
|
|
17
|
+
{ command: '/healthcheck', description: '主机安全加固和风险容忍度配置', icon: '🏥' },
|
|
18
|
+
{ command: '/session_logs', description: '搜索和分析会话日志', icon: '📜' },
|
|
19
|
+
{ command: '/skill_creator', description: '创建或更新 AgentSkill', icon: '🧩' },
|
|
20
|
+
{ command: '/weather', description: '获取天气和预报', icon: '🌤️' },
|
|
21
|
+
{ command: '/new', description: '开始新会话', icon: '💬' },
|
|
22
|
+
{ command: '/reset', description: '重置当前会话', icon: '🔄' },
|
|
23
|
+
{ command: '/compact', description: '压缩会话上下文', icon: '📦' },
|
|
24
|
+
{ command: '/session', description: '管理会话级设置', icon: '⚙️' },
|
|
25
|
+
{ command: '/export_session', description: '导出当前会话为 HTML 文件', icon: '📤' },
|
|
26
|
+
{ command: '/context', description: '查看上下文构建方式', icon: '🔍' },
|
|
27
|
+
{ command: '/usage', description: '使用量和费用摘要', icon: '💰' },
|
|
28
|
+
{ command: '/stop', description: '停止当前运行', icon: '⏹️' },
|
|
29
|
+
{ command: '/restart', description: '重启 OpenClaw', icon: '🔁' },
|
|
30
|
+
{ command: '/approve', description: '批准或拒绝执行请求', icon: '✅' },
|
|
31
|
+
{ command: '/kill', description: '终止运行中的子代理', icon: '💀' },
|
|
32
|
+
{ command: '/steer', description: '向运行中的子代理发送指导', icon: '🎯' },
|
|
33
|
+
{ command: '/subagents', description: '管理子代理运行状态', icon: '🤖' },
|
|
34
|
+
{ command: '/agents', description: '列出会话绑定的代理', icon: '👥' },
|
|
35
|
+
{ command: '/acp', description: '管理 ACP 会话和运行时选项', icon: '🔗' },
|
|
36
|
+
{ command: '/model', description: '显示或设置模型', icon: '🧠' },
|
|
37
|
+
{ command: '/models', description: '列出可用模型', icon: '📂' },
|
|
38
|
+
{ command: '/think', description: '设置思考级别', icon: '💭' },
|
|
39
|
+
{ command: '/verbose', description: '切换详细模式', icon: '📝' },
|
|
40
|
+
{ command: '/reasoning', description: '切换推理可见性', icon: '🧮' },
|
|
41
|
+
{ command: '/elevated', description: '切换提权模式', icon: '🔑' },
|
|
42
|
+
{ command: '/exec', description: '设置执行默认值', icon: '⚡' },
|
|
43
|
+
{ command: '/queue', description: '调整队列设置', icon: '📥' },
|
|
44
|
+
{ command: '/tts', description: '控制文字转语音 (TTS)', icon: '🔊' },
|
|
45
|
+
{ command: '/activation', description: '设置群组激活模式', icon: '🔔' },
|
|
46
|
+
{ command: '/send', description: '设置发送策略', icon: '📨' },
|
|
47
|
+
{ command: '/focus', description: '绑定会话目标', icon: '🎯' },
|
|
48
|
+
{ command: '/unfocus', description: '解除会话绑定', icon: '🔓' },
|
|
49
|
+
{ command: '/dock_telegram', description: '切换到 Telegram 回复', icon: '✈️' },
|
|
50
|
+
{ command: '/dock_discord', description: '切换到 Discord 回复', icon: '🎮' },
|
|
51
|
+
{ command: '/dock_slack', description: '切换到 Slack 回复', icon: '💼' },
|
|
52
|
+
];
|
|
53
|
+
// ========== 命令分类 ==========
|
|
54
|
+
const COMMAND_ALIASES = {
|
|
55
|
+
'/id': '/whoami',
|
|
56
|
+
'/thinking': '/think',
|
|
57
|
+
'/t': '/think',
|
|
58
|
+
'/v': '/verbose',
|
|
59
|
+
'/reason': '/reasoning',
|
|
60
|
+
'/elev': '/elevated',
|
|
61
|
+
};
|
|
62
|
+
const LOCAL_SHORTCUT_SET = new Set([
|
|
63
|
+
'/help',
|
|
64
|
+
'/commands',
|
|
65
|
+
'/whoami',
|
|
66
|
+
]);
|
|
67
|
+
const GATEWAY_RPC_SET = new Set([
|
|
68
|
+
'/think', '/model', '/models', '/verbose', '/reasoning', '/elevated',
|
|
69
|
+
'/status', '/stop', '/new', '/reset', '/compact', '/usage',
|
|
70
|
+
'/tts', '/exec', '/agents',
|
|
71
|
+
'/queue', '/activation', '/send',
|
|
72
|
+
'/export_session', '/session',
|
|
73
|
+
'/focus', '/unfocus',
|
|
74
|
+
'/dock_telegram', '/dock_discord', '/dock_slack',
|
|
75
|
+
]);
|
|
76
|
+
const GATEWAY_PASSTHROUGH_SET = new Set([
|
|
77
|
+
'/approve',
|
|
78
|
+
'/kill',
|
|
79
|
+
'/steer',
|
|
80
|
+
'/tell',
|
|
81
|
+
'/subagents',
|
|
82
|
+
'/acp',
|
|
83
|
+
'/context',
|
|
84
|
+
'/bash',
|
|
85
|
+
'/debug',
|
|
86
|
+
'/config',
|
|
87
|
+
'/skill',
|
|
88
|
+
'/healthcheck',
|
|
89
|
+
'/session_logs',
|
|
90
|
+
'/skill_creator',
|
|
91
|
+
'/weather',
|
|
92
|
+
'/poll',
|
|
93
|
+
'/last',
|
|
94
|
+
'/caps',
|
|
95
|
+
'/allowlist',
|
|
96
|
+
'/restart',
|
|
97
|
+
]);
|
|
98
|
+
function normalizeCommandName(command) {
|
|
99
|
+
let normalized = String(command || '').trim().toLowerCase();
|
|
100
|
+
if (!normalized)
|
|
101
|
+
return normalized;
|
|
102
|
+
if (normalized.endsWith(':'))
|
|
103
|
+
normalized = normalized.slice(0, -1);
|
|
104
|
+
if (normalized.startsWith('!'))
|
|
105
|
+
normalized = `/${normalized.slice(1)}`;
|
|
106
|
+
normalized = normalized.replace(/-/g, '_');
|
|
107
|
+
return COMMAND_ALIASES[normalized] || normalized;
|
|
108
|
+
}
|
|
109
|
+
function parseCommandInput(content) {
|
|
110
|
+
const text = String(content || '').trim();
|
|
111
|
+
if (!text)
|
|
112
|
+
return null;
|
|
113
|
+
const head = text.split(/\s+/, 1)[0] || '';
|
|
114
|
+
if (!head.startsWith('/') && !head.startsWith('!'))
|
|
115
|
+
return null;
|
|
116
|
+
const rawCommand = head.replace(/:$/, '').toLowerCase();
|
|
117
|
+
const command = normalizeCommandName(rawCommand);
|
|
118
|
+
const args = text.slice(head.length).trimStart().replace(/^:\s*/, '');
|
|
119
|
+
return {
|
|
120
|
+
rawCommand,
|
|
121
|
+
command,
|
|
122
|
+
args,
|
|
123
|
+
route: resolveCommandRoute(command),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function resolveCommandRoute(command) {
|
|
127
|
+
const normalized = normalizeCommandName(command);
|
|
128
|
+
if (LOCAL_SHORTCUT_SET.has(normalized))
|
|
129
|
+
return 'local';
|
|
130
|
+
if (GATEWAY_RPC_SET.has(normalized))
|
|
131
|
+
return 'gateway-rpc';
|
|
132
|
+
if (GATEWAY_PASSTHROUGH_SET.has(normalized))
|
|
133
|
+
return 'gateway-passthrough';
|
|
134
|
+
return normalized.startsWith('/') ? 'model' : 'model';
|
|
135
|
+
}
|
|
136
|
+
function handleBuiltinCommand(command, _args) {
|
|
137
|
+
switch (normalizeCommandName(command)) {
|
|
138
|
+
case '/help': return formatHelpMessage();
|
|
139
|
+
case '/commands': return formatCommandsList();
|
|
140
|
+
case '/id':
|
|
141
|
+
case '/whoami': return '👤 当前客户端: WeChat Mini Program\n📱 渠道: wechat';
|
|
142
|
+
default: return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
exports.GATEWAY_API_COMMANDS = Array.from(GATEWAY_RPC_SET);
|
|
146
|
+
exports.GATEWAY_PASSTHROUGH_COMMANDS = Array.from(GATEWAY_PASSTHROUGH_SET);
|
|
147
|
+
async function handleBuiltinCommandAsync(command, args, context = {}) {
|
|
148
|
+
const normalizedCommand = normalizeCommandName(command);
|
|
149
|
+
try {
|
|
150
|
+
switch (normalizedCommand) {
|
|
151
|
+
case '/think': return await handleThinkCmd(args);
|
|
152
|
+
case '/model': return await handleModelCmd(args);
|
|
153
|
+
case '/verbose': return await handleToggleConfig('verbose', '详细模式', 'agents.defaults.verbose', args);
|
|
154
|
+
case '/reasoning': return await handleToggleConfig('reasoning', '推理可见性', 'agents.defaults.showReasoning', args);
|
|
155
|
+
case '/elevated': return await handleToggleConfig('elevated', '提权模式', 'agents.defaults.elevated', args);
|
|
156
|
+
case '/status': return await handleStatusCmd();
|
|
157
|
+
case '/usage': return await handleUsageCmd();
|
|
158
|
+
case '/models': return await handleModelsCmd();
|
|
159
|
+
case '/agents': return await handleAgentsCmd();
|
|
160
|
+
case '/new':
|
|
161
|
+
case '/reset': return await handleSessionResetCmd(context);
|
|
162
|
+
case '/compact': return await handleCompactCmd(context);
|
|
163
|
+
case '/export_session': return '📤 导出会话功能在微信小程序中暂不可用。\n请在网页端或 Telegram 中使用此功能。';
|
|
164
|
+
case '/session': return await handleSessionInfoCmd();
|
|
165
|
+
case '/stop': return await handleStopCmd(context);
|
|
166
|
+
case '/tts': return await handleTtsCmd(args);
|
|
167
|
+
case '/exec': return await handleExecCmd(args);
|
|
168
|
+
case '/queue': return await handleConfigToggle('queue', '队列设置', 'agents.defaults.queue', args);
|
|
169
|
+
case '/activation': return await handleConfigToggle('activation', '激活模式', 'channels.activation', args);
|
|
170
|
+
case '/send': return await handleConfigToggle('send', '发送策略', 'channels.sendStrategy', args);
|
|
171
|
+
case '/focus': return '🎯 当前渠道已聚焦';
|
|
172
|
+
case '/unfocus': return '🔓 已解除聚焦';
|
|
173
|
+
case '/dock_telegram': return '✈️ 已切换到 Telegram';
|
|
174
|
+
case '/dock_discord': return '🎮 已切换到 Discord';
|
|
175
|
+
case '/dock_slack': return '💼 已切换到 Slack';
|
|
176
|
+
default: return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
console.error(`[Command] ${normalizedCommand} 执行失败:`, err);
|
|
181
|
+
return `❌ 命令执行失败: ${err.message}`;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// ========== 各命令实现 ==========
|
|
185
|
+
const THINK_LEVELS = ['off', 'minimal', 'low', 'medium', 'high'];
|
|
186
|
+
const THINK_LABELS = {
|
|
187
|
+
off: '❌ 关闭', minimal: '💡 极简', low: '🔅 低', medium: '💭 中等', high: '🧠 高',
|
|
188
|
+
};
|
|
189
|
+
// --- 配置类(本地文件读写,和 Telegram bot 一样)---
|
|
190
|
+
async function handleThinkCmd(args) {
|
|
191
|
+
const level = args.trim().toLowerCase();
|
|
192
|
+
if (level && THINK_LEVELS.includes(level)) {
|
|
193
|
+
await gateway.patchConfigLocal([{ path: 'agents.defaults.thinkingDefault', value: level }]);
|
|
194
|
+
return `✅ 思考级别已设置为 ${THINK_LABELS[level] || level}`;
|
|
195
|
+
}
|
|
196
|
+
const cfg = gateway.readConfig();
|
|
197
|
+
const current = cfg?.agents?.defaults?.thinkingDefault || 'medium';
|
|
198
|
+
let msg = `💭 **思考级别**\n📌 当前: ${THINK_LABELS[current] || current}\n\n`;
|
|
199
|
+
for (const lv of THINK_LEVELS) {
|
|
200
|
+
msg += `${lv === current ? '👉 ' : ' '}${THINK_LABELS[lv]} → \`/think ${lv}\`\n`;
|
|
201
|
+
}
|
|
202
|
+
return msg;
|
|
203
|
+
}
|
|
204
|
+
async function handleModelCmd(args) {
|
|
205
|
+
if (args.trim()) {
|
|
206
|
+
await gateway.patchConfigLocal([{ path: 'agents.defaults.model.primary', value: args.trim() }]);
|
|
207
|
+
return `✅ 模型已切换为: ${args.trim()}`;
|
|
208
|
+
}
|
|
209
|
+
const cfg = gateway.readConfig();
|
|
210
|
+
const model = cfg?.agents?.defaults?.model;
|
|
211
|
+
const name = typeof model === 'string' ? model : (model?.primary || '未知');
|
|
212
|
+
return `🧠 **当前模型**: ${name}\n\n发 \`/model <名称>\` 切换\n发 \`/models\` 查看所有可用模型`;
|
|
213
|
+
}
|
|
214
|
+
async function handleExecCmd(args) {
|
|
215
|
+
const val = args.trim().toLowerCase();
|
|
216
|
+
const labels = {
|
|
217
|
+
auto: '自动执行', confirm: '需要确认', deny: '禁止执行',
|
|
218
|
+
allow: '自动执行', ask: '需要确认', block: '禁止执行',
|
|
219
|
+
};
|
|
220
|
+
if (val && labels[val]) {
|
|
221
|
+
const norm = val === 'allow' ? 'auto' : val === 'ask' ? 'confirm' : val === 'block' ? 'deny' : val;
|
|
222
|
+
await gateway.patchConfigLocal([{ path: 'agents.defaults.execPolicy', value: norm }]);
|
|
223
|
+
return `✅ 执行策略已设置为: ${labels[val]}`;
|
|
224
|
+
}
|
|
225
|
+
const cfg = gateway.readConfig();
|
|
226
|
+
const cur = cfg?.agents?.defaults?.execPolicy || '未知';
|
|
227
|
+
return `⚡ **执行策略**: ${labels[cur] || cur}\n\n自动 → \`/exec auto\`\n确认 → \`/exec confirm\`\n禁止 → \`/exec deny\``;
|
|
228
|
+
}
|
|
229
|
+
async function handleToggleConfig(name, label, configPath, args) {
|
|
230
|
+
const val = args.trim().toLowerCase();
|
|
231
|
+
if (val === 'on' || val === 'true') {
|
|
232
|
+
await gateway.patchConfigLocal([{ path: configPath, value: true }]);
|
|
233
|
+
return `✅ ${label}: 已开启`;
|
|
234
|
+
}
|
|
235
|
+
if (val === 'off' || val === 'false') {
|
|
236
|
+
await gateway.patchConfigLocal([{ path: configPath, value: false }]);
|
|
237
|
+
return `✅ ${label}: 已关闭`;
|
|
238
|
+
}
|
|
239
|
+
const cfg = gateway.readConfig();
|
|
240
|
+
let cur = cfg;
|
|
241
|
+
for (const p of configPath.split('.'))
|
|
242
|
+
cur = cur?.[p];
|
|
243
|
+
return `📝 **${label}**: ${cur ? '✅ 开启' : '❌ 关闭'}\n\n开启 → \`/${name} on\`\n关闭 → \`/${name} off\``;
|
|
244
|
+
}
|
|
245
|
+
async function handleConfigToggle(name, label, configPath, args) {
|
|
246
|
+
const val = args.trim();
|
|
247
|
+
if (val) {
|
|
248
|
+
await gateway.patchConfigLocal([{ path: configPath, value: val }]);
|
|
249
|
+
return `✅ ${label}已设置为: ${val}`;
|
|
250
|
+
}
|
|
251
|
+
const cfg = gateway.readConfig();
|
|
252
|
+
let cur = cfg;
|
|
253
|
+
for (const p of configPath.split('.'))
|
|
254
|
+
cur = cur?.[p];
|
|
255
|
+
return `📝 **${label}**: ${cur || '默认'}\n\n设置 → \`/${name} <值>\``;
|
|
256
|
+
}
|
|
257
|
+
// --- Gateway RPC 类(通过 call() 秒回)---
|
|
258
|
+
async function handleModelsCmd() {
|
|
259
|
+
const modelMap = new Map();
|
|
260
|
+
const normalizeModelName = (name, provider) => {
|
|
261
|
+
const raw = String(name || '').trim();
|
|
262
|
+
const p = String(provider || '').trim().toLowerCase();
|
|
263
|
+
if (!raw || !p)
|
|
264
|
+
return raw;
|
|
265
|
+
const lower = raw.toLowerCase();
|
|
266
|
+
const prefix = `${p}/`;
|
|
267
|
+
if (lower.startsWith(prefix)) {
|
|
268
|
+
return raw.slice(prefix.length).trim();
|
|
269
|
+
}
|
|
270
|
+
return raw;
|
|
271
|
+
};
|
|
272
|
+
const addModel = (name, provider) => {
|
|
273
|
+
const modelName = normalizeModelName(name, provider);
|
|
274
|
+
if (!modelName)
|
|
275
|
+
return;
|
|
276
|
+
const key = modelName.toLowerCase();
|
|
277
|
+
const current = modelMap.get(key);
|
|
278
|
+
if (!current) {
|
|
279
|
+
modelMap.set(key, { name: modelName, provider: provider || '' });
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (!current.provider && provider) {
|
|
283
|
+
modelMap.set(key, { name: current.name, provider });
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
try {
|
|
287
|
+
const result = await gateway.call('models.list', {});
|
|
288
|
+
const remoteModels = Array.isArray(result?.models) ? result.models : [];
|
|
289
|
+
for (const m of remoteModels) {
|
|
290
|
+
const n = typeof m === 'string' ? m : (m?.id || m?.name || '');
|
|
291
|
+
const p = typeof m === 'object' ? String(m?.provider || '').trim() : '';
|
|
292
|
+
addModel(n, p);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
// ignore remote failure and continue with local config
|
|
297
|
+
}
|
|
298
|
+
const cfg = gateway.readConfig();
|
|
299
|
+
const providers = cfg?.models?.providers;
|
|
300
|
+
if (providers && typeof providers === 'object') {
|
|
301
|
+
for (const [providerId, raw] of Object.entries(providers)) {
|
|
302
|
+
const models = Array.isArray(raw?.models) ? raw.models : [];
|
|
303
|
+
for (const m of models) {
|
|
304
|
+
if (typeof m === 'string')
|
|
305
|
+
addModel(m, providerId);
|
|
306
|
+
else
|
|
307
|
+
addModel(String(m?.id || m?.name || '').trim(), providerId);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const defaultModels = cfg?.agents?.defaults?.models;
|
|
312
|
+
if (defaultModels && typeof defaultModels === 'object') {
|
|
313
|
+
for (const [name, meta] of Object.entries(defaultModels)) {
|
|
314
|
+
const provider = typeof meta?.provider === 'string' ? meta.provider : '';
|
|
315
|
+
addModel(name, provider);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
const rows = Array.from(modelMap.values())
|
|
319
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
320
|
+
.slice(0, 80);
|
|
321
|
+
if (rows.length === 0)
|
|
322
|
+
return '📂 暂无可用模型信息';
|
|
323
|
+
let msg = `📂 **可用模型**(${rows.length}):\n\n`;
|
|
324
|
+
for (const row of rows) {
|
|
325
|
+
msg += `• ${row.name}${row.provider ? ` (${row.provider})` : ''}\n`;
|
|
326
|
+
}
|
|
327
|
+
return msg;
|
|
328
|
+
}
|
|
329
|
+
async function handleStatusCmd() {
|
|
330
|
+
try {
|
|
331
|
+
const s = await gateway.call('status', {});
|
|
332
|
+
const recent = s?.sessions?.recent?.[0];
|
|
333
|
+
const model = s?.sessions?.defaults?.model || recent?.model || '未知';
|
|
334
|
+
const ctxW = s?.sessions?.defaults?.contextTokens || recent?.contextTokens || 0;
|
|
335
|
+
const cfg = gateway.readConfig();
|
|
336
|
+
const thinking = cfg?.agents?.defaults?.thinkingDefault || 'medium';
|
|
337
|
+
let msg = `🐙 OpenClaw 状态\n🧠 Model: ${model} · Think: ${thinking}\n`;
|
|
338
|
+
if (recent) {
|
|
339
|
+
const inT = recent.inputTokens ? `${(recent.inputTokens / 1000).toFixed(0)}k` : '0';
|
|
340
|
+
msg += `🎰 Tokens: ${inT} in / ${recent.outputTokens || 0} out\n`;
|
|
341
|
+
const totT = recent.totalTokens ? `${(recent.totalTokens / 1000).toFixed(0)}k` : '?';
|
|
342
|
+
const ctxT = ctxW ? `${(ctxW / 1000).toFixed(0)}k` : '?';
|
|
343
|
+
msg += `📦 Context: ${totT}/${ctxT} (${recent.percentUsed ?? '?'}%)\n`;
|
|
344
|
+
let age = '刚刚';
|
|
345
|
+
if (recent.age > 0) {
|
|
346
|
+
const m2 = Math.floor(recent.age / 60000);
|
|
347
|
+
age = m2 < 1 ? '刚刚' : m2 < 60 ? `${m2}分钟前` : m2 < 1440 ? `${Math.floor(m2 / 60)}小时前` : `${Math.floor(m2 / 1440)}天前`;
|
|
348
|
+
}
|
|
349
|
+
msg += `🏠 Session: ${recent.key || '?'} · ${age}\n`;
|
|
350
|
+
msg += `🫠 Runtime: ${recent.kind || 'direct'}\n`;
|
|
351
|
+
}
|
|
352
|
+
if (s?.channelSummary?.length) {
|
|
353
|
+
msg += `\n📡 渠道:\n`;
|
|
354
|
+
for (const l of s.channelSummary)
|
|
355
|
+
msg += ` ${l}\n`;
|
|
356
|
+
}
|
|
357
|
+
if (s?.queuedSystemEvents?.length)
|
|
358
|
+
msg += `\n🎯 Queue: ${s.queuedSystemEvents.length} 个待处理事件\n`;
|
|
359
|
+
return msg;
|
|
360
|
+
}
|
|
361
|
+
catch (err) {
|
|
362
|
+
const cfg = gateway.readConfig();
|
|
363
|
+
const m = cfg?.agents?.defaults?.model;
|
|
364
|
+
const model = typeof m === 'string' ? m : (m?.primary || '未知');
|
|
365
|
+
return `🐙 OpenClaw 状态 (local)\n🧠 Model: ${model}\n💭 Think: ${cfg?.agents?.defaults?.thinkingDefault || 'medium'}`;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
async function handleUsageCmd() {
|
|
369
|
+
try {
|
|
370
|
+
const u = await gateway.call('usage.cost', {});
|
|
371
|
+
const daily = u?.daily || [];
|
|
372
|
+
let totalIn = 0, totalOut = 0, totalCost = 0;
|
|
373
|
+
for (const d of daily) {
|
|
374
|
+
totalIn += d.input || 0;
|
|
375
|
+
totalOut += d.output || 0;
|
|
376
|
+
totalCost += d.totalCost || 0;
|
|
377
|
+
}
|
|
378
|
+
return `💰 **使用量摘要** (${u?.days || '?'}天)\n📥 输入: ${(totalIn / 1000).toFixed(0)}k tokens\n📤 输出: ${(totalOut / 1000).toFixed(0)}k tokens\n💵 费用: $${totalCost.toFixed(4)}`;
|
|
379
|
+
}
|
|
380
|
+
catch (err) {
|
|
381
|
+
return `💰 使用量查询失败: ${err.message}`;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
async function handleAgentsCmd() {
|
|
385
|
+
try {
|
|
386
|
+
const r = await gateway.call('agents.list', {});
|
|
387
|
+
const agents = r?.agents || [];
|
|
388
|
+
if (agents.length === 0)
|
|
389
|
+
return '👥 暂无已配置的代理';
|
|
390
|
+
let msg = '👥 **已配置的代理**:\n\n';
|
|
391
|
+
for (const a of agents)
|
|
392
|
+
msg += `• **${a.id || '?'}**${a.id === r.defaultId ? ' (默认)' : ''}\n`;
|
|
393
|
+
return msg;
|
|
394
|
+
}
|
|
395
|
+
catch (err) {
|
|
396
|
+
return `👥 代理列表获取失败: ${err.message}`;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
function getContextSessionKey(context) {
|
|
400
|
+
const key = (context.gatewaySessionKey || '').trim().toLowerCase();
|
|
401
|
+
return key || 'agent:main:main';
|
|
402
|
+
}
|
|
403
|
+
async function handleSessionResetCmd(context) {
|
|
404
|
+
try {
|
|
405
|
+
const key = getContextSessionKey(context);
|
|
406
|
+
await gateway.call('sessions.reset', { key });
|
|
407
|
+
// 从本地配置读取模型(和电报一样,不依赖 session entry)
|
|
408
|
+
const cfg = gateway.readConfig();
|
|
409
|
+
const modelObj = cfg?.agents?.defaults?.model;
|
|
410
|
+
const model = typeof modelObj === 'string' ? modelObj : (modelObj?.primary || '未知');
|
|
411
|
+
return `✅ New session started · model: ${model}`;
|
|
412
|
+
}
|
|
413
|
+
catch (err) {
|
|
414
|
+
return `🔄 会话重置失败: ${err.message}`;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
async function handleCompactCmd(context) {
|
|
418
|
+
try {
|
|
419
|
+
const key = getContextSessionKey(context);
|
|
420
|
+
await gateway.call('sessions.compact', { key });
|
|
421
|
+
return `📦 会话上下文已压缩 (${key})。`;
|
|
422
|
+
}
|
|
423
|
+
catch (err) {
|
|
424
|
+
return `📦 压缩失败: ${err.message}`;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
async function handleSessionInfoCmd() {
|
|
428
|
+
try {
|
|
429
|
+
const r = await gateway.call('sessions.list', {});
|
|
430
|
+
const sessions = r?.sessions || [];
|
|
431
|
+
if (sessions.length === 0)
|
|
432
|
+
return '⚙️ 当前没有活跃会话';
|
|
433
|
+
let msg = '⚙️ **会话列表**:\n\n';
|
|
434
|
+
for (const s of sessions.slice(0, 10)) {
|
|
435
|
+
const pct = s.percentUsed != null ? ` (${s.percentUsed}%)` : '';
|
|
436
|
+
const model = s.model ? ` · ${s.model}` : '';
|
|
437
|
+
msg += `• ${s.key || s.displayName || '?'}${pct}${model}\n`;
|
|
438
|
+
}
|
|
439
|
+
return msg;
|
|
440
|
+
}
|
|
441
|
+
catch (err) {
|
|
442
|
+
return `⚙️ 会话信息获取失败: ${err.message}`;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
async function handleStopCmd(context) {
|
|
446
|
+
const sessionKey = getContextSessionKey(context);
|
|
447
|
+
try {
|
|
448
|
+
const params = { sessionKey };
|
|
449
|
+
if (context.runId)
|
|
450
|
+
params.runId = context.runId;
|
|
451
|
+
const result = await gateway.call('chat.abort', params);
|
|
452
|
+
const aborted = Boolean(result?.aborted);
|
|
453
|
+
const runIds = Array.isArray(result?.runIds) ? result.runIds.length : 0;
|
|
454
|
+
if (aborted || runIds > 0)
|
|
455
|
+
return '⏹️ 已中断当前生成。';
|
|
456
|
+
return '⏹️ 已发送停止请求(当前会话暂无活跃运行)。';
|
|
457
|
+
}
|
|
458
|
+
catch {
|
|
459
|
+
return '⏹️ 当前没有正在运行的任务。';
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
async function handleTtsCmd(args) {
|
|
463
|
+
const val = args.trim().toLowerCase();
|
|
464
|
+
try {
|
|
465
|
+
if (val === 'on' || val === 'enable') {
|
|
466
|
+
await gateway.call('tts.enable', {});
|
|
467
|
+
return '🔊 TTS 已开启';
|
|
468
|
+
}
|
|
469
|
+
if (val === 'off' || val === 'disable') {
|
|
470
|
+
await gateway.call('tts.disable', {});
|
|
471
|
+
return '🔇 TTS 已关闭';
|
|
472
|
+
}
|
|
473
|
+
const st = await gateway.call('tts.status', {});
|
|
474
|
+
return `🔊 **TTS**: ${st?.enabled ? '✅ 开启' : '❌ 关闭'} · Provider: ${st?.provider || '?'}\n\n开启 → \`/tts on\`\n关闭 → \`/tts off\``;
|
|
475
|
+
}
|
|
476
|
+
catch (err) {
|
|
477
|
+
return `🔊 TTS 操作失败: ${err.message}`;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
// ========== 格式化 ==========
|
|
481
|
+
function formatHelpMessage() {
|
|
482
|
+
let msg = '❓ 可用命令列表:\n\n';
|
|
483
|
+
const groups = [
|
|
484
|
+
{ name: '📌 基础', cmds: ['/help', '/commands', '/status', '/whoami'] },
|
|
485
|
+
{ name: '🛠️ 技能', cmds: ['/skill', '/healthcheck', '/session_logs', '/skill_creator', '/weather'] },
|
|
486
|
+
{ name: '💬 会话', cmds: ['/new', '/reset', '/compact', '/session', '/export_session', '/context', '/usage'] },
|
|
487
|
+
{ name: '🤖 Agent', cmds: ['/stop', '/restart', '/approve', '/kill', '/steer', '/subagents', '/agents', '/acp'] },
|
|
488
|
+
{ name: '⚙️ 配置', cmds: ['/model', '/models', '/think', '/verbose', '/reasoning', '/elevated', '/exec', '/queue'] },
|
|
489
|
+
{ name: '📡 渠道', cmds: ['/tts', '/activation', '/send', '/focus', '/unfocus'] },
|
|
490
|
+
];
|
|
491
|
+
for (const grp of groups) {
|
|
492
|
+
msg += `${grp.name}\n`;
|
|
493
|
+
for (const cmdName of grp.cmds) {
|
|
494
|
+
const cmd = exports.BOT_COMMANDS.find(c => c.command === cmdName);
|
|
495
|
+
if (cmd)
|
|
496
|
+
msg += ` ${cmd.icon} ${cmd.command} - ${cmd.description}\n`;
|
|
497
|
+
}
|
|
498
|
+
msg += '\n';
|
|
499
|
+
}
|
|
500
|
+
msg += '💡 你也可以直接发送文字、语音或图片与 AI 对话。';
|
|
501
|
+
return msg;
|
|
502
|
+
}
|
|
503
|
+
function formatCommandsList() {
|
|
504
|
+
let msg = `📋 所有命令 (${exports.BOT_COMMANDS.length}):\n\n`;
|
|
505
|
+
for (const cmd of exports.BOT_COMMANDS)
|
|
506
|
+
msg += `${cmd.icon} ${cmd.command}\n`;
|
|
507
|
+
return msg;
|
|
508
|
+
}
|
|
509
|
+
//# sourceMappingURL=commands.js.map
|