@yvhitxcel/opencode-remote 0.15.0 → 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.
@@ -2,46 +2,36 @@ import { getOrCreateSession } from '../core/session.js';
2
2
  import { splitMessage } from '../core/notifications.js';
3
3
  import { initOpenCode, createSession, sendMessage, checkConnection, resumeSession, shareSession } from '../opencode/client.js';
4
4
  import { isAuthorized, hasOwner } from '../core/auth.js';
5
- import { detectCommand } from '../core/router.js';
5
+ import { detectCommand, EXPERT_SYSTEM_PROMPT } from '../core/router.js';
6
6
  import { handleCommand, formatTimeAgo } from './commands.js';
7
7
  import { existsSync, readFileSync } from 'fs';
8
8
  import { join } from 'path';
9
9
 
10
- const EXPERT_SYSTEM_PROMPT = `你是一个专家角色扮演系统,严格按照 AGENTS.md 中的"专家点评系统"流程执行。
11
-
12
- 当用户输入包含触发词(z / 叫全部专家 / 叫所有专家 / 呼叫专家点评 / 专家点评 / 专家意见 / call all experts / expert review)时,启动专家评审。
13
-
14
- ## 规则
15
- - 严格遵循 AGENTS.md 中定义的 13 位角色和点评流程
16
- - 言辞必须苛刻犀利,不讨好不委婉
17
- - 不说客套话
18
- - 直接指出问题`;
19
-
20
10
  async function handleMessage(adapter, ctx, text, openCodeSessions) {
21
11
  const session = await getOrCreateSession(ctx.threadId, 'feishu');
22
12
 
13
+ const expertTriggers = ['z', 'Z', '叫全部专家', '叫所有专家', '呼叫专家点评', '专家点评', '专家意见', 'call all experts', 'expert review', '专家会诊', '团队评审', '代码审查', '全员review', 'review all', '请专家', '叫专家', '找专家'];
14
+ let expertPrompt = null;
15
+
23
16
  if (text.startsWith('/z')) {
24
17
  const arg = text.slice(2).trim();
25
18
  if (arg === 'off' || arg === 'reset' || arg === '关闭') {
26
- session.expertMode = false;
27
- session.systemPrompt = null;
28
- await adapter.reply(ctx.threadId, '⏹️ 专家模式已关闭');
19
+ await adapter.reply(ctx.threadId, '⏹️ 自定义 prompt 已清除');
29
20
  return;
30
21
  }
31
22
  if (arg) {
32
- session.expertMode = true;
33
- session.systemPrompt = arg;
34
- await adapter.reply(ctx.threadId, `✅ 自定义专家 prompt 已设置 (${arg.length}字)`);
35
- return;
36
- }
37
- if (!session.expertMode) {
38
- session.expertMode = true;
39
- session.systemPrompt = EXPERT_SYSTEM_PROMPT;
23
+ expertPrompt = arg;
24
+ } else {
25
+ expertPrompt = EXPERT_SYSTEM_PROMPT;
40
26
  }
41
- await adapter.reply(ctx.threadId, '✅ 专家模式已启动,直接发送你的问题\n/z off — 关闭\n/z <内容> — 自定义 prompt');
27
+ await forwardToOpenCode(adapter, ctx, text, openCodeSessions, session, expertPrompt);
42
28
  return;
43
29
  }
44
30
 
31
+ if (expertTriggers.some(t => text.trim().toLowerCase().includes(t))) {
32
+ expertPrompt = EXPERT_SYSTEM_PROMPT;
33
+ }
34
+
45
35
  const parsed = detectCommand(text);
46
36
  if (parsed) {
47
37
  await handleCommand(adapter, ctx, parsed.name, parsed.arg, openCodeSessions);
@@ -222,7 +212,7 @@ async function handleMessage(adapter, ctx, text, openCodeSessions) {
222
212
  await forwardToOpenCode(adapter, ctx, text, openCodeSessions, session);
223
213
  }
224
214
 
225
- async function forwardToOpenCode(adapter, ctx, text, openCodeSessions, session) {
215
+ async function forwardToOpenCode(adapter, ctx, text, openCodeSessions, session, expertPrompt) {
226
216
  await adapter.sendTypingIndicator(ctx.threadId);
227
217
  let openCodeSession = openCodeSessions.get(ctx.threadId);
228
218
  if (!openCodeSession) {
@@ -258,8 +248,8 @@ async function forwardToOpenCode(adapter, ctx, text, openCodeSessions, session)
258
248
  }
259
249
  }
260
250
 
261
- if (session.expertMode && session.systemPrompt) {
262
- scopedText = `${session.systemPrompt}\n\n${scopedText}`;
251
+ if (expertPrompt) {
252
+ scopedText = `${expertPrompt}\n\n${scopedText}`;
263
253
  }
264
254
 
265
255
  console.log(`📤 Forwarding to OpenCode: ${text.substring(0, 80)}...`);
@@ -268,6 +258,7 @@ async function forwardToOpenCode(adapter, ctx, text, openCodeSessions, session)
268
258
  let hasToolActivity = false;
269
259
  let toolCallCount = 0;
270
260
  let response = await sendMessage(openCodeSession, scopedText, {
261
+ idleThreshold: expertPrompt ? 30 : 10,
271
262
  onEvent: (event) => {
272
263
  if (event.type === 'tool.call') {
273
264
  const props = event.properties || {};
@@ -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
  }