@yvhitxcel/opencode-remote 0.15.1 → 0.16.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.
- package/README.md +78 -8
- package/dist/core/auth.js +41 -108
- package/dist/core/notifications.js +11 -0
- package/dist/core/router.js +291 -61
- package/dist/feishu/commands.js +29 -35
- package/dist/feishu/handler.js +17 -26
- package/dist/opencode/client.js +48 -77
- package/dist/plugins/agents/claude-code/index.js +46 -4
- package/dist/telegram/adapter.js +75 -0
- package/dist/telegram/bot.js +66 -208
- package/dist/weixin/bot.js +12 -2
- package/dist/weixin/commands.js +29 -42
- package/dist/weixin/handler.js +80 -107
- package/package.json +2 -3
package/dist/telegram/bot.js
CHANGED
|
@@ -1,189 +1,62 @@
|
|
|
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('
|
|
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.on('callback_query:data', async (ctx) => {
|
|
48
|
+
if (!ctx.callbackQuery.data.startsWith('cmd:')) return;
|
|
49
|
+
const cmd = ctx.callbackQuery.data.slice(4);
|
|
50
|
+
try {
|
|
51
|
+
await ctx.answerCallbackQuery({ text: `执行: /${cmd}` });
|
|
52
|
+
const msg = await telegramAdapter.bot.api.sendMessage(ctx.chat.id, `/${cmd}`);
|
|
53
|
+
} catch (e) { console.error('[Telegram] callback error:', e.message); }
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
telegramAdapter.bot.start().catch((err) => {
|
|
57
|
+
if (telegramAdapter.isRunning) console.error('[Telegram] Polling error:', err);
|
|
58
|
+
});
|
|
59
|
+
|
|
187
60
|
telegramAdapter.onMessage(async (ctx) => {
|
|
188
61
|
const { message, platform, channelId } = ctx;
|
|
189
62
|
|
|
@@ -198,23 +71,22 @@ export async function startBot() {
|
|
|
198
71
|
try {
|
|
199
72
|
const session = await sessionManager.getExistingSession(platform, channelId, message.threadId);
|
|
200
73
|
if (session) await sessionManager.resetConversation(platform, channelId, message.threadId);
|
|
201
|
-
} catch (e) { console.warn('[Telegram]
|
|
74
|
+
} catch (e) { console.warn('[Telegram] Reset error:', e.message); }
|
|
202
75
|
}
|
|
203
76
|
|
|
204
77
|
if (parsed.type === 'default') {
|
|
205
78
|
const connected = await checkConnection();
|
|
206
79
|
if (!connected) {
|
|
207
80
|
await telegramAdapter.sendTyping(message.threadId, false);
|
|
208
|
-
await telegramAdapter.sendMessage(message.threadId, '❌ OpenCode
|
|
81
|
+
await telegramAdapter.sendMessage(message.threadId, '❌ OpenCode 离线');
|
|
209
82
|
return;
|
|
210
83
|
}
|
|
211
|
-
|
|
212
84
|
let session = openCodeSessions.get(message.threadId);
|
|
213
85
|
if (!session) {
|
|
214
86
|
const newSession = await createSession(message.threadId, `Telegram ${message.threadId}`);
|
|
215
87
|
if (!newSession) {
|
|
216
88
|
await telegramAdapter.sendTyping(message.threadId, false);
|
|
217
|
-
await telegramAdapter.sendMessage(message.threadId, '❌
|
|
89
|
+
await telegramAdapter.sendMessage(message.threadId, '❌ 无法创建会话');
|
|
218
90
|
return;
|
|
219
91
|
}
|
|
220
92
|
session = newSession;
|
|
@@ -222,67 +94,53 @@ export async function startBot() {
|
|
|
222
94
|
}
|
|
223
95
|
opencodeSessionId = session.sessionId;
|
|
224
96
|
|
|
97
|
+
const taskStart = Date.now();
|
|
225
98
|
const response = await sendToOpenCode(session, parsed.prompt, {
|
|
226
99
|
onTextDelta: () => {},
|
|
227
100
|
onEvent: (event) => {
|
|
228
101
|
if (event.type === 'tool.call') {
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
telegramAdapter.sendMessage(message.threadId, `🔧 ${toolName}`).catch(() => {});
|
|
102
|
+
const n = event.properties?.name || 'unknown';
|
|
103
|
+
telegramAdapter.sendMessage(message.threadId, `🔧 ${n}`).catch(() => {});
|
|
232
104
|
}
|
|
233
105
|
},
|
|
234
106
|
});
|
|
235
107
|
|
|
236
108
|
await telegramAdapter.sendTyping(message.threadId, false);
|
|
237
|
-
|
|
238
109
|
if (response) {
|
|
239
|
-
const
|
|
240
|
-
for (const chunk of chunks) {
|
|
110
|
+
for (const chunk of splitMessage(response)) {
|
|
241
111
|
if (chunk.trim()) await telegramAdapter.sendMessage(message.threadId, chunk);
|
|
242
112
|
}
|
|
243
113
|
}
|
|
114
|
+
const { formatTaskCompletion } = await import('../core/notifications.js');
|
|
115
|
+
await telegramAdapter.sendMessage(message.threadId, formatTaskCompletion('AI 任务', taskStart));
|
|
244
116
|
return;
|
|
245
117
|
}
|
|
246
118
|
|
|
247
119
|
const result = await routeMessage(parsed, {
|
|
248
|
-
threadId: message.threadId,
|
|
249
|
-
|
|
250
|
-
platform,
|
|
251
|
-
defaultAgent: 'opencode',
|
|
252
|
-
opencodeSessionId,
|
|
120
|
+
threadId: message.threadId, channelId, platform,
|
|
121
|
+
defaultAgent: 'opencode', opencodeSessionId,
|
|
253
122
|
});
|
|
254
123
|
|
|
255
124
|
await telegramAdapter.sendTyping(message.threadId, false);
|
|
256
|
-
|
|
257
125
|
if (typeof result === 'string') {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
126
|
+
if (parsed.type === 'command' && (parsed.command === 'help' || parsed.command === 'start')) {
|
|
127
|
+
await telegramAdapter.sendMessage(message.threadId, result);
|
|
128
|
+
await telegramAdapter.sendCommandMenu(message.threadId, '📱 快速选择指令:');
|
|
129
|
+
} else {
|
|
130
|
+
await telegramAdapter.sendMessage(message.threadId, result);
|
|
263
131
|
}
|
|
264
|
-
|
|
132
|
+
} else if (result) {
|
|
133
|
+
let full = '';
|
|
134
|
+
for await (const chunk of result) full += chunk;
|
|
135
|
+
if (full) await telegramAdapter.sendMessage(message.threadId, full);
|
|
265
136
|
}
|
|
266
137
|
} catch (error) {
|
|
267
|
-
console.error('Error
|
|
138
|
+
console.error('[Telegram] Error:', error);
|
|
268
139
|
await telegramAdapter.sendTyping(message.threadId, false);
|
|
269
|
-
await telegramAdapter.sendMessage(message.threadId, '❌
|
|
140
|
+
await telegramAdapter.sendMessage(message.threadId, '❌ 处理失败');
|
|
270
141
|
}
|
|
271
142
|
});
|
|
272
|
-
|
|
273
|
-
console.log('🚀
|
|
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
|
|
143
|
+
|
|
144
|
+
console.log('🚀 Telegram bot ready');
|
|
287
145
|
await new Promise(() => {});
|
|
288
146
|
}
|
package/dist/weixin/bot.js
CHANGED
|
@@ -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
|
-
|
|
174
|
-
|
|
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
|
|
package/dist/weixin/commands.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { detectCommand, COMMAND_ALIASES } from '../core/router.js';
|
|
1
|
+
import { detectCommand, COMMAND_ALIASES, getHelpText, DEMO_RESPONSES, setDemoMode, isDemoMode } 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,37 +91,20 @@ 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;
|
|
96
|
+
case 'tutorial': {
|
|
97
|
+
const { TUTORIAL_STEPS } = await import('../core/router.js');
|
|
98
|
+
const stepNum = parseInt(arg, 10);
|
|
99
|
+
const step = !isNaN(stepNum) && stepNum >= 1 && stepNum <= TUTORIAL_STEPS.length ? stepNum : 1;
|
|
100
|
+
const s = TUTORIAL_STEPS[step - 1];
|
|
101
|
+
let msg = `📚 教程 · 第 ${s.step}/${TUTORIAL_STEPS.length} 步\n━━━━━━━━━━━━━━━━\n\n${s.title}\n\n${s.desc}\n\n`;
|
|
102
|
+
if (s.action) msg += `👉 ${s.action}`;
|
|
103
|
+
msg += `\n\n回复 /tutorial${step < TUTORIAL_STEPS.length ? ` 继续第${step + 1}步` : ''} 进入下一步`;
|
|
104
|
+
const msgs = splitMessage(msg);
|
|
105
|
+
for (const m of msgs) await adapter.reply(ctx.threadId, m);
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
125
108
|
case 'status': {
|
|
126
109
|
const connected = await checkConnection();
|
|
127
110
|
const running = session.taskStartTime ? Math.round((Date.now() - session.taskStartTime) / 1000) : 0;
|
|
@@ -489,17 +472,6 @@ async function handleCommand(adapter, ctx, command, arg, openCodeSessions) {
|
|
|
489
472
|
return true;
|
|
490
473
|
}
|
|
491
474
|
|
|
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
475
|
case 'upload': {
|
|
504
476
|
const projectDir = session.projectDir || globalThis.__autoProjectDir;
|
|
505
477
|
|
|
@@ -738,6 +710,21 @@ async function handleCommand(adapter, ctx, command, arg, openCodeSessions) {
|
|
|
738
710
|
|
|
739
711
|
|
|
740
712
|
|
|
713
|
+
case 'demo': {
|
|
714
|
+
const argText = (arg || '').trim().toLowerCase();
|
|
715
|
+
if (argText === 'off' || argText === 'exit' || argText === 'stop') {
|
|
716
|
+
setDemoMode(ctx.threadId, false);
|
|
717
|
+
await adapter.reply(ctx.threadId, '⏹️ 已退出沙箱模式');
|
|
718
|
+
return true;
|
|
719
|
+
}
|
|
720
|
+
setDemoMode(ctx.threadId, true);
|
|
721
|
+
let msg = '🎮 沙箱模式已启动\n\n在此模式下所有命令返回模拟输出,无需连接 OpenCode。\n\n';
|
|
722
|
+
msg += '试试发送: /help /status /model /agents /loop /copy\n';
|
|
723
|
+
msg += '发送 /demo off 退出';
|
|
724
|
+
await adapter.reply(ctx.threadId, msg);
|
|
725
|
+
return true;
|
|
726
|
+
}
|
|
727
|
+
|
|
741
728
|
case 'diagnose': {
|
|
742
729
|
const { checkConnection } = await import('../opencode/client.js');
|
|
743
730
|
const diag = ['🔍 诊断报告\n'];
|