@yvhitxcel/opencode-remote 0.16.2 → 0.17.0
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/autonomous/decisions.js +73 -0
- package/dist/autonomous/index.js +141 -0
- package/dist/cli.js +1 -10
- package/dist/core/config.js +1 -1
- package/dist/core/git-push.js +120 -0
- package/dist/core/notifications.js +2 -2
- package/dist/core/router.js +97 -305
- package/dist/feishu/bot.js +0 -2
- package/dist/feishu/commands.js +82 -417
- package/dist/feishu/handler.js +26 -217
- package/dist/opencode/client.js +167 -145
- package/dist/plugins/agents/claude-code/index.js +6 -2
- package/dist/plugins/agents/opencode/index.js +40 -10
- package/dist/telegram/adapter.js +3 -6
- package/dist/telegram/bot.js +1 -6
- package/dist/weixin/api.js +9 -2
- package/dist/weixin/bot.js +123 -69
- package/dist/weixin/commands.js +361 -584
- package/dist/weixin/handler.js +170 -422
- package/dist/weixin/user-adapter-map.js +1 -0
- package/package.json +1 -1
- package/dist/core/session.js +0 -403
package/dist/core/router.js
CHANGED
|
@@ -1,70 +1,28 @@
|
|
|
1
1
|
// Message router - full command definitions shared across all platforms
|
|
2
2
|
import { registry } from './registry.js';
|
|
3
|
-
import { initOpenCode,
|
|
3
|
+
import { initOpenCode, checkConnection, setThreadModel, getThreadModel, getRecentModels, setRawDebug, isRawDebug } from '../opencode/client.js';
|
|
4
4
|
import { formatTaskCompletion } from './notifications.js';
|
|
5
5
|
|
|
6
|
-
const demoModeMap = new Map();
|
|
7
|
-
|
|
8
|
-
export function setDemoMode(threadId, enabled) {
|
|
9
|
-
if (enabled) demoModeMap.set(threadId, true);
|
|
10
|
-
else demoModeMap.delete(threadId);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function isDemoMode(threadId) {
|
|
14
|
-
return demoModeMap.has(threadId);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
6
|
export const COMMAND_ALIASES = {
|
|
18
7
|
start: ['start'],
|
|
19
8
|
help: ['help', 'h', '?'],
|
|
20
|
-
status: ['status'],
|
|
21
9
|
reset: ['reset'],
|
|
22
10
|
restart: ['restart'],
|
|
23
|
-
sessions: ['sessions', 'sw'],
|
|
24
|
-
delsessions: ['delsessions', 'del'],
|
|
25
|
-
loop: ['loop'],
|
|
26
|
-
edit: ['edit'],
|
|
27
11
|
diagnose: ['diagnose'],
|
|
28
|
-
refresh: ['refresh'],
|
|
29
|
-
copy: ['copy'],
|
|
30
|
-
revert: ['revert'],
|
|
31
|
-
upload: ['upload'],
|
|
32
|
-
delete: ['delete'],
|
|
33
12
|
oc: ['oc'],
|
|
34
13
|
cc: ['cc'],
|
|
35
14
|
cx: ['cx'],
|
|
36
15
|
copilot: ['copilot'],
|
|
37
|
-
agents: ['agents'],
|
|
38
16
|
model: ['model'],
|
|
39
17
|
expert: ['expert', 'z', 'Z', 'review'],
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
reset: '🔄 会话已重置,下次发送消息将创建新会话',
|
|
49
|
-
restart: '🔄 重启信号已发送,bot 即将重启...',
|
|
50
|
-
sessions: '📂 最近会话:\n\n1. Telegram 会话 (2分钟前)\n2. 微信开发会话 (15分钟前)\n3. 专家评审 (1小时前)',
|
|
51
|
-
delsessions: '🗑️ 选择要删除的会话(回复编号):\n\n1. Telegram 会话\n2. 微信开发会话\n\n回复编号删除',
|
|
52
|
-
loop: '🔄 循环任务已启动\n指令: 智能模式\n限制: 最多10次迭代或30分钟\n\n发送 /loop off 停止',
|
|
53
|
-
diagnose: '🔍 诊断报告\n\nOpenCode: ✅\n七牛云: ✅\nTelegram: ✅\n飞书: ❌ 未配置\n会话: ✅',
|
|
54
|
-
refresh: '✅ 会话已刷新',
|
|
55
|
-
copy: '📋 最新回复:\n\n这是 AI 的示例回复内容,演示 /copy 命令的功能。',
|
|
56
|
-
revert: '↩️ 已撤销最近的消息\n\n发送 /revert undo 恢复',
|
|
57
|
-
upload: '⬆️ 用法: /upload <文件路径>\n\n当前项目构建产物:\n📦 build/app.apk (12.5 MB)',
|
|
58
|
-
delete: '🗑️ 用法: /delete <key>\n\n示例: /delete uploads/1234567890-app.apk',
|
|
59
|
-
model: '🧠 可用模型:\n\nOpenAI (openai):\n gpt-4o\n gpt-4o-mini\n o3-mini\n\nAnthropic (anthropic):\n claude-sonnet-4-20250514\n\n用法: /model <provider/model>',
|
|
60
|
-
agents: '🤖 可用 AI Agent:\n\n✅ opencode\n✅ claude-code\n✅ codex\n❌ copilot\n\n切换: /oc /cc /cx /copilot',
|
|
61
|
-
oc: '✅ 已切换到 OpenCode\n\n💬 发送消息给 OpenCode 开始工作',
|
|
62
|
-
cc: '✅ 已切换到 Claude Code',
|
|
63
|
-
cx: '✅ 已切换到 Codex',
|
|
64
|
-
copilot: '✅ 已切换到 GitHub Copilot',
|
|
65
|
-
edit: '✏️ 用法: /edit <消息编号>\n\n选择要修改的消息,然后发送修正后的内容。',
|
|
66
|
-
expert: '🧠 专家评审模式已启动\n\n14 位 AI 专家正在分析您的项目...\n\n架构师、安全研究员、测试工程师、VC/投资人等角色将依次给出评审意见。',
|
|
67
|
-
tutorial: '📚 教程已启动\n发送 /tutorial 1 开始第1步',
|
|
18
|
+
raw: ['raw'],
|
|
19
|
+
think: ['think'],
|
|
20
|
+
share: ['share'],
|
|
21
|
+
invite: ['invite'],
|
|
22
|
+
push: ['push'],
|
|
23
|
+
who: ['who'],
|
|
24
|
+
deploy: ['deploy', 'gitpush'],
|
|
25
|
+
auto: ['auto'],
|
|
68
26
|
};
|
|
69
27
|
|
|
70
28
|
export const EXPERT_SYSTEM_PROMPT = `你是一个专家评审系统。用户消息含触发词(z/c/叫全部专家/专家点评)时启动评审,前后可带具体问题则聚焦该问题。
|
|
@@ -154,28 +112,23 @@ export const EXPERT_SYSTEM_PROMPT = `你是一个专家评审系统。用户消
|
|
|
154
112
|
const COMMAND_HELP = {
|
|
155
113
|
start: '认领所有权',
|
|
156
114
|
help: '显示帮助',
|
|
157
|
-
status: '连接状态',
|
|
158
115
|
reset: '重置会话',
|
|
159
116
|
restart: '重启 Bot',
|
|
160
|
-
sessions: '浏览会话',
|
|
161
|
-
delsessions: '删除会话',
|
|
162
|
-
loop: '循环任务',
|
|
163
|
-
edit: '编辑消息',
|
|
164
117
|
diagnose: '系统诊断',
|
|
165
|
-
refresh: '刷新上下文',
|
|
166
|
-
copy: '复制回复',
|
|
167
|
-
revert: '撤销消息',
|
|
168
|
-
upload: '上传文件',
|
|
169
|
-
delete: '删除上传文件',
|
|
170
118
|
oc: '使用 OpenCode',
|
|
171
119
|
cc: '使用 Claude Code',
|
|
172
120
|
cx: '使用 Codex',
|
|
173
121
|
copilot: '使用 Copilot',
|
|
174
|
-
agents: '查看 Agent',
|
|
175
122
|
model: '切换模型',
|
|
176
123
|
expert: '专家评审(z/叫全部专家)',
|
|
177
|
-
|
|
178
|
-
|
|
124
|
+
raw: '开启/关闭 RAW 调试输出',
|
|
125
|
+
think: '开启/关闭思考过程显示',
|
|
126
|
+
share: '共享会话管理',
|
|
127
|
+
bind: '绑定新 Bot 用户',
|
|
128
|
+
push: '推送消息给其他 Bot 用户',
|
|
129
|
+
who: '查看在线用户',
|
|
130
|
+
deploy: '推送代码到所有 Git 镜像',
|
|
131
|
+
auto: '自主开发模式',
|
|
179
132
|
};
|
|
180
133
|
|
|
181
134
|
const COMMAND_MAP = {};
|
|
@@ -205,22 +158,26 @@ export function startTypingPing(adapter, threadId) {
|
|
|
205
158
|
export function getHelpText() {
|
|
206
159
|
const lines = ['📖 指令\n'];
|
|
207
160
|
const groups = [
|
|
208
|
-
['
|
|
209
|
-
['
|
|
210
|
-
['
|
|
211
|
-
['
|
|
212
|
-
['
|
|
213
|
-
['
|
|
161
|
+
['start', 'help'], // 系统
|
|
162
|
+
['reset', 'restart', 'diagnose'], // 会话
|
|
163
|
+
['oc', 'cc', 'cx', 'copilot'], // Agent
|
|
164
|
+
['model', 'raw', 'think'], // 配置
|
|
165
|
+
['share', 'bind', 'push', 'who'], // 协作
|
|
166
|
+
['expert', 'deploy', 'auto'], // 专家+部署+自主
|
|
214
167
|
];
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
168
|
+
let first = true;
|
|
169
|
+
for (const group of groups) {
|
|
170
|
+
if (!first) lines.push('');
|
|
171
|
+
first = false;
|
|
172
|
+
for (const cmd of group) {
|
|
173
|
+
if (!COMMAND_ALIASES[cmd]) continue;
|
|
218
174
|
const aliases = COMMAND_ALIASES[cmd];
|
|
219
|
-
const
|
|
175
|
+
const shortAlias = aliases.slice(1).find(a => a.length <= 2);
|
|
176
|
+
const aliasStr = shortAlias ? ` (${shortAlias})` : '';
|
|
220
177
|
lines.push(` /${cmd}${aliasStr} — ${COMMAND_HELP[cmd] || cmd}`);
|
|
221
178
|
}
|
|
222
|
-
lines.push('');
|
|
223
179
|
}
|
|
180
|
+
lines.push('');
|
|
224
181
|
lines.push('💬 直接发消息给 AI!');
|
|
225
182
|
return lines.join('\n');
|
|
226
183
|
}
|
|
@@ -264,155 +221,14 @@ export function parseMessage(text) {
|
|
|
264
221
|
return { type: 'default', prompt: trimmed };
|
|
265
222
|
}
|
|
266
223
|
|
|
267
|
-
function formatTimeAgo(timestamp) {
|
|
268
|
-
const diff = Date.now() - timestamp;
|
|
269
|
-
const seconds = Math.floor(diff / 1000);
|
|
270
|
-
if (seconds < 60) return `${seconds}秒前`;
|
|
271
|
-
const minutes = Math.floor(seconds / 60);
|
|
272
|
-
if (minutes < 60) return `${minutes}分钟前`;
|
|
273
|
-
const hours = Math.floor(minutes / 60);
|
|
274
|
-
if (hours < 24) return `${hours}小时前`;
|
|
275
|
-
return `${Math.floor(hours / 24)}天前`;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
async function getSessionsList() {
|
|
279
|
-
const opencode = await initOpenCode();
|
|
280
|
-
if (!opencode) return null;
|
|
281
|
-
const result = await opencode.client.session.list();
|
|
282
|
-
if (result.error || !result.data) return [];
|
|
283
|
-
return result.data.sort((a, b) => (b.time?.updated || b.updated_at || 0) - (a.time?.updated || a.updated_at || 0));
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
async function getSessionMessages(sessionId) {
|
|
287
|
-
const opencode = await initOpenCode();
|
|
288
|
-
if (!opencode) return null;
|
|
289
|
-
const result = await opencode.client.session.messages({ path: { id: sessionId } });
|
|
290
|
-
if (result.error) return null;
|
|
291
|
-
return result.data || [];
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
export const TUTORIAL_STEPS = [
|
|
295
|
-
{
|
|
296
|
-
step: 1,
|
|
297
|
-
title: '💬 发送第一条消息',
|
|
298
|
-
desc: '直接发一条消息给 bot,比如:"帮我写一个 Hello World 程序"\nAI 会自动接收并在你的电脑上执行。',
|
|
299
|
-
action: '现在试试:输入 "你好" 或 "帮我写一个 Python 程序"',
|
|
300
|
-
},
|
|
301
|
-
{
|
|
302
|
-
step: 2,
|
|
303
|
-
title: '📊 查看状态',
|
|
304
|
-
desc: '发送 /status 查看 OpenCode 是否在线、当前会话信息、运行中的任务。',
|
|
305
|
-
action: '试试:发送 /status',
|
|
306
|
-
},
|
|
307
|
-
{
|
|
308
|
-
step: 3,
|
|
309
|
-
title: '📋 复制 AI 回复',
|
|
310
|
-
desc: 'AI 回复了长篇代码?用 /copy 一键复制最新 AI 回复的内容。',
|
|
311
|
-
action: '试试:发送 /copy',
|
|
312
|
-
},
|
|
313
|
-
{
|
|
314
|
-
step: 4,
|
|
315
|
-
title: '🤖 切换 AI 模型',
|
|
316
|
-
desc: '不同模型擅长不同任务。用 /model 查看可用模型,/model provider/model 切换。',
|
|
317
|
-
action: '试试:发送 /model 查看列表',
|
|
318
|
-
},
|
|
319
|
-
{
|
|
320
|
-
step: 5,
|
|
321
|
-
title: '🧠 召唤专家评审',
|
|
322
|
-
desc: '发送 /z 启动专家评审模式,14 位 AI 专家分析你的项目,自动出修复方案并执行。',
|
|
323
|
-
action: '试试:发送 /z,然后发送 z',
|
|
324
|
-
},
|
|
325
|
-
{
|
|
326
|
-
step: 6,
|
|
327
|
-
title: '🔄 循环任务',
|
|
328
|
-
desc: '让 AI 持续工作。发送 /loop 启动循环任务,AI 会反复推进项目。\n停止:/loop off',
|
|
329
|
-
action: '试试:发送 /loop 检查测试覆盖率',
|
|
330
|
-
},
|
|
331
|
-
{
|
|
332
|
-
step: 7,
|
|
333
|
-
title: '🔍 系统诊断',
|
|
334
|
-
desc: '出问题了?/diagnose 一键检查 OpenCode、七牛云、各平台连接状态。',
|
|
335
|
-
action: '试试:发送 /diagnose',
|
|
336
|
-
},
|
|
337
|
-
{
|
|
338
|
-
step: 8,
|
|
339
|
-
title: '🎉 全部搞定',
|
|
340
|
-
desc: '你已经掌握了所有核心功能!\n下一步建议:\n• /help 查看全部 22 条命令\n• 设置你的项目目录并开始真正的开发\n• 尝试多 Agent 切换:/cc 用 Claude Code,/cx 用 Codex',
|
|
341
|
-
action: '',
|
|
342
|
-
},
|
|
343
|
-
];
|
|
344
|
-
|
|
345
|
-
function getTutorialText(step) {
|
|
346
|
-
const s = TUTORIAL_STEPS[step - 1];
|
|
347
|
-
if (!s) return getTutorialText(1);
|
|
348
|
-
let msg = `📚 教程 · 第 ${s.step}/${TUTORIAL_STEPS.length} 步\n━━━━━━━━━━━━━━━━\n\n${s.title}\n\n${s.desc}\n\n`;
|
|
349
|
-
if (s.action) msg += `👉 ${s.action}`;
|
|
350
|
-
msg += `\n\n回复 /tutorial${step < TUTORIAL_STEPS.length ? ` 继续第${step + 1}步\n发送 /tutorial ${step + 1}` : ''} 进入下一步`;
|
|
351
|
-
return msg;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
224
|
export async function routeMessage(parsed, ctx) {
|
|
355
225
|
const threadId = ctx.threadId;
|
|
356
|
-
if (demoModeMap.has(threadId) && parsed.type === 'command' && DEMO_RESPONSES[parsed.command]) {
|
|
357
|
-
return DEMO_RESPONSES[parsed.command];
|
|
358
|
-
}
|
|
359
226
|
switch (parsed.type) {
|
|
360
227
|
case 'command': {
|
|
361
228
|
switch (parsed.command) {
|
|
362
229
|
case 'help':
|
|
363
230
|
return getHelpText();
|
|
364
231
|
|
|
365
|
-
case 'tutorial': {
|
|
366
|
-
const stepNum = parseInt(parsed.arg, 10);
|
|
367
|
-
const step = !isNaN(stepNum) && stepNum >= 1 && stepNum <= TUTORIAL_STEPS.length ? stepNum : 1;
|
|
368
|
-
return getTutorialText(step);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
case 'demo': {
|
|
372
|
-
const arg = (parsed.arg || '').trim().toLowerCase();
|
|
373
|
-
if (arg === 'off' || arg === 'exit' || arg === 'stop') {
|
|
374
|
-
setDemoMode(threadId, false);
|
|
375
|
-
return '⏹️ 已退出沙箱模式\n\n现在所有命令将正常连接 OpenCode 执行。';
|
|
376
|
-
}
|
|
377
|
-
setDemoMode(threadId, true);
|
|
378
|
-
let msg = '🎮 **沙箱模式已启动**\n\n';
|
|
379
|
-
msg += '在此模式下,所有命令返回模拟输出,无需连接 OpenCode。\n\n';
|
|
380
|
-
msg += '可用命令:\n';
|
|
381
|
-
const groups = [
|
|
382
|
-
['🟢 常用', ['/help', '/start', '/status', '/reset']],
|
|
383
|
-
['🔄 任务', ['/loop', '/refresh', '/diagnose']],
|
|
384
|
-
['🤖 AI', ['/model', '/agents', '/oc', '/cc']],
|
|
385
|
-
['📂 会话', ['/sessions', '/delsessions', '/copy', '/revert']],
|
|
386
|
-
['⬆️ 文件', ['/upload', '/delete']],
|
|
387
|
-
];
|
|
388
|
-
for (const [title, cmds] of groups) {
|
|
389
|
-
msg += `\n${title}\n ${cmds.join(' ')}\n`;
|
|
390
|
-
}
|
|
391
|
-
msg += '\n试试发送上面的命令体验效果!\n发送 /demo off 退出沙箱模式';
|
|
392
|
-
return msg;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
case 'agents': {
|
|
396
|
-
const agents = registry.listAgents();
|
|
397
|
-
const lines = ['🤖 可用 AI Agent:'];
|
|
398
|
-
for (const name of agents) {
|
|
399
|
-
const agent = registry.findAgent(name);
|
|
400
|
-
const aliases = agent?.aliases || [];
|
|
401
|
-
const available = await agent?.isAvailable().catch(() => false);
|
|
402
|
-
const status = available ? '✅' : '❌';
|
|
403
|
-
const aliasStr = aliases.length > 0 ? ` (${aliases.join(', ')})` : '';
|
|
404
|
-
lines.push(`${status} ${name}${aliasStr}`);
|
|
405
|
-
}
|
|
406
|
-
lines.push('');
|
|
407
|
-
lines.push('切换: /oc /cc /cx /copilot');
|
|
408
|
-
return lines.join('\n');
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
case 'status': {
|
|
412
|
-
const connected = await checkConnection();
|
|
413
|
-
return `${connected ? '✅' : '❌'} OpenCode ${connected ? '在线' : '离线'}`;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
232
|
case 'start':
|
|
417
233
|
return '🚀 准备就绪,发送消息给 OpenCode 开始工作';
|
|
418
234
|
|
|
@@ -423,106 +239,70 @@ export async function routeMessage(parsed, ctx) {
|
|
|
423
239
|
case 'restart':
|
|
424
240
|
return '🔄 重启信号已发送,bot 即将重启...';
|
|
425
241
|
|
|
426
|
-
case 'sessions': {
|
|
427
|
-
const sessions = await getSessionsList();
|
|
428
|
-
if (!sessions || sessions.length === 0) return '📭 暂无会话';
|
|
429
|
-
let msg = '📂 最近会话:\n\n';
|
|
430
|
-
sessions.slice(0, 10).forEach((s, i) => {
|
|
431
|
-
const title = s.title || '无标题';
|
|
432
|
-
const time = s.updated_at ? formatTimeAgo(s.updated_at * 1000) : '';
|
|
433
|
-
msg += `${i + 1}. ${title} (${time})\n`;
|
|
434
|
-
});
|
|
435
|
-
return msg;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
case 'delsessions': {
|
|
439
|
-
const sessions = await getSessionsList();
|
|
440
|
-
if (!sessions || sessions.length === 0) return '📭 暂无会话可删除';
|
|
441
|
-
let msg = '🗑️ 选择要删除的会话:\n\n';
|
|
442
|
-
sessions.slice(0, 10).forEach((s, i) => {
|
|
443
|
-
msg += `${i + 1}. ${s.title || '无标题'}\n`;
|
|
444
|
-
});
|
|
445
|
-
msg += '\n(在当前平台无法交互式选择,请使用 WeChat)';
|
|
446
|
-
return msg;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
case 'loop':
|
|
450
|
-
if (parsed.arg === 'off' || parsed.arg === 'stop') return '⏹️ 循环任务已停止';
|
|
451
|
-
if (parsed.arg === 'status') return '🔄 循环任务状态(在微信中查看详情)';
|
|
452
|
-
return '🔄 循环任务已启动(完整控制请使用 WeChat)';
|
|
453
|
-
|
|
454
|
-
case 'refresh': {
|
|
455
|
-
if (!ctx.opencodeSessionId) return '❌ 没有活跃的会话';
|
|
456
|
-
const opencode = await initOpenCode();
|
|
457
|
-
if (!opencode) return '❌ 无法连接 OpenCode';
|
|
458
|
-
try {
|
|
459
|
-
await opencode.client.session.compact({ path: { id: ctx.opencodeSessionId } });
|
|
460
|
-
const result = await opencode.client.session.summarize({ path: { id: ctx.opencodeSessionId } });
|
|
461
|
-
return result.error ? '⚠️ 压缩完成,但摘要生成失败' : '✅ 会话已刷新';
|
|
462
|
-
} catch (e) {
|
|
463
|
-
console.error('[refresh] Error:', e.message);
|
|
464
|
-
return `❌ 刷新失败: ${e.message}`;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
case 'copy': {
|
|
469
|
-
if (!ctx.opencodeSessionId) return '❌ 没有活跃的会话';
|
|
470
|
-
const msgs = await getSessionMessages(ctx.opencodeSessionId);
|
|
471
|
-
if (!msgs || msgs.length === 0) return '❌ 无法获取消息';
|
|
472
|
-
const aiMsg = msgs.filter(m => m.info?.role === 'assistant').slice(-1)[0];
|
|
473
|
-
if (!aiMsg) return '❌ 未找到 AI 回复';
|
|
474
|
-
let content = '';
|
|
475
|
-
if (aiMsg.parts) {
|
|
476
|
-
for (const part of aiMsg.parts) {
|
|
477
|
-
if (part.type === 'text') content += part.text + '\n';
|
|
478
|
-
if (part.type === 'code') content += '```' + (part.language || '') + '\n' + part.code + '\n```\n';
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
return content ? `📋 最新回复:\n\n${content.substring(0, 2000)}` : '❌ 没有可复制的内容';
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
case 'revert': {
|
|
485
|
-
if (!ctx.opencodeSessionId) return '❌ 没有活跃的会话';
|
|
486
|
-
const opencode = await initOpenCode();
|
|
487
|
-
if (!opencode) return '❌ 无法连接 OpenCode';
|
|
488
|
-
if (parsed.arg === 'undo') {
|
|
489
|
-
const ok = await opencode.client.session.unrevert?.({ path: { id: ctx.opencodeSessionId } });
|
|
490
|
-
return ok ? '↩️ 已恢复撤销的内容' : '❌ 恢复失败';
|
|
491
|
-
}
|
|
492
|
-
const msgs = await getSessionMessages(ctx.opencodeSessionId);
|
|
493
|
-
if (!msgs) return '❌ 无法获取消息';
|
|
494
|
-
const lastAssistant = msgs.filter(m => m.info?.role === 'assistant' && m.time?.created).slice(-1)[0];
|
|
495
|
-
if (!lastAssistant) return '📭 没有可撤销的消息';
|
|
496
|
-
const ok = await opencode.client.session.revert({ path: { id: ctx.opencodeSessionId }, body: { messageID: lastAssistant.id } });
|
|
497
|
-
return ok ? '↩️ 已撤销最近的消息' : '❌ 撤销失败';
|
|
498
|
-
}
|
|
499
|
-
|
|
500
242
|
case 'model': {
|
|
501
243
|
try {
|
|
244
|
+
const opencode = await initOpenCode();
|
|
245
|
+
if (!opencode) return '❌ OpenCode 不可用';
|
|
246
|
+
|
|
502
247
|
if (parsed.arg) {
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
248
|
+
const arg = parsed.arg.trim();
|
|
249
|
+
|
|
250
|
+
// Search mode: /model <keyword>
|
|
251
|
+
if (!arg.includes('/')) {
|
|
252
|
+
const result = await opencode.client.config.providers();
|
|
253
|
+
if (result.error || !result.data?.providers) return '❌ 无法获取模型列表';
|
|
254
|
+
const q = arg.toLowerCase();
|
|
255
|
+
const matches = [];
|
|
256
|
+
for (const p of result.data.providers) {
|
|
257
|
+
for (const mid of Object.keys(p.models || {})) {
|
|
258
|
+
const label = `${p.id}/${mid}`;
|
|
259
|
+
if (label.toLowerCase().includes(q)) {
|
|
260
|
+
matches.push(label);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (matches.length === 0) return `🔍 未找到包含 "${arg}" 的模型`;
|
|
265
|
+
matches.sort().splice(30);
|
|
266
|
+
let msg = `🔍 搜索 "${arg}" (${matches.length > 30 ? '30+' : matches.length} 个):\n`;
|
|
267
|
+
for (const m of matches.slice(0, 30)) {
|
|
268
|
+
msg += ` ${m}\n`;
|
|
269
|
+
}
|
|
270
|
+
msg += '\n切换: /model <provider>/<modelID>';
|
|
271
|
+
return msg;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Switch mode: /model <provider>/<modelID>
|
|
275
|
+
const entry = setThreadModel(ctx.threadId, arg);
|
|
276
|
+
if (entry) {
|
|
277
|
+
return `✅ 已切换模型至: ${entry.providerID}/${entry.modelID}`;
|
|
278
|
+
}
|
|
279
|
+
return '❌ 格式错误,请使用: /model <provider>/<modelID>';
|
|
506
280
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
281
|
+
|
|
282
|
+
// No arg: show current + recent
|
|
283
|
+
const current = getThreadModel(ctx.threadId);
|
|
284
|
+
let msg = current
|
|
285
|
+
? `🧠 当前模型: ${current.providerID}/${current.modelID}\n━━━━━━━━━━━━━━━━\n\n`
|
|
286
|
+
: '━━━━━━━━━━━━━━━━\n\n';
|
|
287
|
+
const recent = getRecentModels();
|
|
288
|
+
if (recent.length > 0) {
|
|
289
|
+
msg += '最近使用:\n';
|
|
290
|
+
for (const r of recent) {
|
|
291
|
+
const mark = (current && r.providerID === current.providerID && r.modelID === current.modelID) ? ' ←' : '';
|
|
292
|
+
msg += ` ${r.providerID}/${r.modelID}${mark}\n`;
|
|
516
293
|
}
|
|
517
|
-
|
|
294
|
+
msg += '\n';
|
|
295
|
+
}
|
|
296
|
+
if (!current) {
|
|
297
|
+
msg += '提示: 用 /model <关键词> 搜索模型,/model <provider>/<modelID> 切换\n';
|
|
298
|
+
} else {
|
|
299
|
+
msg += '用法:\n /model <关键词> — 搜索\n /model <provider>/<modelID> — 切换';
|
|
518
300
|
}
|
|
519
|
-
msg += '\n用法: /model <provider/model>';
|
|
520
301
|
return msg;
|
|
521
302
|
} catch (e) {
|
|
522
303
|
return `❌ 模型操作失败: ${e.message}`;
|
|
523
304
|
}
|
|
524
305
|
}
|
|
525
|
-
|
|
526
306
|
case 'diagnose': {
|
|
527
307
|
const diag = ['🔍 诊断报告\n'];
|
|
528
308
|
const qiniuOk = !!process.env.QINIU_ACCESS_KEY;
|
|
@@ -553,6 +333,18 @@ export async function routeMessage(parsed, ctx) {
|
|
|
553
333
|
return (response || '无响应') + notification;
|
|
554
334
|
}
|
|
555
335
|
|
|
336
|
+
case 'raw': {
|
|
337
|
+
const val = parsed.arg?.trim().toLowerCase();
|
|
338
|
+
if (val === 'on' || val === '1' || val === 'true') {
|
|
339
|
+
setRawDebug(true);
|
|
340
|
+
return '📄 RAW 输出已开启';
|
|
341
|
+
} else if (val === 'off' || val === '0' || val === 'false') {
|
|
342
|
+
setRawDebug(false);
|
|
343
|
+
return '📄 RAW 输出已关闭';
|
|
344
|
+
}
|
|
345
|
+
return `📄 RAW 输出当前: ${isRawDebug() ? '🟢 ON' : '🔴 OFF'}\n用法: /raw on 或 /raw off`;
|
|
346
|
+
}
|
|
347
|
+
|
|
556
348
|
default:
|
|
557
349
|
return '❓ 未知指令';
|
|
558
350
|
}
|
package/dist/feishu/bot.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as lark from '@larksuiteoapi/node-sdk';
|
|
2
|
-
import { initSessionManager } from '../core/session.js';
|
|
3
2
|
import { initOpenCode } from '../opencode/client.js';
|
|
4
3
|
import { getAuthStatus } from '../core/auth.js';
|
|
5
4
|
import { createFeishuAdapter } from './adapter.js';
|
|
@@ -46,7 +45,6 @@ export async function startFeishuBot(botConfig) {
|
|
|
46
45
|
appId: config.feishuAppId,
|
|
47
46
|
appSecret: config.feishuAppSecret,
|
|
48
47
|
});
|
|
49
|
-
initSessionManager(config);
|
|
50
48
|
openCodeSessions = new Map();
|
|
51
49
|
console.log('🔧 正在初始化 OpenCode...');
|
|
52
50
|
try {
|