agentp 0.8.0 → 0.9.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/AGENTS.md +31 -9
- package/README.md +44 -9
- package/bin/agentp +142 -28
- package/bin/ocmux +24 -6
- package/bin/tgagentp +219 -68
- package/debugging_tg_integration.md +5594 -0
- package/lib/opencode.js +45 -9
- package/package.json +1 -1
- package/tgagentp.log +2156 -0
package/bin/tgagentp
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
const http = require('http');
|
|
3
4
|
const https = require('https');
|
|
4
5
|
const fs = require('fs');
|
|
5
6
|
const path = require('path');
|
|
@@ -15,8 +16,39 @@ const log = {
|
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
const STARTUP_CHAT_FILE = '/tmp/tgagentp-startup-chat';
|
|
19
|
+
const TGAGENTP_PORT = parseInt(process.env.TGAGENTP_PORT || '0', 10);
|
|
20
|
+
const PORT_FILE = '/tmp/tgagentp-port';
|
|
21
|
+
const TGAGENTP_DEBOUNCE_MS = parseInt(process.env.TGAGENTP_DEBOUNCE_MS || '5000', 10);
|
|
18
22
|
const { listServers, activateServer } = require('../lib/ocmux.js');
|
|
19
23
|
|
|
24
|
+
// Agentp gateway message queue, keyed by server URL
|
|
25
|
+
const agentpQueues = {};
|
|
26
|
+
|
|
27
|
+
function validateAgentpAuth(req) {
|
|
28
|
+
const password = process.env.OPENCODE_SERVER_PASSWORD;
|
|
29
|
+
if (!password) return true;
|
|
30
|
+
const username = process.env.OPENCODE_SERVER_USERNAME || 'opencode';
|
|
31
|
+
const expected = 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64');
|
|
32
|
+
return req.headers.authorization === expected;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function flushAgentpQueue(server, token, chatId) {
|
|
36
|
+
if (!chatId) return;
|
|
37
|
+
const q = agentpQueues[server];
|
|
38
|
+
if (!q || q.messages.length === 0) return;
|
|
39
|
+
const msgs = q.messages.splice(0);
|
|
40
|
+
q.notified = false;
|
|
41
|
+
if (q.notifyTimer) { clearTimeout(q.notifyTimer); q.notifyTimer = null; }
|
|
42
|
+
sendLongMessage(token, chatId,
|
|
43
|
+
`📨 Delivering ${msgs.length} queued agentp message(s):`)
|
|
44
|
+
.then(() => {
|
|
45
|
+
for (const msg of msgs) {
|
|
46
|
+
sendLongMessage(token, chatId, msg.trim()).catch(() => {});
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
.catch(() => {});
|
|
50
|
+
}
|
|
51
|
+
|
|
20
52
|
// ---------------------------------------------------------------------------
|
|
21
53
|
// Telegram Bot API helpers
|
|
22
54
|
// ---------------------------------------------------------------------------
|
|
@@ -72,7 +104,7 @@ function convertMarkdownToHtml(text) {
|
|
|
72
104
|
|
|
73
105
|
return parts.map((part, i) => {
|
|
74
106
|
if (i % 2 === 1) {
|
|
75
|
-
// Code block
|
|
107
|
+
// Code block ─ strip language tag, escape entities, wrap in <pre>
|
|
76
108
|
const nl = part.indexOf('\n');
|
|
77
109
|
const inner = nl >= 0 ? part.slice(nl + 1) : part;
|
|
78
110
|
return '<pre>' + inner
|
|
@@ -207,25 +239,25 @@ async function cmdHelp(token, chatId, args) {
|
|
|
207
239
|
const topic = args[0].toLowerCase();
|
|
208
240
|
if (topic === 'servers') {
|
|
209
241
|
await sendLongMessage(token, chatId,
|
|
210
|
-
'`/servers`
|
|
211
|
-
'`/servers switch <name>`
|
|
242
|
+
'`/servers` ─ list all opencode servers (ocmux sessions)\n' +
|
|
243
|
+
'`/servers switch <name>` ─ switch target server\n\n' +
|
|
212
244
|
'`<name>` can be a full path or basename of the project directory.');
|
|
213
245
|
return;
|
|
214
246
|
}
|
|
215
247
|
if (topic === 'sessions') {
|
|
216
248
|
await sendLongMessage(token, chatId,
|
|
217
|
-
'`/sessions`
|
|
218
|
-
'`/sessions switch <number-or-name>`
|
|
219
|
-
'`/sessions new [name]`
|
|
220
|
-
'`/sessions rename <name>`
|
|
249
|
+
'`/sessions` ─ list active sessions (numbered, newest first, max 50)\n' +
|
|
250
|
+
'`/sessions switch <number-or-name>` ─ send messages to a specific session\n' +
|
|
251
|
+
'`/sessions new [name]` ─ create a new session\n' +
|
|
252
|
+
'`/sessions rename <name>` ─ rename the active session\n\n' +
|
|
221
253
|
'Switch by number (from the list) or by case-insensitive partial name match.\n' +
|
|
222
254
|
'Use `/sessions` to find session numbers.');
|
|
223
255
|
return;
|
|
224
256
|
}
|
|
225
257
|
if (topic === 'shutdown') {
|
|
226
258
|
await sendLongMessage(token, chatId,
|
|
227
|
-
'`/shutdown`
|
|
228
|
-
'`/shutdown force`
|
|
259
|
+
'`/shutdown` ─ stop tgagentp process\n' +
|
|
260
|
+
'`/shutdown force` ─ force shutdown even if busy (in-flight work will be lost)\n\n' +
|
|
229
261
|
'Only available when tgagentp is started with `--dev` flag.\n' +
|
|
230
262
|
'Useful when running tgagentp in a restart loop:\n' +
|
|
231
263
|
'`while true; do tgagentp --dev; done`');
|
|
@@ -233,32 +265,32 @@ async function cmdHelp(token, chatId, args) {
|
|
|
233
265
|
}
|
|
234
266
|
if (topic === 'agents') {
|
|
235
267
|
await sendLongMessage(token, chatId,
|
|
236
|
-
'`/agents`
|
|
237
|
-
'`/agents switch <name>`
|
|
268
|
+
'`/agents` ─ list primary agents (with ▶ marker for the active one).\n' +
|
|
269
|
+
'`/agents switch <name>` ─ switch the active agent. Subsequent messages will use this agent.\n\n' +
|
|
238
270
|
'Agent switching requires an active session (`/sessions switch` or `/sessions new`).');
|
|
239
271
|
return;
|
|
240
272
|
}
|
|
241
273
|
if (topic === 'session') {
|
|
242
274
|
await sendLongMessage(token, chatId,
|
|
243
275
|
'`/session` is an alias for `/sessions`.\n\n' +
|
|
244
|
-
'`/sessions`
|
|
245
|
-
'`/sessions switch <number-or-name>`
|
|
246
|
-
'`/sessions new [name]`
|
|
247
|
-
'`/sessions rename <name>`
|
|
276
|
+
'`/sessions` ─ list active sessions\n' +
|
|
277
|
+
'`/sessions switch <number-or-name>` ─ switch to a session\n' +
|
|
278
|
+
'`/sessions new [name]` ─ create a new session\n' +
|
|
279
|
+
'`/sessions rename <name>` ─ rename the active session');
|
|
248
280
|
return;
|
|
249
281
|
}
|
|
250
282
|
if (topic === 'models') {
|
|
251
283
|
await sendLongMessage(token, chatId,
|
|
252
|
-
'`/models`
|
|
284
|
+
'`/models` ─ list all providers and their models.\n' +
|
|
253
285
|
'Shows provider name, connection status, default model, and up to 5 models per provider\n' +
|
|
254
286
|
'with status, context limit, and input/output costs.');
|
|
255
287
|
return;
|
|
256
288
|
}
|
|
257
289
|
if (topic === 'allow' || topic === 'reject' || topic === 'always') {
|
|
258
290
|
await sendLongMessage(token, chatId,
|
|
259
|
-
'`/allow`
|
|
260
|
-
'`/reject`
|
|
261
|
-
'`/always`
|
|
291
|
+
'`/allow` ─ approve a permission request once\n' +
|
|
292
|
+
'`/reject` ─ deny a permission request\n' +
|
|
293
|
+
'`/always` ─ approve and remember for the session\n\n' +
|
|
262
294
|
'These commands respond to OpenCode permission prompts forwarded by tgagentp.\n' +
|
|
263
295
|
'When OpenCode needs approval (e.g. to run a shell command), tgagentp sends a\n' +
|
|
264
296
|
'🔐 notification to the chat. Use one of these commands to answer.');
|
|
@@ -266,10 +298,10 @@ async function cmdHelp(token, chatId, args) {
|
|
|
266
298
|
}
|
|
267
299
|
if (topic === 'think') {
|
|
268
300
|
await sendLongMessage(token, chatId,
|
|
269
|
-
'`/think`
|
|
270
|
-
'`/think on`
|
|
271
|
-
'`/think off`
|
|
272
|
-
'`/think switch`
|
|
301
|
+
'`/think` ─ toggle thinking message forwarding\n' +
|
|
302
|
+
'`/think on` ─ forward model thinking to the chat in real-time\n' +
|
|
303
|
+
'`/think off` ─ stop forwarding thinking messages (default)\n' +
|
|
304
|
+
'`/think switch` ─ toggle current state\n\n' +
|
|
273
305
|
'When enabled, the model\'s internal reasoning steps are sent to the chat\n' +
|
|
274
306
|
'as they are generated, prefixed with 💭.\n' +
|
|
275
307
|
'Can also be enabled at startup with `--think` flag.');
|
|
@@ -280,23 +312,23 @@ async function cmdHelp(token, chatId, args) {
|
|
|
280
312
|
return;
|
|
281
313
|
}
|
|
282
314
|
await sendLongMessage(token, chatId,
|
|
283
|
-
'`/help`
|
|
284
|
-
'`/help <topic>`
|
|
285
|
-
'`/status`
|
|
286
|
-
'`/cancel`
|
|
287
|
-
'`/servers`
|
|
288
|
-
'`/servers switch <name>`
|
|
289
|
-
'`/sessions`
|
|
290
|
-
'`/sessions switch <number-or-name>`
|
|
291
|
-
'`/sessions new [name]`
|
|
292
|
-
'`/sessions rename <name>`
|
|
293
|
-
'`/session`
|
|
294
|
-
'`/agents`
|
|
295
|
-
'`/agents switch <name>`
|
|
296
|
-
'`/models`
|
|
297
|
-
'`/think`
|
|
298
|
-
'`/allow` / `/reject` / `/always`
|
|
299
|
-
'`/shutdown`
|
|
315
|
+
'`/help` ─ show this message\n' +
|
|
316
|
+
'`/help <topic>` ─ detailed help\n' +
|
|
317
|
+
'`/status` ─ show current server and session\n' +
|
|
318
|
+
'`/cancel` ─ cancel the running prompt\n' +
|
|
319
|
+
'`/servers` ─ list running opencode servers\n' +
|
|
320
|
+
'`/servers switch <name>` ─ switch to a different server\n' +
|
|
321
|
+
'`/sessions` ─ list/create/switch/rename sessions\n' +
|
|
322
|
+
'`/sessions switch <number-or-name>` ─ switch to a session\n' +
|
|
323
|
+
'`/sessions new [name]` ─ create a new session\n' +
|
|
324
|
+
'`/sessions rename <name>` ─ rename the active session\n' +
|
|
325
|
+
'`/session` ─ alias for `/sessions`\n' +
|
|
326
|
+
'`/agents` ─ list available agents\n' +
|
|
327
|
+
'`/agents switch <name>` ─ switch active agent\n' +
|
|
328
|
+
'`/models` ─ list providers and models\n' +
|
|
329
|
+
'`/think` ─ toggle thinking message forwarding\n' +
|
|
330
|
+
'`/allow` / `/reject` / `/always` ─ respond to permission requests\n' +
|
|
331
|
+
'`/shutdown` ─ stop tgagentp (only with `--dev` flag)\n\n' +
|
|
300
332
|
'Everything else is forwarded to the active server as `[Telegram] <text>`.');
|
|
301
333
|
}
|
|
302
334
|
|
|
@@ -493,10 +525,10 @@ async function cmdSessions(token, chatId, args, setServer, setActiveSession, ser
|
|
|
493
525
|
await sendLongMessage(token, chatId,
|
|
494
526
|
`Unknown subcommand: /sessions ${sub}\n\n` +
|
|
495
527
|
'Usage:\n' +
|
|
496
|
-
' `/sessions`
|
|
497
|
-
' `/sessions switch <number-or-name>`
|
|
498
|
-
' `/sessions new [name]`
|
|
499
|
-
' `/sessions rename <name>`
|
|
528
|
+
' `/sessions` ─ list sessions\n' +
|
|
529
|
+
' `/sessions switch <number-or-name>` ─ switch to a session\n' +
|
|
530
|
+
' `/sessions new [name]` ─ create a new session\n' +
|
|
531
|
+
' `/sessions rename <name>` ─ rename the active session');
|
|
500
532
|
return;
|
|
501
533
|
}
|
|
502
534
|
|
|
@@ -596,10 +628,14 @@ async function cmdAgents(token, chatId, args, setServer, setActiveSession, serve
|
|
|
596
628
|
}
|
|
597
629
|
const target = matches[0];
|
|
598
630
|
getState(server).activeAgentName = target.name;
|
|
599
|
-
const
|
|
600
|
-
|
|
631
|
+
const st = getState(server);
|
|
632
|
+
if (st.activeSessionId) {
|
|
633
|
+
updateSession(server, st.activeSessionId, undefined, target.name);
|
|
634
|
+
selectSession(server, st.activeSessionId);
|
|
635
|
+
}
|
|
636
|
+
const model = target.model ? ` ─ \`${target.model.providerID}/${target.model.modelID}\`` : '';
|
|
601
637
|
await sendLongMessage(token, chatId,
|
|
602
|
-
`Switched to agent: **${target.name}**${
|
|
638
|
+
`Switched to agent: **${target.name}**${model}\nSubsequent messages will use this agent.`);
|
|
603
639
|
} catch (err) {
|
|
604
640
|
await sendLongMessage(token, chatId, `Error switching agent: ${err.message}`);
|
|
605
641
|
}
|
|
@@ -612,13 +648,19 @@ async function cmdAgents(token, chatId, args, setServer, setActiveSession, serve
|
|
|
612
648
|
await sendLongMessage(token, chatId, 'No agents available.');
|
|
613
649
|
return;
|
|
614
650
|
}
|
|
615
|
-
const
|
|
616
|
-
|
|
617
|
-
|
|
651
|
+
const primary = agents.filter(a => a.mode === 'primary');
|
|
652
|
+
if (primary.length === 0) {
|
|
653
|
+
await sendLongMessage(token, chatId, 'No primary agents available.');
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
const activeAgentName = getState(server).activeAgentName;
|
|
657
|
+
const lines = primary.map(a => {
|
|
658
|
+
const bullet = a.name === activeAgentName ? '▶ ' : ' · ';
|
|
659
|
+
const model = a.model ? ` ─ \`${a.model.providerID}/${a.model.modelID}\`` : '';
|
|
618
660
|
const desc = a.description ? `. ${a.description}` : '';
|
|
619
|
-
return '**' + a.name + '**' +
|
|
661
|
+
return bullet + '**' + a.name + '**' + model + desc;
|
|
620
662
|
});
|
|
621
|
-
await sendLongMessage(token, chatId, '
|
|
663
|
+
await sendLongMessage(token, chatId, 'Primary agents:\n' + lines.join('\n'));
|
|
622
664
|
} catch (err) {
|
|
623
665
|
await sendLongMessage(token, chatId, `Error listing agents: ${err.message}`);
|
|
624
666
|
}
|
|
@@ -664,6 +706,88 @@ async function cmdModels(token, chatId, args, setServer, setActiveSession, serve
|
|
|
664
706
|
|
|
665
707
|
const PERM_RESPONSE_HELP = ' No pending permission. Use `/status` to check the current state.';
|
|
666
708
|
|
|
709
|
+
// ---------------------------------------------------------------------------
|
|
710
|
+
// Agentp gateway ─ tiny HTTP server for agentp --tg
|
|
711
|
+
// ---------------------------------------------------------------------------
|
|
712
|
+
|
|
713
|
+
function startAgentpGateway(token, chatIdResolver, serverBaseResolver) {
|
|
714
|
+
return new Promise((resolve) => {
|
|
715
|
+
const server = http.createServer((req, res) => {
|
|
716
|
+
try {
|
|
717
|
+
if (req.method !== 'POST' || req.url !== '/send') {
|
|
718
|
+
res.writeHead(405);
|
|
719
|
+
res.end('Method Not Allowed');
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
if (!validateAgentpAuth(req)) {
|
|
723
|
+
res.writeHead(401);
|
|
724
|
+
res.end('Unauthorized');
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
let body = '';
|
|
728
|
+
req.on('data', (chunk) => { body += chunk; });
|
|
729
|
+
req.on('end', () => {
|
|
730
|
+
try {
|
|
731
|
+
let data;
|
|
732
|
+
try { data = JSON.parse(body); } catch {
|
|
733
|
+
res.writeHead(400);
|
|
734
|
+
res.end('Bad Request');
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
const { text, server } = data;
|
|
738
|
+
if (typeof text !== 'string' || typeof server !== 'string') {
|
|
739
|
+
res.writeHead(400);
|
|
740
|
+
res.end('Bad Request: text and server are required');
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
res.writeHead(200);
|
|
744
|
+
res.end('OK');
|
|
745
|
+
|
|
746
|
+
const chatId = chatIdResolver();
|
|
747
|
+
if (!chatId) return;
|
|
748
|
+
const activeUrl = normalizeUrl(serverBaseResolver());
|
|
749
|
+
const targetUrl = normalizeUrl(server);
|
|
750
|
+
|
|
751
|
+
if (targetUrl === activeUrl) {
|
|
752
|
+
sendLongMessage(token, chatId, text.trim()).catch(() => {});
|
|
753
|
+
} else {
|
|
754
|
+
if (!agentpQueues[server]) {
|
|
755
|
+
agentpQueues[server] = { messages: [], notifyTimer: null, notified: false };
|
|
756
|
+
}
|
|
757
|
+
const q = agentpQueues[server];
|
|
758
|
+
q.messages.push(text);
|
|
759
|
+
if (!q.notifyTimer) {
|
|
760
|
+
q.notifyTimer = setTimeout(() => {
|
|
761
|
+
q.notifyTimer = null;
|
|
762
|
+
if (q.messages.length > 0 && !q.notified) {
|
|
763
|
+
q.notified = true;
|
|
764
|
+
sendLongMessage(token, chatId,
|
|
765
|
+
`📥 ${q.messages.length} agentp message(s) queued for server \`${server}\`. Switch servers to view them.`)
|
|
766
|
+
.catch(() => {});
|
|
767
|
+
}
|
|
768
|
+
}, TGAGENTP_DEBOUNCE_MS);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
} catch (err) {
|
|
772
|
+
log.error(`Agentp gateway handler error: ${err.message}`);
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
} catch (err) {
|
|
776
|
+
log.error(`Agentp gateway handler error: ${err.message}`);
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
server.on('error', (err) => {
|
|
780
|
+
log.error(`Agentp gateway server error: ${err.message}`);
|
|
781
|
+
});
|
|
782
|
+
server.listen(TGAGENTP_PORT, '127.0.0.1', () => {
|
|
783
|
+
const port = server.address().port;
|
|
784
|
+
try { fs.writeFileSync(PORT_FILE, String(port)); } catch (_) {}
|
|
785
|
+
log.info(`Agentp gateway listening on port ${port}`);
|
|
786
|
+
resolve(port);
|
|
787
|
+
});
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
|
|
667
791
|
async function cmdAllow(token, chatId, args, setServer, setActiveSession, server) {
|
|
668
792
|
const st = getState(server);
|
|
669
793
|
if (!st.pendingPermission) {
|
|
@@ -797,10 +921,15 @@ async function main() {
|
|
|
797
921
|
console.error('');
|
|
798
922
|
console.error('Environment variables:');
|
|
799
923
|
console.error(' TELEGRAM_BOT_TOKEN Telegram bot token (required)');
|
|
800
|
-
console.error(' OPENCODE_SERVER_PASSWORD OpenCode server password (optional)');
|
|
924
|
+
console.error(' OPENCODE_SERVER_PASSWORD OpenCode server password (optional, also used for agentp gateway auth)');
|
|
801
925
|
console.error(' OPENCODE_SERVER_USERNAME OpenCode server username (optional, default: opencode)');
|
|
802
926
|
console.error(' TGAGENTP_ALLOWED_CHAT_IDS Comma-separated Telegram chat IDs (optional)');
|
|
803
927
|
console.error(' Example: "123456,-789012"');
|
|
928
|
+
console.error(' TGAGENTP_PORT Agentp gateway listen port (optional, default: random)');
|
|
929
|
+
console.error(' TGAGENTP_DEBOUNCE_MS Debounce interval for queued agentp messages (optional, default: 5000)');
|
|
930
|
+
console.error('');
|
|
931
|
+
console.error('Also starts an agentp gateway (tiny HTTP server on localhost) that');
|
|
932
|
+
console.error('accepts POST /send from agentp --tg and forwards answers to Telegram.');
|
|
804
933
|
console.error('');
|
|
805
934
|
console.error('See also: agentp, ocmux');
|
|
806
935
|
console.error('');
|
|
@@ -859,10 +988,19 @@ async function main() {
|
|
|
859
988
|
|
|
860
989
|
log.info('Waiting for Telegram messages... (Ctrl+C to stop)');
|
|
861
990
|
|
|
991
|
+
// Start agentp gateway (tiny HTTP server for agentp --tg)
|
|
992
|
+
const chatIdResolver = () => lockedChatId;
|
|
993
|
+
const serverBaseResolver = () => serverBase;
|
|
994
|
+
startAgentpGateway(token, chatIdResolver, serverBaseResolver);
|
|
995
|
+
|
|
862
996
|
const setServer = async (url) => {
|
|
863
997
|
serverBase = url;
|
|
864
998
|
getState(url);
|
|
865
999
|
log.info(`Switched to server: ${url}`);
|
|
1000
|
+
|
|
1001
|
+
// Flush agentp queue for the new active server
|
|
1002
|
+
flushAgentpQueue(url, token, lockedChatId);
|
|
1003
|
+
|
|
866
1004
|
const [discovered, agents] = await Promise.all([
|
|
867
1005
|
discoverActiveSession(url),
|
|
868
1006
|
listAgents(url).catch(() => []),
|
|
@@ -894,18 +1032,27 @@ async function main() {
|
|
|
894
1032
|
const st = getState(url);
|
|
895
1033
|
const isCancelled = () => getState(url).taskId !== myTaskId;
|
|
896
1034
|
const cancelRef = { current: null };
|
|
1035
|
+
const sessionCancelRef = { current: null };
|
|
897
1036
|
st._cancelRef = cancelRef;
|
|
1037
|
+
st._sessionReq = sessionCancelRef;
|
|
898
1038
|
try {
|
|
899
1039
|
let answer;
|
|
900
1040
|
if (useSession) {
|
|
901
|
-
//
|
|
1041
|
+
// Dual approach:
|
|
1042
|
+
// 1. SSE for permissions/thinking events (connected before sending)
|
|
1043
|
+
// 2. Synchronous sendToSession (POST /session/:id/message) for reliable answer
|
|
902
1044
|
const notifiedPerms = new Set();
|
|
903
1045
|
let thinkingBuf = '';
|
|
904
|
-
|
|
1046
|
+
let sseConnected;
|
|
1047
|
+
const onConnected = Promise.race([
|
|
1048
|
+
new Promise((resolve) => { sseConnected = resolve; }),
|
|
1049
|
+
new Promise((resolve) => setTimeout(resolve, 5000)),
|
|
1050
|
+
]);
|
|
1051
|
+
listenForSessionEvents(url, sessionId, {
|
|
905
1052
|
onText: null,
|
|
1053
|
+
onConnected: sseConnected,
|
|
906
1054
|
onThinking: (chunk) => {
|
|
907
1055
|
if (!getState(url).thinkingEnabled) { thinkingBuf = ''; return; }
|
|
908
|
-
// Forward thinking chunks in real-time
|
|
909
1056
|
thinkingBuf += chunk;
|
|
910
1057
|
if (thinkingBuf.length >= 50 || chunk.endsWith('\n')) {
|
|
911
1058
|
const html = '💭 ' + thinkingBuf.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
@@ -942,23 +1089,18 @@ async function main() {
|
|
|
942
1089
|
log.debug(` → permission_replied: clearing pendingPermission`);
|
|
943
1090
|
getState(url).pendingPermission = null;
|
|
944
1091
|
},
|
|
945
|
-
}, cancelRef);
|
|
946
|
-
//
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
} catch (err) {
|
|
950
|
-
// Abort SSE listener on send failure to avoid resource leak
|
|
951
|
-
if (cancelRef.current) { cancelRef.current.destroy(); cancelRef.current = null; }
|
|
952
|
-
throw err;
|
|
953
|
-
}
|
|
954
|
-
// Wait for the SSE listener to collect the full answer
|
|
955
|
-
answer = await ssePromise;
|
|
1092
|
+
}, cancelRef).catch(() => {});
|
|
1093
|
+
// Wait for SSE to connect, then send synchronously
|
|
1094
|
+
await onConnected;
|
|
1095
|
+
answer = await sendToSession(url, sessionId, `[Telegram] ${text}`, st.activeAgentName, sessionCancelRef);
|
|
956
1096
|
// Flush any remaining thinking buffer
|
|
957
1097
|
if (thinkingBuf) {
|
|
958
1098
|
const html = '💭 ' + thinkingBuf.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
959
1099
|
sendLongMessage(token, chatId, html, replyTo).catch(() => {});
|
|
960
1100
|
thinkingBuf = '';
|
|
961
1101
|
}
|
|
1102
|
+
// Clean up SSE listener
|
|
1103
|
+
if (cancelRef.current) { cancelRef.current.destroy(); cancelRef.current = null; }
|
|
962
1104
|
} else {
|
|
963
1105
|
await sendText(url, `[Telegram] ${text}`);
|
|
964
1106
|
answer = await listenForFinalAnswer(url, null, cancelRef);
|
|
@@ -978,6 +1120,8 @@ async function main() {
|
|
|
978
1120
|
await sendLongMessage(token, chatId, `✅ Server \`${url}\` finished processing. Switch back to see the response.`, replyTo);
|
|
979
1121
|
}
|
|
980
1122
|
} catch (err) {
|
|
1123
|
+
// Clean up SSE listener if it's still active
|
|
1124
|
+
if (cancelRef.current) { cancelRef.current.destroy(); cancelRef.current = null; }
|
|
981
1125
|
if (isCancelled()) {
|
|
982
1126
|
log.debug(` → cancelled, ignoring error from ${url}: ${err.message}`);
|
|
983
1127
|
st.pendingPermission = null;
|
|
@@ -990,6 +1134,7 @@ async function main() {
|
|
|
990
1134
|
} finally {
|
|
991
1135
|
st.pendingPermission = null;
|
|
992
1136
|
if (st._cancelRef === cancelRef) st._cancelRef = null;
|
|
1137
|
+
if (st._sessionReq === sessionCancelRef) st._sessionReq = null;
|
|
993
1138
|
if (!isCancelled()) {
|
|
994
1139
|
getState(url).busy = false;
|
|
995
1140
|
}
|
|
@@ -1009,6 +1154,11 @@ async function main() {
|
|
|
1009
1154
|
st._cancelRef.current.destroy();
|
|
1010
1155
|
st._cancelRef.current = null;
|
|
1011
1156
|
}
|
|
1157
|
+
// Abort the synchronous session request if in progress
|
|
1158
|
+
if (st._sessionReq && st._sessionReq.current) {
|
|
1159
|
+
st._sessionReq.current.destroy();
|
|
1160
|
+
st._sessionReq.current = null;
|
|
1161
|
+
}
|
|
1012
1162
|
await sendLongMessage(token, chatId, '✅ Cancelled.');
|
|
1013
1163
|
log.debug(` → /cancel on ${serverBase}`);
|
|
1014
1164
|
}
|
|
@@ -1062,6 +1212,7 @@ async function main() {
|
|
|
1062
1212
|
lines.push('**Session:** ' + sessionLabel(st));
|
|
1063
1213
|
lines.push('**Agent:** ' + (st.activeAgentName ? '`' + st.activeAgentName + '`' : '*default*'));
|
|
1064
1214
|
try { await sendLongMessage(token, savedChatId, lines.join('\n')); } catch (_) {}
|
|
1215
|
+
lockedChatId = savedChatId;
|
|
1065
1216
|
try { fs.unlinkSync(STARTUP_CHAT_FILE); } catch (_) {}
|
|
1066
1217
|
}
|
|
1067
1218
|
|
|
@@ -1139,7 +1290,7 @@ async function main() {
|
|
|
1139
1290
|
continue;
|
|
1140
1291
|
}
|
|
1141
1292
|
|
|
1142
|
-
// Non-command message
|
|
1293
|
+
// Non-command message ─ process in background
|
|
1143
1294
|
const st = getState(serverBase);
|
|
1144
1295
|
if (st.busy) {
|
|
1145
1296
|
await sendLongMessage(token, chatId, '⏳ Server is busy processing a previous message. Please wait for it to finish, or use /cancel to cancel it.');
|