linkgravity 1.7.3 → 1.7.5
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/bin/cli.js +161 -43
- package/bin/pm2.js +3 -0
- package/bin/setup.js +2 -2
- package/npm-scripts/venv-paths.js +4 -0
- package/package.json +1 -1
- package/src/cogs/general_cog.py +2 -1
- package/src/cogs/voice_cog.py +1 -1
- package/src/config.py +18 -1
- package/src/core/agy_runner.py +1 -1
- package/src/main_discord.py +2 -2
- package/src/main_slack.py +4 -1
- package/src/services/response.py +3 -3
package/bin/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ const {
|
|
|
14
14
|
LGY_SCRIPT_PATH,
|
|
15
15
|
} = require('./platforms');
|
|
16
16
|
|
|
17
|
-
const {
|
|
17
|
+
const { daemonPython, isWin } = require('../npm-scripts/venv-paths');
|
|
18
18
|
const { isEnvironmentReady } = require('../npm-scripts/ensure-env');
|
|
19
19
|
|
|
20
20
|
const cmd = process.argv[2];
|
|
@@ -203,15 +203,34 @@ function verifyStartup() {
|
|
|
203
203
|
cwd: PM2_CWD,
|
|
204
204
|
});
|
|
205
205
|
|
|
206
|
+
const baseline = getPm2Proc()?.pm2_env?.restart_time ?? 0;
|
|
207
|
+
|
|
206
208
|
let settled = false;
|
|
207
209
|
const finish = (ok) => {
|
|
208
210
|
if (settled) return;
|
|
209
211
|
settled = true;
|
|
210
212
|
clearTimeout(timer);
|
|
213
|
+
clearInterval(watchdog);
|
|
211
214
|
cp.kill();
|
|
212
215
|
resolve(ok);
|
|
213
216
|
};
|
|
214
217
|
|
|
218
|
+
// A process that exits on startup never logs anything for the stream below to match, so
|
|
219
|
+
// pm2's own counters are the only signal that it is dying and being restarted.
|
|
220
|
+
const watchdog = setInterval(() => {
|
|
221
|
+
const proc = getPm2Proc();
|
|
222
|
+
if (!proc) return;
|
|
223
|
+
const { status, restart_time: restarts = 0 } = proc.pm2_env;
|
|
224
|
+
if (status === 'errored' || restarts > baseline) {
|
|
225
|
+
console.log(
|
|
226
|
+
`\n\n${color.yellow}❌ The daemon keeps exiting - pm2 has restarted it ` +
|
|
227
|
+
`${restarts - baseline} time(s) (status: ${status}).${color.reset}`,
|
|
228
|
+
);
|
|
229
|
+
console.log(` Run ${color.cyan}lgy logs${color.reset} to see why.\n`);
|
|
230
|
+
finish(false);
|
|
231
|
+
}
|
|
232
|
+
}, 2000);
|
|
233
|
+
|
|
215
234
|
let timer = setTimeout(() => {
|
|
216
235
|
console.log(
|
|
217
236
|
`\n\n${color.yellow}⏳ Startup verification timed out. Run 'lgy logs' to check status manually.${color.reset}`,
|
|
@@ -302,7 +321,31 @@ function findAgyBin() {
|
|
|
302
321
|
return null;
|
|
303
322
|
}
|
|
304
323
|
|
|
305
|
-
|
|
324
|
+
// Returns null when the daemon can be started, or the reason it can't.
|
|
325
|
+
function launchBlocker() {
|
|
326
|
+
const settings = getSettings();
|
|
327
|
+
const anyConfigured = Object.keys(PLATFORMS).some(
|
|
328
|
+
(key) => platformState(key, settings).configured,
|
|
329
|
+
);
|
|
330
|
+
if (!anyConfigured) {
|
|
331
|
+
return (
|
|
332
|
+
'No messenger is configured yet - set up at least one of Discord, Telegram, or ' +
|
|
333
|
+
`Slack first: ${color.cyan}lgy setup${color.reset}`
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
if (!findAgyBin()) {
|
|
337
|
+
return (
|
|
338
|
+
"Couldn't find the agy CLI (checked $AGY_BIN_PATH, ~/.local/bin/agy, and PATH). " +
|
|
339
|
+
'Install/configure agy first, or set the AGY_BIN_PATH environment variable to its location.'
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// pm2 keys an app by name AND script path, so an install that moved (node version switch, npm prefix
|
|
346
|
+
// change, npm link) registers a second app under the same name instead of replacing the first, and
|
|
347
|
+
// every name-based pm2 command then acts on both at once.
|
|
348
|
+
function getPm2Procs() {
|
|
306
349
|
const jlist = spawnSync(process.execPath, [PM2_BIN, 'jlist'], { stdio: 'pipe' });
|
|
307
350
|
if (jlist.status !== 0) return null;
|
|
308
351
|
|
|
@@ -313,7 +356,7 @@ function getPm2Proc() {
|
|
|
313
356
|
if (out[i] !== '[') continue;
|
|
314
357
|
try {
|
|
315
358
|
const procs = JSON.parse(out.slice(i));
|
|
316
|
-
if (Array.isArray(procs)) return procs.
|
|
359
|
+
if (Array.isArray(procs)) return procs.filter((p) => p.name === LGY_PM2_NAME);
|
|
317
360
|
} catch (e) {}
|
|
318
361
|
}
|
|
319
362
|
|
|
@@ -323,6 +366,71 @@ function getPm2Proc() {
|
|
|
323
366
|
return null;
|
|
324
367
|
}
|
|
325
368
|
|
|
369
|
+
function isOurRegistration(proc) {
|
|
370
|
+
const registered = proc.pm2_env.pm_exec_path || '';
|
|
371
|
+
if (!isWin) return registered === LGY_SCRIPT_PATH;
|
|
372
|
+
return registered.toLowerCase() === LGY_SCRIPT_PATH.toLowerCase();
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function printRegistrations(procs) {
|
|
376
|
+
for (const proc of procs) {
|
|
377
|
+
const mine = isOurRegistration(proc) ? ' <- this install' : '';
|
|
378
|
+
console.log(
|
|
379
|
+
` ${String(proc.pm2_env.status).padEnd(8)} ${proc.pm2_env.pm_exec_path}${mine}`,
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
let duplicatesReported = false;
|
|
385
|
+
function pickPm2Proc(procs) {
|
|
386
|
+
if (!procs || procs.length === 0) return null;
|
|
387
|
+
|
|
388
|
+
if (procs.length > 1 && !duplicatesReported) {
|
|
389
|
+
duplicatesReported = true;
|
|
390
|
+
console.log(
|
|
391
|
+
`\n${color.yellow}⚠${color.reset} pm2 has ${procs.length} apps registered as '${LGY_PM2_NAME}' - only this install's should be:`,
|
|
392
|
+
);
|
|
393
|
+
printRegistrations(procs);
|
|
394
|
+
console.log(
|
|
395
|
+
` Everything below reports on one of them. Run ${color.cyan}lgy start${color.reset} to drop the stale ones.\n`,
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const isOnline = (proc) => proc.pm2_env.status === 'online';
|
|
400
|
+
return (
|
|
401
|
+
procs.find((proc) => isOnline(proc) && isOurRegistration(proc)) ||
|
|
402
|
+
procs.find(isOnline) ||
|
|
403
|
+
procs.find(isOurRegistration) ||
|
|
404
|
+
procs[0]
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function getPm2Proc() {
|
|
409
|
+
return pickPm2Proc(getPm2Procs());
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function startDaemon() {
|
|
413
|
+
runPm2([
|
|
414
|
+
'start',
|
|
415
|
+
LGY_SCRIPT_PATH,
|
|
416
|
+
'--interpreter',
|
|
417
|
+
daemonPython,
|
|
418
|
+
'--name',
|
|
419
|
+
LGY_PM2_NAME,
|
|
420
|
+
'--update-env',
|
|
421
|
+
]);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// The saved autostart list keeps the removed paths until pm2 save runs, so a reboot restores them.
|
|
425
|
+
function clearRegistrations(procs) {
|
|
426
|
+
console.log(
|
|
427
|
+
`\n${color.yellow}⚠${color.reset} pm2 has ${procs.length} app(s) registered as '${LGY_PM2_NAME}', not all from this install:`,
|
|
428
|
+
);
|
|
429
|
+
printRegistrations(procs);
|
|
430
|
+
console.log(' Removing all of them and registering this install alone.\n');
|
|
431
|
+
runPm2(['delete', LGY_PM2_NAME]);
|
|
432
|
+
}
|
|
433
|
+
|
|
326
434
|
// Best-effort: pm2 has no API for "is this registered to start on boot", so this checks the OS directly and returns null (unknown) if that check itself isn't available.
|
|
327
435
|
function isAutostartEnabled() {
|
|
328
436
|
if (isWin) return null;
|
|
@@ -363,8 +471,11 @@ if (cmd === 'version' || cmd === '-v' || cmd === '--version') {
|
|
|
363
471
|
const pkg = require('../package.json');
|
|
364
472
|
console.log(`linkgravity v${pkg.version}`);
|
|
365
473
|
} else if (cmd === 'start') {
|
|
366
|
-
const
|
|
367
|
-
|
|
474
|
+
const registered = getPm2Procs() || [];
|
|
475
|
+
const stale = registered.filter((proc) => !isOurRegistration(proc));
|
|
476
|
+
const existing = registered.find(isOurRegistration);
|
|
477
|
+
|
|
478
|
+
if (!stale.length && existing && existing.pm2_env.status === 'online') {
|
|
368
479
|
console.log(
|
|
369
480
|
`\n${color.yellow}⚠${color.reset} LinkGravity is already running. ` +
|
|
370
481
|
`Use ${color.cyan}lgy restart${color.reset} to apply changes, or ${color.cyan}lgy stop${color.reset} first.\n`,
|
|
@@ -372,24 +483,9 @@ if (cmd === 'version' || cmd === '-v' || cmd === '--version') {
|
|
|
372
483
|
process.exit(1);
|
|
373
484
|
}
|
|
374
485
|
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
(
|
|
378
|
-
);
|
|
379
|
-
if (!anyConfigured) {
|
|
380
|
-
console.log(
|
|
381
|
-
`\n${color.yellow}⚠${color.reset} No messenger is configured yet - ` +
|
|
382
|
-
`set up at least one of Discord, Telegram, or Slack first: ${color.cyan}lgy setup${color.reset}\n`,
|
|
383
|
-
);
|
|
384
|
-
process.exit(1);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
if (!findAgyBin()) {
|
|
388
|
-
console.log(
|
|
389
|
-
`\n${color.yellow}⚠${color.reset} Couldn't find the agy CLI ` +
|
|
390
|
-
`(checked $AGY_BIN_PATH, ~/.local/bin/agy, and PATH). Install/configure agy first, ` +
|
|
391
|
-
`or set the AGY_BIN_PATH environment variable to its location.\n`,
|
|
392
|
-
);
|
|
486
|
+
const blocker = launchBlocker();
|
|
487
|
+
if (blocker) {
|
|
488
|
+
console.log(`\n${color.yellow}⚠${color.reset} ${blocker}\n`);
|
|
393
489
|
process.exit(1);
|
|
394
490
|
}
|
|
395
491
|
|
|
@@ -400,16 +496,11 @@ if (cmd === 'version' || cmd === '-v' || cmd === '--version') {
|
|
|
400
496
|
|
|
401
497
|
repairHookRegistration();
|
|
402
498
|
|
|
499
|
+
if (stale.length) clearRegistrations(registered);
|
|
500
|
+
|
|
403
501
|
info('Starting LinkGravity daemon...');
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
LGY_SCRIPT_PATH,
|
|
407
|
-
'--interpreter',
|
|
408
|
-
pythonExe,
|
|
409
|
-
'--name',
|
|
410
|
-
LGY_PM2_NAME,
|
|
411
|
-
'--update-env',
|
|
412
|
-
]);
|
|
502
|
+
startDaemon();
|
|
503
|
+
if (stale.length) runPm2(['save']);
|
|
413
504
|
verifyStartup().then((ok) => process.exit(ok ? 0 : 1));
|
|
414
505
|
} else if (cmd === 'stop') {
|
|
415
506
|
info('Stopping LinkGravity daemon...');
|
|
@@ -417,6 +508,18 @@ if (cmd === 'version' || cmd === '-v' || cmd === '--version') {
|
|
|
417
508
|
runPm2(['reset', LGY_PM2_NAME]);
|
|
418
509
|
success('Daemon stopped successfully.\n');
|
|
419
510
|
} else if (cmd === 'restart') {
|
|
511
|
+
const registered = getPm2Procs() || [];
|
|
512
|
+
if (registered.some((proc) => !isOurRegistration(proc))) {
|
|
513
|
+
console.log(
|
|
514
|
+
`\n${color.yellow}⚠${color.reset} pm2 has ${registered.length} app(s) registered as '${LGY_PM2_NAME}', and a restart would start every one of them:`,
|
|
515
|
+
);
|
|
516
|
+
printRegistrations(registered);
|
|
517
|
+
console.log(
|
|
518
|
+
` Run ${color.cyan}lgy start${color.reset} instead - it drops the stale ones first.\n`,
|
|
519
|
+
);
|
|
520
|
+
process.exit(1);
|
|
521
|
+
}
|
|
522
|
+
|
|
420
523
|
info('Restarting LinkGravity daemon...');
|
|
421
524
|
runPm2(['restart', LGY_PM2_NAME, '--update-env']);
|
|
422
525
|
runPm2(['reset', LGY_PM2_NAME]);
|
|
@@ -615,8 +718,10 @@ if (cmd === 'version' || cmd === '-v' || cmd === '--version') {
|
|
|
615
718
|
process.exit(0);
|
|
616
719
|
}
|
|
617
720
|
|
|
618
|
-
const
|
|
721
|
+
const registeredBeforeUpdate = getPm2Procs() || [];
|
|
722
|
+
const procBeforeUpdate = pickPm2Proc(registeredBeforeUpdate);
|
|
619
723
|
const wasOnline = !!procBeforeUpdate && procBeforeUpdate.pm2_env.status === 'online';
|
|
724
|
+
const hadStale = registeredBeforeUpdate.some((proc) => !isOurRegistration(proc));
|
|
620
725
|
|
|
621
726
|
info(`Updating: v${currentVersion} -> v${latestVersion}...`);
|
|
622
727
|
const installResult = runNpm(['install', '-g', 'linkgravity@latest'], { stdio: 'inherit' });
|
|
@@ -634,16 +739,27 @@ if (cmd === 'version' || cmd === '-v' || cmd === '--version') {
|
|
|
634
739
|
repairHookRegistration({ fresh: true });
|
|
635
740
|
|
|
636
741
|
if (!procBeforeUpdate) {
|
|
742
|
+
const blocker = launchBlocker();
|
|
743
|
+
if (blocker) {
|
|
744
|
+
console.log(`\n${color.yellow}⚠${color.reset} ${blocker}\n`);
|
|
745
|
+
success('Update finished - the daemon was left stopped.\n');
|
|
746
|
+
process.exit(0);
|
|
747
|
+
}
|
|
637
748
|
info("Daemon wasn't running - starting it fresh...");
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
'
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
749
|
+
startDaemon();
|
|
750
|
+
verifyStartup().then((ok) => process.exit(ok ? 0 : 1));
|
|
751
|
+
} else if (hadStale) {
|
|
752
|
+
clearRegistrations(registeredBeforeUpdate);
|
|
753
|
+
if (!wasOnline) {
|
|
754
|
+
runPm2(['save']);
|
|
755
|
+
success(
|
|
756
|
+
`Daemon was stopped - leaving it stopped. Run 'lgy start' when you're ready.\n`,
|
|
757
|
+
);
|
|
758
|
+
process.exit(0);
|
|
759
|
+
}
|
|
760
|
+
info('Starting the daemon from this install...');
|
|
761
|
+
startDaemon();
|
|
762
|
+
runPm2(['save']);
|
|
647
763
|
verifyStartup().then((ok) => process.exit(ok ? 0 : 1));
|
|
648
764
|
} else if (wasOnline) {
|
|
649
765
|
info('Restarting daemon to apply the update...');
|
|
@@ -722,7 +838,9 @@ if (cmd === 'version' || cmd === '-v' || cmd === '--version') {
|
|
|
722
838
|
spawnSync(process.argv[0], [process.argv[1], action], { stdio: 'inherit' });
|
|
723
839
|
})();
|
|
724
840
|
} else {
|
|
725
|
-
|
|
841
|
+
// Not stdout: an eval "$(lgy ...)" line in a shell rc would run this message as commands.
|
|
842
|
+
console.error(
|
|
726
843
|
`\n❌ Unknown command: ${cmd || 'none'}\n💡 Run 'lgy help' to see available commands.`,
|
|
727
844
|
);
|
|
845
|
+
process.exit(1);
|
|
728
846
|
}
|
package/bin/pm2.js
CHANGED
|
@@ -10,6 +10,9 @@ function pm2Env() {
|
|
|
10
10
|
...process.env,
|
|
11
11
|
// pm2 gives Python a pipe not a TTY, so it block-buffers stdout and can sit on log lines indefinitely - force line buffering.
|
|
12
12
|
PYTHONUNBUFFERED: '1',
|
|
13
|
+
// Without this Python inherits the console codepage (cp949 on Korean Windows) and loguru
|
|
14
|
+
// drops every line it can't encode - including the one verifyStartup waits for.
|
|
15
|
+
PYTHONIOENCODING: 'utf-8',
|
|
13
16
|
// pm2 merges --update-env rather than replacing, so a LOG_LEVEL from an earlier run
|
|
14
17
|
// survives unless a value is passed every time.
|
|
15
18
|
LOG_LEVEL: process.env.LOG_LEVEL || 'INFO',
|
package/bin/setup.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const p = require('@clack/prompts');
|
|
2
2
|
const { spawnSync } = require('child_process');
|
|
3
|
-
const {
|
|
3
|
+
const { daemonPython } = require('../npm-scripts/venv-paths');
|
|
4
4
|
const { PM2_BIN, PM2_CWD, pm2Env } = require('./pm2');
|
|
5
5
|
const {
|
|
6
6
|
getSettings,
|
|
@@ -224,7 +224,7 @@ function startOrRestartDaemon(pm2Name, scriptPath, label) {
|
|
|
224
224
|
'start',
|
|
225
225
|
scriptPath,
|
|
226
226
|
'--interpreter',
|
|
227
|
-
|
|
227
|
+
daemonPython,
|
|
228
228
|
'--name',
|
|
229
229
|
pm2Name,
|
|
230
230
|
]);
|
|
@@ -40,6 +40,10 @@ module.exports = {
|
|
|
40
40
|
isWin,
|
|
41
41
|
venvBinDir,
|
|
42
42
|
python: venvBin('python'),
|
|
43
|
+
// Only for pm2's --interpreter: pm2 spawns with detached:true, which on Windows forces a
|
|
44
|
+
// console window and ignores its own windowsHide option. pythonw is GUI-subsystem so no
|
|
45
|
+
// console is ever allocated, and pm2 pipes stdio anyway so no output is lost.
|
|
46
|
+
daemonPython: venvBin(isWin ? 'pythonw' : 'python'),
|
|
43
47
|
pip: venvBin('pip'),
|
|
44
48
|
preCommit: venvBin('pre-commit'),
|
|
45
49
|
};
|
package/package.json
CHANGED
package/src/cogs/general_cog.py
CHANGED
|
@@ -51,7 +51,7 @@ async def fetch_models_background():
|
|
|
51
51
|
import time
|
|
52
52
|
|
|
53
53
|
try:
|
|
54
|
-
from config import AGY_BIN
|
|
54
|
+
from config import AGY_BIN, CREATION_FLAGS
|
|
55
55
|
|
|
56
56
|
logger.debug(f"Starting fetch_models_background using AGY_BIN: {AGY_BIN}")
|
|
57
57
|
env = os.environ.copy()
|
|
@@ -63,6 +63,7 @@ async def fetch_models_background():
|
|
|
63
63
|
stderr=asyncio.subprocess.PIPE,
|
|
64
64
|
stdin=asyncio.subprocess.DEVNULL,
|
|
65
65
|
env=env,
|
|
66
|
+
creationflags=CREATION_FLAGS,
|
|
66
67
|
)
|
|
67
68
|
try:
|
|
68
69
|
stdout, stderr = await asyncio.wait_for(p.communicate(), timeout=30.0)
|
package/src/cogs/voice_cog.py
CHANGED
|
@@ -583,7 +583,7 @@ class VoiceCog(commands.Cog):
|
|
|
583
583
|
wake_syllables = len(re.sub(r"[^\w가-힣]", "", matched_wake_word or ""))
|
|
584
584
|
min_prefix_similarity = 0.55 if wake_syllables <= 2 else 0.35
|
|
585
585
|
if is_waking_up and prefix_similarity is not None and prefix_similarity < min_prefix_similarity:
|
|
586
|
-
self.logger.
|
|
586
|
+
self.logger.debug(
|
|
587
587
|
f"STT: ignoring wake - '{text}' doesn't resemble '{matched_wake_word}' "
|
|
588
588
|
f"(prefix similarity {prefix_similarity:.2f}, needed {min_prefix_similarity:.2f})"
|
|
589
589
|
)
|
package/src/config.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import secrets
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
3
5
|
from pathlib import Path
|
|
4
6
|
|
|
5
7
|
from core.atomic_io import atomic_write_json, safe_load_json
|
|
@@ -158,7 +160,22 @@ MODEL_CHOICES = {
|
|
|
158
160
|
"pro": "Gemini 3.1 Pro",
|
|
159
161
|
}
|
|
160
162
|
|
|
161
|
-
|
|
163
|
+
|
|
164
|
+
def _resolve_agy_bin() -> str:
|
|
165
|
+
fallback = str(Path.home() / ".local/bin/agy")
|
|
166
|
+
for candidate in (os.getenv("AGY_BIN_PATH"), fallback):
|
|
167
|
+
if candidate and Path(candidate).is_file():
|
|
168
|
+
return candidate
|
|
169
|
+
# Windows installs agy under AppData and adds it to PATH instead.
|
|
170
|
+
return shutil.which("agy") or fallback
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# Must stay in sync with findAgyBin() in bin/cli.js.
|
|
174
|
+
AGY_BIN = _resolve_agy_bin()
|
|
175
|
+
|
|
176
|
+
# pm2 spawns the daemon with detached:true, so on Windows it owns no console; without this
|
|
177
|
+
# flag every child it launches gets a console window of its own.
|
|
178
|
+
CREATION_FLAGS = subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0
|
|
162
179
|
|
|
163
180
|
session_manager = SessionManager(DATA_DIR)
|
|
164
181
|
|
package/src/core/agy_runner.py
CHANGED
|
@@ -163,7 +163,7 @@ async def run_agy(
|
|
|
163
163
|
if os.name == "nt":
|
|
164
164
|
import subprocess
|
|
165
165
|
|
|
166
|
-
kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP
|
|
166
|
+
kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.CREATE_NO_WINDOW
|
|
167
167
|
elif hasattr(os, "setsid"):
|
|
168
168
|
kwargs["preexec_fn"] = os.setsid
|
|
169
169
|
|
package/src/main_discord.py
CHANGED
|
@@ -11,7 +11,7 @@ from functools import partial
|
|
|
11
11
|
import discord
|
|
12
12
|
from discord.ext import commands
|
|
13
13
|
|
|
14
|
-
from config import DISCORD_TOKEN, logger, session_manager
|
|
14
|
+
from config import CREATION_FLAGS, DISCORD_TOKEN, logger, session_manager
|
|
15
15
|
from handlers.message_router import handle_message
|
|
16
16
|
from messengers.discord_adapter import DiscordAdapter
|
|
17
17
|
from messengers.registry import register_adapter
|
|
@@ -128,7 +128,7 @@ async def status_updater_task():
|
|
|
128
128
|
|
|
129
129
|
|
|
130
130
|
def _spawn_voice_process(voice_dir: str) -> subprocess.Popen:
|
|
131
|
-
return subprocess.Popen(["node", "index.js"], cwd=voice_dir)
|
|
131
|
+
return subprocess.Popen(["node", "index.js"], cwd=voice_dir, creationflags=CREATION_FLAGS)
|
|
132
132
|
|
|
133
133
|
|
|
134
134
|
async def _supervise_voice_process(voice_dir: str):
|
package/src/main_slack.py
CHANGED
|
@@ -258,7 +258,7 @@ async def run_slack(stop_event: asyncio.Event) -> None:
|
|
|
258
258
|
|
|
259
259
|
app, adapter = build_app()
|
|
260
260
|
try:
|
|
261
|
-
await adapter.resolve_bot_user_id()
|
|
261
|
+
bot_user_id = await adapter.resolve_bot_user_id()
|
|
262
262
|
except SlackApiError as e:
|
|
263
263
|
logger.critical(f"Slack auth_test failed - check slack_bot_token: {e}")
|
|
264
264
|
return
|
|
@@ -266,6 +266,9 @@ async def run_slack(stop_event: asyncio.Event) -> None:
|
|
|
266
266
|
handler = AsyncSocketModeHandler(app, SLACK_APP_TOKEN)
|
|
267
267
|
logger.info("✅ Slack bot starting (Socket Mode)...")
|
|
268
268
|
await handler.connect_async()
|
|
269
|
+
# cli.js's verifyStartup() waits for this exact sentence - without it a Slack-only install
|
|
270
|
+
# never reports a successful startup and every lgy start/restart/update times out.
|
|
271
|
+
logger.info(f"✅ Bot is fully online and ready! Logged in as {bot_user_id}")
|
|
269
272
|
platform_health.set_status("slack", "running")
|
|
270
273
|
try:
|
|
271
274
|
await stop_event.wait()
|
package/src/services/response.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
from typing import Any
|
|
3
3
|
|
|
4
|
-
from config import MAX_EMBED_LEN,
|
|
4
|
+
from config import MAX_EMBED_LEN, bot_settings, session_manager
|
|
5
5
|
from messengers.registry import get_adapter_for_platform
|
|
6
6
|
from services.discord_helpers import split_message
|
|
7
7
|
from utils.utils import get_current_model
|
|
@@ -18,8 +18,8 @@ async def send_agy_response(
|
|
|
18
18
|
adapter = get_adapter_for_platform(session.get("platform", "discord"))
|
|
19
19
|
session_manager.save_sessions()
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
model_display =
|
|
21
|
+
# Same precedence as the /model autocomplete in general_cog.
|
|
22
|
+
model_display = session.get("model") or bot_settings.get("default_model") or get_current_model()
|
|
23
23
|
|
|
24
24
|
parts = split_message(response_text, MAX_EMBED_LEN)
|
|
25
25
|
for idx, part in enumerate(parts):
|