@yvhitxcel/opencode-remote 0.15.1 → 0.16.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.
@@ -172,6 +172,9 @@ export async function initFetchConfig() {
172
172
  }
173
173
  let opencodeInstance = null;
174
174
  let opencodeServer = null;
175
+ let lastStdoutTime = 0;
176
+ let lastStdoutLine = '';
177
+ let lastReportedStatus = '';
175
178
  const PORTS_TO_TRY = [4096, 4097, 4098];
176
179
 
177
180
  // TCP-level port probe: true = occupied, false = free
@@ -241,8 +244,9 @@ export async function initOpenCode() {
241
244
  windowsHide: isWindows,
242
245
  });
243
246
  opencodeServer.stdout.on('data', (d) => {
247
+ lastStdoutTime = Date.now();
244
248
  const msg = d.toString().trim();
245
- if (msg) console.log(`[opencode] ${msg}`);
249
+ if (msg) { lastStdoutLine = msg.slice(0, 120); console.log(`[opencode] ${msg}`); }
246
250
  });
247
251
  opencodeServer.stderr.on('data', (d) => {
248
252
  const msg = d.toString().trim();
@@ -389,13 +393,11 @@ export async function sendMessage(session, message, callbacks) {
389
393
  body: promptBody,
390
394
  });
391
395
 
392
- // Poll for new response - multi-turn: continue until truly idle
396
+ // Poll for new response - keep going as long as new content keeps arriving
393
397
  const startTime = Date.now();
394
398
  let responseText = '';
395
399
  let hasToolActivity = false;
396
- let lastStatus = '';
397
- let idleCycles = 0;
398
- const IDLE_THRESHOLD = 3; // Exit after 3 idle polls (no new content, not processing)
400
+ let idleSince = 0; // 最后一次收到新内容的时间戳
399
401
 
400
402
  while (Date.now() - startTime < TIMEOUT_MS) {
401
403
  await new Promise(r => setTimeout(r, POLL_INTERVAL));
@@ -405,93 +407,61 @@ export async function sendMessage(session, message, callbacks) {
405
407
  path: { id: session.sessionId }
406
408
  });
407
409
 
408
- if (msgsResult.error) {
409
- console.error('[sendMessage] Messages error:', msgsResult.error);
410
- break;
411
- }
412
-
413
- if (!msgsResult.data?.length) {
414
- continue;
415
- }
410
+ if (msgsResult.error) { console.error('[sendMessage] Messages error:', msgsResult.error); break; }
411
+ if (!msgsResult.data?.length) continue;
416
412
 
417
413
  const messages = msgsResult.data;
418
- const newMsgCount = messages.length;
419
-
420
- // Check session status
421
- const latestMsg = messages[messages.length - 1];
422
- const currentStatus = latestMsg?.info?.status;
423
- if (currentStatus !== lastStatus) {
424
- lastStatus = currentStatus;
425
- if (lastStatus) {
426
- console.log(`[sendMessage] Session status: ${lastStatus}`);
427
- }
428
- }
429
414
 
430
- // If actively processing, reset idle counter and wait
431
- if (lastStatus === 'pending_tool' || lastStatus === 'thinking') {
432
- idleCycles = 0;
433
- continue;
434
- }
435
-
436
- // Check if there was tool activity and notify via callback
437
- if (newMsgCount > msgCountBefore) {
438
- for (let i = msgCountBefore; i < newMsgCount; i++) {
439
- const msg = messages[i];
440
- if (msg.parts) {
441
- for (const part of msg.parts) {
442
- if (part.type === 'tool_use' || part.type === 'tool_result') {
443
- hasToolActivity = true;
444
- callbacks?.onEvent?.({
445
- type: 'tool.call',
446
- properties: {
447
- name: part.name || part.tool_name || 'unknown',
448
- input: part.input || {}
449
- }
450
- });
451
- break;
452
- }
453
- }
415
+ // 工具活动
416
+ for (let i = msgCountBefore; i < messages.length; i++) {
417
+ const msg = messages[i];
418
+ if (msg.parts) for (const part of msg.parts) {
419
+ if (part.type === 'tool_use' || part.type === 'tool_result') {
420
+ hasToolActivity = true;
421
+ callbacks?.onEvent?.({ type: 'tool.call', properties: { name: part.name || part.tool_name || 'unknown', input: part.input || {} } });
422
+ break;
454
423
  }
455
- if (hasToolActivity) break;
456
424
  }
425
+ if (hasToolActivity) break;
457
426
  }
458
427
 
459
- // Find messages after our last message
460
- let startIdx = 0;
428
+ // 收集所有新的 assistant 回复(累加,不丢内容)
461
429
  if (lastMsgId) {
462
430
  const idx = messages.findIndex(m => m.info?.id === lastMsgId);
463
- if (idx >= 0) startIdx = idx + 1;
464
- }
465
-
466
- // Collect the latest assistant response text
467
- let newText = '';
468
- for (let i = messages.length - 1; i >= startIdx; i--) {
469
- const msg = messages[i];
470
- if (msg.info?.role === 'assistant') {
471
- const textParts = msg.parts
472
- ?.filter(p => p.type === 'text' && p.text)
473
- .map(p => p.text) || [];
474
-
475
- if (textParts.length > 0) {
476
- newText = textParts.join('\n');
477
- break;
431
+ const startIdx = idx >= 0 ? idx + 1 : 0;
432
+ const newParts = [];
433
+ for (let i = startIdx; i < messages.length; i++) {
434
+ const msg = messages[i];
435
+ if (msg.info?.role === 'assistant' && msg.parts) {
436
+ for (const p of msg.parts) {
437
+ if (p.type === 'text' && p.text) newParts.push(p.text);
438
+ }
478
439
  }
479
440
  }
441
+ const fullText = newParts.join('\n');
442
+ if (fullText && fullText !== responseText) {
443
+ const delta = fullText.slice(responseText.length);
444
+ responseText = fullText;
445
+ callbacks?.onTextDelta?.(delta);
446
+ callbacks?.onNewContent?.(delta);
447
+ idleSince = Date.now();
448
+ continue;
449
+ }
480
450
  }
481
451
 
482
- if (newText && newText !== responseText) {
483
- const delta = newText.slice(responseText.length);
484
- responseText = newText;
485
- callbacks?.onTextDelta?.(delta);
486
- idleCycles = 0;
452
+ // 检查 AI 是否还在忙(thinking/pending_tool 说明还没干完)
453
+ const latestStatus = msgsResult.data?.length ? msgsResult.data[msgsResult.data.length - 1]?.info?.status : '';
454
+ if (latestStatus === 'thinking' || latestStatus === 'pending_tool') {
455
+ idleSince = Date.now();
456
+ }
457
+ if (latestStatus && latestStatus !== lastReportedStatus) {
458
+ lastReportedStatus = latestStatus;
459
+ console.log(`[AI状态] ${latestStatus}`);
487
460
  }
488
461
 
489
- // Exit only when truly idle: have response, not processing, no new text for N cycles
490
- if (responseText && lastStatus !== 'pending_tool' && lastStatus !== 'thinking') {
491
- idleCycles++;
492
- if (idleCycles >= IDLE_THRESHOLD) {
493
- break;
494
- }
462
+ // 有回复后:等 30 秒无新内容且 AI 不忙才退出
463
+ if (responseText && Date.now() - idleSince > 30000) {
464
+ break;
495
465
  }
496
466
  } catch (e) {
497
467
  console.warn('Poll error:', e.message);
@@ -523,7 +493,6 @@ export async function sendMessage(session, message, callbacks) {
523
493
  }
524
494
 
525
495
  callbacks?.onStatusChange?.({ type: 'idle', hasToolActivity });
526
- console.log(`💬 Response: ${responseText.slice(0, 100)}...`);
527
496
  return responseText;
528
497
  }
529
498
  catch (error) {
@@ -0,0 +1,55 @@
1
+ import { Bot } from 'grammy';
2
+ import { splitMessage } from '../utils/message-split.js';
3
+
4
+ export class TelegramAdapter {
5
+ name = 'telegram';
6
+ bot = null;
7
+ config = null;
8
+ messageHandler = null;
9
+ isRunning = false;
10
+ typingIntervals = new Map();
11
+
12
+ async start(config) {
13
+ this.config = config;
14
+ if (!config.telegramBotToken || config.telegramBotToken === 'your_bot_token_here') {
15
+ throw new Error('Telegram bot token not configured');
16
+ }
17
+ this.bot = new Bot(config.telegramBotToken);
18
+ this.isRunning = true;
19
+ console.log('🚀 Telegram adapter started');
20
+ }
21
+
22
+ async stop() {
23
+ this.isRunning = false;
24
+ for (const interval of this.typingIntervals.values()) clearInterval(interval);
25
+ this.typingIntervals.clear();
26
+ if (this.bot) { await this.bot.stop(); this.bot = null; }
27
+ console.log('👋 Telegram adapter stopped');
28
+ }
29
+
30
+ onMessage(handler) { this.messageHandler = handler; }
31
+
32
+ async sendMessage(threadId, text) {
33
+ if (!this.bot) throw new Error('Telegram adapter not started');
34
+ const chunks = splitMessage(text, { maxLength: 4000, addContinuationMarker: false });
35
+ for (const chunk of chunks) await this.bot.api.sendMessage(threadId, chunk, { parse_mode: 'Markdown' });
36
+ }
37
+
38
+ async sendTyping(threadId, isTyping) {
39
+ if (!this.bot) return;
40
+ if (isTyping) {
41
+ try { await this.bot.api.sendChatAction(threadId, 'typing'); } catch {}
42
+ const existing = this.typingIntervals.get(threadId);
43
+ if (existing) clearInterval(existing);
44
+ const interval = setInterval(async () => {
45
+ try { await this.bot.api.sendChatAction(threadId, 'typing'); } catch {}
46
+ }, 4000);
47
+ this.typingIntervals.set(threadId, interval);
48
+ } else {
49
+ const interval = this.typingIntervals.get(threadId);
50
+ if (interval) { clearInterval(interval); this.typingIntervals.delete(threadId); }
51
+ }
52
+ }
53
+ }
54
+
55
+ export const telegramAdapter = new TelegramAdapter();
@@ -1,189 +1,53 @@
1
- // Telegram Bot adapter with multi-agent support
2
- import { Bot } from 'grammy';
3
- import { splitMessage } from '../utils/message-split.js';
4
1
  import { registry } from '../core/registry.js';
5
2
  import { sessionManager } from '../core/session.js';
6
3
  import { initOpenCode, createSession, sendMessage as sendToOpenCode, checkConnection } from '../opencode/client.js';
7
4
  import { parseMessage, routeMessage } from '../core/router.js';
5
+ import { telegramAdapter } from './adapter.js';
8
6
 
9
- export class TelegramAdapter {
10
- name = 'telegram';
11
- bot = null;
12
- config = null;
13
- messageHandler = null;
14
- isRunning = false;
15
- typingIntervals = new Map();
16
-
17
- async start(config) {
18
- this.config = config;
19
-
20
- if (!config.telegramBotToken || config.telegramBotToken === 'your_bot_token_here') {
21
- throw new Error('Telegram bot token not configured. Run "opencode-remote config" first.');
22
- }
23
-
24
- this.bot = new Bot(config.telegramBotToken);
25
-
26
- this.bot.on('message:text', async (ctx) => {
27
- console.log('[Telegram] Received message:', ctx.message.text);
28
-
29
- if (ctx.message.from.is_bot) {
30
- console.log('[Telegram] Ignoring bot message');
31
- return;
32
- }
33
-
34
- if (!this.messageHandler) {
35
- console.log('[Telegram] No message handler registered');
36
- return;
37
- }
38
-
39
- try {
40
- const message = {
41
- id: ctx.message.message_id.toString(),
42
- threadId: ctx.chat.id.toString(),
43
- userId: ctx.message.from?.id?.toString() || 'unknown',
44
- text: ctx.message.text || '',
45
- timestamp: new Date(ctx.message.date * 1000),
46
- channelId: 'default',
47
- };
48
-
49
- const msgCtx = {
50
- message,
51
- platform: 'telegram',
52
- channelId: 'default',
53
- };
54
-
55
- await this.messageHandler(msgCtx);
56
- } catch (err) {
57
- console.error('[Telegram] Error in message handler:', err);
58
- }
59
- });
60
-
61
- this.bot.start().then(() => {
62
- console.log('[Telegram] Bot stopped gracefully');
63
- }).catch((err) => {
64
- if (this.isRunning) {
65
- console.error('[Telegram] Bot polling error:', err);
66
- }
67
- });
68
-
69
- this.isRunning = true;
70
- console.log('🚀 Telegram adapter started');
71
- }
72
-
73
- async stop() {
74
- this.isRunning = false;
75
-
76
- for (const interval of this.typingIntervals.values()) {
77
- clearInterval(interval);
78
- }
79
- this.typingIntervals.clear();
80
-
81
- if (this.bot) {
82
- await this.bot.stop();
83
- this.bot = null;
84
- }
85
-
86
- console.log('👋 Telegram adapter stopped');
87
- }
88
-
89
- onMessage(handler) {
90
- this.messageHandler = handler;
91
- }
92
-
93
- async sendMessage(threadId, text) {
94
- if (!this.bot) {
95
- throw new Error('Telegram adapter not started');
96
- }
97
-
98
- const chunks = splitMessage(text, { maxLength: 4000, addContinuationMarker: false });
99
-
100
- for (const chunk of chunks) {
101
- await this.bot.api.sendMessage(threadId, chunk, { parse_mode: 'Markdown' });
102
- }
103
- }
104
-
105
- async sendTyping(threadId, isTyping) {
106
- if (!this.bot) {
107
- return;
108
- }
109
-
110
- if (isTyping) {
111
- try {
112
- await this.bot.api.sendChatAction(threadId, 'typing');
113
- } catch {
114
- // Ignore errors
115
- }
116
-
117
- const existing = this.typingIntervals.get(threadId);
118
- if (existing) {
119
- clearInterval(existing);
120
- }
121
-
122
- const interval = setInterval(async () => {
123
- try {
124
- await this.bot.api.sendChatAction(threadId, 'typing');
125
- } catch {
126
- // Ignore errors
127
- }
128
- }, 4000);
129
-
130
- this.typingIntervals.set(threadId, interval);
131
- } else {
132
- const interval = this.typingIntervals.get(threadId);
133
- if (interval) {
134
- clearInterval(interval);
135
- this.typingIntervals.delete(threadId);
136
- }
137
- }
138
- }
139
- }
140
-
141
- export const telegramAdapter = new TelegramAdapter();
142
-
143
- // Legacy startBot function for backward compatibility
144
7
  export async function startBot() {
145
8
  const { loadConfig } = await import('../core/config.js');
146
9
  const config = loadConfig();
147
-
10
+
148
11
  if (!config.telegramBotToken || config.telegramBotToken === 'your_bot_token_here') {
149
- console.log('');
150
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
151
- console.log(' Telegram Bot Token not configured');
152
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
153
- console.log('');
154
- console.log(' To get your bot token:');
155
- console.log(' 1. Open Telegram app');
156
- console.log(' 2. Search for @BotFather');
157
- console.log(' 3. Send: /newbot');
158
- console.log(' 4. Follow the instructions to create your bot');
159
- console.log(' 5. Copy the token');
160
- console.log('');
161
- console.log(' Then run: opencode-remote config');
162
- console.log('');
163
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
12
+ console.log('\n❌ Telegram Bot Token not configured\n');
13
+ console.log('To get your bot token:');
14
+ console.log(' 1. Open Telegram app, search @BotFather');
15
+ console.log(' 2. Send /newbot and follow instructions');
16
+ console.log(' 3. Then run: opencode-remote config\n');
164
17
  process.exit(1);
165
18
  }
166
-
167
- console.log('');
168
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
169
- console.log(' OpenCode Remote Control');
170
- console.log(' Control OpenCode from Telegram');
171
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
172
- console.log('');
173
-
174
- // Initialize session manager
19
+
175
20
  await sessionManager.start();
176
-
177
- // Load plugins
178
21
  await registry.loadBuiltInPlugins();
179
-
180
- // Initialize Telegram adapter
181
22
  await telegramAdapter.start(config);
182
-
183
- // Set up message handler
23
+
184
24
  let openCodeSessions = new Map();
185
25
  let opencodeSessionId = null;
186
26
 
27
+ telegramAdapter.bot.on('message:text', async (ctx) => {
28
+ if (ctx.message.from.is_bot) return;
29
+ if (!telegramAdapter.messageHandler) return;
30
+
31
+ try {
32
+ const message = {
33
+ id: ctx.message.message_id.toString(),
34
+ threadId: ctx.chat.id.toString(),
35
+ userId: ctx.message.from?.id?.toString() || 'unknown',
36
+ text: ctx.message.text || '',
37
+ timestamp: new Date(ctx.message.date * 1000),
38
+ channelId: 'default',
39
+ };
40
+ const msgCtx = { message, platform: 'telegram', channelId: 'default' };
41
+ await telegramAdapter.messageHandler(msgCtx);
42
+ } catch (err) {
43
+ console.error('[Telegram] Error:', err);
44
+ }
45
+ });
46
+
47
+ telegramAdapter.bot.start().catch((err) => {
48
+ if (telegramAdapter.isRunning) console.error('[Telegram] Polling error:', err);
49
+ });
50
+
187
51
  telegramAdapter.onMessage(async (ctx) => {
188
52
  const { message, platform, channelId } = ctx;
189
53
 
@@ -198,23 +62,22 @@ export async function startBot() {
198
62
  try {
199
63
  const session = await sessionManager.getExistingSession(platform, channelId, message.threadId);
200
64
  if (session) await sessionManager.resetConversation(platform, channelId, message.threadId);
201
- } catch (e) { console.warn('[Telegram] Error resetting session:', e.message); }
65
+ } catch (e) { console.warn('[Telegram] Reset error:', e.message); }
202
66
  }
203
67
 
204
68
  if (parsed.type === 'default') {
205
69
  const connected = await checkConnection();
206
70
  if (!connected) {
207
71
  await telegramAdapter.sendTyping(message.threadId, false);
208
- await telegramAdapter.sendMessage(message.threadId, '❌ OpenCode 离线,请检查服务是否运行');
72
+ await telegramAdapter.sendMessage(message.threadId, '❌ OpenCode 离线');
209
73
  return;
210
74
  }
211
-
212
75
  let session = openCodeSessions.get(message.threadId);
213
76
  if (!session) {
214
77
  const newSession = await createSession(message.threadId, `Telegram ${message.threadId}`);
215
78
  if (!newSession) {
216
79
  await telegramAdapter.sendTyping(message.threadId, false);
217
- await telegramAdapter.sendMessage(message.threadId, '❌ 无法创建 OpenCode 会话');
80
+ await telegramAdapter.sendMessage(message.threadId, '❌ 无法创建会话');
218
81
  return;
219
82
  }
220
83
  session = newSession;
@@ -226,18 +89,15 @@ export async function startBot() {
226
89
  onTextDelta: () => {},
227
90
  onEvent: (event) => {
228
91
  if (event.type === 'tool.call') {
229
- const props = event.properties || {};
230
- const toolName = props.name || 'unknown';
231
- telegramAdapter.sendMessage(message.threadId, `🔧 ${toolName}`).catch(() => {});
92
+ const n = event.properties?.name || 'unknown';
93
+ telegramAdapter.sendMessage(message.threadId, `🔧 ${n}`).catch(() => {});
232
94
  }
233
95
  },
234
96
  });
235
97
 
236
98
  await telegramAdapter.sendTyping(message.threadId, false);
237
-
238
99
  if (response) {
239
- const chunks = splitMessage(response);
240
- for (const chunk of chunks) {
100
+ for (const chunk of splitMessage(response)) {
241
101
  if (chunk.trim()) await telegramAdapter.sendMessage(message.threadId, chunk);
242
102
  }
243
103
  }
@@ -245,44 +105,25 @@ export async function startBot() {
245
105
  }
246
106
 
247
107
  const result = await routeMessage(parsed, {
248
- threadId: message.threadId,
249
- channelId,
250
- platform,
251
- defaultAgent: 'opencode',
252
- opencodeSessionId,
108
+ threadId: message.threadId, channelId, platform,
109
+ defaultAgent: 'opencode', opencodeSessionId,
253
110
  });
254
111
 
255
112
  await telegramAdapter.sendTyping(message.threadId, false);
256
-
257
113
  if (typeof result === 'string') {
258
114
  await telegramAdapter.sendMessage(message.threadId, result);
259
115
  } else if (result) {
260
- let fullResponse = '';
261
- for await (const chunk of result) {
262
- fullResponse += chunk;
263
- }
264
- if (fullResponse) await telegramAdapter.sendMessage(message.threadId, fullResponse);
116
+ let full = '';
117
+ for await (const chunk of result) full += chunk;
118
+ if (full) await telegramAdapter.sendMessage(message.threadId, full);
265
119
  }
266
120
  } catch (error) {
267
- console.error('Error handling message:', error);
121
+ console.error('[Telegram] Error:', error);
268
122
  await telegramAdapter.sendTyping(message.threadId, false);
269
- await telegramAdapter.sendMessage(message.threadId, '❌ 处理失败,请重试。');
123
+ await telegramAdapter.sendMessage(message.threadId, '❌ 处理失败');
270
124
  }
271
125
  });
272
-
273
- console.log('🚀 Starting Telegram bot...');
274
- console.log('');
275
- console.log('Available commands:');
276
- console.log(' /start - Start bot');
277
- console.log(' /help - Show all commands');
278
- console.log(' /agents - List available agents');
279
- console.log(' /new - Start new conversation');
280
- console.log(' /reset - Reset session');
281
- console.log(' /oc <prompt> - Use OpenCode');
282
- console.log(' /cc <prompt> - Use Claude Code');
283
- console.log(' /cx <prompt> - Use Codex');
284
- console.log('');
285
-
286
- // Keep process alive
126
+
127
+ console.log('🚀 Telegram bot ready');
287
128
  await new Promise(() => {});
288
129
  }
@@ -134,6 +134,7 @@ export async function startWeixinBot(botConfig, restartFn) {
134
134
  globalThis.__weixinBotRunning = () => running;
135
135
 
136
136
  let buf = '';
137
+ let retryCount = 0;
137
138
  console.log('Polling for messages...');
138
139
 
139
140
  if (process.env.OPENCODE_RESTART === '1') {
@@ -170,8 +171,17 @@ export async function startWeixinBot(botConfig, restartFn) {
170
171
  }
171
172
  } catch (e) {
172
173
  if (!running) break;
173
- console.error('Polling error:', e);
174
- await new Promise(r => setTimeout(r, 2000));
174
+ const errMsg = e.message || '';
175
+ const isConnReset = errMsg.includes('ECONNRESET') || errMsg.includes('fetch failed');
176
+ if (isConnReset) {
177
+ retryCount++;
178
+ const delay = Math.min(2000 * retryCount, 15000);
179
+ console.error(`[bot] Connection error (${retryCount}), retry in ${delay}ms...`);
180
+ await new Promise(r => setTimeout(r, delay));
181
+ } else {
182
+ console.error('Polling error:', e);
183
+ await new Promise(r => setTimeout(r, 2000));
184
+ }
175
185
  }
176
186
  }
177
187
 
@@ -1,4 +1,4 @@
1
- import { detectCommand, COMMAND_ALIASES } from '../core/router.js';
1
+ import { detectCommand, COMMAND_ALIASES, getHelpText } from '../core/router.js';
2
2
  import { getOrCreateSession, saveSessionMapping, sessionManager } from '../core/session.js';
3
3
  import { splitMessage } from '../core/notifications.js';
4
4
  import { initOpenCode, checkConnection, abortSession, resumeSession, revertSessionMessage, unrevertSession, listProviders, updateGlobalModel } from '../opencode/client.js';
@@ -91,36 +91,7 @@ async function handleCommand(adapter, ctx, command, arg, openCodeSessions) {
91
91
  return true;
92
92
  }
93
93
  case 'help':
94
- await adapter.reply(ctx.threadId, `📖 指令
95
-
96
- 🟢 常用:
97
- /start — 首次认证
98
- /help — 帮助
99
- /status — 查看状态
100
- /reset — 重置会话
101
- /copy — 复制回复
102
- /revert — 撤销消息
103
-
104
- 🔄 任务:
105
- /loop — 循环执行
106
- /refresh — 刷新上下文
107
- /restart — 重启 bot
108
- /stop — 停止 bot
109
-
110
- 📂 会话:
111
- /sessions — 浏览会话
112
- /delsessions — 删除会话
113
-
114
- 🤖 AI 模型:
115
- /model — 切换模型
116
- /agents — 查看可用 Agent
117
- /oc — 使用 OpenCode
118
- /cc — 使用 Claude Code
119
-
120
- ⬆️ 文件:
121
- /upload — 上传构建产物
122
-
123
- 💬 直接发消息给 AI!`);
94
+ await adapter.reply(ctx.threadId, getHelpText());
124
95
  return true;
125
96
  case 'status': {
126
97
  const connected = await checkConnection();
@@ -489,17 +460,6 @@ async function handleCommand(adapter, ctx, command, arg, openCodeSessions) {
489
460
  return true;
490
461
  }
491
462
 
492
- case 'stop': {
493
- await adapter.reply(ctx.threadId, '🛑 正在停止 bot...');
494
- setTimeout(() => {
495
- if (globalThis.__weixinBotShutdown) {
496
- globalThis.__weixinBotShutdown(false);
497
- }
498
- setTimeout(() => process.exit(0), 1000);
499
- }, 500);
500
- return true;
501
- }
502
-
503
463
  case 'upload': {
504
464
  const projectDir = session.projectDir || globalThis.__autoProjectDir;
505
465