@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.
- package/README.md +78 -8
- package/dist/core/auth.js +41 -108
- package/dist/core/router.js +152 -60
- package/dist/feishu/commands.js +2 -35
- package/dist/feishu/handler.js +17 -26
- package/dist/opencode/client.js +47 -78
- package/dist/telegram/adapter.js +55 -0
- package/dist/telegram/bot.js +48 -207
- package/dist/weixin/bot.js +12 -2
- package/dist/weixin/commands.js +2 -42
- package/dist/weixin/handler.js +80 -107
- package/package.json +2 -3
package/dist/opencode/client.js
CHANGED
|
@@ -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 -
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
-
//
|
|
460
|
-
let startIdx = 0;
|
|
428
|
+
// 收集所有新的 assistant 回复(累加,不丢内容)
|
|
461
429
|
if (lastMsgId) {
|
|
462
430
|
const idx = messages.findIndex(m => m.info?.id === lastMsgId);
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
-
//
|
|
490
|
-
if (responseText &&
|
|
491
|
-
|
|
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();
|
package/dist/telegram/bot.js
CHANGED
|
@@ -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('
|
|
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]
|
|
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, '❌
|
|
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
|
|
230
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
261
|
-
for await (const chunk of result)
|
|
262
|
-
|
|
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
|
|
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('🚀
|
|
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
|
}
|
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 } 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
|
|