@visorcraft/idlehands 1.0.1 → 1.0.2
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 +150 -11
- package/dist/agent.js +16 -0
- package/dist/agent.js.map +1 -1
- package/dist/anton/controller.js +2 -2
- package/dist/bot/commands.js +1 -1
- package/dist/bot/commands.js.map +1 -1
- package/dist/bot/discord.js +77 -70
- package/dist/bot/discord.js.map +1 -1
- package/dist/bot/format.js +5 -0
- package/dist/bot/format.js.map +1 -1
- package/dist/bot/telegram.js +18 -4
- package/dist/bot/telegram.js.map +1 -1
- package/dist/config.js +5 -0
- package/dist/config.js.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/tools.js +4 -0
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
package/dist/bot/discord.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Client, Events, GatewayIntentBits, Partials, } from 'discord.js';
|
|
2
2
|
import { createSession } from '../agent.js';
|
|
3
3
|
import { DiscordConfirmProvider } from './confirm-discord.js';
|
|
4
|
+
import { sanitizeBotOutputText } from './format.js';
|
|
4
5
|
import { projectDir } from '../utils.js';
|
|
5
6
|
import path from 'node:path';
|
|
6
7
|
import fs from 'node:fs/promises';
|
|
@@ -36,7 +37,7 @@ function splitDiscord(text, limit = 1900) {
|
|
|
36
37
|
return chunks;
|
|
37
38
|
}
|
|
38
39
|
function safeContent(text) {
|
|
39
|
-
const t = text.trim();
|
|
40
|
+
const t = sanitizeBotOutputText(text).trim();
|
|
40
41
|
return t.length ? t : '(empty response)';
|
|
41
42
|
}
|
|
42
43
|
function sessionKeyForMessage(msg, allowGuilds) {
|
|
@@ -65,6 +66,7 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
65
66
|
const sessionTimeoutMs = (botConfig.session_timeout_min ?? 30) * 60_000;
|
|
66
67
|
const approvalMode = normalizeApprovalMode(botConfig.approval_mode, config.approval_mode ?? 'auto-edit');
|
|
67
68
|
const defaultDir = botConfig.default_dir || projectDir(config);
|
|
69
|
+
const replyToUserMessages = botConfig.reply_to_user_messages === true;
|
|
68
70
|
const sessions = new Map();
|
|
69
71
|
const client = new Client({
|
|
70
72
|
intents: [
|
|
@@ -75,6 +77,11 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
75
77
|
],
|
|
76
78
|
partials: [Partials.Channel],
|
|
77
79
|
});
|
|
80
|
+
const sendUserVisible = async (msg, content) => {
|
|
81
|
+
if (replyToUserMessages)
|
|
82
|
+
return await msg.reply(content);
|
|
83
|
+
return await msg.channel.send(content);
|
|
84
|
+
};
|
|
78
85
|
async function getOrCreate(msg) {
|
|
79
86
|
const key = sessionKeyForMessage(msg, allowGuilds);
|
|
80
87
|
const existing = sessions.get(key);
|
|
@@ -193,7 +200,7 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
193
200
|
if (!turn)
|
|
194
201
|
return;
|
|
195
202
|
const turnId = turn.turnId;
|
|
196
|
-
const placeholder = await msg
|
|
203
|
+
const placeholder = await sendUserVisible(msg, '⏳ Thinking...').catch(() => null);
|
|
197
204
|
let streamed = '';
|
|
198
205
|
const hooks = {
|
|
199
206
|
onToken: (t) => {
|
|
@@ -223,7 +230,7 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
223
230
|
await placeholder.edit(chunks[0]).catch(() => { });
|
|
224
231
|
}
|
|
225
232
|
else {
|
|
226
|
-
await msg
|
|
233
|
+
await sendUserVisible(msg, chunks[0]).catch(() => { });
|
|
227
234
|
}
|
|
228
235
|
for (let i = 1; i < chunks.length && i < 10; i++) {
|
|
229
236
|
if (!isTurnActive(managed, turnId))
|
|
@@ -242,7 +249,7 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
242
249
|
if (placeholder)
|
|
243
250
|
await placeholder.edit('⏹ Cancelled.').catch(() => { });
|
|
244
251
|
else
|
|
245
|
-
await msg
|
|
252
|
+
await sendUserVisible(msg, '⏹ Cancelled.').catch(() => { });
|
|
246
253
|
}
|
|
247
254
|
else {
|
|
248
255
|
const errMsg = raw.slice(0, 400);
|
|
@@ -250,7 +257,7 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
250
257
|
await placeholder.edit(`❌ ${errMsg}`).catch(() => { });
|
|
251
258
|
}
|
|
252
259
|
else {
|
|
253
|
-
await msg
|
|
260
|
+
await sendUserVisible(msg, `❌ ${errMsg}`).catch(() => { });
|
|
254
261
|
}
|
|
255
262
|
}
|
|
256
263
|
}
|
|
@@ -315,17 +322,17 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
315
322
|
const key = sessionKeyForMessage(msg, allowGuilds);
|
|
316
323
|
if (content === '/new') {
|
|
317
324
|
destroySession(key);
|
|
318
|
-
await msg
|
|
325
|
+
await sendUserVisible(msg, '✨ New session started. Send a message to begin.').catch(() => { });
|
|
319
326
|
return;
|
|
320
327
|
}
|
|
321
328
|
const managed = await getOrCreate(msg);
|
|
322
329
|
if (!managed) {
|
|
323
|
-
await msg
|
|
330
|
+
await sendUserVisible(msg, '⚠️ Too many active sessions. Please retry later.').catch(() => { });
|
|
324
331
|
return;
|
|
325
332
|
}
|
|
326
333
|
if (content === '/cancel') {
|
|
327
334
|
const res = cancelActive(managed);
|
|
328
|
-
await msg
|
|
335
|
+
await sendUserVisible(msg, res.message).catch(() => { });
|
|
329
336
|
return;
|
|
330
337
|
}
|
|
331
338
|
if (content === '/start') {
|
|
@@ -338,7 +345,7 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
338
345
|
'',
|
|
339
346
|
'Send me a coding task, or use /help for commands.',
|
|
340
347
|
];
|
|
341
|
-
await msg
|
|
348
|
+
await sendUserVisible(msg, lines.join('\n')).catch(() => { });
|
|
342
349
|
return;
|
|
343
350
|
}
|
|
344
351
|
if (content === '/help') {
|
|
@@ -361,22 +368,22 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
361
368
|
'/anton <file> — Start autonomous task runner',
|
|
362
369
|
'/anton status | /anton stop | /anton last',
|
|
363
370
|
];
|
|
364
|
-
await msg
|
|
371
|
+
await sendUserVisible(msg, lines.join('\n')).catch(() => { });
|
|
365
372
|
return;
|
|
366
373
|
}
|
|
367
374
|
if (content === '/model') {
|
|
368
|
-
await msg
|
|
375
|
+
await sendUserVisible(msg, `Model: \`${managed.session.model}\`\nHarness: \`${managed.session.harness}\``).catch(() => { });
|
|
369
376
|
return;
|
|
370
377
|
}
|
|
371
378
|
if (content === '/compact') {
|
|
372
379
|
managed.session.reset();
|
|
373
|
-
await msg
|
|
380
|
+
await sendUserVisible(msg, '🗜 Session context compacted (reset to system prompt).').catch(() => { });
|
|
374
381
|
return;
|
|
375
382
|
}
|
|
376
383
|
if (content === '/dir' || content.startsWith('/dir ')) {
|
|
377
384
|
const arg = content.slice('/dir'.length).trim();
|
|
378
385
|
if (!arg) {
|
|
379
|
-
await msg
|
|
386
|
+
await sendUserVisible(msg, `Working directory: \`${managed.config.dir || defaultDir}\``).catch(() => { });
|
|
380
387
|
return;
|
|
381
388
|
}
|
|
382
389
|
const allowedDirs = botConfig.allowed_dirs ?? ['~'];
|
|
@@ -384,7 +391,7 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
384
391
|
const resolvedDir = arg.replace(/^~/, homeDir);
|
|
385
392
|
const allowed = allowedDirs.some((d) => resolvedDir.startsWith(d.replace(/^~/, homeDir)));
|
|
386
393
|
if (!allowed) {
|
|
387
|
-
await msg
|
|
394
|
+
await sendUserVisible(msg, '❌ Directory not allowed. Check bot.discord.allowed_dirs.').catch(() => { });
|
|
388
395
|
return;
|
|
389
396
|
}
|
|
390
397
|
const cfg = {
|
|
@@ -392,68 +399,68 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
392
399
|
dir: resolvedDir,
|
|
393
400
|
};
|
|
394
401
|
await recreateSession(managed, cfg);
|
|
395
|
-
await msg
|
|
402
|
+
await sendUserVisible(msg, `✅ Working directory set to \`${resolvedDir}\``).catch(() => { });
|
|
396
403
|
return;
|
|
397
404
|
}
|
|
398
405
|
if (content === '/approval' || content.startsWith('/approval ')) {
|
|
399
406
|
const arg = content.slice('/approval'.length).trim().toLowerCase();
|
|
400
407
|
const modes = ['plan', 'default', 'auto-edit', 'yolo'];
|
|
401
408
|
if (!arg) {
|
|
402
|
-
await msg
|
|
409
|
+
await sendUserVisible(msg, `Approval mode: \`${managed.config.approval_mode || approvalMode}\`\nOptions: ${modes.join(', ')}`).catch(() => { });
|
|
403
410
|
return;
|
|
404
411
|
}
|
|
405
412
|
if (!modes.includes(arg)) {
|
|
406
|
-
await msg
|
|
413
|
+
await sendUserVisible(msg, `Invalid mode. Options: ${modes.join(', ')}`).catch(() => { });
|
|
407
414
|
return;
|
|
408
415
|
}
|
|
409
416
|
managed.config.approval_mode = arg;
|
|
410
417
|
managed.config.no_confirm = arg === 'yolo';
|
|
411
|
-
await msg
|
|
418
|
+
await sendUserVisible(msg, `✅ Approval mode set to \`${arg}\``).catch(() => { });
|
|
412
419
|
return;
|
|
413
420
|
}
|
|
414
421
|
if (content === '/mode' || content.startsWith('/mode ')) {
|
|
415
422
|
const arg = content.slice('/mode'.length).trim().toLowerCase();
|
|
416
423
|
if (!arg) {
|
|
417
|
-
await msg
|
|
424
|
+
await sendUserVisible(msg, `Mode: \`${managed.config.mode || 'code'}\``).catch(() => { });
|
|
418
425
|
return;
|
|
419
426
|
}
|
|
420
427
|
if (arg !== 'code' && arg !== 'sys') {
|
|
421
|
-
await msg
|
|
428
|
+
await sendUserVisible(msg, 'Invalid mode. Options: code, sys').catch(() => { });
|
|
422
429
|
return;
|
|
423
430
|
}
|
|
424
431
|
managed.config.mode = arg;
|
|
425
432
|
if (arg === 'sys' && managed.config.approval_mode === 'auto-edit') {
|
|
426
433
|
managed.config.approval_mode = 'default';
|
|
427
434
|
}
|
|
428
|
-
await msg
|
|
435
|
+
await sendUserVisible(msg, `✅ Mode set to \`${arg}\``).catch(() => { });
|
|
429
436
|
return;
|
|
430
437
|
}
|
|
431
438
|
if (content === '/subagents' || content.startsWith('/subagents ')) {
|
|
432
439
|
const arg = content.slice('/subagents'.length).trim().toLowerCase();
|
|
433
440
|
const current = managed.config.sub_agents?.enabled !== false;
|
|
434
441
|
if (!arg) {
|
|
435
|
-
await msg
|
|
442
|
+
await sendUserVisible(msg, `Sub-agents: \`${current ? 'on' : 'off'}\`\nUsage: /subagents on | off`).catch(() => { });
|
|
436
443
|
return;
|
|
437
444
|
}
|
|
438
445
|
if (arg !== 'on' && arg !== 'off') {
|
|
439
|
-
await msg
|
|
446
|
+
await sendUserVisible(msg, 'Invalid value. Usage: /subagents on | off').catch(() => { });
|
|
440
447
|
return;
|
|
441
448
|
}
|
|
442
449
|
const enabled = arg === 'on';
|
|
443
450
|
managed.config.sub_agents = { ...(managed.config.sub_agents ?? {}), enabled };
|
|
444
|
-
await msg
|
|
451
|
+
await sendUserVisible(msg, `✅ Sub-agents \`${enabled ? 'on' : 'off'}\`${!enabled ? ' — spawn_task disabled for this session' : ''}`).catch(() => { });
|
|
445
452
|
return;
|
|
446
453
|
}
|
|
447
454
|
if (content === '/changes') {
|
|
448
455
|
const replay = managed.session.replay;
|
|
449
456
|
if (!replay) {
|
|
450
|
-
await msg
|
|
457
|
+
await sendUserVisible(msg, 'Replay is disabled. No change tracking available.').catch(() => { });
|
|
451
458
|
return;
|
|
452
459
|
}
|
|
453
460
|
try {
|
|
454
461
|
const checkpoints = await replay.list(50);
|
|
455
462
|
if (!checkpoints.length) {
|
|
456
|
-
await msg
|
|
463
|
+
await sendUserVisible(msg, 'No file changes this session.').catch(() => { });
|
|
457
464
|
return;
|
|
458
465
|
}
|
|
459
466
|
const byFile = new Map();
|
|
@@ -462,44 +469,44 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
462
469
|
const lines = [`Session changes (${byFile.size} files):`];
|
|
463
470
|
for (const [fp, count] of byFile)
|
|
464
471
|
lines.push(`✎ \`${fp}\` (${count} edit${count > 1 ? 's' : ''})`);
|
|
465
|
-
await msg
|
|
472
|
+
await sendUserVisible(msg, lines.join('\n')).catch(() => { });
|
|
466
473
|
}
|
|
467
474
|
catch (e) {
|
|
468
|
-
await msg
|
|
475
|
+
await sendUserVisible(msg, `Error listing changes: ${e?.message ?? String(e)}`).catch(() => { });
|
|
469
476
|
}
|
|
470
477
|
return;
|
|
471
478
|
}
|
|
472
479
|
if (content === '/undo') {
|
|
473
480
|
const lastPath = managed.session.lastEditedPath;
|
|
474
481
|
if (!lastPath) {
|
|
475
|
-
await msg
|
|
482
|
+
await sendUserVisible(msg, 'No recent edits to undo.').catch(() => { });
|
|
476
483
|
return;
|
|
477
484
|
}
|
|
478
485
|
try {
|
|
479
486
|
const { undo_path } = await import('../tools.js');
|
|
480
487
|
const result = await undo_path({ cwd: managed.config.dir || defaultDir, noConfirm: true, dryRun: false }, { path: lastPath });
|
|
481
|
-
await msg
|
|
488
|
+
await sendUserVisible(msg, `✅ ${result}`).catch(() => { });
|
|
482
489
|
}
|
|
483
490
|
catch (e) {
|
|
484
|
-
await msg
|
|
491
|
+
await sendUserVisible(msg, `❌ Undo failed: ${e?.message ?? String(e)}`).catch(() => { });
|
|
485
492
|
}
|
|
486
493
|
return;
|
|
487
494
|
}
|
|
488
495
|
if (content === '/vault' || content.startsWith('/vault ')) {
|
|
489
496
|
const query = content.slice('/vault'.length).trim();
|
|
490
497
|
if (!query) {
|
|
491
|
-
await msg
|
|
498
|
+
await sendUserVisible(msg, 'Usage: /vault <search query>').catch(() => { });
|
|
492
499
|
return;
|
|
493
500
|
}
|
|
494
501
|
const vault = managed.session.vault;
|
|
495
502
|
if (!vault) {
|
|
496
|
-
await msg
|
|
503
|
+
await sendUserVisible(msg, 'Vault is disabled.').catch(() => { });
|
|
497
504
|
return;
|
|
498
505
|
}
|
|
499
506
|
try {
|
|
500
507
|
const results = await vault.search(query, 5);
|
|
501
508
|
if (!results.length) {
|
|
502
|
-
await msg
|
|
509
|
+
await sendUserVisible(msg, `No vault results for "${query}"`).catch(() => { });
|
|
503
510
|
return;
|
|
504
511
|
}
|
|
505
512
|
const lines = [`Vault results for "${query}":`];
|
|
@@ -508,10 +515,10 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
508
515
|
const body = (r.value ?? r.snippet ?? r.content ?? '').replace(/\s+/g, ' ').slice(0, 120);
|
|
509
516
|
lines.push(`• ${title}: ${body}`);
|
|
510
517
|
}
|
|
511
|
-
await msg
|
|
518
|
+
await sendUserVisible(msg, lines.join('\n')).catch(() => { });
|
|
512
519
|
}
|
|
513
520
|
catch (e) {
|
|
514
|
-
await msg
|
|
521
|
+
await sendUserVisible(msg, `Error searching vault: ${e?.message ?? String(e)}`).catch(() => { });
|
|
515
522
|
}
|
|
516
523
|
return;
|
|
517
524
|
}
|
|
@@ -520,7 +527,7 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
520
527
|
const pct = managed.session.contextWindow > 0
|
|
521
528
|
? ((used / managed.session.contextWindow) * 100).toFixed(1)
|
|
522
529
|
: '?';
|
|
523
|
-
await msg
|
|
530
|
+
await sendUserVisible(msg, [
|
|
524
531
|
`Mode: ${managed.config.mode ?? 'code'}`,
|
|
525
532
|
`Approval: ${managed.config.approval_mode}`,
|
|
526
533
|
`Model: ${managed.session.model}`,
|
|
@@ -537,20 +544,20 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
537
544
|
const config = await loadRuntimes();
|
|
538
545
|
const redacted = redactConfig(config);
|
|
539
546
|
if (!redacted.hosts.length) {
|
|
540
|
-
await msg
|
|
547
|
+
await sendUserVisible(msg, 'No hosts configured. Use `idlehands hosts add` in CLI.').catch(() => { });
|
|
541
548
|
return;
|
|
542
549
|
}
|
|
543
550
|
const lines = redacted.hosts.map((h) => `${h.enabled ? '🟢' : '🔴'} ${h.display_name} (\`${h.id}\`)\n Transport: ${h.transport}`);
|
|
544
551
|
const chunks = splitDiscord(lines.join('\n\n'));
|
|
545
552
|
for (const [i, chunk] of chunks.entries()) {
|
|
546
553
|
if (i === 0)
|
|
547
|
-
await msg
|
|
554
|
+
await sendUserVisible(msg, chunk).catch(() => { });
|
|
548
555
|
else
|
|
549
556
|
await msg.channel.send(chunk).catch(() => { });
|
|
550
557
|
}
|
|
551
558
|
}
|
|
552
559
|
catch (e) {
|
|
553
|
-
await msg
|
|
560
|
+
await sendUserVisible(msg, `❌ Failed to load hosts: ${e?.message ?? String(e)}`).catch(() => { });
|
|
554
561
|
}
|
|
555
562
|
return;
|
|
556
563
|
}
|
|
@@ -560,20 +567,20 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
560
567
|
const config = await loadRuntimes();
|
|
561
568
|
const redacted = redactConfig(config);
|
|
562
569
|
if (!redacted.backends.length) {
|
|
563
|
-
await msg
|
|
570
|
+
await sendUserVisible(msg, 'No backends configured. Use `idlehands backends add` in CLI.').catch(() => { });
|
|
564
571
|
return;
|
|
565
572
|
}
|
|
566
573
|
const lines = redacted.backends.map((b) => `${b.enabled ? '🟢' : '🔴'} ${b.display_name} (\`${b.id}\`)\n Type: ${b.type}`);
|
|
567
574
|
const chunks = splitDiscord(lines.join('\n\n'));
|
|
568
575
|
for (const [i, chunk] of chunks.entries()) {
|
|
569
576
|
if (i === 0)
|
|
570
|
-
await msg
|
|
577
|
+
await sendUserVisible(msg, chunk).catch(() => { });
|
|
571
578
|
else
|
|
572
579
|
await msg.channel.send(chunk).catch(() => { });
|
|
573
580
|
}
|
|
574
581
|
}
|
|
575
582
|
catch (e) {
|
|
576
|
-
await msg
|
|
583
|
+
await sendUserVisible(msg, `❌ Failed to load backends: ${e?.message ?? String(e)}`).catch(() => { });
|
|
577
584
|
}
|
|
578
585
|
return;
|
|
579
586
|
}
|
|
@@ -582,20 +589,20 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
582
589
|
const { loadRuntimes } = await import('../runtime/store.js');
|
|
583
590
|
const config = await loadRuntimes();
|
|
584
591
|
if (!config.models.length) {
|
|
585
|
-
await msg
|
|
592
|
+
await sendUserVisible(msg, 'No runtime models configured.').catch(() => { });
|
|
586
593
|
return;
|
|
587
594
|
}
|
|
588
595
|
const lines = config.models.map((m) => `${m.enabled ? '🟢' : '🔴'} ${m.display_name} (\`${m.id}\`)\n Source: \`${m.source}\``);
|
|
589
596
|
const chunks = splitDiscord(lines.join('\n\n'));
|
|
590
597
|
for (const [i, chunk] of chunks.entries()) {
|
|
591
598
|
if (i === 0)
|
|
592
|
-
await msg
|
|
599
|
+
await sendUserVisible(msg, chunk).catch(() => { });
|
|
593
600
|
else
|
|
594
601
|
await msg.channel.send(chunk).catch(() => { });
|
|
595
602
|
}
|
|
596
603
|
}
|
|
597
604
|
catch (e) {
|
|
598
|
-
await msg
|
|
605
|
+
await sendUserVisible(msg, `❌ Failed to load runtime models: ${e?.message ?? String(e)}`).catch(() => { });
|
|
599
606
|
}
|
|
600
607
|
return;
|
|
601
608
|
}
|
|
@@ -604,7 +611,7 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
604
611
|
const { loadActiveRuntime } = await import('../runtime/executor.js');
|
|
605
612
|
const active = await loadActiveRuntime();
|
|
606
613
|
if (!active) {
|
|
607
|
-
await msg
|
|
614
|
+
await sendUserVisible(msg, 'No active runtime.').catch(() => { });
|
|
608
615
|
return;
|
|
609
616
|
}
|
|
610
617
|
const lines = [
|
|
@@ -619,13 +626,13 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
619
626
|
const chunks = splitDiscord(lines.join('\n'));
|
|
620
627
|
for (const [i, chunk] of chunks.entries()) {
|
|
621
628
|
if (i === 0)
|
|
622
|
-
await msg
|
|
629
|
+
await sendUserVisible(msg, chunk).catch(() => { });
|
|
623
630
|
else
|
|
624
631
|
await msg.channel.send(chunk).catch(() => { });
|
|
625
632
|
}
|
|
626
633
|
}
|
|
627
634
|
catch (e) {
|
|
628
|
-
await msg
|
|
635
|
+
await sendUserVisible(msg, `❌ Failed to read runtime status: ${e?.message ?? String(e)}`).catch(() => { });
|
|
629
636
|
}
|
|
630
637
|
return;
|
|
631
638
|
}
|
|
@@ -633,7 +640,7 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
633
640
|
try {
|
|
634
641
|
const modelId = content.slice('/switch'.length).trim();
|
|
635
642
|
if (!modelId) {
|
|
636
|
-
await msg
|
|
643
|
+
await sendUserVisible(msg, 'Usage: /switch <model-id>').catch(() => { });
|
|
637
644
|
return;
|
|
638
645
|
}
|
|
639
646
|
const { plan } = await import('../runtime/planner.js');
|
|
@@ -643,14 +650,14 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
643
650
|
const active = await loadActiveRuntime();
|
|
644
651
|
const result = plan({ modelId, mode: 'live' }, rtConfig, active);
|
|
645
652
|
if (!result.ok) {
|
|
646
|
-
await msg
|
|
653
|
+
await sendUserVisible(msg, `❌ Plan failed: ${result.reason}`).catch(() => { });
|
|
647
654
|
return;
|
|
648
655
|
}
|
|
649
656
|
if (result.reuse) {
|
|
650
|
-
await msg
|
|
657
|
+
await sendUserVisible(msg, '✅ Runtime already active and healthy.').catch(() => { });
|
|
651
658
|
return;
|
|
652
659
|
}
|
|
653
|
-
const statusMsg = await msg
|
|
660
|
+
const statusMsg = await sendUserVisible(msg, `⏳ Switching to \`${result.model.display_name}\`...`).catch(() => null);
|
|
654
661
|
const execResult = await execute(result, {
|
|
655
662
|
onStep: async (step, status) => {
|
|
656
663
|
if (status === 'done' && statusMsg) {
|
|
@@ -658,7 +665,7 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
658
665
|
}
|
|
659
666
|
},
|
|
660
667
|
confirm: async (prompt) => {
|
|
661
|
-
await msg
|
|
668
|
+
await sendUserVisible(msg, `⚠️ ${prompt}\nAuto-approving for bot context.`).catch(() => { });
|
|
662
669
|
return true;
|
|
663
670
|
},
|
|
664
671
|
});
|
|
@@ -667,7 +674,7 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
667
674
|
await statusMsg.edit(`✅ Switched to \`${result.model.display_name}\``).catch(() => { });
|
|
668
675
|
}
|
|
669
676
|
else {
|
|
670
|
-
await msg
|
|
677
|
+
await sendUserVisible(msg, `✅ Switched to \`${result.model.display_name}\``).catch(() => { });
|
|
671
678
|
}
|
|
672
679
|
}
|
|
673
680
|
else {
|
|
@@ -676,12 +683,12 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
676
683
|
await statusMsg.edit(err).catch(() => { });
|
|
677
684
|
}
|
|
678
685
|
else {
|
|
679
|
-
await msg
|
|
686
|
+
await sendUserVisible(msg, err).catch(() => { });
|
|
680
687
|
}
|
|
681
688
|
}
|
|
682
689
|
}
|
|
683
690
|
catch (e) {
|
|
684
|
-
await msg
|
|
691
|
+
await sendUserVisible(msg, `❌ Switch failed: ${e?.message ?? String(e)}`).catch(() => { });
|
|
685
692
|
}
|
|
686
693
|
return;
|
|
687
694
|
}
|
|
@@ -692,11 +699,11 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
692
699
|
}
|
|
693
700
|
if (managed.inFlight) {
|
|
694
701
|
if (managed.pendingQueue.length >= maxQueue) {
|
|
695
|
-
await msg
|
|
702
|
+
await sendUserVisible(msg, `⏳ Queue full (${managed.pendingQueue.length}/${maxQueue}). Use /cancel.`).catch(() => { });
|
|
696
703
|
return;
|
|
697
704
|
}
|
|
698
705
|
managed.pendingQueue.push(msg);
|
|
699
|
-
await msg
|
|
706
|
+
await sendUserVisible(msg, `⏳ Queued (#${managed.pendingQueue.length}).`).catch(() => { });
|
|
700
707
|
return;
|
|
701
708
|
}
|
|
702
709
|
console.error(`[bot:discord] ${msg.author.id}: ${content.slice(0, 50)}${content.length > 50 ? '…' : ''}`);
|
|
@@ -708,40 +715,40 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
708
715
|
const sub = args.split(/\s+/)[0]?.toLowerCase() || '';
|
|
709
716
|
if (!sub || sub === 'status') {
|
|
710
717
|
if (!managed.antonActive) {
|
|
711
|
-
await msg
|
|
718
|
+
await sendUserVisible(msg, 'No Anton run in progress.').catch(() => { });
|
|
712
719
|
}
|
|
713
720
|
else if (managed.antonProgress) {
|
|
714
|
-
await msg
|
|
721
|
+
await sendUserVisible(msg, formatProgressBar(managed.antonProgress)).catch(() => { });
|
|
715
722
|
}
|
|
716
723
|
else {
|
|
717
|
-
await msg
|
|
724
|
+
await sendUserVisible(msg, '🤖 Anton is running (no progress data yet).').catch(() => { });
|
|
718
725
|
}
|
|
719
726
|
return;
|
|
720
727
|
}
|
|
721
728
|
if (sub === 'stop') {
|
|
722
729
|
if (!managed.antonActive || !managed.antonAbortSignal) {
|
|
723
|
-
await msg
|
|
730
|
+
await sendUserVisible(msg, 'No Anton run in progress.').catch(() => { });
|
|
724
731
|
return;
|
|
725
732
|
}
|
|
726
733
|
managed.antonAbortSignal.aborted = true;
|
|
727
|
-
await msg
|
|
734
|
+
await sendUserVisible(msg, '🛑 Anton stop requested.').catch(() => { });
|
|
728
735
|
return;
|
|
729
736
|
}
|
|
730
737
|
if (sub === 'last') {
|
|
731
738
|
if (!managed.antonLastResult) {
|
|
732
|
-
await msg
|
|
739
|
+
await sendUserVisible(msg, 'No previous Anton run.').catch(() => { });
|
|
733
740
|
return;
|
|
734
741
|
}
|
|
735
|
-
await msg
|
|
742
|
+
await sendUserVisible(msg, formatRunSummary(managed.antonLastResult)).catch(() => { });
|
|
736
743
|
return;
|
|
737
744
|
}
|
|
738
745
|
const filePart = sub === 'run' ? args.replace(/^\S+\s*/, '').trim() : args;
|
|
739
746
|
if (!filePart) {
|
|
740
|
-
await msg
|
|
747
|
+
await sendUserVisible(msg, '/anton <file> — start | /anton status | /anton stop | /anton last').catch(() => { });
|
|
741
748
|
return;
|
|
742
749
|
}
|
|
743
750
|
if (managed.antonActive) {
|
|
744
|
-
await msg
|
|
751
|
+
await sendUserVisible(msg, '⚠️ Anton is already running. Use /anton stop first.').catch(() => { });
|
|
745
752
|
return;
|
|
746
753
|
}
|
|
747
754
|
const cwd = managed.config.dir || process.cwd();
|
|
@@ -750,7 +757,7 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
750
757
|
await fs.stat(filePath);
|
|
751
758
|
}
|
|
752
759
|
catch {
|
|
753
|
-
await msg
|
|
760
|
+
await sendUserVisible(msg, `File not found: ${filePath}`).catch(() => { });
|
|
754
761
|
return;
|
|
755
762
|
}
|
|
756
763
|
const defaults = managed.config.anton || {};
|
|
@@ -814,7 +821,7 @@ export async function startDiscordBot(config, botConfig) {
|
|
|
814
821
|
pendingCount = tf.pending.length;
|
|
815
822
|
}
|
|
816
823
|
catch { }
|
|
817
|
-
await msg
|
|
824
|
+
await sendUserVisible(msg, `🤖 Anton started on ${filePart} (${pendingCount} tasks pending)`).catch(() => { });
|
|
818
825
|
runAnton({
|
|
819
826
|
config: runConfig,
|
|
820
827
|
idlehandsConfig: managed.config,
|