@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.
@@ -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, createSession, sendMessage, checkConnection, abortSession, resumeSession, revertSessionMessage, unrevertSession, setThreadModel, getThreadModel, getRecentModels } from '../opencode/client.js';
3
+ import { initOpenCode, checkConnection, abortSession, setThreadModel, getThreadModel, getRecentModels, setRawDebug, isRawDebug } from '../opencode/client.js';
5
4
  import { claimOwnership } from '../core/auth.js';
6
- import { COMMAND_ALIASES, detectCommand, getHelpText, DEMO_RESPONSES, setDemoMode, isDemoMode } from '../core/router.js';
5
+ import { getHelpText } from '../core/router.js';
7
6
  import { registry } from '../core/registry.js';
8
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
9
- import { join, basename } from 'path';
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.provider.list();
122
- if (result.error || !result.data?.all) {
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.all) {
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 history = session.commandHistory || [];
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
- const pending = session.pendingApprovals?.[0];
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
- const pending = session.pendingApprovals?.[0];
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
- const pending = session.pendingApprovals?.[0];
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, formatTimeAgo };
260
+ export { handleCommand };