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