agentp 0.11.11 → 1.12.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 +50 -7
- package/bin/agentp +272 -83
- package/bin/tgagentp +356 -276
- package/lib/ocmux.js +27 -9
- package/package.json +1 -1
package/bin/tgagentp
CHANGED
|
@@ -264,46 +264,80 @@ async function cmdHelp(token, chatId, args, chatState) {
|
|
|
264
264
|
const topic = args[0].toLowerCase();
|
|
265
265
|
if (topic === 'servers') {
|
|
266
266
|
await sendLongMessage(token, chatState,
|
|
267
|
-
'`/servers` ─ list
|
|
268
|
-
'
|
|
269
|
-
'
|
|
270
|
-
'
|
|
271
|
-
'
|
|
267
|
+
'`/servers` ─ list running servers\n' +
|
|
268
|
+
' • Shows alive and dead servers\n' +
|
|
269
|
+
' • Your server marked with ▶\n\n' +
|
|
270
|
+
'`/server <name>` ─ switch to a server by directory path or basename\n' +
|
|
271
|
+
'`/server --force <name>` ─ take over a server from another chat');
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (topic === 'agents') {
|
|
275
|
+
await sendLongMessage(token, chatState,
|
|
276
|
+
'`/agents` ─ list primary agents\n' +
|
|
277
|
+
' • Current agent marked with ▶\n' +
|
|
278
|
+
' • Shows model and description\n\n' +
|
|
279
|
+
'`/agent <name>` ─ switch to an agent');
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (topic === 'sessions') {
|
|
283
|
+
await sendLongMessage(token, chatState,
|
|
284
|
+
'`/sessions` ─ list sessions\n' +
|
|
285
|
+
' • Primary-agent sessions shown by default (includes subagent sessions to maintain context)\n' +
|
|
286
|
+
' • Active session marked with ▶\n' +
|
|
287
|
+
' • Grouped by recency: Today, this week, older months\n\n' +
|
|
288
|
+
'`/session <name-or-number>` ─ switch to a session by name or position\n' +
|
|
289
|
+
'`/session new [name]` ─ create a new session\n' +
|
|
290
|
+
'`/session rename <name>` ─ rename the active session');
|
|
272
291
|
return;
|
|
273
292
|
}
|
|
274
293
|
if (topic === 'sessions') {
|
|
275
294
|
await sendLongMessage(token, chatState,
|
|
276
295
|
'`/sessions` ─ list active sessions (numbered, newest first, max 50)\n' +
|
|
277
|
-
'`/
|
|
278
|
-
'`/
|
|
279
|
-
'`/
|
|
296
|
+
'`/session <name-or-number>` ─ switch to a session by name or position\n' +
|
|
297
|
+
'`/session new [name]` ─ create a new session\n' +
|
|
298
|
+
'`/session rename <name>` ─ rename the active session\n\n' +
|
|
280
299
|
'Switch by number (from the list) or by case-insensitive partial name match.\n' +
|
|
281
300
|
'Use `/sessions` to find session numbers.');
|
|
282
301
|
return;
|
|
283
302
|
}
|
|
284
|
-
if (topic === '
|
|
303
|
+
if (topic === 'servers') {
|
|
304
|
+
await sendLongMessage(token, chatState,
|
|
305
|
+
'`/servers` ─ list running servers\n' +
|
|
306
|
+
' • Shows alive and dead servers\n' +
|
|
307
|
+
' • Your server marked with ▶\n\n' +
|
|
308
|
+
'`/server <name>` ─ switch to a server by directory path or basename\n' +
|
|
309
|
+
'`/server --force <name>` ─ take over a server from another chat');
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (topic === 'server') {
|
|
285
313
|
await sendLongMessage(token, chatState,
|
|
286
|
-
'`/
|
|
287
|
-
'`/
|
|
288
|
-
'
|
|
289
|
-
'
|
|
290
|
-
'`while true; do tgagentp --dev; done`');
|
|
314
|
+
'`/server <name>` ─ switch to a server by directory path or basename.\n' +
|
|
315
|
+
'`/server --force <name>` ─ take over a server from another chat.\n\n' +
|
|
316
|
+
'Matches by full directory path, basename, or case-insensitive substring.\n' +
|
|
317
|
+
'Use `/servers` to see available servers.');
|
|
291
318
|
return;
|
|
292
319
|
}
|
|
293
320
|
if (topic === 'agents') {
|
|
294
321
|
await sendLongMessage(token, chatState,
|
|
295
322
|
'`/agents` ─ list primary agents (with ▶ marker for the active one).\n' +
|
|
296
|
-
'`/
|
|
297
|
-
'Agent switching requires an active session (`/
|
|
323
|
+
'`/agent <name>` ─ switch the active agent. Subsequent messages will use this agent.\n\n' +
|
|
324
|
+
'Agent switching requires an active session (`/session <name>` or `/session new`).');
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (topic === 'agent') {
|
|
328
|
+
await sendLongMessage(token, chatState,
|
|
329
|
+
'`/agent <name>` ─ switch the active agent.\n\n' +
|
|
330
|
+
'Matches by exact name first, then case-insensitive substring.\n' +
|
|
331
|
+
'The agent is synced to the server session via API.\n' +
|
|
332
|
+
'Use `/agents` to list available agents.');
|
|
298
333
|
return;
|
|
299
334
|
}
|
|
300
335
|
if (topic === 'session') {
|
|
301
336
|
await sendLongMessage(token, chatState,
|
|
302
|
-
'`/session
|
|
303
|
-
'`/
|
|
304
|
-
'`/
|
|
305
|
-
'`/sessions
|
|
306
|
-
'`/sessions rename <name>` ─ rename the active session');
|
|
337
|
+
'`/session <name-or-number>` ─ switch to a session by name or position\n' +
|
|
338
|
+
'`/session new [name]` ─ create a new session\n' +
|
|
339
|
+
'`/session rename <name>` ─ rename the active session\n\n' +
|
|
340
|
+
'Use `/sessions` to list available sessions with numbers.');
|
|
307
341
|
return;
|
|
308
342
|
}
|
|
309
343
|
if (topic === 'models') {
|
|
@@ -390,7 +424,7 @@ async function cmdHelp(token, chatId, args, chatState) {
|
|
|
390
424
|
if (topic === 'disconnect') {
|
|
391
425
|
await sendLongMessage(token, chatState,
|
|
392
426
|
'`/disconnect` ─ disconnect this chat from its server\n\n' +
|
|
393
|
-
'Clears the server connection for this chat. Use `/
|
|
427
|
+
'Clears the server connection for this chat. Use `/server <name>` to reconnect.');
|
|
394
428
|
return;
|
|
395
429
|
}
|
|
396
430
|
if (topic === 'status') {
|
|
@@ -453,7 +487,7 @@ async function cmdHelp(token, chatId, args, chatState) {
|
|
|
453
487
|
return;
|
|
454
488
|
}
|
|
455
489
|
await sendLongMessage(token, chatState,
|
|
456
|
-
'Unknown topic. Try `/help servers`, `/help sessions`, `/help agents`, `/help models`, `/help allow`, `/help think`, `/help record`, `/help queue`, `/help flush`, `/help note`, `/help comment`, `/help disconnect`, `/help serve`, `/help new`, `/help markdown`, or just `/help`.');
|
|
490
|
+
'Unknown topic. Try `/help servers`, `/help server`, `/help sessions`, `/help session`, `/help agents`, `/help agent`, `/help models`, `/help model`, `/help allow`, `/help think`, `/help record`, `/help queue`, `/help flush`, `/help note`, `/help comment`, `/help disconnect`, `/help serve`, `/help new`, `/help markdown`, or just `/help`.');
|
|
457
491
|
return;
|
|
458
492
|
}
|
|
459
493
|
await sendLongMessage(token, chatState,
|
|
@@ -462,16 +496,16 @@ async function cmdHelp(token, chatId, args, chatState) {
|
|
|
462
496
|
'`/status` ─ show current server and session\n' +
|
|
463
497
|
'`/cancel` ─ cancel the running prompt\n' +
|
|
464
498
|
'`/servers` ─ list running opencode servers\n' +
|
|
465
|
-
'`/
|
|
466
|
-
'`/force
|
|
467
|
-
'`/sessions` ─ list
|
|
468
|
-
'`/
|
|
469
|
-
'`/
|
|
470
|
-
'`/
|
|
471
|
-
'`/session` ─ alias for `/sessions`\n' +
|
|
499
|
+
'`/server <name>` ─ switch to a different server\n' +
|
|
500
|
+
'`/server --force <name>` ─ take over a server from another chat\n' +
|
|
501
|
+
'`/sessions` ─ list sessions\n' +
|
|
502
|
+
'`/session <name-or-number>` ─ switch to a session\n' +
|
|
503
|
+
'`/session new [name]` ─ create a new session\n' +
|
|
504
|
+
'`/session rename <name>` ─ rename the active session\n' +
|
|
472
505
|
'`/agents` ─ list available agents\n' +
|
|
473
|
-
'`/
|
|
506
|
+
'`/agent <name>` ─ switch active agent\n' +
|
|
474
507
|
'`/models` ─ list providers and models\n' +
|
|
508
|
+
'`/model <provider/model>` ─ switch session model\n' +
|
|
475
509
|
'`/think` ─ toggle thinking message forwarding\n' +
|
|
476
510
|
'`/record` ─ record Telegram conversation for agentp context\n' +
|
|
477
511
|
'`/queue <message>` ─ queue a message when server is busy\n' +
|
|
@@ -489,90 +523,18 @@ async function cmdHelp(token, chatId, args, chatState) {
|
|
|
489
523
|
'Everything else is forwarded to the active server as `[Telegram] <text>`.');
|
|
490
524
|
}
|
|
491
525
|
|
|
492
|
-
async function cmdForceSwitch(token, chatId, args, chatState, setServerForChat, setActiveSessionForChat) {
|
|
493
|
-
return cmdServers(token, chatId, ['switch', '--force', ...args], chatState, setServerForChat, setActiveSessionForChat);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
526
|
async function cmdServers(token, chatId, args, chatState, setServerForChat, setActiveSessionForChat) {
|
|
497
|
-
|
|
498
|
-
if (args.length > 0
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
'Usage: `/servers switch <name>`\n' +
|
|
505
|
-
'To force-switch (take over a server from another chat), use `/force-switch <name>`.\n' +
|
|
506
|
-
'`<name>` can be a full directory path or a basename.');
|
|
507
|
-
return;
|
|
508
|
-
}
|
|
509
|
-
const name = switchArgs.join(' ');
|
|
510
|
-
const servers = listServers();
|
|
511
|
-
if (servers.length === 0) {
|
|
512
|
-
await sendLongMessage(token, chatState, 'No servers running.');
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Phase 1: case-insensitive exact match on dir or basename
|
|
517
|
-
let matches = servers.filter(s => s.dir.toLowerCase() === name.toLowerCase());
|
|
518
|
-
if (matches.length === 0) {
|
|
519
|
-
matches = servers.filter(s => path.basename(s.dir).toLowerCase() === name.toLowerCase());
|
|
520
|
-
}
|
|
521
|
-
// Fallback: case-insensitive substring match
|
|
522
|
-
if (matches.length === 0) {
|
|
523
|
-
const lowerName = name.toLowerCase();
|
|
524
|
-
matches = servers.filter(s => s.dir.toLowerCase().includes(lowerName) || path.basename(s.dir).toLowerCase().includes(lowerName));
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
if (matches.length === 1) {
|
|
528
|
-
// Phase 1 found a single match — use it directly
|
|
529
|
-
} else if (matches.length === 0) {
|
|
530
|
-
await sendLongMessage(token, chatState, `No server found matching "${name}".`);
|
|
531
|
-
return;
|
|
532
|
-
} else {
|
|
533
|
-
// Phase 2: case-sensitive exact match to disambiguate
|
|
534
|
-
let exact = servers.filter(s => s.dir === name);
|
|
535
|
-
if (exact.length === 0) {
|
|
536
|
-
exact = servers.filter(s => path.basename(s.dir) === name);
|
|
537
|
-
}
|
|
538
|
-
if (exact.length === 1) {
|
|
539
|
-
matches = exact;
|
|
540
|
-
} else {
|
|
541
|
-
const list = matches.map(s => ` ${s.url} → ${s.dir}`).join('\n');
|
|
542
|
-
await sendLongMessage(token, chatState,
|
|
543
|
-
`Multiple matches for "${name}":\n${list}\n\nPlease be more specific.`);
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
527
|
+
// Backward compatibility: redirect old subcommands to singular commands
|
|
528
|
+
if (args.length > 0) {
|
|
529
|
+
const sub = args[0].toLowerCase();
|
|
530
|
+
if (sub === 'switch') {
|
|
531
|
+
const rest = args.slice(1).filter(a => a !== '--force');
|
|
532
|
+
const force = args.includes('--force');
|
|
533
|
+
return cmdServer(token, chatId, force ? ['--force', ...rest] : rest, chatState, setServerForChat, setActiveSessionForChat);
|
|
546
534
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
if (existing && existing.chatId !== chatState.chatId && !force) {
|
|
550
|
-
await sendLongMessage(token, chatState,
|
|
551
|
-
`⚠️ Server \`${target.url}\` is already connected to another chat.\n\n` +
|
|
552
|
-
`Use \`/servers force-switch ${name}\` to take over (the other chat will be disconnected).`);
|
|
553
|
-
return;
|
|
535
|
+
if (sub === 'force-switch') {
|
|
536
|
+
return cmdServer(token, chatId, ['--force', ...args.slice(1)], chatState, setServerForChat, setActiveSessionForChat);
|
|
554
537
|
}
|
|
555
|
-
if (existing && existing.chatId === chatState.chatId) {
|
|
556
|
-
// Already connected — re-run setup to refresh state
|
|
557
|
-
}
|
|
558
|
-
if (force && existing && existing.chatId !== chatState.chatId) {
|
|
559
|
-
// Reset the old owner's server and notify them
|
|
560
|
-
const oldState = getChatState(existing.chatId, existing.threadId);
|
|
561
|
-
oldState.serverBase = null;
|
|
562
|
-
sendLongMessage(token, oldState,
|
|
563
|
-
`⚠️ Server \`${target.url}\` has been taken over by another chat.\n\n` +
|
|
564
|
-
`Use \`/servers\` to see available servers and reconnect.`).catch(() => {});
|
|
565
|
-
}
|
|
566
|
-
const pending = await setServerForChat(chatState, target.url);
|
|
567
|
-
activateServer(target.dir, target.index, target.url);
|
|
568
|
-
let msg = `Switched to server: ${target.url} → ${target.dir}`;
|
|
569
|
-
if (pending && pending.trim()) {
|
|
570
|
-
await sendLongMessage(token, chatState, msg);
|
|
571
|
-
await sendLongMessage(token, chatState, pending.trim());
|
|
572
|
-
} else {
|
|
573
|
-
await sendLongMessage(token, chatState, msg);
|
|
574
|
-
}
|
|
575
|
-
return;
|
|
576
538
|
}
|
|
577
539
|
|
|
578
540
|
// Default: list servers (alive + dead)
|
|
@@ -650,106 +612,19 @@ async function discoverActiveSession(server) {
|
|
|
650
612
|
|
|
651
613
|
async function cmdSessions(token, chatId, args, chatState, setServerForChat, setActiveSessionForChat) {
|
|
652
614
|
const server = chatState.serverBase;
|
|
615
|
+
// Backward compatibility: redirect old subcommands to singular commands
|
|
653
616
|
if (args.length > 0) {
|
|
654
617
|
const sub = args[0].toLowerCase();
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
const title = args.slice(1).join(' ') || undefined;
|
|
658
|
-
try {
|
|
659
|
-
const session = await createSession(server, title);
|
|
660
|
-
const name = session.title || session.slug || session.id;
|
|
661
|
-
setActiveSessionForChat(chatState, session.id, name);
|
|
662
|
-
selectSession(server, session.id);
|
|
663
|
-
await sendLongMessage(token, chatState,
|
|
664
|
-
`Created session: \`${name}\`\nIt is now the active session.`);
|
|
665
|
-
} catch (err) {
|
|
666
|
-
await sendLongMessage(token, chatState, `Error creating session: ${err.message}`);
|
|
667
|
-
}
|
|
668
|
-
return;
|
|
618
|
+
if (sub === 'switch' || sub === 'new' || sub === 'rename') {
|
|
619
|
+
return cmdSession(token, chatId, args, chatState, setServerForChat, setActiveSessionForChat);
|
|
669
620
|
}
|
|
670
|
-
|
|
671
|
-
if (sub === 'rename') {
|
|
672
|
-
if (args.length < 2) {
|
|
673
|
-
await sendLongMessage(token, chatState, 'Usage: `/sessions rename <name>`');
|
|
674
|
-
return;
|
|
675
|
-
}
|
|
676
|
-
const st = getState(server);
|
|
677
|
-
if (!st.activeSessionId) {
|
|
678
|
-
await sendLongMessage(token, chatState, 'No active session. Use `/sessions switch` or `/sessions new` first.');
|
|
679
|
-
return;
|
|
680
|
-
}
|
|
681
|
-
const newTitle = args.slice(1).join(' ');
|
|
682
|
-
try {
|
|
683
|
-
const session = await updateSession(server, st.activeSessionId, newTitle);
|
|
684
|
-
const name = session.title || session.slug || session.id;
|
|
685
|
-
await sendLongMessage(token, chatState,
|
|
686
|
-
`Renamed session to: \`${name}\``);
|
|
687
|
-
} catch (err) {
|
|
688
|
-
await sendLongMessage(token, chatState, `Error renaming session: ${err.message}`);
|
|
689
|
-
}
|
|
690
|
-
return;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
if (sub === 'switch') {
|
|
694
|
-
if (args.length < 2) {
|
|
695
|
-
await sendLongMessage(token, chatState,
|
|
696
|
-
'Usage: `/sessions switch <number-or-name>`\n' +
|
|
697
|
-
'Use `/sessions` to see numbered list. You can switch by number or by name (case-insensitive partial match).');
|
|
698
|
-
return;
|
|
699
|
-
}
|
|
700
|
-
const query = args.slice(1).join(' ');
|
|
701
|
-
try {
|
|
702
|
-
const projectDir = serverDir(server);
|
|
703
|
-
const sessions = await listSessions(server, projectDir);
|
|
704
|
-
const activeId = getState(server).activeSessionId;
|
|
705
|
-
const filtered = sessions.filter(s =>
|
|
706
|
-
(s.agent || s.directory || s.id === activeId) && (!projectDir || s.directory === projectDir));
|
|
707
|
-
const sorted = sortedSessions(filtered, 50);
|
|
708
|
-
|
|
709
|
-
// Try by position number first
|
|
710
|
-
const num = parseInt(query, 10);
|
|
711
|
-
if (/^\d+$/.test(query) && num >= 1 && num <= sorted.length) {
|
|
712
|
-
const target = sorted[num - 1];
|
|
713
|
-
setActiveSessionForChat(chatState, target.id, sessionName(target));
|
|
714
|
-
selectSession(server, target.id);
|
|
715
|
-
await sendLongMessage(token, chatState,
|
|
716
|
-
`Switched to session #${num}: \`${sessionName(target)}\`\nSubsequent messages will be sent to this session.`);
|
|
717
|
-
return;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
// Try by name (case-insensitive partial match)
|
|
721
|
-
const q = query.toLowerCase();
|
|
722
|
-
const matches = sorted.filter(s => sessionName(s).toLowerCase().includes(q));
|
|
723
|
-
if (matches.length === 0) {
|
|
724
|
-
await sendLongMessage(token, chatState,
|
|
725
|
-
`No session matching "${query}". Use /sessions to list active sessions.`);
|
|
726
|
-
return;
|
|
727
|
-
}
|
|
728
|
-
if (matches.length > 1) {
|
|
729
|
-
const list = matches.map(s => ` \`${sessionName(s)}\``).join('\n');
|
|
730
|
-
await sendLongMessage(token, chatState,
|
|
731
|
-
`Multiple matches for "${query}":\n${list}\n\nPlease be more specific or use a number.`);
|
|
732
|
-
return;
|
|
733
|
-
}
|
|
734
|
-
const target = matches[0];
|
|
735
|
-
setActiveSessionForChat(chatState, target.id);
|
|
736
|
-
selectSession(server, target.id);
|
|
737
|
-
await sendLongMessage(token, chatState,
|
|
738
|
-
`Switched to session: \`${sessionName(target)}\`\nSubsequent messages will be sent to this session.`);
|
|
739
|
-
} catch (err) {
|
|
740
|
-
await sendLongMessage(token, chatState, `Error switching session: ${err.message}`);
|
|
741
|
-
}
|
|
742
|
-
return;
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
// Unknown subcommand
|
|
746
621
|
await sendLongMessage(token, chatState,
|
|
747
622
|
`Unknown subcommand: /sessions ${sub}\n\n` +
|
|
748
623
|
'Usage:\n' +
|
|
749
624
|
' `/sessions` ─ list sessions\n' +
|
|
750
|
-
' `/
|
|
751
|
-
' `/
|
|
752
|
-
' `/
|
|
625
|
+
' `/session <name-or-number>` ─ switch to a session\n' +
|
|
626
|
+
' `/session new [name]` ─ create a new session\n' +
|
|
627
|
+
' `/session rename <name>` ─ rename the active session');
|
|
753
628
|
return;
|
|
754
629
|
}
|
|
755
630
|
|
|
@@ -823,48 +698,9 @@ async function cmdSessions(token, chatId, args, chatState, setServerForChat, set
|
|
|
823
698
|
|
|
824
699
|
async function cmdAgents(token, chatId, args, chatState, setServerForChat, setActiveSessionForChat) {
|
|
825
700
|
const server = chatState.serverBase;
|
|
701
|
+
// Backward compatibility: redirect /agents switch to /agent
|
|
826
702
|
if (args.length > 0 && args[0].toLowerCase() === 'switch') {
|
|
827
|
-
|
|
828
|
-
await sendLongMessage(token, chatState,
|
|
829
|
-
'Usage: `/agents switch <name>`\n' +
|
|
830
|
-
'Use `/agents` to list available agents.');
|
|
831
|
-
return;
|
|
832
|
-
}
|
|
833
|
-
const name = args.slice(1).join(' ');
|
|
834
|
-
try {
|
|
835
|
-
const agents = await listAgents(server);
|
|
836
|
-
if (!agents || agents.length === 0) {
|
|
837
|
-
await sendLongMessage(token, chatState, 'No agents available.');
|
|
838
|
-
return;
|
|
839
|
-
}
|
|
840
|
-
let matches = agents.filter(a => a.name === name);
|
|
841
|
-
if (matches.length === 0) {
|
|
842
|
-
matches = agents.filter(a => a.name.toLowerCase().includes(name.toLowerCase()));
|
|
843
|
-
}
|
|
844
|
-
if (matches.length === 0) {
|
|
845
|
-
await sendLongMessage(token, chatState, `No agent matching "${name}".`);
|
|
846
|
-
return;
|
|
847
|
-
}
|
|
848
|
-
if (matches.length > 1) {
|
|
849
|
-
const list = matches.map(a => ` \`${a.name}\``).join('\n');
|
|
850
|
-
await sendLongMessage(token, chatState,
|
|
851
|
-
`Multiple matches for "${name}":\n${list}\n\nPlease be more specific.`);
|
|
852
|
-
return;
|
|
853
|
-
}
|
|
854
|
-
const target = matches[0];
|
|
855
|
-
getState(server).activeAgentName = target.name;
|
|
856
|
-
const st = getState(server);
|
|
857
|
-
if (st.activeSessionId) {
|
|
858
|
-
updateSession(server, st.activeSessionId, undefined, target.name);
|
|
859
|
-
selectSession(server, st.activeSessionId);
|
|
860
|
-
}
|
|
861
|
-
const model = target.model ? ` ─ \`${target.model.providerID}/${target.model.modelID}\`` : '';
|
|
862
|
-
await sendLongMessage(token, chatState,
|
|
863
|
-
`Switched to agent: **${target.name}**${model}\nSubsequent messages will use this agent.`);
|
|
864
|
-
} catch (err) {
|
|
865
|
-
await sendLongMessage(token, chatState, `Error switching agent: ${err.message}`);
|
|
866
|
-
}
|
|
867
|
-
return;
|
|
703
|
+
return cmdAgent(token, chatId, args.slice(1), chatState, setServerForChat, setActiveSessionForChat);
|
|
868
704
|
}
|
|
869
705
|
|
|
870
706
|
try {
|
|
@@ -1181,20 +1017,38 @@ async function processTelegramCommands(text, server, token, chatState) {
|
|
|
1181
1017
|
const filesSent = [];
|
|
1182
1018
|
const dir = serverDir(server);
|
|
1183
1019
|
|
|
1020
|
+
// Resolve a file path: absolute paths used directly, relative paths resolved
|
|
1021
|
+
// against the server's working directory. Returns null and sends an error
|
|
1022
|
+
// message if the resolved path is outside the server's working directory.
|
|
1023
|
+
function resolveUploadPath(p, errPrefix) {
|
|
1024
|
+
const absolute = path.isAbsolute(p) ? p : path.resolve(dir || '', p);
|
|
1025
|
+
if (!dir) return absolute; // no server dir — trust the path
|
|
1026
|
+
let realDir, realPath;
|
|
1027
|
+
try { realDir = fs.realpathSync(dir); } catch { realDir = dir; }
|
|
1028
|
+
try { realPath = fs.realpathSync(absolute); } catch { realPath = absolute; }
|
|
1029
|
+
if (!realPath.startsWith(realDir + path.sep) && realPath !== realDir) {
|
|
1030
|
+
sendLongMessage(token, chatState,
|
|
1031
|
+
`❌ ${errPrefix}: path outside project directory.\n` +
|
|
1032
|
+
` Path: \`${p}\`\n` +
|
|
1033
|
+
` Project: \`${realDir}\`\n\n` +
|
|
1034
|
+
`Use a full path (starting with /) or a path relative to the project root.`).catch(() => {});
|
|
1035
|
+
return null;
|
|
1036
|
+
}
|
|
1037
|
+
return absolute;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1184
1040
|
for (let i = 0; i < lines.length; i++) {
|
|
1185
1041
|
const payload = parseTelegramLine(lines[i]);
|
|
1186
1042
|
if (!payload) continue;
|
|
1187
1043
|
|
|
1188
1044
|
if (payload.command === 'upload' && payload.path) {
|
|
1189
1045
|
toStrip.add(i);
|
|
1190
|
-
const resolved =
|
|
1191
|
-
if (
|
|
1192
|
-
log.error(`processTelegramCommands: path outside project rejected: ${payload.path}`);
|
|
1193
|
-
continue;
|
|
1194
|
-
}
|
|
1046
|
+
const resolved = resolveUploadPath(payload.path, 'Upload');
|
|
1047
|
+
if (!resolved) continue;
|
|
1195
1048
|
try {
|
|
1196
1049
|
if (!fs.existsSync(resolved)) {
|
|
1197
|
-
|
|
1050
|
+
await sendLongMessage(token, chatState,
|
|
1051
|
+
`❌ File not found: \`${resolved}\``);
|
|
1198
1052
|
continue;
|
|
1199
1053
|
}
|
|
1200
1054
|
const fileData = fs.readFileSync(resolved);
|
|
@@ -1203,22 +1057,21 @@ async function processTelegramCommands(text, server, token, chatState) {
|
|
|
1203
1057
|
await sendTelegramDocument(token, chatState, fileData, fileName, caption);
|
|
1204
1058
|
filesSent.push(fileName);
|
|
1205
1059
|
} catch (err) {
|
|
1206
|
-
|
|
1060
|
+
await sendLongMessage(token, chatState,
|
|
1061
|
+
`❌ Failed to upload \`${resolved}\`: ${err.message}`);
|
|
1207
1062
|
}
|
|
1208
1063
|
} else if (payload.command === 'download' && payload.fileId && payload.path) {
|
|
1209
1064
|
toStrip.add(i);
|
|
1210
|
-
const resolved =
|
|
1211
|
-
if (
|
|
1212
|
-
log.error(`processTelegramCommands: download path outside project rejected: ${payload.path}`);
|
|
1213
|
-
continue;
|
|
1214
|
-
}
|
|
1065
|
+
const resolved = resolveUploadPath(payload.path, 'Download');
|
|
1066
|
+
if (!resolved) continue;
|
|
1215
1067
|
try {
|
|
1216
1068
|
const fileData = await downloadTelegramFile(token, payload.fileId);
|
|
1217
1069
|
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
1218
1070
|
fs.writeFileSync(resolved, fileData);
|
|
1219
1071
|
filesSent.push(path.basename(resolved));
|
|
1220
1072
|
} catch (err) {
|
|
1221
|
-
|
|
1073
|
+
await sendLongMessage(token, chatState,
|
|
1074
|
+
`❌ Failed to download file to \`${resolved}\`: ${err.message}`);
|
|
1222
1075
|
}
|
|
1223
1076
|
} else if (payload.command === 'help') {
|
|
1224
1077
|
toStrip.add(i);
|
|
@@ -1638,7 +1491,7 @@ async function cmdServe(token, chatId, args, chatState, setServerForChat, setAct
|
|
|
1638
1491
|
} else {
|
|
1639
1492
|
await sendLongMessage(token, chatState,
|
|
1640
1493
|
`⚠️ Server already exists for \`${relPath}\`.\n` +
|
|
1641
|
-
'Use `/
|
|
1494
|
+
'Use `/server <name>` to connect to it.');
|
|
1642
1495
|
}
|
|
1643
1496
|
return;
|
|
1644
1497
|
}
|
|
@@ -1836,6 +1689,210 @@ async function cmdModel(token, chatId, args, chatState, setServerForChat, setAct
|
|
|
1836
1689
|
}
|
|
1837
1690
|
}
|
|
1838
1691
|
|
|
1692
|
+
async function cmdServer(token, chatId, args, chatState, setServerForChat, setActiveSessionForChat) {
|
|
1693
|
+
if (args.length === 0) {
|
|
1694
|
+
await sendLongMessage(token, chatState,
|
|
1695
|
+
'Usage: `/server <name>`\n' +
|
|
1696
|
+
'To force-switch (take over a server from another chat), use `/server --force <name>`.\n' +
|
|
1697
|
+
'`<name>` can be a full directory path or a basename.\n' +
|
|
1698
|
+
'Use `/servers` to list available servers.');
|
|
1699
|
+
return;
|
|
1700
|
+
}
|
|
1701
|
+
const forceIdx = args.indexOf('--force');
|
|
1702
|
+
const force = forceIdx !== -1;
|
|
1703
|
+
const nameArgs = args.filter((a, i) => a !== '--force');
|
|
1704
|
+
if (nameArgs.length === 0) {
|
|
1705
|
+
await sendLongMessage(token, chatState,
|
|
1706
|
+
'Usage: `/server <name>`\n' +
|
|
1707
|
+
'To force-switch, use `/server --force <name>`.');
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
const name = nameArgs.join(' ');
|
|
1711
|
+
const servers = listServers();
|
|
1712
|
+
if (servers.length === 0) {
|
|
1713
|
+
await sendLongMessage(token, chatState, 'No servers running.');
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1716
|
+
let matches = servers.filter(s => s.dir.toLowerCase() === name.toLowerCase());
|
|
1717
|
+
if (matches.length === 0) {
|
|
1718
|
+
matches = servers.filter(s => path.basename(s.dir).toLowerCase() === name.toLowerCase());
|
|
1719
|
+
}
|
|
1720
|
+
if (matches.length === 0) {
|
|
1721
|
+
const lowerName = name.toLowerCase();
|
|
1722
|
+
matches = servers.filter(s => s.dir.toLowerCase().includes(lowerName) || path.basename(s.dir).toLowerCase().includes(lowerName));
|
|
1723
|
+
}
|
|
1724
|
+
if (matches.length === 0) {
|
|
1725
|
+
await sendLongMessage(token, chatState, `No server found matching "${name}".`);
|
|
1726
|
+
return;
|
|
1727
|
+
}
|
|
1728
|
+
if (matches.length > 1) {
|
|
1729
|
+
let exact = servers.filter(s => s.dir === name);
|
|
1730
|
+
if (exact.length === 0) exact = servers.filter(s => path.basename(s.dir) === name);
|
|
1731
|
+
if (exact.length === 1) {
|
|
1732
|
+
matches = exact;
|
|
1733
|
+
} else {
|
|
1734
|
+
const list = matches.map(s => ` ${s.url} → ${s.dir}`).join('\n');
|
|
1735
|
+
await sendLongMessage(token, chatState,
|
|
1736
|
+
`Multiple matches for "${name}":\n${list}\n\nPlease be more specific.`);
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
const target = matches[0];
|
|
1741
|
+
const existing = serverOwners[target.url];
|
|
1742
|
+
if (existing && existing.chatId !== chatState.chatId && !force) {
|
|
1743
|
+
await sendLongMessage(token, chatState,
|
|
1744
|
+
`⚠️ Server \`${target.url}\` is already connected to another chat.\n\n` +
|
|
1745
|
+
`Use \`/server --force ${name}\` to take over (the other chat will be disconnected).`);
|
|
1746
|
+
return;
|
|
1747
|
+
}
|
|
1748
|
+
if (force && existing && existing.chatId !== chatState.chatId) {
|
|
1749
|
+
const oldState = getChatState(existing.chatId, existing.threadId);
|
|
1750
|
+
oldState.serverBase = null;
|
|
1751
|
+
sendLongMessage(token, oldState,
|
|
1752
|
+
`⚠️ Server \`${target.url}\` has been taken over by another chat.\n\n` +
|
|
1753
|
+
`Use \`/servers\` to see available servers and reconnect.`).catch(() => {});
|
|
1754
|
+
}
|
|
1755
|
+
const pending = await setServerForChat(chatState, target.url);
|
|
1756
|
+
activateServer(target.dir, target.index, target.url);
|
|
1757
|
+
let msg = `Switched to server: ${target.url} → ${target.dir}`;
|
|
1758
|
+
if (pending && pending.trim()) {
|
|
1759
|
+
await sendLongMessage(token, chatState, msg);
|
|
1760
|
+
await sendLongMessage(token, chatState, pending.trim());
|
|
1761
|
+
} else {
|
|
1762
|
+
await sendLongMessage(token, chatState, msg);
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
async function cmdSession(token, chatId, args, chatState, setServerForChat, setActiveSessionForChat) {
|
|
1767
|
+
const server = chatState.serverBase;
|
|
1768
|
+
if (args.length === 0) {
|
|
1769
|
+
await sendLongMessage(token, chatState,
|
|
1770
|
+
'`/session <name-or-number>` ─ switch to a session\n' +
|
|
1771
|
+
'`/session new [name]` ─ create a new session\n' +
|
|
1772
|
+
'`/session rename <name>` ─ rename the active session\n' +
|
|
1773
|
+
'Use `/sessions` to list available sessions.');
|
|
1774
|
+
return;
|
|
1775
|
+
}
|
|
1776
|
+
const sub = args[0].toLowerCase();
|
|
1777
|
+
if (sub === 'new') {
|
|
1778
|
+
const title = args.slice(1).join(' ') || undefined;
|
|
1779
|
+
try {
|
|
1780
|
+
const session = await createSession(server, title);
|
|
1781
|
+
const name = session.title || session.slug || session.id;
|
|
1782
|
+
setActiveSessionForChat(chatState, session.id, name);
|
|
1783
|
+
selectSession(server, session.id);
|
|
1784
|
+
await sendLongMessage(token, chatState,
|
|
1785
|
+
`Created session: \`${name}\`\nIt is now the active session.`);
|
|
1786
|
+
} catch (err) {
|
|
1787
|
+
await sendLongMessage(token, chatState, `Error creating session: ${err.message}`);
|
|
1788
|
+
}
|
|
1789
|
+
return;
|
|
1790
|
+
}
|
|
1791
|
+
if (sub === 'rename') {
|
|
1792
|
+
if (args.length < 2) {
|
|
1793
|
+
await sendLongMessage(token, chatState, 'Usage: `/session rename <name>`');
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
const st = getState(server);
|
|
1797
|
+
if (!st.activeSessionId) {
|
|
1798
|
+
await sendLongMessage(token, chatState, 'No active session. Use `/session <name>` to select one first.');
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
const newTitle = args.slice(1).join(' ');
|
|
1802
|
+
try {
|
|
1803
|
+
const session = await updateSession(server, st.activeSessionId, newTitle);
|
|
1804
|
+
const name = session.title || session.slug || session.id;
|
|
1805
|
+
await sendLongMessage(token, chatState, `Renamed session to: \`${name}\``);
|
|
1806
|
+
} catch (err) {
|
|
1807
|
+
await sendLongMessage(token, chatState, `Error renaming session: ${err.message}`);
|
|
1808
|
+
}
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
// Switch to session by name or number
|
|
1812
|
+
const query = args.join(' ');
|
|
1813
|
+
try {
|
|
1814
|
+
const projectDir = serverDir(server);
|
|
1815
|
+
const sessions = await listSessions(server, projectDir);
|
|
1816
|
+
const activeId = getState(server).activeSessionId;
|
|
1817
|
+
const filtered = sessions.filter(s =>
|
|
1818
|
+
(s.agent || s.directory || s.id === activeId) && (!projectDir || s.directory === projectDir));
|
|
1819
|
+
const sorted = sortedSessions(filtered, 50);
|
|
1820
|
+
const num = parseInt(query, 10);
|
|
1821
|
+
if (/^\d+$/.test(query) && num >= 1 && num <= sorted.length) {
|
|
1822
|
+
const target = sorted[num - 1];
|
|
1823
|
+
setActiveSessionForChat(chatState, target.id, sessionName(target));
|
|
1824
|
+
selectSession(server, target.id);
|
|
1825
|
+
await sendLongMessage(token, chatState,
|
|
1826
|
+
`Switched to session #${num}: \`${sessionName(target)}\`\nSubsequent messages will be sent to this session.`);
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
const q = query.toLowerCase();
|
|
1830
|
+
const matches = sorted.filter(s => sessionName(s).toLowerCase().includes(q));
|
|
1831
|
+
if (matches.length === 0) {
|
|
1832
|
+
await sendLongMessage(token, chatState,
|
|
1833
|
+
`No session matching "${query}". Use /sessions to list active sessions.`);
|
|
1834
|
+
return;
|
|
1835
|
+
}
|
|
1836
|
+
if (matches.length > 1) {
|
|
1837
|
+
const list = matches.map(s => ` \`${sessionName(s)}\``).join('\n');
|
|
1838
|
+
await sendLongMessage(token, chatState,
|
|
1839
|
+
`Multiple matches for "${query}":\n${list}\n\nPlease be more specific or use a number.`);
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
const target = matches[0];
|
|
1843
|
+
setActiveSessionForChat(chatState, target.id);
|
|
1844
|
+
selectSession(server, target.id);
|
|
1845
|
+
await sendLongMessage(token, chatState,
|
|
1846
|
+
`Switched to session: \`${sessionName(target)}\`\nSubsequent messages will be sent to this session.`);
|
|
1847
|
+
} catch (err) {
|
|
1848
|
+
await sendLongMessage(token, chatState, `Error switching session: ${err.message}`);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
async function cmdAgent(token, chatId, args, chatState, setServerForChat, setActiveSessionForChat) {
|
|
1853
|
+
const server = chatState.serverBase;
|
|
1854
|
+
if (args.length === 0) {
|
|
1855
|
+
await sendLongMessage(token, chatState,
|
|
1856
|
+
'Usage: `/agent <name>`\n' +
|
|
1857
|
+
'Use `/agents` to list available agents.');
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
const name = args.join(' ');
|
|
1861
|
+
try {
|
|
1862
|
+
const agents = await listAgents(server);
|
|
1863
|
+
if (!agents || agents.length === 0) {
|
|
1864
|
+
await sendLongMessage(token, chatState, 'No agents available.');
|
|
1865
|
+
return;
|
|
1866
|
+
}
|
|
1867
|
+
let matches = agents.filter(a => a.name === name);
|
|
1868
|
+
if (matches.length === 0) {
|
|
1869
|
+
matches = agents.filter(a => a.name.toLowerCase().includes(name.toLowerCase()));
|
|
1870
|
+
}
|
|
1871
|
+
if (matches.length === 0) {
|
|
1872
|
+
await sendLongMessage(token, chatState, `No agent matching "${name}".`);
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
1875
|
+
if (matches.length > 1) {
|
|
1876
|
+
const list = matches.map(a => ` \`${a.name}\``).join('\n');
|
|
1877
|
+
await sendLongMessage(token, chatState,
|
|
1878
|
+
`Multiple matches for "${name}":\n${list}\n\nPlease be more specific.`);
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
const target = matches[0];
|
|
1882
|
+
getState(server).activeAgentName = target.name;
|
|
1883
|
+
const st = getState(server);
|
|
1884
|
+
if (st.activeSessionId) {
|
|
1885
|
+
updateSession(server, st.activeSessionId, undefined, target.name);
|
|
1886
|
+
selectSession(server, st.activeSessionId);
|
|
1887
|
+
}
|
|
1888
|
+
const model = target.model ? ` ─ \`${target.model.providerID}/${target.model.modelID}\`` : '';
|
|
1889
|
+
await sendLongMessage(token, chatState,
|
|
1890
|
+
`Switched to agent: **${target.name}**${model}\nSubsequent messages will use this agent.`);
|
|
1891
|
+
} catch (err) {
|
|
1892
|
+
await sendLongMessage(token, chatState, `Error switching agent: ${err.message}`);
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1839
1896
|
async function cmdMarkdown(token, chatId, args, chatState, setServerForChat, setActiveSessionForChat, replyTo, replyToMsgId) {
|
|
1840
1897
|
let entry = null;
|
|
1841
1898
|
if (replyToMsgId) {
|
|
@@ -1859,10 +1916,11 @@ async function cmdMarkdown(token, chatId, args, chatState, setServerForChat, set
|
|
|
1859
1916
|
const COMMANDS = {
|
|
1860
1917
|
'help': cmdHelp,
|
|
1861
1918
|
'servers': cmdServers,
|
|
1862
|
-
'
|
|
1919
|
+
'server': cmdServer,
|
|
1863
1920
|
'sessions': cmdSessions,
|
|
1864
|
-
'session':
|
|
1921
|
+
'session': cmdSession,
|
|
1865
1922
|
'agents': cmdAgents,
|
|
1923
|
+
'agent': cmdAgent,
|
|
1866
1924
|
'models': cmdModels,
|
|
1867
1925
|
'model': cmdModel,
|
|
1868
1926
|
'think': cmdThink,
|
|
@@ -2113,8 +2171,13 @@ async function main() {
|
|
|
2113
2171
|
'Do NOT use CLI tools to generate them — just include them directly in your response:\n\n' +
|
|
2114
2172
|
'To send a file to the user:\n' +
|
|
2115
2173
|
'[Telegram]{"command":"upload","path":"<relative-path>","msg":"<optional caption>"}\n\n' +
|
|
2174
|
+
'**Important:** `path` is relative to the project root (the OpenCode server\'s working directory).\n' +
|
|
2175
|
+
'If the project uses a git worktree, the worktree directory IS the project root.\n' +
|
|
2176
|
+
'Use CLI commands like `cat ./path/to/file` to verify the file exists before uploading.\n\n' +
|
|
2116
2177
|
'To download a file the user sent:\n' +
|
|
2117
2178
|
'[Telegram]{"command":"download","fileId":"<id>","path":"<relative-destination>"}\n\n' +
|
|
2179
|
+
'**Important:** `path` is also relative to the project root (the OpenCode server\'s working directory).\n' +
|
|
2180
|
+
'Use `ls` or `pwd` to confirm the current working directory before specifying download paths.\n\n' +
|
|
2118
2181
|
'For more details about a command:\n' +
|
|
2119
2182
|
'[Telegram]{"command":"help","topic":"<upload|download|help>"}';
|
|
2120
2183
|
|
|
@@ -2133,6 +2196,7 @@ async function main() {
|
|
|
2133
2196
|
const monRef = { current: null };
|
|
2134
2197
|
st._monitorRef = monRef;
|
|
2135
2198
|
st._monitorStarted = true;
|
|
2199
|
+
st._monitoredSessionId = sessionId;
|
|
2136
2200
|
|
|
2137
2201
|
const notifiedPerms = new Set();
|
|
2138
2202
|
|
|
@@ -2198,6 +2262,7 @@ async function main() {
|
|
|
2198
2262
|
if (getState(server)._monitorRef === monRef) {
|
|
2199
2263
|
getState(server)._monitorRef = null;
|
|
2200
2264
|
getState(server)._monitorStarted = false;
|
|
2265
|
+
getState(server)._monitoredSessionId = null;
|
|
2201
2266
|
}
|
|
2202
2267
|
});
|
|
2203
2268
|
}
|
|
@@ -2230,6 +2295,20 @@ async function main() {
|
|
|
2230
2295
|
|
|
2231
2296
|
async function processMessageAsync(chatId, text, url, useSession, sessionId, myTaskId, replyTo, threadId) {
|
|
2232
2297
|
const st = getState(url);
|
|
2298
|
+
// Auto-discover active session if not set
|
|
2299
|
+
if (!sessionId) {
|
|
2300
|
+
try {
|
|
2301
|
+
const discovered = await discoverActiveSession(url);
|
|
2302
|
+
if (discovered) {
|
|
2303
|
+
st.activeSessionId = discovered.id;
|
|
2304
|
+
st.activeSessionName = discovered.name;
|
|
2305
|
+
sessionId = discovered.id;
|
|
2306
|
+
useSession = true;
|
|
2307
|
+
}
|
|
2308
|
+
} catch (err) {
|
|
2309
|
+
log.debug(`processMessageAsync: auto-discover error: ${err.message}`);
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2233
2312
|
const isCancelled = () => getState(url).taskId !== myTaskId;
|
|
2234
2313
|
const cancelRef = { current: null };
|
|
2235
2314
|
const chatState = { chatId, threadId };
|
|
@@ -2240,11 +2319,12 @@ async function main() {
|
|
|
2240
2319
|
st._cancelRef = cancelRef;
|
|
2241
2320
|
st._sessionReq = sessionCancelRef;
|
|
2242
2321
|
|
|
2243
|
-
// Ensure the persistent session monitor is running
|
|
2244
|
-
//
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2322
|
+
// Ensure the persistent session monitor is running and tracking the current session.
|
|
2323
|
+
// Always restart when sessionId changes so TUI-initiated session switches are followed.
|
|
2324
|
+
if (useSession && sessionId) {
|
|
2325
|
+
if (!st._monitorStarted || st._monitoredSessionId !== sessionId) {
|
|
2326
|
+
startSessionMonitor(url, sessionId);
|
|
2327
|
+
}
|
|
2248
2328
|
}
|
|
2249
2329
|
|
|
2250
2330
|
try {
|
|
@@ -2338,7 +2418,7 @@ async function main() {
|
|
|
2338
2418
|
? `⚠️ Server \`${url}\` is unreachable. The message has been requeued and will be sent when the server is available again.\n\n` +
|
|
2339
2419
|
`**Options:**\n` +
|
|
2340
2420
|
`• Restart it: \`/resurrect\`\n` +
|
|
2341
|
-
`• Switch to another server: \`/
|
|
2421
|
+
`• Switch to another server: \`/server <name>\`\n` +
|
|
2342
2422
|
`• Flush the queue: \`/flush\``
|
|
2343
2423
|
: `Error: ${err.message}`;
|
|
2344
2424
|
try { await sendLongMessage(token, chatState, msg, replyTo, threadId); } catch (_) {}
|
|
@@ -2576,7 +2656,7 @@ async function main() {
|
|
|
2576
2656
|
await sendLongMessage(token, cs,
|
|
2577
2657
|
'🔄 Bot started. Previously connected to `' + dir + '`.\n' +
|
|
2578
2658
|
'⚠️ The server at `' + state.url + '` is not reachable.\n' +
|
|
2579
|
-
'Use `/resurrect` to restart it, or `/
|
|
2659
|
+
'Use `/resurrect` to restart it, or `/server <name>` to connect elsewhere.');
|
|
2580
2660
|
} catch (_) {}
|
|
2581
2661
|
}
|
|
2582
2662
|
}
|
|
@@ -2725,7 +2805,7 @@ async function main() {
|
|
|
2725
2805
|
`Your file upload has been queued (#${qlen}). It will be sent when the server becomes available.\n\n` +
|
|
2726
2806
|
`**Options:**\n` +
|
|
2727
2807
|
`• Restart it: \`/resurrect\`\n` +
|
|
2728
|
-
`• Switch to another server: \`/
|
|
2808
|
+
`• Switch to another server: \`/server <name>\`\n` +
|
|
2729
2809
|
`• Flush the queue: \`/flush\``);
|
|
2730
2810
|
continue;
|
|
2731
2811
|
}
|
|
@@ -2875,11 +2955,11 @@ async function main() {
|
|
|
2875
2955
|
const cmdArgs = parts.slice(1);
|
|
2876
2956
|
const handler = cmds[cmd];
|
|
2877
2957
|
if (handler) {
|
|
2878
|
-
// Disconnected guard:
|
|
2879
|
-
if (!cs.serverBase && cmd !== 'help' && cmd !== 'servers' && cmd !== '
|
|
2958
|
+
// Disconnected guard: /help, /servers, /server, /start, /comment, /shutdown, /serve, /new, and /resurrect work without a server
|
|
2959
|
+
if (!cs.serverBase && cmd !== 'help' && cmd !== 'servers' && cmd !== 'server' && cmd !== 'start' && cmd !== 'comment' && cmd !== 'shutdown' && cmd !== 'serve' && cmd !== 'new' && cmd !== 'resurrect') {
|
|
2880
2960
|
await sendLongMessage(token, cs,
|
|
2881
2961
|
'🔌 Not connected to any server.\n\n' +
|
|
2882
|
-
'Use `/servers` to see available servers and `/
|
|
2962
|
+
'Use `/servers` to see available servers and `/server <name>` to connect.');
|
|
2883
2963
|
continue;
|
|
2884
2964
|
}
|
|
2885
2965
|
try {
|
|
@@ -2891,7 +2971,7 @@ async function main() {
|
|
|
2891
2971
|
}
|
|
2892
2972
|
} else {
|
|
2893
2973
|
await sendLongMessage(token, cs,
|
|
2894
|
-
`Unknown command: /${cmd}\n\nAvailable commands: /help, /status, /servers, /sessions, /agents, /models, /model, /think, /record, /queue, /flush, /note, /comment, /disconnect, /serve, /new, /markdown`);
|
|
2974
|
+
`Unknown command: /${cmd}\n\nAvailable commands: /help, /status, /servers, /server, /sessions, /session, /agents, /agent, /models, /model, /think, /record, /queue, /flush, /note, /comment, /disconnect, /serve, /new, /markdown`);
|
|
2895
2975
|
}
|
|
2896
2976
|
continue;
|
|
2897
2977
|
}
|
|
@@ -2900,7 +2980,7 @@ async function main() {
|
|
|
2900
2980
|
if (!cs.serverBase) {
|
|
2901
2981
|
await sendLongMessage(token, cs,
|
|
2902
2982
|
'🔌 Not connected to any server.\n\n' +
|
|
2903
|
-
'Use `/servers` to see available servers and `/
|
|
2983
|
+
'Use `/servers` to see available servers and `/server <name>` to connect.');
|
|
2904
2984
|
continue;
|
|
2905
2985
|
}
|
|
2906
2986
|
|
|
@@ -2927,7 +3007,7 @@ async function main() {
|
|
|
2927
3007
|
const suggestions = [];
|
|
2928
3008
|
suggestions.push(`• Restart it: \`/resurrect\``);
|
|
2929
3009
|
if (servers.length > 0) {
|
|
2930
|
-
suggestions.push(`• Switch to another server: \`/
|
|
3010
|
+
suggestions.push(`• Switch to another server: \`/server <name>\``);
|
|
2931
3011
|
}
|
|
2932
3012
|
suggestions.push(`• Flush the queue: \`/flush\``);
|
|
2933
3013
|
await sendLongMessage(token, cs,
|