@yvhitxcel/opencode-remote 0.16.3 → 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/git-push.js +120 -0
- package/dist/core/notifications.js +2 -2
- package/dist/core/router.js +47 -293
- package/dist/feishu/bot.js +0 -2
- package/dist/feishu/commands.js +26 -395
- package/dist/feishu/handler.js +25 -216
- package/dist/opencode/client.js +126 -144
- package/dist/plugins/agents/claude-code/index.js +6 -2
- package/dist/plugins/agents/opencode/index.js +9 -5
- 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 +357 -604
- package/dist/weixin/handler.js +169 -421
- package/dist/weixin/user-adapter-map.js +1 -0
- package/package.json +1 -1
- package/dist/core/session.js +0 -403
package/dist/feishu/commands.js
CHANGED
|
@@ -1,26 +1,13 @@
|
|
|
1
|
-
import { getOrCreateSession } from '../core/session.js';
|
|
2
1
|
import { splitMessage } from '../core/notifications.js';
|
|
3
2
|
import { EMOJI } from '../core/types.js';
|
|
4
|
-
import { initOpenCode,
|
|
3
|
+
import { initOpenCode, checkConnection, abortSession, setThreadModel, getThreadModel, getRecentModels, setRawDebug, isRawDebug } from '../opencode/client.js';
|
|
5
4
|
import { claimOwnership } from '../core/auth.js';
|
|
6
|
-
import {
|
|
5
|
+
import { getHelpText } from '../core/router.js';
|
|
7
6
|
import { registry } from '../core/registry.js';
|
|
8
|
-
import { existsSync,
|
|
9
|
-
import { join
|
|
10
|
-
|
|
11
|
-
function formatTimeAgo(timestamp) {
|
|
12
|
-
const diff = Date.now() - timestamp;
|
|
13
|
-
const seconds = Math.floor(diff / 1000);
|
|
14
|
-
if (seconds < 60) return `${seconds}秒前`;
|
|
15
|
-
const minutes = Math.floor(seconds / 60);
|
|
16
|
-
if (minutes < 60) return `${minutes}分钟前`;
|
|
17
|
-
const hours = Math.floor(minutes / 60);
|
|
18
|
-
if (hours < 24) return `${hours}小时前`;
|
|
19
|
-
return `${Math.floor(hours / 24)}天前`;
|
|
20
|
-
}
|
|
7
|
+
import { existsSync, writeFileSync, mkdirSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
21
9
|
|
|
22
10
|
async function handleCommand(adapter, ctx, command, arg, openCodeSessions) {
|
|
23
|
-
const session = await getOrCreateSession(ctx.threadId, 'feishu');
|
|
24
11
|
switch (command) {
|
|
25
12
|
case 'start': {
|
|
26
13
|
const result = claimOwnership('feishu', ctx.userId);
|
|
@@ -43,19 +30,16 @@ async function handleCommand(adapter, ctx, command, arg, openCodeSessions) {
|
|
|
43
30
|
|
|
44
31
|
🚀 **准备就绪!**
|
|
45
32
|
💬 发送提示词开始编程
|
|
46
|
-
/help —
|
|
47
|
-
/status — 查看连接状态`);
|
|
33
|
+
/help — 查看所有指令`);
|
|
48
34
|
}
|
|
49
35
|
else {
|
|
50
36
|
await adapter.reply(ctx.threadId, `🚀 OpenCode 远程控制就绪
|
|
51
37
|
|
|
52
38
|
💬 发送消息给 OpenCode 开始工作
|
|
53
39
|
/help — 查看所有指令
|
|
54
|
-
/status — 查看连接状态
|
|
55
40
|
|
|
56
41
|
指令:
|
|
57
42
|
/start — 首次认证
|
|
58
|
-
/status — 查看连接
|
|
59
43
|
/reset — 重置会话
|
|
60
44
|
/approve — 同意变更
|
|
61
45
|
/reject — 拒绝变更
|
|
@@ -78,34 +62,6 @@ async function handleCommand(adapter, ctx, command, arg, openCodeSessions) {
|
|
|
78
62
|
case 'help':
|
|
79
63
|
await adapter.reply(ctx.threadId, getHelpText());
|
|
80
64
|
return true;
|
|
81
|
-
case 'tutorial': {
|
|
82
|
-
const { TUTORIAL_STEPS } = await import('../core/router.js');
|
|
83
|
-
const stepNum = parseInt(arg, 10);
|
|
84
|
-
const step = !isNaN(stepNum) && stepNum >= 1 && stepNum <= TUTORIAL_STEPS.length ? stepNum : 1;
|
|
85
|
-
const s = TUTORIAL_STEPS[step - 1];
|
|
86
|
-
let msg = `📚 教程 · 第 ${s.step}/${TUTORIAL_STEPS.length} 步\n━━━━━━━━━━━━━━━━\n\n${s.title}\n\n${s.desc}\n\n`;
|
|
87
|
-
if (s.action) msg += `👉 ${s.action}`;
|
|
88
|
-
msg += `\n\n回复 /tutorial${step < TUTORIAL_STEPS.length ? ` 继续第${step + 1}步` : ''} 进入下一步`;
|
|
89
|
-
const msgs = splitMessage(msg);
|
|
90
|
-
for (const m of msgs) await adapter.reply(ctx.threadId, m);
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
case 'agents': {
|
|
94
|
-
const agents = registry.listAgents();
|
|
95
|
-
const lines = ['🤖 可用 AI Agent:'];
|
|
96
|
-
for (const name of agents) {
|
|
97
|
-
const agent = registry.findAgent(name);
|
|
98
|
-
const aliases = agent?.aliases || [];
|
|
99
|
-
const available = await agent?.isAvailable().catch(() => false);
|
|
100
|
-
const status = available ? '✅' : '❌';
|
|
101
|
-
const aliasStr = aliases.length > 0 ? ` (${aliases.join(', ')})` : '';
|
|
102
|
-
lines.push(`${status} ${name}${aliasStr}`);
|
|
103
|
-
}
|
|
104
|
-
lines.push('');
|
|
105
|
-
lines.push('切换: /oc /cc /cx /copilot');
|
|
106
|
-
await adapter.reply(ctx.threadId, lines.join('\n'));
|
|
107
|
-
return true;
|
|
108
|
-
}
|
|
109
65
|
case 'model': {
|
|
110
66
|
try {
|
|
111
67
|
if (arg) {
|
|
@@ -118,14 +74,14 @@ async function handleCommand(adapter, ctx, command, arg, openCodeSessions) {
|
|
|
118
74
|
await adapter.reply(ctx.threadId, '❌ OpenCode 不可用');
|
|
119
75
|
return true;
|
|
120
76
|
}
|
|
121
|
-
const result = await opencode.client.
|
|
122
|
-
if (result.error || !result.data?.
|
|
77
|
+
const result = await opencode.client.config.providers();
|
|
78
|
+
if (result.error || !result.data?.providers) {
|
|
123
79
|
await adapter.reply(ctx.threadId, '❌ 无法获取模型列表');
|
|
124
80
|
return true;
|
|
125
81
|
}
|
|
126
82
|
const q = modelStr.toLowerCase();
|
|
127
83
|
const matches = [];
|
|
128
|
-
for (const p of result.data.
|
|
84
|
+
for (const p of result.data.providers) {
|
|
129
85
|
for (const mid of Object.keys(p.models || {})) {
|
|
130
86
|
if (`${p.id}/${mid}`.toLowerCase().includes(q)) {
|
|
131
87
|
matches.push(`${p.id}/${mid}`);
|
|
@@ -197,23 +153,18 @@ async function handleCommand(adapter, ctx, command, arg, openCodeSessions) {
|
|
|
197
153
|
await adapter.reply(ctx.threadId, `❌ ${agentName} 不可用`);
|
|
198
154
|
return true;
|
|
199
155
|
}
|
|
200
|
-
session.currentAgent = agentName;
|
|
201
156
|
if (!arg) {
|
|
202
157
|
await adapter.reply(ctx.threadId, `✅ 已切换到 ${agentName}`);
|
|
203
158
|
return true;
|
|
204
159
|
}
|
|
205
160
|
await adapter.sendTypingIndicator(ctx.threadId);
|
|
206
161
|
try {
|
|
207
|
-
const
|
|
208
|
-
const response = await agent.sendPrompt(session.id, arg, history, { projectDir: session.projectDir || globalThis.__autoProjectDir });
|
|
162
|
+
const response = await agent.sendPrompt(agentName, arg, [], { projectDir: globalThis.__autoProjectDir });
|
|
209
163
|
await adapter.sendTypingIndicator(ctx.threadId);
|
|
210
164
|
const chunks = splitMessage(response || '无响应');
|
|
211
165
|
for (const chunk of chunks) {
|
|
212
166
|
await adapter.reply(ctx.threadId, chunk);
|
|
213
167
|
}
|
|
214
|
-
session.commandHistory = session.commandHistory || [];
|
|
215
|
-
session.commandHistory.push({ role: 'user', content: arg });
|
|
216
|
-
session.commandHistory.push({ role: 'assistant', content: response });
|
|
217
168
|
} catch (error) {
|
|
218
169
|
await adapter.sendTypingIndicator(ctx.threadId);
|
|
219
170
|
await adapter.reply(ctx.threadId, `❌ 错误: ${error.message}`);
|
|
@@ -221,64 +172,16 @@ async function handleCommand(adapter, ctx, command, arg, openCodeSessions) {
|
|
|
221
172
|
return true;
|
|
222
173
|
}
|
|
223
174
|
case 'approve': {
|
|
224
|
-
|
|
225
|
-
if (!pending) {
|
|
226
|
-
await adapter.reply(ctx.threadId, '🤷 没有待审批的变更');
|
|
227
|
-
return true;
|
|
228
|
-
}
|
|
229
|
-
await adapter.reply(ctx.threadId, '✅ 已批准');
|
|
175
|
+
await adapter.reply(ctx.threadId, '🤷 没有待审批的变更');
|
|
230
176
|
return true;
|
|
231
177
|
}
|
|
232
178
|
case 'reject': {
|
|
233
|
-
|
|
234
|
-
if (!pending) {
|
|
235
|
-
await adapter.reply(ctx.threadId, '🤷 没有待拒绝的变更');
|
|
236
|
-
return true;
|
|
237
|
-
}
|
|
238
|
-
session.pendingApprovals.shift();
|
|
239
|
-
await adapter.reply(ctx.threadId, '❌ 已拒绝');
|
|
179
|
+
await adapter.reply(ctx.threadId, '🤷 没有待拒绝的变更');
|
|
240
180
|
return true;
|
|
241
181
|
}
|
|
242
182
|
|
|
243
183
|
case 'files': {
|
|
244
|
-
|
|
245
|
-
if (!pending || !pending.files?.length) {
|
|
246
|
-
await adapter.reply(ctx.threadId, '📄 此会话没有文件变更');
|
|
247
|
-
return true;
|
|
248
|
-
}
|
|
249
|
-
const fileList = pending.files.map(f => `• ${f.path} (+${f.additions}, -${f.deletions})`).join('\n');
|
|
250
|
-
await adapter.reply(ctx.threadId, `📄 已修改文件:\n${fileList}`);
|
|
251
|
-
return true;
|
|
252
|
-
}
|
|
253
|
-
case 'status': {
|
|
254
|
-
const openCodeConnected = await checkConnection();
|
|
255
|
-
const actualSession = openCodeSessions?.get(ctx.threadId) ||
|
|
256
|
-
(session.opencodeSessionId ? { sessionId: session.opencodeSessionId } : null);
|
|
257
|
-
const running = session.taskStartTime ? Math.round((Date.now() - session.taskStartTime) / 1000) : 0;
|
|
258
|
-
let msg = `${openCodeConnected ? '✅' : '❌'} OpenCode ${openCodeConnected ? '在线' : '离线'}\n\n`;
|
|
259
|
-
msg += `会话: ${actualSession?.sessionId?.slice(0, 8) || '无'}\n`;
|
|
260
|
-
if (running > 0) {
|
|
261
|
-
const m = Math.floor(running / 60);
|
|
262
|
-
const s = running % 60;
|
|
263
|
-
msg += `运行中: ${m}分${s}秒\n`;
|
|
264
|
-
}
|
|
265
|
-
if (session.currentTool) {
|
|
266
|
-
msg += `当前工具: ${session.currentTool}\n`;
|
|
267
|
-
}
|
|
268
|
-
if (session.modifiedFiles?.length > 0 || session.modifiedFiles?.size > 0) {
|
|
269
|
-
msg += `已修改: ${(session.modifiedFiles?.length || session.modifiedFiles?.size || 0)} 个文件\n`;
|
|
270
|
-
}
|
|
271
|
-
const projectDir = session.projectDir || globalThis.__autoProjectDir;
|
|
272
|
-
if (projectDir) {
|
|
273
|
-
msg += `项目目录: ${projectDir}\n`;
|
|
274
|
-
} else {
|
|
275
|
-
msg += `项目目录: 未设置\n`;
|
|
276
|
-
}
|
|
277
|
-
msg += `工作目录: ${process.cwd()}\n`;
|
|
278
|
-
if (session.originalProjectDir && session.originalProjectDir !== projectDir) {
|
|
279
|
-
msg += `原始目录: ${session.originalProjectDir}\n`;
|
|
280
|
-
}
|
|
281
|
-
await adapter.reply(ctx.threadId, msg);
|
|
184
|
+
await adapter.reply(ctx.threadId, '📄 此会话没有文件变更');
|
|
282
185
|
return true;
|
|
283
186
|
}
|
|
284
187
|
case 'reset': {
|
|
@@ -286,35 +189,6 @@ async function handleCommand(adapter, ctx, command, arg, openCodeSessions) {
|
|
|
286
189
|
if (oldSession) {
|
|
287
190
|
abortSession(oldSession).catch(() => {});
|
|
288
191
|
}
|
|
289
|
-
session.pendingApprovals = [];
|
|
290
|
-
session.opencodeSessionId = undefined;
|
|
291
|
-
session.loopMode = false;
|
|
292
|
-
session.loopPrompt = null;
|
|
293
|
-
session.projectDir = null;
|
|
294
|
-
session.currentAgent = null;
|
|
295
|
-
session.messages = [];
|
|
296
|
-
session.commandHistory = [];
|
|
297
|
-
session.taskStartTime = null;
|
|
298
|
-
session.currentTool = null;
|
|
299
|
-
session.modifiedFiles = null;
|
|
300
|
-
session.lastUserMessage = null;
|
|
301
|
-
session._lastPrompt = null;
|
|
302
|
-
session._contextScope = null;
|
|
303
|
-
session.originalProjectDir = null;
|
|
304
|
-
session._switchSessionList = null;
|
|
305
|
-
session._deleteSessionList = null;
|
|
306
|
-
session._pendingSwitchSession = null;
|
|
307
|
-
session._editTarget = null;
|
|
308
|
-
session._editList = null;
|
|
309
|
-
session._editSessionId = null;
|
|
310
|
-
session._historyList = null;
|
|
311
|
-
session._forkList = null;
|
|
312
|
-
session._forkSessionId = null;
|
|
313
|
-
session.expertMode = false;
|
|
314
|
-
session.systemPrompt = null;
|
|
315
|
-
session._analyzeMode = false;
|
|
316
|
-
session._analyzeTask = null;
|
|
317
|
-
session._showSessionState = null;
|
|
318
192
|
openCodeSessions?.delete(ctx.threadId);
|
|
319
193
|
globalThis.__latestOpenCodeSession = null;
|
|
320
194
|
await adapter.reply(ctx.threadId, '🔄 会话已重置,下次发送消息将创建新会话');
|
|
@@ -329,131 +203,6 @@ async function handleCommand(adapter, ctx, command, arg, openCodeSessions) {
|
|
|
329
203
|
}
|
|
330
204
|
return true;
|
|
331
205
|
}
|
|
332
|
-
case 'sessions': {
|
|
333
|
-
try {
|
|
334
|
-
const opencode = await initOpenCode();
|
|
335
|
-
if (!opencode) {
|
|
336
|
-
await adapter.reply(ctx.threadId, '❌ 无法连接 OpenCode');
|
|
337
|
-
return true;
|
|
338
|
-
}
|
|
339
|
-
const result = await opencode.client.session.list();
|
|
340
|
-
if (result.error || !result.data || result.data.length === 0) {
|
|
341
|
-
await adapter.reply(ctx.threadId, '📭 暂无会话');
|
|
342
|
-
return true;
|
|
343
|
-
}
|
|
344
|
-
const sorted = result.data.sort((a, b) => (b.time?.updated || 0) - (a.time?.updated || 0));
|
|
345
|
-
session._switchSessionList = sorted;
|
|
346
|
-
session._showSessionState = true;
|
|
347
|
-
let msg = '📂 选择会话(回复编号):\n\n';
|
|
348
|
-
sorted.slice(0, 10).forEach((s, i) => {
|
|
349
|
-
const n = i + 1;
|
|
350
|
-
const title = s.title || '无标题';
|
|
351
|
-
const time = s.updated_at ? formatTimeAgo(s.updated_at * 1000) : '';
|
|
352
|
-
msg += `${n}. ${title} (${time})\n`;
|
|
353
|
-
});
|
|
354
|
-
if (sorted.length > 10) {
|
|
355
|
-
msg += `\n... 共 ${sorted.length} 个会话`;
|
|
356
|
-
}
|
|
357
|
-
msg += '\n\n回复编号切换会话';
|
|
358
|
-
await adapter.reply(ctx.threadId, msg);
|
|
359
|
-
} catch (e) {
|
|
360
|
-
await adapter.reply(ctx.threadId, `❌ 获取会话失败: ${e.message}`);
|
|
361
|
-
}
|
|
362
|
-
return true;
|
|
363
|
-
}
|
|
364
|
-
case 'delsessions': {
|
|
365
|
-
try {
|
|
366
|
-
const opencode = await initOpenCode();
|
|
367
|
-
if (!opencode) {
|
|
368
|
-
await adapter.reply(ctx.threadId, '❌ 无法连接 OpenCode');
|
|
369
|
-
return true;
|
|
370
|
-
}
|
|
371
|
-
const result = await opencode.client.session.list();
|
|
372
|
-
if (result.error || !result.data || result.data.length === 0) {
|
|
373
|
-
await adapter.reply(ctx.threadId, '📭 暂无会话可删除');
|
|
374
|
-
return true;
|
|
375
|
-
}
|
|
376
|
-
const sorted = result.data.sort((a, b) => (b.time?.updated || 0) - (a.time?.updated || 0));
|
|
377
|
-
session._deleteSessionList = sorted;
|
|
378
|
-
let msg = '🗑️ 选择要删除的会话(回复编号):\n\n';
|
|
379
|
-
sorted.slice(0, 10).forEach((s, i) => {
|
|
380
|
-
const n = i + 1;
|
|
381
|
-
const title = s.title || '无标题';
|
|
382
|
-
const time = s.updated_at ? formatTimeAgo(s.updated_at * 1000) : '';
|
|
383
|
-
msg += `${n}. ${title} (${time})\n`;
|
|
384
|
-
});
|
|
385
|
-
if (sorted.length > 10) {
|
|
386
|
-
msg += `\n... 共 ${sorted.length} 个会话`;
|
|
387
|
-
}
|
|
388
|
-
msg += '\n\n回复编号删除';
|
|
389
|
-
await adapter.reply(ctx.threadId, msg);
|
|
390
|
-
} catch (e) {
|
|
391
|
-
await adapter.reply(ctx.threadId, `❌ 获取会话失败: ${e.message}`);
|
|
392
|
-
}
|
|
393
|
-
return true;
|
|
394
|
-
}
|
|
395
|
-
case 'loop': {
|
|
396
|
-
const argText = arg || '';
|
|
397
|
-
if (argText === 'off' || argText === 'stop') {
|
|
398
|
-
session.loopMode = false;
|
|
399
|
-
session.loopPrompt = null;
|
|
400
|
-
session.loopIterationCount = 0;
|
|
401
|
-
session.loopStartTime = null;
|
|
402
|
-
saveSessionMapping();
|
|
403
|
-
await adapter.reply(ctx.threadId, '⏹️ 循环任务已停止');
|
|
404
|
-
return true;
|
|
405
|
-
}
|
|
406
|
-
if (argText === 'status') {
|
|
407
|
-
if (session.loopMode) {
|
|
408
|
-
const elapsed = session.loopStartTime
|
|
409
|
-
? `已运行: ${Math.floor((Date.now() - session.loopStartTime) / 60000)}分钟`
|
|
410
|
-
: '';
|
|
411
|
-
const count = session.loopIterationCount || 0;
|
|
412
|
-
const limit = session.loopMaxIterations || 10;
|
|
413
|
-
await adapter.reply(ctx.threadId, `🔄 循环任务运行中\n指令: ${session.loopPrompt || '智能模式'}\n迭代: ${count}/${limit} ${elapsed}`);
|
|
414
|
-
} else {
|
|
415
|
-
await adapter.reply(ctx.threadId, '⏹️ 循环任务未运行\n发送 /loop 开始');
|
|
416
|
-
}
|
|
417
|
-
return true;
|
|
418
|
-
}
|
|
419
|
-
session.loopMode = true;
|
|
420
|
-
session.loopPrompt = argText || null;
|
|
421
|
-
session.lastLoopTime = Date.now();
|
|
422
|
-
session.loopStartTime = Date.now();
|
|
423
|
-
session.loopIterationCount = 0;
|
|
424
|
-
session.loopMaxIterations = 10;
|
|
425
|
-
session.loopMaxTimeMs = 30 * 60 * 1000;
|
|
426
|
-
saveSessionMapping();
|
|
427
|
-
const modeDesc = argText ? `指令: ${argText}` : '智能模式(根据上下文自动生成指令)';
|
|
428
|
-
await adapter.reply(ctx.threadId, `🔄 循环任务已启动\n${modeDesc}\n限制: 最多10次迭代或30分钟\n\n发送 /loop off 停止`);
|
|
429
|
-
return true;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
case 'refresh': {
|
|
433
|
-
const ocSession = openCodeSessions.get(ctx.threadId);
|
|
434
|
-
if (!ocSession) {
|
|
435
|
-
await adapter.reply(ctx.threadId, '❌ 没有活跃的会话');
|
|
436
|
-
return true;
|
|
437
|
-
}
|
|
438
|
-
await adapter.reply(ctx.threadId, '🔄 正在刷新会话...');
|
|
439
|
-
try {
|
|
440
|
-
await ocSession.client.session.compact({ path: { id: ocSession.sessionId } });
|
|
441
|
-
await ocSession.client.session.summarize({ path: { id: ocSession.sessionId } });
|
|
442
|
-
await adapter.reply(ctx.threadId, '✅ 会话已刷新');
|
|
443
|
-
} catch (e) {
|
|
444
|
-
await adapter.reply(ctx.threadId, '✅ 会话已刷新');
|
|
445
|
-
}
|
|
446
|
-
return true;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
case 'upload': {
|
|
450
|
-
await adapter.reply(ctx.threadId, 'ℹ️ 上传功能目前仅在微信客户端可用。\n请使用微信客户端上传文件。');
|
|
451
|
-
return true;
|
|
452
|
-
}
|
|
453
|
-
case 'delete': {
|
|
454
|
-
await adapter.reply(ctx.threadId, 'ℹ️ 删除功能目前仅在微信客户端可用。\n请使用微信客户端管理上传文件。');
|
|
455
|
-
return true;
|
|
456
|
-
}
|
|
457
206
|
case 'restart': {
|
|
458
207
|
console.log('[feishu-bot] restart command received');
|
|
459
208
|
await adapter.reply(ctx.threadId, '🔄 正在重启 bot...');
|
|
@@ -479,151 +228,33 @@ async function handleCommand(adapter, ctx, command, arg, openCodeSessions) {
|
|
|
479
228
|
return true;
|
|
480
229
|
}
|
|
481
230
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
case 'copy': {
|
|
486
|
-
const ocSession = openCodeSessions?.get(ctx.threadId);
|
|
487
|
-
if (!ocSession) {
|
|
488
|
-
await adapter.reply(ctx.threadId, '❌ 没有活跃的会话');
|
|
489
|
-
return true;
|
|
490
|
-
}
|
|
491
|
-
try {
|
|
492
|
-
const opencode = await initOpenCode();
|
|
493
|
-
if (!opencode) {
|
|
494
|
-
await adapter.reply(ctx.threadId, '❌ 无法连接 OpenCode');
|
|
495
|
-
return true;
|
|
496
|
-
}
|
|
497
|
-
const msgsResult = await opencode.client.session.messages({
|
|
498
|
-
path: { id: ocSession.sessionId },
|
|
499
|
-
query: { limit: 1 }
|
|
500
|
-
});
|
|
501
|
-
if (msgsResult.error || !msgsResult.data || msgsResult.data.length === 0) {
|
|
502
|
-
await adapter.reply(ctx.threadId, '❌ 无法获取最新消息');
|
|
503
|
-
return true;
|
|
504
|
-
}
|
|
505
|
-
let latestMsg = msgsResult.data[0];
|
|
506
|
-
if (latestMsg.info?.role !== 'assistant') {
|
|
507
|
-
const allMsgsResult = await opencode.client.session.messages({
|
|
508
|
-
path: { id: ocSession.sessionId },
|
|
509
|
-
query: { limit: 10 }
|
|
510
|
-
});
|
|
511
|
-
if (allMsgsResult.error || !allMsgsResult.data) {
|
|
512
|
-
await adapter.reply(ctx.threadId, '❌ 无法获取会话消息');
|
|
513
|
-
return true;
|
|
514
|
-
}
|
|
515
|
-
const aiMsg = allMsgsResult.data.find(m => m.info?.role === 'assistant');
|
|
516
|
-
if (!aiMsg) {
|
|
517
|
-
await adapter.reply(ctx.threadId, '❌ 未找到 AI 回复');
|
|
518
|
-
return true;
|
|
519
|
-
}
|
|
520
|
-
latestMsg = aiMsg;
|
|
521
|
-
}
|
|
522
|
-
let content = '';
|
|
523
|
-
if (latestMsg.parts) {
|
|
524
|
-
for (const part of latestMsg.parts) {
|
|
525
|
-
if (part.type === 'text') {
|
|
526
|
-
content += part.text + '\n';
|
|
527
|
-
}
|
|
528
|
-
if (part.type === 'code') {
|
|
529
|
-
content += `\`\`\`${part.language || ''}\n${part.code}\n\`\`\`\n`;
|
|
530
|
-
}
|
|
531
|
-
if (part.type === 'file' && part.content) {
|
|
532
|
-
content += `📁 ${part.filename}:\n${part.content}\n`;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
if (!content.trim()) {
|
|
537
|
-
await adapter.reply(ctx.threadId, '❌ AI 回复中没有可复制的文本内容');
|
|
538
|
-
return true;
|
|
539
|
-
}
|
|
540
|
-
await adapter.reply(ctx.threadId, `📋 已复制最新 AI 回复内容:\n\n${content.substring(0, 2000)}${content.length > 2000 ? '...' : ''}`);
|
|
541
|
-
} catch (e) {
|
|
542
|
-
await adapter.reply(ctx.threadId, `❌ 复制失败: ${e.message}`);
|
|
543
|
-
}
|
|
544
|
-
return true;
|
|
545
|
-
}
|
|
546
|
-
case 'revert': {
|
|
547
|
-
const ocS = openCodeSessions?.get(ctx.threadId);
|
|
548
|
-
if (!ocS) {
|
|
549
|
-
await adapter.reply(ctx.threadId, '❌ 没有活跃的会话');
|
|
550
|
-
return true;
|
|
551
|
-
}
|
|
552
|
-
try {
|
|
553
|
-
if (arg === 'undo') {
|
|
554
|
-
const ok = await unrevertSession(ocS.sessionId);
|
|
555
|
-
if (ok) {
|
|
556
|
-
await adapter.reply(ctx.threadId, '↩️ 已恢复撤销的内容');
|
|
557
|
-
} else {
|
|
558
|
-
await adapter.reply(ctx.threadId, '❌ 恢复失败');
|
|
559
|
-
}
|
|
560
|
-
return true;
|
|
561
|
-
}
|
|
562
|
-
const opencode = await initOpenCode();
|
|
563
|
-
if (!opencode) {
|
|
564
|
-
await adapter.reply(ctx.threadId, '❌ 无法连接 OpenCode');
|
|
565
|
-
return true;
|
|
566
|
-
}
|
|
567
|
-
const msgsResult = await opencode.client.session.messages({ path: { id: ocS.sessionId } });
|
|
568
|
-
if (msgsResult.error || !msgsResult.data) {
|
|
569
|
-
await adapter.reply(ctx.threadId, '❌ 无法获取消息');
|
|
570
|
-
return true;
|
|
571
|
-
}
|
|
572
|
-
const assistantMsgs = msgsResult.data.filter(m => m.info?.role === 'assistant' && m.time?.created);
|
|
573
|
-
if (assistantMsgs.length === 0) {
|
|
574
|
-
await adapter.reply(ctx.threadId, '📭 没有可撤销的消息');
|
|
575
|
-
return true;
|
|
576
|
-
}
|
|
577
|
-
const lastMsg = assistantMsgs[assistantMsgs.length - 1];
|
|
578
|
-
const ok = await revertSessionMessage(ocS.sessionId, lastMsg.id);
|
|
579
|
-
if (ok) {
|
|
580
|
-
const preview = lastMsg.info?.content?.slice(0, 100) || '(无内容)';
|
|
581
|
-
await adapter.reply(ctx.threadId, `↩️ 已撤销最近的消息\n\n${preview}\n\n发送 /revert undo 恢复`);
|
|
582
|
-
} else {
|
|
583
|
-
await adapter.reply(ctx.threadId, '❌ 撤销失败');
|
|
584
|
-
}
|
|
585
|
-
} catch (e) {
|
|
586
|
-
await adapter.reply(ctx.threadId, `❌ 撤销失败: ${e.message}`);
|
|
587
|
-
}
|
|
588
|
-
return true;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
case 'demo': {
|
|
598
|
-
const argText = (arg || '').trim().toLowerCase();
|
|
599
|
-
if (argText === 'off' || argText === 'exit' || argText === 'stop') {
|
|
600
|
-
setDemoMode(ctx.threadId, false);
|
|
601
|
-
await adapter.reply(ctx.threadId, '⏹️ 已退出沙箱模式');
|
|
602
|
-
return true;
|
|
603
|
-
}
|
|
604
|
-
setDemoMode(ctx.threadId, true);
|
|
605
|
-
let msg = '🎮 沙箱模式已启动\n\n在此模式下所有命令返回模拟输出,无需连接 OpenCode。\n\n';
|
|
606
|
-
msg += '试试发送: /help /status /model /agents /loop /copy\n';
|
|
607
|
-
msg += '发送 /demo off 退出';
|
|
608
|
-
await adapter.reply(ctx.threadId, msg);
|
|
609
|
-
return true;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
231
|
case 'diagnose': {
|
|
613
232
|
const { checkConnection } = await import('../opencode/client.js');
|
|
614
233
|
const diag = ['🔍 诊断报告\n'];
|
|
615
234
|
diag.push(`OpenCode: ${await checkConnection().then(() => '✅').catch(() => '❌')}`);
|
|
616
235
|
diag.push(`七牛云: ${process.env.QINIU_ACCESS_KEY ? '✅' : '❌'}`);
|
|
617
|
-
diag.push(`项目目录: ${session.projectDir || globalThis.__autoProjectDir || '❌ 未设置'}`);
|
|
618
236
|
diag.push(`会话: ${openCodeSessions?.get(ctx.threadId) ? '✅' : '❌'}`);
|
|
619
237
|
const msgs = splitMessage(diag.join('\n'));
|
|
620
238
|
for (const m of msgs) await adapter.reply(ctx.threadId, m);
|
|
621
239
|
return true;
|
|
622
240
|
}
|
|
241
|
+
case 'raw': {
|
|
242
|
+
const val = arg?.trim().toLowerCase();
|
|
243
|
+
if (val === 'on' || val === '1' || val === 'true') {
|
|
244
|
+
setRawDebug(true);
|
|
245
|
+
await adapter.reply(ctx.threadId, '📄 RAW 输出已开启');
|
|
246
|
+
} else if (val === 'off' || val === '0' || val === 'false') {
|
|
247
|
+
setRawDebug(false);
|
|
248
|
+
await adapter.reply(ctx.threadId, '📄 RAW 输出已关闭');
|
|
249
|
+
} else {
|
|
250
|
+
await adapter.reply(ctx.threadId, `📄 RAW 输出当前: ${isRawDebug() ? '🟢 ON' : '🔴 OFF'}\n用法: /raw on 或 /raw off`);
|
|
251
|
+
}
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
623
254
|
default:
|
|
624
255
|
await adapter.reply(ctx.threadId, `${EMOJI.WARNING} 未知指令: ${command}\n\n请发送 /help 查看可用指令`);
|
|
625
256
|
return true;
|
|
626
257
|
}
|
|
627
258
|
}
|
|
628
259
|
|
|
629
|
-
export { handleCommand
|
|
260
|
+
export { handleCommand };
|