blun-king-cli 9.1.609 → 9.1.610
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/CHANGELOG.md +9 -0
- package/LIESMICH.txt +1 -1
- package/README.md +1 -1
- package/bin/launcher-runtime.js +120 -39
- package/bin/private-paths.js +75 -37
- package/bin/profile-launch.cjs +42 -0
- package/blun.mjs +276 -154
- package/package.json +2 -1
- package/telegram-plugin/dist/bridge.mjs +20 -3
- package/telegram-plugin/dist/mcp-server.mjs +6 -0
- package/telegram-plugin/dist/noise.mjs +11 -1
- package/worker-host.mjs +66 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# BLUN King Changelog
|
|
2
2
|
|
|
3
|
+
## 9.1.610
|
|
4
|
+
|
|
5
|
+
- Mit `@all` erreichst du in einer Telegram-Gruppe alle angebundenen Kings. Nachrichten ohne passende Erwähnung bleiben vom Modellkontext ausgeschlossen, wenn die Erwähnungsregel aktiv ist.
|
|
6
|
+
- Konkrete Aufträge und Fragen bleiben beantwortbar; bloße Informationen und ausdrücklich bestätigungsfreie Nachrichten lösen keine unnötige Gruppenbestätigung aus.
|
|
7
|
+
- Telegram-`/`-Befehle werden vor normalen TUI-Nachrichten zugestellt und nicht in die Arbeitswarteschlange gelegt. Kann ein Fernbefehl während eines laufenden Zugs nicht sicher ausgeführt werden, erhältst du sofort eine Ablehnung und kannst ihn erneut senden; er läuft nicht später überraschend an.
|
|
8
|
+
- Ein leerer oder ausdrücklich stiller Headless-Zug sendet keine automatische Telegram-Antwort. Als eindeutiges Stille-Zeichen gilt `[[BLUN_TELEGRAM_SILENT]]` allein als Ausgabetext; die fünf beobachteten Stille-Formulierungen werden ebenfalls unterdrückt. Echte Antworten ohne `reply` bleiben zustellbar.
|
|
9
|
+
- Auf Linux ist das npm-Paket wieder installierbar. `king --profile serve` und `king serve` öffnen wieder das bestehende Profil; dessen Konfiguration bleibt getrennt, während vorhandene gemeinsame Zugangsdaten weiter genutzt werden.
|
|
10
|
+
- Diese Änderungen belegen weder eine kürzere Modellwartezeit noch geringeren Tokenverbrauch.
|
|
11
|
+
|
|
3
12
|
## 9.1.609
|
|
4
13
|
|
|
5
14
|
- Open the existing effort picker with Ctrl+L while preserving the editor draft. Reuse the existing effort settings and model restrictions rather than adding a second configuration path.
|
package/LIESMICH.txt
CHANGED
package/README.md
CHANGED
package/bin/launcher-runtime.js
CHANGED
|
@@ -33,29 +33,68 @@ const { unsupportedNodeMessage } = require('./node-version');
|
|
|
33
33
|
const { repairConfiguredNativeModules } = require('./native-module-repair');
|
|
34
34
|
const { acquireSharedRuntimeLease, tryAcquireUpdateLease } = require('./update-lease');
|
|
35
35
|
const { runExplicitUpdate, runUpdateNotice } = require('./update-notice');
|
|
36
|
-
const {
|
|
37
|
-
ensurePrivateDirectory,
|
|
38
|
-
securePrivateFile,
|
|
39
|
-
writePrivateFile,
|
|
40
|
-
} = require('./private-paths');
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
36
|
+
const {
|
|
37
|
+
ensurePrivateDirectory,
|
|
38
|
+
securePrivateFile,
|
|
39
|
+
writePrivateFile,
|
|
40
|
+
} = require('./private-paths');
|
|
41
|
+
const { parseProfileLaunchArgs, resolveSelectedProfile } = require('./profile-launch.cjs');
|
|
42
|
+
|
|
43
|
+
const PKG = path.resolve(__dirname, '..');
|
|
44
|
+
const PROFILE = parseProfileLaunchArgs(process.argv.slice(2), launcherModeFromArgv(process.argv));
|
|
45
|
+
const ARGS = PROFILE.args;
|
|
44
46
|
const CORE_LOAD_TIMEOUT_MS = 30_000;
|
|
45
47
|
|
|
46
|
-
function normalizeWindowsPath(value) {
|
|
47
|
-
return path.win32.normalize(value).replace(/\\+$/u, '').toLowerCase();
|
|
48
|
-
}
|
|
48
|
+
function normalizeWindowsPath(value) {
|
|
49
|
+
return path.win32.normalize(value).replace(/\\+$/u, '').toLowerCase();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function runningTuiPid(blunDir, options = {}) {
|
|
53
|
+
const fsImpl = options.fsImpl || fs;
|
|
54
|
+
const kill = options.kill || process.kill.bind(process);
|
|
55
|
+
const now = options.now ?? Date.now();
|
|
56
|
+
const stateDir = path.join(blunDir, 'channels', 'telegram');
|
|
57
|
+
try {
|
|
58
|
+
const leaseFile = path.join(stateDir, 'tui.pid');
|
|
59
|
+
const owner = JSON.parse(fsImpl.readFileSync(path.join(stateDir, 'tui-owner.json'), 'utf8'));
|
|
60
|
+
const leasePid = Number(fsImpl.readFileSync(leaseFile, 'utf8').trim());
|
|
61
|
+
if (owner?.version !== 1 || !Number.isSafeInteger(owner.pid) || owner.pid <= 1
|
|
62
|
+
|| owner.pid !== leasePid || now - fsImpl.statSync(leaseFile).mtimeMs > 90_000) return null;
|
|
63
|
+
kill(owner.pid, 0);
|
|
64
|
+
return owner.pid;
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isInteractiveKingStart(args) {
|
|
71
|
+
return args.length === 0 || (args.length === 1 && args[0] === '-c');
|
|
72
|
+
}
|
|
49
73
|
|
|
50
|
-
function resolveLauncherPrivatePaths(options = {}) {
|
|
51
|
-
const env = options.env || process.env;
|
|
52
|
-
const platform = options.platform || process.platform;
|
|
53
|
-
const configuredHome = String(env.BLUN_HOME || '').trim();
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
74
|
+
function resolveLauncherPrivatePaths(options = {}) {
|
|
75
|
+
const env = options.env || process.env;
|
|
76
|
+
const platform = options.platform || process.platform;
|
|
77
|
+
const configuredHome = String(env.BLUN_HOME || '').trim();
|
|
78
|
+
const selectedProfile = options.profileName ?? PROFILE.name;
|
|
79
|
+
if (platform !== 'win32') {
|
|
80
|
+
const posix = path.posix;
|
|
81
|
+
const userHome = (options.homedir || os.homedir)();
|
|
82
|
+
if (selectedProfile !== undefined) {
|
|
83
|
+
const configuredShared = String(env.BLUN_SHARED_HOME || '').trim();
|
|
84
|
+
const sharedHome = configuredShared
|
|
85
|
+
? posix.resolve(configuredShared)
|
|
86
|
+
: configuredHome && posix.basename(posix.dirname(configuredHome)) === 'profile'
|
|
87
|
+
? posix.resolve(configuredHome, '..', '..')
|
|
88
|
+
: configuredHome ? posix.resolve(configuredHome) : posix.join(userHome, '.blun');
|
|
89
|
+
const blunHome = resolveSelectedProfile(sharedHome, selectedProfile, posix);
|
|
90
|
+
if (configuredShared && configuredHome && posix.resolve(configuredHome) !== blunHome) {
|
|
91
|
+
throw new Error(`BLUN_HOME passt nicht zum Profil ${selectedProfile}: ${blunHome}`);
|
|
92
|
+
}
|
|
93
|
+
return { blunHome, sharedHome, userHome };
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
blunHome: configuredHome ? path.resolve(configuredHome) : path.join(userHome, '.blun'),
|
|
97
|
+
userHome,
|
|
59
98
|
};
|
|
60
99
|
}
|
|
61
100
|
|
|
@@ -78,8 +117,20 @@ function resolveLauncherPrivatePaths(options = {}) {
|
|
|
78
117
|
throw new Error('Das lokale Windows-Benutzerprofil ist kein kanonischer lokaler Pfad.');
|
|
79
118
|
}
|
|
80
119
|
|
|
81
|
-
const blunHome = path.win32.join(userHome, '.blun');
|
|
82
|
-
if (
|
|
120
|
+
const blunHome = path.win32.join(userHome, '.blun');
|
|
121
|
+
if (selectedProfile !== undefined) {
|
|
122
|
+
const profileHome = resolveSelectedProfile(blunHome, selectedProfile, path.win32);
|
|
123
|
+
const configuredShared = String(env.BLUN_SHARED_HOME || '').trim();
|
|
124
|
+
if (configuredShared && normalizeWindowsPath(path.win32.resolve(configuredShared)) !== normalizeWindowsPath(blunHome)) {
|
|
125
|
+
throw new Error(`BLUN_SHARED_HOME muss unter Windows exakt im Windows-Benutzerprofil liegen: ${blunHome}`);
|
|
126
|
+
}
|
|
127
|
+
if (configuredHome && normalizeWindowsPath(path.win32.resolve(configuredHome)) !== normalizeWindowsPath(profileHome)
|
|
128
|
+
&& normalizeWindowsPath(path.win32.resolve(configuredHome)) !== normalizeWindowsPath(blunHome)) {
|
|
129
|
+
throw new Error(`BLUN_HOME passt nicht zum Profil ${selectedProfile}: ${profileHome}`);
|
|
130
|
+
}
|
|
131
|
+
return { blunHome: profileHome, sharedHome: blunHome, userHome };
|
|
132
|
+
}
|
|
133
|
+
if (configuredHome
|
|
83
134
|
&& normalizeWindowsPath(path.win32.resolve(configuredHome)) !== normalizeWindowsPath(blunHome)) {
|
|
84
135
|
throw new Error(`BLUN_HOME muss unter Windows exakt im Windows-Benutzerprofil liegen: ${blunHome}`);
|
|
85
136
|
}
|
|
@@ -367,18 +418,29 @@ async function runLauncher(options = {}) {
|
|
|
367
418
|
}
|
|
368
419
|
process.stdout.write(launcherHelpText());
|
|
369
420
|
const env = createLauncherEnvironment(process.env, mode, readPackageVersion());
|
|
370
|
-
const informationalEnv = { ...process.env };
|
|
371
|
-
delete informationalEnv.BLUN_HOME;
|
|
372
|
-
|
|
421
|
+
const informationalEnv = { ...process.env };
|
|
422
|
+
delete informationalEnv.BLUN_HOME;
|
|
423
|
+
const paths = resolveLauncherPrivatePaths({ env: informationalEnv });
|
|
424
|
+
env.BLUN_HOME = paths.blunHome;
|
|
425
|
+
if (PROFILE.name !== undefined) {
|
|
426
|
+
env.BLUN_SHARED_HOME = paths.sharedHome;
|
|
427
|
+
env.BLUN_PROFILE = PROFILE.name;
|
|
428
|
+
}
|
|
373
429
|
process.exitCode = await runInformationalCore(ARGS, env, callerCwd, releaseNotice);
|
|
374
430
|
return;
|
|
375
431
|
}
|
|
376
432
|
|
|
377
|
-
let privatePaths;
|
|
378
|
-
const getPrivatePaths = () => {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
433
|
+
let privatePaths;
|
|
434
|
+
const getPrivatePaths = () => {
|
|
435
|
+
if (!privatePaths) {
|
|
436
|
+
const resolved = resolveLauncherPrivatePaths();
|
|
437
|
+
if (PROFILE.implicit && !fs.existsSync(resolved.blunHome)) {
|
|
438
|
+
throw new Error(`Profil ${PROFILE.name} gibt es nicht. Anlegen mit king --profile ${PROFILE.name}.`);
|
|
439
|
+
}
|
|
440
|
+
privatePaths = resolved;
|
|
441
|
+
}
|
|
442
|
+
return privatePaths;
|
|
443
|
+
};
|
|
382
444
|
const nodeRuntime = await prepareManagedNodeRuntime({
|
|
383
445
|
getBlunDir: () => getPrivatePaths().blunHome,
|
|
384
446
|
});
|
|
@@ -397,15 +459,28 @@ async function runLauncher(options = {}) {
|
|
|
397
459
|
if (ARGS.some((arg) => arg === '--help' || arg === '-h')) {
|
|
398
460
|
process.stdout.write(launcherHelpText());
|
|
399
461
|
const env = createLauncherEnvironment(process.env, mode, readPackageVersion());
|
|
400
|
-
const informationalEnv = { ...process.env };
|
|
401
|
-
delete informationalEnv.BLUN_HOME;
|
|
402
|
-
|
|
462
|
+
const informationalEnv = { ...process.env };
|
|
463
|
+
delete informationalEnv.BLUN_HOME;
|
|
464
|
+
const paths = resolveLauncherPrivatePaths({ env: informationalEnv });
|
|
465
|
+
env.BLUN_HOME = paths.blunHome;
|
|
466
|
+
if (PROFILE.name !== undefined) {
|
|
467
|
+
env.BLUN_SHARED_HOME = paths.sharedHome;
|
|
468
|
+
env.BLUN_PROFILE = PROFILE.name;
|
|
469
|
+
}
|
|
403
470
|
process.exitCode = await superviseProtectedCore(ARGS, env, callerCwd, releaseNotice);
|
|
404
471
|
return;
|
|
405
472
|
}
|
|
406
473
|
|
|
407
|
-
const blunDir = getPrivatePaths().blunHome;
|
|
408
|
-
|
|
474
|
+
const blunDir = getPrivatePaths().blunHome;
|
|
475
|
+
if (mode === LAUNCHER_MODES.KING && isInteractiveKingStart(ARGS)) {
|
|
476
|
+
const activePid = runningTuiPid(blunDir);
|
|
477
|
+
if (activePid !== null) {
|
|
478
|
+
process.stderr.write(`King läuft bereits (PID ${activePid}).\n`);
|
|
479
|
+
process.exitCode = 1;
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
ensurePrivateDirectory(blunDir);
|
|
409
484
|
|
|
410
485
|
// --- 1. Oeffentlicher npm-Update-Dialog. -------------------------------
|
|
411
486
|
const updateResult = options.skipUpdateNotice === true
|
|
@@ -470,8 +545,12 @@ async function runLauncher(options = {}) {
|
|
|
470
545
|
}
|
|
471
546
|
|
|
472
547
|
// --- 6. Start -----------------------------------------------------------
|
|
473
|
-
const env = createLauncherEnvironment(process.env, mode, readPackageVersion(), blunDir);
|
|
474
|
-
env.BLUN_HOME = blunDir;
|
|
548
|
+
const env = createLauncherEnvironment(process.env, mode, readPackageVersion(), blunDir);
|
|
549
|
+
env.BLUN_HOME = blunDir;
|
|
550
|
+
if (PROFILE.name !== undefined) {
|
|
551
|
+
env.BLUN_SHARED_HOME = getPrivatePaths().sharedHome;
|
|
552
|
+
env.BLUN_PROFILE = PROFILE.name;
|
|
553
|
+
}
|
|
475
554
|
process.exitCode = await superviseProtectedCore(
|
|
476
555
|
ARGS,
|
|
477
556
|
env,
|
|
@@ -483,8 +562,10 @@ async function runLauncher(options = {}) {
|
|
|
483
562
|
}
|
|
484
563
|
}
|
|
485
564
|
|
|
486
|
-
module.exports = {
|
|
487
|
-
resolveLauncherPrivatePaths,
|
|
565
|
+
module.exports = {
|
|
566
|
+
resolveLauncherPrivatePaths,
|
|
567
|
+
isInteractiveKingStart,
|
|
568
|
+
runningTuiPid,
|
|
488
569
|
runLauncher,
|
|
489
570
|
shouldDetachProtectedCore,
|
|
490
571
|
spawnManagedLauncher,
|
package/bin/private-paths.js
CHANGED
|
@@ -44,7 +44,10 @@ public static class BlunPrivateAclNative
|
|
|
44
44
|
private const uint FILE_DIRECTORY_FILE = 0x00000001;
|
|
45
45
|
private const uint FILE_SYNCHRONOUS_IO_NONALERT = 0x00000020;
|
|
46
46
|
private const uint FILE_OPEN_REPARSE_POINT = 0x00200000;
|
|
47
|
-
private const uint FILE_OPEN_IF = 3;
|
|
47
|
+
private const uint FILE_OPEN_IF = 3;
|
|
48
|
+
private const uint FILE_SHARE_READ = 0x00000001;
|
|
49
|
+
private const uint FILE_SHARE_WRITE = 0x00000002;
|
|
50
|
+
private const uint FILE_SHARE_DELETE = 0x00000004;
|
|
48
51
|
private const uint OBJ_CASE_INSENSITIVE = 0x00000040;
|
|
49
52
|
private const uint SE_FILE_OBJECT = 1;
|
|
50
53
|
private const uint OWNER_SECURITY_INFORMATION = 0x00000001;
|
|
@@ -602,17 +605,30 @@ public static class BlunPrivateAclNative
|
|
|
602
605
|
IoStatusBlock ioStatus;
|
|
603
606
|
int status = NtCreateFile(
|
|
604
607
|
out rawHandle,
|
|
605
|
-
GENERIC_READ | READ_CONTROL | WRITE_DAC | DELETE | SYNCHRONIZE,
|
|
608
|
+
GENERIC_READ | READ_CONTROL | WRITE_DAC | DELETE | SYNCHRONIZE,
|
|
606
609
|
ref attributes,
|
|
607
610
|
out ioStatus,
|
|
608
611
|
IntPtr.Zero,
|
|
609
612
|
0,
|
|
610
|
-
|
|
611
|
-
FILE_OPEN_IF,
|
|
613
|
+
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
614
|
+
FILE_OPEN_IF,
|
|
612
615
|
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_REPARSE_POINT,
|
|
613
|
-
IntPtr.Zero,
|
|
614
|
-
0);
|
|
615
|
-
if (status < 0)
|
|
616
|
+
IntPtr.Zero,
|
|
617
|
+
0);
|
|
618
|
+
if (status < 0 && RtlNtStatusToDosError(status) == 32)
|
|
619
|
+
status = NtCreateFile(
|
|
620
|
+
out rawHandle,
|
|
621
|
+
GENERIC_READ | READ_CONTROL | WRITE_DAC | SYNCHRONIZE,
|
|
622
|
+
ref attributes,
|
|
623
|
+
out ioStatus,
|
|
624
|
+
IntPtr.Zero,
|
|
625
|
+
0,
|
|
626
|
+
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
627
|
+
FILE_OPEN_IF,
|
|
628
|
+
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_REPARSE_POINT,
|
|
629
|
+
IntPtr.Zero,
|
|
630
|
+
0);
|
|
631
|
+
if (status < 0)
|
|
616
632
|
throw new Win32Exception((int)RtlNtStatusToDosError(status));
|
|
617
633
|
return new SafeFileHandle(rawHandle, true);
|
|
618
634
|
}
|
|
@@ -1058,8 +1074,8 @@ public static class BlunPrivateAclNative
|
|
|
1058
1074
|
}
|
|
1059
1075
|
}`;
|
|
1060
1076
|
|
|
1061
|
-
const WINDOWS_ACL_CSHARP_GZIP = gzipSync(
|
|
1062
|
-
Buffer.from(WINDOWS_ACL_CSHARP),
|
|
1077
|
+
const WINDOWS_ACL_CSHARP_GZIP = gzipSync(
|
|
1078
|
+
Buffer.from(WINDOWS_ACL_CSHARP.replace(/\n[ \t]*\n/gu, '\n')),
|
|
1063
1079
|
{ level: 9 },
|
|
1064
1080
|
).toString('base64');
|
|
1065
1081
|
|
|
@@ -1068,9 +1084,9 @@ const WINDOWS_ACL_SCRIPT = [
|
|
|
1068
1084
|
"$ProgressPreference = 'SilentlyContinue'",
|
|
1069
1085
|
'try {',
|
|
1070
1086
|
'Set-StrictMode -Version 3',
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1087
|
+
'$target=$env:BLUN_PRIVATE_ACL_TARGET',
|
|
1088
|
+
'$kind=$env:BLUN_PRIVATE_ACL_KIND',
|
|
1089
|
+
'$sid=$env:BLUN_PRIVATE_ACL_USER_SID',
|
|
1074
1090
|
"if ([string]::IsNullOrWhiteSpace($target) -or -not [IO.Path]::IsPathRooted($target)) { throw 'Invalid target.' }",
|
|
1075
1091
|
"if ($kind -ne 'directory' -and $kind -ne 'file' -and $kind -ne 'file-write' -and $kind -ne 'tree') { throw 'Invalid kind.' }",
|
|
1076
1092
|
`$bytes=[Convert]::FromBase64String('${WINDOWS_ACL_CSHARP_GZIP}')`,
|
|
@@ -1105,15 +1121,18 @@ const WINDOWS_ACL_SCRIPT = [
|
|
|
1105
1121
|
'$mem.Dispose()',
|
|
1106
1122
|
'}',
|
|
1107
1123
|
'}',
|
|
1108
|
-
"[Console]::Out.Write('BLUN_PRIVATE_ACL_OK_V1')",
|
|
1109
|
-
'exit 0',
|
|
1110
|
-
'} catch {',
|
|
1111
|
-
"[Console]::Error.Write(
|
|
1112
|
-
'exit 1',
|
|
1124
|
+
"[Console]::Out.Write('BLUN_PRIVATE_ACL_OK_V1')",
|
|
1125
|
+
'exit 0',
|
|
1126
|
+
'} catch {',
|
|
1127
|
+
"$e='BLUN_PRIVATE_ACL_ERROR_V1'; if ($kind -eq 'directory') { $e += '|' + $_.Exception.Message }; [Console]::Error.Write($e)",
|
|
1128
|
+
'exit 1',
|
|
1113
1129
|
'}',
|
|
1114
1130
|
].join('\n');
|
|
1115
1131
|
|
|
1116
|
-
const WINDOWS_ACL_ENCODED_COMMAND = Buffer.from(
|
|
1132
|
+
const WINDOWS_ACL_ENCODED_COMMAND = Buffer.from(
|
|
1133
|
+
WINDOWS_ACL_SCRIPT.replace(/\n[ \t]*\n/gu, '\n'),
|
|
1134
|
+
'utf16le',
|
|
1135
|
+
).toString('base64');
|
|
1117
1136
|
|
|
1118
1137
|
function windowsSystemDirectory(options) {
|
|
1119
1138
|
const env = options.env || process.env;
|
|
@@ -1127,10 +1146,16 @@ function windowsSystemDirectory(options) {
|
|
|
1127
1146
|
return path.win32.normalize(directory);
|
|
1128
1147
|
}
|
|
1129
1148
|
|
|
1130
|
-
function windowsHostEnvironment(systemDirectory) {
|
|
1131
|
-
const systemRoot = path.win32.dirname(systemDirectory);
|
|
1132
|
-
|
|
1133
|
-
|
|
1149
|
+
function windowsHostEnvironment(systemDirectory, options) {
|
|
1150
|
+
const systemRoot = path.win32.dirname(systemDirectory);
|
|
1151
|
+
const source = options.env || process.env;
|
|
1152
|
+
const temporaryDirectory = source.TEMP || source.TMP;
|
|
1153
|
+
return {
|
|
1154
|
+
SystemRoot: systemRoot,
|
|
1155
|
+
WINDIR: systemRoot,
|
|
1156
|
+
...(temporaryDirectory ? { TEMP: temporaryDirectory, TMP: temporaryDirectory } : {}),
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1134
1159
|
|
|
1135
1160
|
function runWindowsCommand(executable, args, options, env, input) {
|
|
1136
1161
|
const execFileSyncImpl = options.execFileSyncImpl || execFileSync;
|
|
@@ -1190,14 +1215,14 @@ function resolveWindowsPrivateTarget(targetPath) {
|
|
|
1190
1215
|
throw new Error('Invalid Windows private path.');
|
|
1191
1216
|
}
|
|
1192
1217
|
return absoluteTarget;
|
|
1193
|
-
} catch {
|
|
1194
|
-
throw new Error('Windows private ACL hardening or verification failed.');
|
|
1218
|
+
} catch (error) {
|
|
1219
|
+
throw new Error('Windows private ACL hardening or verification failed: invalid target path.', { cause: error });
|
|
1195
1220
|
}
|
|
1196
1221
|
}
|
|
1197
1222
|
|
|
1198
|
-
function windowsAclController(options) {
|
|
1199
|
-
const systemDirectory = windowsSystemDirectory(options);
|
|
1200
|
-
const hostEnvironment = windowsHostEnvironment(systemDirectory);
|
|
1223
|
+
function windowsAclController(options) {
|
|
1224
|
+
const systemDirectory = windowsSystemDirectory(options);
|
|
1225
|
+
const hostEnvironment = windowsHostEnvironment(systemDirectory, options);
|
|
1201
1226
|
const whoami = path.win32.join(systemDirectory, 'whoami.exe');
|
|
1202
1227
|
const powershell = path.win32.join(
|
|
1203
1228
|
systemDirectory,
|
|
@@ -1228,14 +1253,19 @@ function windowsAclController(options) {
|
|
|
1228
1253
|
return userSid;
|
|
1229
1254
|
};
|
|
1230
1255
|
|
|
1231
|
-
const invoke = (targetPath, kind, input) => {
|
|
1232
|
-
try {
|
|
1233
|
-
const absoluteTarget = resolveWindowsPrivateTarget(targetPath);
|
|
1234
|
-
const
|
|
1256
|
+
const invoke = (targetPath, kind, input) => {
|
|
1257
|
+
try {
|
|
1258
|
+
const absoluteTarget = resolveWindowsPrivateTarget(targetPath);
|
|
1259
|
+
const failure = (reason) => new Error(
|
|
1260
|
+
kind === 'directory'
|
|
1261
|
+
? `Windows private ACL hardening or verification failed for ${absoluteTarget}: ${reason}`
|
|
1262
|
+
: 'Windows private ACL hardening or verification failed.',
|
|
1263
|
+
);
|
|
1264
|
+
const root = path.parse(absoluteTarget).root;
|
|
1235
1265
|
if (!path.isAbsolute(absoluteTarget)
|
|
1236
1266
|
|| root.toLowerCase() === absoluteTarget.toLowerCase()
|
|
1237
1267
|
|| !['directory', 'file', 'file-write', 'tree'].includes(kind)) {
|
|
1238
|
-
throw
|
|
1268
|
+
throw failure('invalid target or operation');
|
|
1239
1269
|
}
|
|
1240
1270
|
const env = {
|
|
1241
1271
|
...hostEnvironment,
|
|
@@ -1253,11 +1283,19 @@ function windowsAclController(options) {
|
|
|
1253
1283
|
env,
|
|
1254
1284
|
input,
|
|
1255
1285
|
);
|
|
1256
|
-
} catch {
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1286
|
+
} catch (error) {
|
|
1287
|
+
const stderr = String(error?.stderr || '');
|
|
1288
|
+
const marker = 'BLUN_PRIVATE_ACL_ERROR_V1|';
|
|
1289
|
+
const detail = kind === 'directory' && stderr.startsWith(marker)
|
|
1290
|
+
? stderr.slice(marker.length).replace(/[\x00-\x1f\x7f]/gu, ' ').slice(0, 400)
|
|
1291
|
+
: 'Windows ACL helper failed without a native diagnosis';
|
|
1292
|
+
if (kind === 'directory') {
|
|
1293
|
+
throw new Error(failure(detail).message, { cause: new Error(detail) });
|
|
1294
|
+
}
|
|
1295
|
+
throw failure(detail);
|
|
1296
|
+
}
|
|
1297
|
+
if (output !== WINDOWS_ACL_OK_SENTINEL) {
|
|
1298
|
+
throw failure('unexpected ACL helper response');
|
|
1261
1299
|
}
|
|
1262
1300
|
} finally {
|
|
1263
1301
|
input?.fill(0);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
|
|
5
|
+
const PROFILE_NAME = /^[a-z0-9][a-z0-9_-]{0,31}$/u;
|
|
6
|
+
const RESERVED = new Set(['doctor', 'export', 'help', 'login', 'mcp', 'server', 'tools', 'update', 'upgrade', 'version']);
|
|
7
|
+
|
|
8
|
+
function profileName(value) {
|
|
9
|
+
const name = String(value || '').trim().toLowerCase();
|
|
10
|
+
if (!PROFILE_NAME.test(name)) {
|
|
11
|
+
throw new Error('Profilnamen duerfen nur Buchstaben, Zahlen, Unterstriche und Bindestriche enthalten.');
|
|
12
|
+
}
|
|
13
|
+
return name;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function parseProfileLaunchArgs(args, mode) {
|
|
17
|
+
const remaining = [...args];
|
|
18
|
+
if (mode !== 'king') return { args: remaining, name: undefined };
|
|
19
|
+
|
|
20
|
+
const flag = remaining.indexOf('--profile');
|
|
21
|
+
if (flag >= 0) {
|
|
22
|
+
if (flag + 1 >= remaining.length) throw new Error('--profile braucht einen Profilnamen.');
|
|
23
|
+
const name = profileName(remaining[flag + 1]);
|
|
24
|
+
remaining.splice(flag, 2);
|
|
25
|
+
return { args: remaining, name };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (remaining.length === 1 && !remaining[0].startsWith('-') && !RESERVED.has(remaining[0].toLowerCase())) {
|
|
29
|
+
return { args: [], name: profileName(remaining[0]), implicit: true };
|
|
30
|
+
}
|
|
31
|
+
if (remaining.length === 2 && remaining.includes('-c')) {
|
|
32
|
+
const candidate = remaining.find(value => value !== '-c');
|
|
33
|
+
if (candidate && !candidate.startsWith('-')) return { args: ['-c'], name: profileName(candidate), implicit: true };
|
|
34
|
+
}
|
|
35
|
+
return { args: remaining, name: undefined };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function resolveSelectedProfile(sharedHome, name, pathImpl = path) {
|
|
39
|
+
return pathImpl.join(sharedHome, 'profile', profileName(name));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { parseProfileLaunchArgs, resolveSelectedProfile };
|
package/blun.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// BLUN_BUILD_INPUT_SHA256:
|
|
2
|
+
// BLUN_BUILD_INPUT_SHA256:8c6e16ea727bc28d31e8a784ec7ab7cc656e35f70f996bb1e64df97776875360
|
|
3
3
|
import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
|
|
4
4
|
import { dirname as __cjsShimDirname } from 'node:path';
|
|
5
5
|
const __filename = __cjsShimFileURLToPath(import.meta.url);
|
|
@@ -350557,6 +350557,7 @@ var BlunAuthTransitionError = class extends Error {
|
|
|
350557
350557
|
function blunAuthWarningsFromError(error) {
|
|
350558
350558
|
return error instanceof BlunAuthTransitionError ? error.warnings : void 0;
|
|
350559
350559
|
}
|
|
350560
|
+
const LEGACY_PROFILE_MODEL = "blun/king";
|
|
350560
350561
|
var BlunAuthFacade = class {
|
|
350561
350562
|
options;
|
|
350562
350563
|
toolkit;
|
|
@@ -350583,6 +350584,57 @@ var BlunAuthFacade = class {
|
|
|
350583
350584
|
}
|
|
350584
350585
|
});
|
|
350585
350586
|
}
|
|
350587
|
+
async restoreMissingProfileModel() {
|
|
350588
|
+
const needsRepair = (config) => config.defaultModel === LEGACY_PROFILE_MODEL && config.models?.[LEGACY_PROFILE_MODEL] === void 0 && config.providers["managed:blun"] === void 0;
|
|
350589
|
+
if (!needsRepair(readConfigFileForUpdate(this.options.configPath))) return false;
|
|
350590
|
+
const runtime = this.resolveRuntimeManagedAuth(BLUN_PROVIDER_NAME$1);
|
|
350591
|
+
if (!(await this.toolkit.status("managed:blun", runtime.oauthRef)).providers.some((provider) => provider.hasToken)) return false;
|
|
350592
|
+
let accessToken;
|
|
350593
|
+
try {
|
|
350594
|
+
accessToken = await this.toolkit.ensureFresh(BLUN_PROVIDER_NAME$1, { oauthRef: runtime.oauthRef });
|
|
350595
|
+
} catch (error) {
|
|
350596
|
+
if (error instanceof OAuthError$1) return false;
|
|
350597
|
+
throw error;
|
|
350598
|
+
}
|
|
350599
|
+
let models;
|
|
350600
|
+
try {
|
|
350601
|
+
models = requireManagedBlunKingCatalog(await fetchManagedBlunCodeModels({
|
|
350602
|
+
accessToken,
|
|
350603
|
+
baseUrl: runtime.baseUrl,
|
|
350604
|
+
headers: this.options.identity === void 0 ? void 0 : createBlunDefaultHeaders({
|
|
350605
|
+
homeDir: this.options.homeDir,
|
|
350606
|
+
...this.options.identity
|
|
350607
|
+
})
|
|
350608
|
+
}));
|
|
350609
|
+
} catch (error) {
|
|
350610
|
+
if (error instanceof ManagedBlunCodeModelsAuthError) return false;
|
|
350611
|
+
throw error;
|
|
350612
|
+
}
|
|
350613
|
+
const repaired = await mutateConfigFile(this.options.configPath, (config) => {
|
|
350614
|
+
if (!needsRepair(config)) return {
|
|
350615
|
+
config,
|
|
350616
|
+
result: false
|
|
350617
|
+
};
|
|
350618
|
+
const existingServices = config.services;
|
|
350619
|
+
applyManagedBlunCodeConfig(config, {
|
|
350620
|
+
models,
|
|
350621
|
+
baseUrl: runtime.baseUrl,
|
|
350622
|
+
oauthKey: runtime.oauthRef.key,
|
|
350623
|
+
oauthHost: runtime.oauthRef.oauthHost,
|
|
350624
|
+
preserveDefaultModel: true
|
|
350625
|
+
});
|
|
350626
|
+
config.services = {
|
|
350627
|
+
...config.services,
|
|
350628
|
+
...existingServices
|
|
350629
|
+
};
|
|
350630
|
+
return {
|
|
350631
|
+
config,
|
|
350632
|
+
result: true
|
|
350633
|
+
};
|
|
350634
|
+
});
|
|
350635
|
+
if (repaired) this.options.onConfigUpdated?.(readConfigFile(this.options.configPath));
|
|
350636
|
+
return repaired;
|
|
350637
|
+
}
|
|
350586
350638
|
async status(providerName) {
|
|
350587
350639
|
const auth = this.resolveManagedAuth(providerName);
|
|
350588
350640
|
const apiKey = nonEmpty(auth.apiKey);
|
|
@@ -351618,6 +351670,15 @@ function errorMessage$9(error) {
|
|
|
351618
351670
|
//#region ../../packages/node-sdk/src/sdk-rpc-client.ts
|
|
351619
351671
|
init_src();
|
|
351620
351672
|
init_src$3();
|
|
351673
|
+
function authHomeForProfile(homeDir) {
|
|
351674
|
+
const sharedHome = process.env["BLUN_SHARED_HOME"]?.trim();
|
|
351675
|
+
const profile = process.env["BLUN_PROFILE"]?.trim();
|
|
351676
|
+
if (!sharedHome || !profile || !/^[a-z0-9][a-z0-9_-]{0,31}$/u.test(profile)) return homeDir;
|
|
351677
|
+
const expected = resolve(join(sharedHome, "profile", profile));
|
|
351678
|
+
const actual = resolve(homeDir);
|
|
351679
|
+
if ((process.platform === "win32" ? expected.toLowerCase() : expected) !== (process.platform === "win32" ? actual.toLowerCase() : actual)) return homeDir;
|
|
351680
|
+
return resolve(sharedHome);
|
|
351681
|
+
}
|
|
351621
351682
|
var SDKRpcClient = class extends SDKRpcClientBase {
|
|
351622
351683
|
homeDir;
|
|
351623
351684
|
configPath;
|
|
@@ -351626,6 +351687,7 @@ var SDKRpcClient = class extends SDKRpcClientBase {
|
|
|
351626
351687
|
auth;
|
|
351627
351688
|
core;
|
|
351628
351689
|
ready;
|
|
351690
|
+
usesDefaultConfigPath;
|
|
351629
351691
|
constructor(options = {}) {
|
|
351630
351692
|
super();
|
|
351631
351693
|
this.identity = options.identity === void 0 ? void 0 : assertBlunHostIdentity(options.identity);
|
|
@@ -351634,9 +351696,10 @@ var SDKRpcClient = class extends SDKRpcClientBase {
|
|
|
351634
351696
|
homeDir: this.homeDir,
|
|
351635
351697
|
configPath: options.configPath
|
|
351636
351698
|
});
|
|
351699
|
+
this.usesDefaultConfigPath = options.configPath === void 0;
|
|
351637
351700
|
this.telemetry = options.telemetry ?? noopTelemetryClient;
|
|
351638
351701
|
this.auth = new BlunAuthFacade({
|
|
351639
|
-
homeDir: this.homeDir,
|
|
351702
|
+
homeDir: authHomeForProfile(this.homeDir),
|
|
351640
351703
|
configPath: this.configPath,
|
|
351641
351704
|
identity: this.identity,
|
|
351642
351705
|
onRefresh: options.onOAuthRefresh
|
|
@@ -351656,6 +351719,7 @@ var SDKRpcClient = class extends SDKRpcClientBase {
|
|
|
351656
351719
|
}
|
|
351657
351720
|
async ensureConfigFile() {
|
|
351658
351721
|
await ensureConfigFile(this.configPath);
|
|
351722
|
+
if (this.usesDefaultConfigPath && authHomeForProfile(this.homeDir) !== this.homeDir) await this.auth.restoreMissingProfileModel();
|
|
351659
351723
|
}
|
|
351660
351724
|
async close() {
|
|
351661
351725
|
try {
|
|
@@ -505096,6 +505160,134 @@ registerUiCatalogFragment({
|
|
|
505096
505160
|
//#region src/tui/constant/telegram-session-queue.ts
|
|
505097
505161
|
const SESSION_QUEUE_READ_BYTES = 65536;
|
|
505098
505162
|
//#endregion
|
|
505163
|
+
//#region src/tui/constant/channel-command.ts
|
|
505164
|
+
const REMOTE_COMMANDS = new Set([
|
|
505165
|
+
"loop",
|
|
505166
|
+
"goal",
|
|
505167
|
+
"idea",
|
|
505168
|
+
"chancenradar",
|
|
505169
|
+
"curiosity",
|
|
505170
|
+
"scout",
|
|
505171
|
+
"reload",
|
|
505172
|
+
"memory",
|
|
505173
|
+
"queue",
|
|
505174
|
+
"usage",
|
|
505175
|
+
"befehle"
|
|
505176
|
+
]);
|
|
505177
|
+
const REMOTE_COMMAND_LIST = "/loop [interval] [prompt]\n/loop status|pause|resume|stop\n/goal\n/idea\n/chancenradar\n/curiosity\n/scout\n/reload\n/memory status|on|off\n/queue [steer|followup|collect|interrupt|reset|default] [debounce:500ms] [batch:20]\n/usage off|tokens|full|reset\n/befehle";
|
|
505178
|
+
//#endregion
|
|
505179
|
+
//#region src/tui/controllers/channel-command.ts
|
|
505180
|
+
function channelSlashCommandCandidate(envelope) {
|
|
505181
|
+
if (!channelMessageAddressed(envelope) || envelope.meta["addressed"]?.trim().toLowerCase() === "semantic") return false;
|
|
505182
|
+
return parseSlashInput(envelope.text.trim()) !== null;
|
|
505183
|
+
}
|
|
505184
|
+
function channelCommandInput(envelope) {
|
|
505185
|
+
if (!channelMessageAddressed(envelope) || envelope.meta["addressed"]?.trim().toLowerCase() === "semantic") return void 0;
|
|
505186
|
+
const parsed = parseSlashInput(envelope.text.trim());
|
|
505187
|
+
if (parsed === null) return void 0;
|
|
505188
|
+
const name = parsed.name.toLowerCase().split("@")[0];
|
|
505189
|
+
if (!REMOTE_COMMANDS.has(name)) return void 0;
|
|
505190
|
+
if (name === "usage" && !isUsageDisplayArgument(parsed.args)) return void 0;
|
|
505191
|
+
return parsed.args.length === 0 ? `/${name}` : `/${name} ${parsed.args}`;
|
|
505192
|
+
}
|
|
505193
|
+
function channelCommandCanInterrupt(input) {
|
|
505194
|
+
const parsed = parseSlashInput(input);
|
|
505195
|
+
if (parsed?.name === "befehle") return true;
|
|
505196
|
+
if (parsed === null) return false;
|
|
505197
|
+
if (parsed.name === "usage") return isUsageDisplayArgument(parsed.args);
|
|
505198
|
+
if ([
|
|
505199
|
+
"goal",
|
|
505200
|
+
"idea",
|
|
505201
|
+
"chancenradar",
|
|
505202
|
+
"curiosity",
|
|
505203
|
+
"scout"
|
|
505204
|
+
].includes(parsed.name)) {
|
|
505205
|
+
if (!/^(?:status|pause|paused|cancel|clear|stop)?$/i.test(parsed.args.trim())) return false;
|
|
505206
|
+
} else if (parsed.name !== "loop" && parsed.name !== "queue") return false;
|
|
505207
|
+
const command = findBuiltInSlashCommand(parsed.name);
|
|
505208
|
+
return command !== void 0 && resolveSlashCommandAvailability(command, parsed.args) === "always";
|
|
505209
|
+
}
|
|
505210
|
+
/** Capture only this command's output, never another room's concurrent turn. */
|
|
505211
|
+
var ChannelCommandController = class {
|
|
505212
|
+
host;
|
|
505213
|
+
execution = new AsyncLocalStorage();
|
|
505214
|
+
constructor(host) {
|
|
505215
|
+
this.host = host;
|
|
505216
|
+
}
|
|
505217
|
+
get context() {
|
|
505218
|
+
const execution = this.execution.getStore();
|
|
505219
|
+
return execution?.active ? {
|
|
505220
|
+
input: execution.input,
|
|
505221
|
+
chatId: execution.envelope.meta.chat_id,
|
|
505222
|
+
...execution.envelope.meta.message_thread_id === void 0 ? {} : { threadId: execution.envelope.meta.message_thread_id }
|
|
505223
|
+
} : void 0;
|
|
505224
|
+
}
|
|
505225
|
+
commit() {
|
|
505226
|
+
const execution = this.execution.getStore();
|
|
505227
|
+
if (execution?.active) this.acknowledge(execution);
|
|
505228
|
+
}
|
|
505229
|
+
bindGoal(goalId) {
|
|
505230
|
+
const execution = this.execution.getStore();
|
|
505231
|
+
if (!execution?.active) return false;
|
|
505232
|
+
execution.goalId = goalId;
|
|
505233
|
+
return true;
|
|
505234
|
+
}
|
|
505235
|
+
acknowledge(execution) {
|
|
505236
|
+
if (execution.acknowledged) return;
|
|
505237
|
+
execution.acknowledged = true;
|
|
505238
|
+
execution.acknowledge?.();
|
|
505239
|
+
}
|
|
505240
|
+
captureStatus(message) {
|
|
505241
|
+
const execution = this.execution.getStore();
|
|
505242
|
+
if (execution?.active) execution.responses.push(message);
|
|
505243
|
+
}
|
|
505244
|
+
capturePrompt(text) {
|
|
505245
|
+
const execution = this.execution.getStore();
|
|
505246
|
+
if (!execution?.active) return false;
|
|
505247
|
+
this.host.sendPrompt(execution.envelope, text, () => {
|
|
505248
|
+
this.acknowledge(execution);
|
|
505249
|
+
}, execution.goalId);
|
|
505250
|
+
execution.sentModelTurn = true;
|
|
505251
|
+
return true;
|
|
505252
|
+
}
|
|
505253
|
+
async run(envelope, input, acknowledge) {
|
|
505254
|
+
const execution = {
|
|
505255
|
+
input,
|
|
505256
|
+
envelope,
|
|
505257
|
+
acknowledge,
|
|
505258
|
+
responses: [],
|
|
505259
|
+
active: true,
|
|
505260
|
+
sentModelTurn: false,
|
|
505261
|
+
acknowledged: false
|
|
505262
|
+
};
|
|
505263
|
+
await this.execution.run(execution, async () => {
|
|
505264
|
+
try {
|
|
505265
|
+
if (parseSlashInput(input)?.name === "befehle") this.host.showStatus(REMOTE_COMMAND_LIST);
|
|
505266
|
+
else await this.host.dispatch(input);
|
|
505267
|
+
} catch (error) {
|
|
505268
|
+
this.host.showError(formatErrorMessage$2(error));
|
|
505269
|
+
} finally {
|
|
505270
|
+
execution.active = false;
|
|
505271
|
+
}
|
|
505272
|
+
if (execution.sentModelTurn) return;
|
|
505273
|
+
this.acknowledge(execution);
|
|
505274
|
+
const text = execution.responses.at(-1) ?? input;
|
|
505275
|
+
if (envelope.meta.message_thread_id === void 0) await this.host.reply(envelope.meta.chat_id, text);
|
|
505276
|
+
else await this.host.reply(envelope.meta.chat_id, text, envelope.meta.message_thread_id);
|
|
505277
|
+
});
|
|
505278
|
+
}
|
|
505279
|
+
async rejectBusy(envelope, acknowledge) {
|
|
505280
|
+
await this.reject(envelope, "Befehl nicht ausgeführt: Die Konsole ist beschäftigt oder hat keine aktive Sitzung. Bitte erneut senden.", acknowledge);
|
|
505281
|
+
}
|
|
505282
|
+
async rejectUnsupported(envelope, acknowledge) {
|
|
505283
|
+
await this.reject(envelope, "Dieser Befehl ist über Telegram nicht freigegeben. Mit /befehle siehst du die verfügbaren Fernbefehle.", acknowledge);
|
|
505284
|
+
}
|
|
505285
|
+
async reject(envelope, message, acknowledge) {
|
|
505286
|
+
acknowledge?.();
|
|
505287
|
+
await this.host.reply(envelope.meta.chat_id, message, envelope.meta.message_thread_id);
|
|
505288
|
+
}
|
|
505289
|
+
};
|
|
505290
|
+
//#endregion
|
|
505099
505291
|
//#region src/tui/controllers/telegram-session-queue.ts
|
|
505100
505292
|
function requireValid(condition) {
|
|
505101
505293
|
if (!condition) throw new Error("Invalid Telegram session queue state or transition");
|
|
@@ -505346,6 +505538,8 @@ var SessionQueueDelivery = class {
|
|
|
505346
505538
|
host;
|
|
505347
505539
|
boundSessionId;
|
|
505348
505540
|
inFlight = /* @__PURE__ */ new Set();
|
|
505541
|
+
commandsInFlight = /* @__PURE__ */ new Set();
|
|
505542
|
+
priorityScanEnd = 0;
|
|
505349
505543
|
queueFile;
|
|
505350
505544
|
stateFile;
|
|
505351
505545
|
constructor(dir, host) {
|
|
@@ -505381,28 +505575,59 @@ var SessionQueueDelivery = class {
|
|
|
505381
505575
|
if (state === void 0) throw new Error("Telegram session queue checkpoint is missing");
|
|
505382
505576
|
state = observeSessionQueue(state, position);
|
|
505383
505577
|
if (JSON.stringify(state) !== before) writeSessionQueue(this.stateFile, state);
|
|
505384
|
-
if (rebind)
|
|
505578
|
+
if (rebind) {
|
|
505579
|
+
this.inFlight.clear();
|
|
505580
|
+
this.commandsInFlight.clear();
|
|
505581
|
+
this.priorityScanEnd = 0;
|
|
505582
|
+
}
|
|
505385
505583
|
this.boundSessionId = scope.sessionId;
|
|
505386
505584
|
if (!deliver) return;
|
|
505387
|
-
|
|
505388
|
-
|
|
505389
|
-
|
|
505390
|
-
|
|
505391
|
-
|
|
505392
|
-
|
|
505393
|
-
|
|
505394
|
-
if (
|
|
505395
|
-
const
|
|
505396
|
-
|
|
505397
|
-
|
|
505398
|
-
|
|
505399
|
-
|
|
505400
|
-
|
|
505401
|
-
|
|
505402
|
-
|
|
505403
|
-
|
|
505404
|
-
|
|
505405
|
-
|
|
505585
|
+
const ranges = pendingSessionQueue(state, scope.sessionId);
|
|
505586
|
+
for (const commandsOnly of [true, false]) {
|
|
505587
|
+
if (!commandsOnly && this.inFlight.size >= 16) return;
|
|
505588
|
+
let delivered = 0;
|
|
505589
|
+
let limitReached = false;
|
|
505590
|
+
for (const pending of ranges) {
|
|
505591
|
+
const start = commandsOnly ? Math.max(pending.start, this.priorityScanEnd) : pending.start;
|
|
505592
|
+
if (start >= pending.end) continue;
|
|
505593
|
+
for (const line of readLines(this.queueFile, {
|
|
505594
|
+
...pending,
|
|
505595
|
+
start
|
|
505596
|
+
})) {
|
|
505597
|
+
if (!this.host.ownsChannel() || this.host.currentSessionId() !== scope.sessionId) return;
|
|
505598
|
+
if (commandsOnly) this.priorityScanEnd = line.range.end;
|
|
505599
|
+
const key = `${line.range.fileId}:${line.range.start}:${line.range.end}`;
|
|
505600
|
+
if (this.inFlight.has(key)) continue;
|
|
505601
|
+
const envelope = parseChannelEnvelope(line.text);
|
|
505602
|
+
const isCommand = envelope !== void 0 && channelSlashCommandCandidate(envelope);
|
|
505603
|
+
if (isCommand !== commandsOnly) continue;
|
|
505604
|
+
if (commandsOnly && this.commandsInFlight.size >= 16) {
|
|
505605
|
+
this.priorityScanEnd = line.range.start;
|
|
505606
|
+
limitReached = true;
|
|
505607
|
+
break;
|
|
505608
|
+
}
|
|
505609
|
+
if (!commandsOnly && this.inFlight.size >= 16) return;
|
|
505610
|
+
const acknowledge = () => {
|
|
505611
|
+
if (!this.host.ownsChannel()) return;
|
|
505612
|
+
const current = readSessionQueue(this.stateFile);
|
|
505613
|
+
if (current === void 0) throw new Error("Telegram session queue checkpoint is missing");
|
|
505614
|
+
writeSessionQueue(this.stateFile, acknowledgeSessionQueue(current, scope.sessionId, line.range));
|
|
505615
|
+
this.inFlight.delete(key);
|
|
505616
|
+
this.commandsInFlight.delete(key);
|
|
505617
|
+
};
|
|
505618
|
+
this.inFlight.add(key);
|
|
505619
|
+
if (isCommand) this.commandsInFlight.add(key);
|
|
505620
|
+
if (envelope === void 0) acknowledge();
|
|
505621
|
+
else this.host.inject(envelope, acknowledge);
|
|
505622
|
+
delivered += 1;
|
|
505623
|
+
if (delivered >= 16) {
|
|
505624
|
+
limitReached = true;
|
|
505625
|
+
break;
|
|
505626
|
+
}
|
|
505627
|
+
}
|
|
505628
|
+
if (limitReached) break;
|
|
505629
|
+
}
|
|
505630
|
+
if (commandsOnly && !limitReached) this.priorityScanEnd = position.frontier;
|
|
505406
505631
|
}
|
|
505407
505632
|
}
|
|
505408
505633
|
};
|
|
@@ -527957,6 +528182,9 @@ var AuthFlowController = class {
|
|
|
527957
528182
|
enterLoginRequiredStartupState() {
|
|
527958
528183
|
this.enterSessionlessStartupState(oauthLoginRequiredStartupNotice());
|
|
527959
528184
|
}
|
|
528185
|
+
enterMissingModelStartupState() {
|
|
528186
|
+
this.enterSessionlessStartupState(uiText("core.error.llmNotSet"));
|
|
528187
|
+
}
|
|
527960
528188
|
enterManagedAccountUnavailableStartupState(kind) {
|
|
527961
528189
|
this.enterSessionlessStartupState(uiText(kind === "invalid_payload" ? "usage.plan.error.invalidPayload" : "usage.plan.error.unavailable"));
|
|
527962
528190
|
}
|
|
@@ -529448,120 +529676,6 @@ function planQueueCollection(messages, policy, now, activeContext) {
|
|
|
529448
529676
|
};
|
|
529449
529677
|
}
|
|
529450
529678
|
//#endregion
|
|
529451
|
-
//#region src/tui/constant/channel-command.ts
|
|
529452
|
-
const REMOTE_COMMANDS = new Set([
|
|
529453
|
-
"loop",
|
|
529454
|
-
"goal",
|
|
529455
|
-
"idea",
|
|
529456
|
-
"chancenradar",
|
|
529457
|
-
"curiosity",
|
|
529458
|
-
"scout",
|
|
529459
|
-
"reload",
|
|
529460
|
-
"memory",
|
|
529461
|
-
"queue",
|
|
529462
|
-
"usage",
|
|
529463
|
-
"befehle"
|
|
529464
|
-
]);
|
|
529465
|
-
const REMOTE_COMMAND_LIST = "/loop [interval] [prompt]\n/loop status|pause|resume|stop\n/goal\n/idea\n/chancenradar\n/curiosity\n/scout\n/reload\n/memory status|on|off\n/queue [steer|followup|collect|interrupt|reset|default] [debounce:500ms] [batch:20]\n/usage off|tokens|full|reset\n/befehle";
|
|
529466
|
-
//#endregion
|
|
529467
|
-
//#region src/tui/controllers/channel-command.ts
|
|
529468
|
-
function channelCommandInput(envelope) {
|
|
529469
|
-
if (!channelMessageAddressed(envelope) || envelope.meta["addressed"]?.trim().toLowerCase() === "semantic") return void 0;
|
|
529470
|
-
const parsed = parseSlashInput(envelope.text.trim());
|
|
529471
|
-
if (parsed === null) return void 0;
|
|
529472
|
-
const name = parsed.name.toLowerCase().split("@")[0];
|
|
529473
|
-
if (!REMOTE_COMMANDS.has(name)) return void 0;
|
|
529474
|
-
if (name === "usage" && !isUsageDisplayArgument(parsed.args)) return void 0;
|
|
529475
|
-
return parsed.args.length === 0 ? `/${name}` : `/${name} ${parsed.args}`;
|
|
529476
|
-
}
|
|
529477
|
-
function channelCommandCanInterrupt(input) {
|
|
529478
|
-
const parsed = parseSlashInput(input);
|
|
529479
|
-
if (parsed?.name === "befehle") return true;
|
|
529480
|
-
if (parsed === null) return false;
|
|
529481
|
-
if (parsed.name === "usage") return isUsageDisplayArgument(parsed.args);
|
|
529482
|
-
if ([
|
|
529483
|
-
"goal",
|
|
529484
|
-
"idea",
|
|
529485
|
-
"chancenradar",
|
|
529486
|
-
"curiosity",
|
|
529487
|
-
"scout"
|
|
529488
|
-
].includes(parsed.name)) {
|
|
529489
|
-
if (!/^(?:status|pause|paused|cancel|clear|stop)?$/i.test(parsed.args.trim())) return false;
|
|
529490
|
-
} else if (parsed.name !== "loop" && parsed.name !== "queue") return false;
|
|
529491
|
-
const command = findBuiltInSlashCommand(parsed.name);
|
|
529492
|
-
return command !== void 0 && resolveSlashCommandAvailability(command, parsed.args) === "always";
|
|
529493
|
-
}
|
|
529494
|
-
/** Capture only this command's output, never another room's concurrent turn. */
|
|
529495
|
-
var ChannelCommandController = class {
|
|
529496
|
-
host;
|
|
529497
|
-
execution = new AsyncLocalStorage();
|
|
529498
|
-
constructor(host) {
|
|
529499
|
-
this.host = host;
|
|
529500
|
-
}
|
|
529501
|
-
get context() {
|
|
529502
|
-
const execution = this.execution.getStore();
|
|
529503
|
-
return execution?.active ? {
|
|
529504
|
-
input: execution.input,
|
|
529505
|
-
chatId: execution.envelope.meta.chat_id,
|
|
529506
|
-
...execution.envelope.meta.message_thread_id === void 0 ? {} : { threadId: execution.envelope.meta.message_thread_id }
|
|
529507
|
-
} : void 0;
|
|
529508
|
-
}
|
|
529509
|
-
commit() {
|
|
529510
|
-
const execution = this.execution.getStore();
|
|
529511
|
-
if (execution?.active) this.acknowledge(execution);
|
|
529512
|
-
}
|
|
529513
|
-
bindGoal(goalId) {
|
|
529514
|
-
const execution = this.execution.getStore();
|
|
529515
|
-
if (!execution?.active) return false;
|
|
529516
|
-
execution.goalId = goalId;
|
|
529517
|
-
return true;
|
|
529518
|
-
}
|
|
529519
|
-
acknowledge(execution) {
|
|
529520
|
-
if (execution.acknowledged) return;
|
|
529521
|
-
execution.acknowledged = true;
|
|
529522
|
-
execution.acknowledge?.();
|
|
529523
|
-
}
|
|
529524
|
-
captureStatus(message) {
|
|
529525
|
-
const execution = this.execution.getStore();
|
|
529526
|
-
if (execution?.active) execution.responses.push(message);
|
|
529527
|
-
}
|
|
529528
|
-
capturePrompt(text) {
|
|
529529
|
-
const execution = this.execution.getStore();
|
|
529530
|
-
if (!execution?.active) return false;
|
|
529531
|
-
this.host.sendPrompt(execution.envelope, text, () => {
|
|
529532
|
-
this.acknowledge(execution);
|
|
529533
|
-
}, execution.goalId);
|
|
529534
|
-
execution.sentModelTurn = true;
|
|
529535
|
-
return true;
|
|
529536
|
-
}
|
|
529537
|
-
async run(envelope, input, acknowledge) {
|
|
529538
|
-
const execution = {
|
|
529539
|
-
input,
|
|
529540
|
-
envelope,
|
|
529541
|
-
acknowledge,
|
|
529542
|
-
responses: [],
|
|
529543
|
-
active: true,
|
|
529544
|
-
sentModelTurn: false,
|
|
529545
|
-
acknowledged: false
|
|
529546
|
-
};
|
|
529547
|
-
await this.execution.run(execution, async () => {
|
|
529548
|
-
try {
|
|
529549
|
-
if (parseSlashInput(input)?.name === "befehle") this.host.showStatus(REMOTE_COMMAND_LIST);
|
|
529550
|
-
else await this.host.dispatch(input);
|
|
529551
|
-
} catch (error) {
|
|
529552
|
-
this.host.showError(formatErrorMessage$2(error));
|
|
529553
|
-
} finally {
|
|
529554
|
-
execution.active = false;
|
|
529555
|
-
}
|
|
529556
|
-
if (execution.sentModelTurn) return;
|
|
529557
|
-
this.acknowledge(execution);
|
|
529558
|
-
const text = execution.responses.at(-1) ?? input;
|
|
529559
|
-
if (envelope.meta.message_thread_id === void 0) await this.host.reply(envelope.meta.chat_id, text);
|
|
529560
|
-
else await this.host.reply(envelope.meta.chat_id, text, envelope.meta.message_thread_id);
|
|
529561
|
-
});
|
|
529562
|
-
}
|
|
529563
|
-
};
|
|
529564
|
-
//#endregion
|
|
529565
529679
|
//#region src/tui/constant/goal-channel-replies.ts
|
|
529566
529680
|
const GOAL_REPLY_RETRY_DELAY_MS = 1e3;
|
|
529567
529681
|
const GOAL_REPLY_REQUEST_TIMEOUT_MS = 15e3;
|
|
@@ -542667,6 +542781,12 @@ var BlunTUI = class {
|
|
|
542667
542781
|
}
|
|
542668
542782
|
if (this.startupWorkspaceSelectionPending) return false;
|
|
542669
542783
|
await this.refreshProviderModelsBeforeSession();
|
|
542784
|
+
const config = await this.harness.getConfig();
|
|
542785
|
+
if (Object.keys(config.providers ?? {}).length === 0 && Object.keys(config.models ?? {}).length === 0) {
|
|
542786
|
+
this.authFlow.enterMissingModelStartupState();
|
|
542787
|
+
this.startupLoginRequired = true;
|
|
542788
|
+
return false;
|
|
542789
|
+
}
|
|
542670
542790
|
const { startup } = this.options;
|
|
542671
542791
|
const { workDir } = this.state.appState;
|
|
542672
542792
|
let session;
|
|
@@ -543361,33 +543481,35 @@ var BlunTUI = class {
|
|
|
543361
543481
|
/**
|
|
543362
543482
|
* Remote channel inbound (Telegram) into THIS visible session.
|
|
543363
543483
|
* Never routes through handleUserInput: only allowlisted slash dispatch (a Telegram
|
|
543364
|
-
* "/new" is
|
|
543484
|
+
* "/new" is rejected without execution), no local editor history. The transcript
|
|
543365
543485
|
* shows a clean user line with a muted origin prefix; the
|
|
543366
|
-
* model receives the full <channel …> payload.
|
|
543367
|
-
*
|
|
543368
|
-
*
|
|
543486
|
+
* model receives the full <channel …> payload. Model messages use the shared
|
|
543487
|
+
* queue; recognized remote controls run now or receive an immediate busy
|
|
543488
|
+
* reply, so they never wait behind ordinary prompts.
|
|
543369
543489
|
*/
|
|
543370
543490
|
injectChannelMessage(envelope, acknowledge) {
|
|
543371
543491
|
const command = channelCommandInput(envelope);
|
|
543372
543492
|
if (command !== void 0) {
|
|
543373
|
-
const
|
|
543493
|
+
const active = this.queueSteerInFlight !== void 0 || this.manualPromptInFlight !== void 0 || this.state.appState.streamingPhase !== "idle" || this.state.appState.isCompacting;
|
|
543494
|
+
if (this.session === void 0 || this.deferUserMessages || this.queueCommandRunning || this.editorReplacementActive || active && !channelCommandCanInterrupt(command)) {
|
|
543495
|
+
this.channelCommands.rejectBusy(envelope, acknowledge).catch((error) => {
|
|
543496
|
+
this.showError(formatErrorMessage$2(error));
|
|
543497
|
+
});
|
|
543498
|
+
return;
|
|
543499
|
+
}
|
|
543500
|
+
this.runChannelCommand({
|
|
543374
543501
|
text: command,
|
|
543375
|
-
displayText: envelope.text,
|
|
543376
543502
|
origin: channelOrigin(envelope),
|
|
543377
|
-
mode: "command",
|
|
543378
543503
|
agentId: this.harness.interactiveAgentId,
|
|
543379
|
-
|
|
543380
|
-
channelThreadId: envelope.meta.message_thread_id,
|
|
543381
|
-
channelCommandEnvelope: envelope,
|
|
543504
|
+
mode: "command",
|
|
543382
543505
|
channelAcknowledge: acknowledge
|
|
543383
|
-
};
|
|
543384
|
-
|
|
543385
|
-
|
|
543386
|
-
|
|
543387
|
-
|
|
543388
|
-
this.
|
|
543389
|
-
|
|
543390
|
-
} else this.runChannelCommand(item, envelope);
|
|
543506
|
+
}, envelope);
|
|
543507
|
+
return;
|
|
543508
|
+
}
|
|
543509
|
+
if (channelSlashCommandCandidate(envelope)) {
|
|
543510
|
+
this.channelCommands.rejectUnsupported(envelope, acknowledge).catch((error) => {
|
|
543511
|
+
this.showError(formatErrorMessage$2(error));
|
|
543512
|
+
});
|
|
543391
543513
|
return;
|
|
543392
543514
|
}
|
|
543393
543515
|
injectChannelEnvelope({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blun-king-cli",
|
|
3
|
-
"version": "9.1.
|
|
3
|
+
"version": "9.1.610",
|
|
4
4
|
"description": "BLUN CLI - your own AI agent with a Telegram channel. Get it done. With BLUN.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"os": [
|
|
19
19
|
"darwin",
|
|
20
|
+
"linux",
|
|
20
21
|
"win32"
|
|
21
22
|
],
|
|
22
23
|
"cpu": [
|
|
@@ -59412,6 +59412,9 @@ function isGermanGroupBroadcast(text) {
|
|
|
59412
59412
|
}
|
|
59413
59413
|
return false;
|
|
59414
59414
|
}
|
|
59415
|
+
function hasAllMention(text) {
|
|
59416
|
+
return /(?:^|[^\p{L}\p{N}_@])@all(?=$|[^\p{L}\p{N}_])/iu.test(text);
|
|
59417
|
+
}
|
|
59415
59418
|
/**
|
|
59416
59419
|
* Bot-to-bot loop guard (Telegram's own safety requirement for Bot API 10.0
|
|
59417
59420
|
* bot-to-bot mode): once King can receive other bots' messages, an unthrottled
|
|
@@ -59479,9 +59482,10 @@ function gate(ctx, botUsername) {
|
|
|
59479
59482
|
if (groupAllowFrom.length > 0 && !groupAllowFrom.includes(senderId)) return { action: "drop" };
|
|
59480
59483
|
const addressesAnother = matchesAny(ctx.text, access.ignorePatterns);
|
|
59481
59484
|
const mentioned = !addressesAnother && isMentioned(ctx, botUsername, access.mentionPatterns);
|
|
59482
|
-
|
|
59485
|
+
const broadcastMention = from.isBot !== true && hasAllMention(ctx.text);
|
|
59486
|
+
if (policy.requireMention && !mentioned && !broadcastMention) return { action: "drop" };
|
|
59483
59487
|
const mentionBypass = from.isBot !== true && (policy.alwaysAllowFrom ?? []).includes(senderId);
|
|
59484
|
-
const addressed = !addressesAnother && (mentionBypass || mentioned || isGermanGroupBroadcast(ctx.text));
|
|
59488
|
+
const addressed = broadcastMention || !addressesAnother && (mentionBypass || mentioned || isGermanGroupBroadcast(ctx.text));
|
|
59485
59489
|
const inferAddressing = !addressesAnother && !addressed && from.isBot !== true;
|
|
59486
59490
|
if (!addressed && from.isBot === true && !botLoopGuardAllows(senderId)) return { action: "drop" };
|
|
59487
59491
|
return {
|
|
@@ -60193,7 +60197,7 @@ var BoundedChannelContextBuffer = class {
|
|
|
60193
60197
|
function buildChannelTag(content, meta, addressed = true, inferAddressing = false) {
|
|
60194
60198
|
const pairs = [["source", "telegram"]];
|
|
60195
60199
|
for (const [key, value] of Object.entries(meta)) if (value !== void 0) pairs.push([key, String(value)]);
|
|
60196
|
-
return `<channel ${pairs.map(([key, value]) => `${key}="${escapeAttr(value)}"`).join(" ")}>\n${content}\n</channel>\n${addressed ? `[REPLY-PFLICHT: Der Absender liest NUR Telegram. Sende deine Antwort mit dem telegram reply-Tool an chat_id=${meta.chat_id}. Sichtbarer Antworttext kann auch automatisch weitergeleitet werden; er ist kein privater Denkbereich.]` : inferAddressing ? `[SEMANTISCHE ADRESSIERUNG: Diese menschliche Gruppennachricht nennt dich nicht explizit. Entscheide anhand von Inhalt und Gesprächskontext, ob sie an dich gerichtet ist oder Arbeit von dir verlangt. Falls ja, antworte kurz mit dem telegram reply-Tool an chat_id=${meta.chat_id}. Falls nein, beende den Turn ohne Ausgabe; sende keine Bestätigung deiner Stille.]` : "[NUR MITLESEN — diese Nachricht ist NICHT an dich gerichtet (kein @King, keine Antwort an dich, sie spricht jemand anderen oder die Gruppe allgemein an). ANTWORTE NICHT: kein reply-Tool, keine Bestätigung, kein \"verstanden\", kein \"halte mich raus\" — beende den Turn einfach ohne Ausgabe. NUR wenn du ein echtes, konkretes Ergebnis oder einen echten Blocker beitragen kannst, antwortest du kurz. Im Zweifel: schweigen.]"}`;
|
|
60200
|
+
return `<channel ${pairs.map(([key, value]) => `${key}="${escapeAttr(value)}"`).join(" ")}>\n${content}\n</channel>\n${addressed && meta.chat_id.startsWith("-") ? `[GRUPPENANTWORT: Eine bloße Information oder eine Nachricht, die ausdrücklich keine Bestätigung verlangt, macht keine Antwort erforderlich. Beende den Turn dann ohne Ausgabe und ohne Bestätigung deiner Stille. Einen konkreten Auftrag an dich nimm kurz an und beginne die Arbeit; melde danach das Ergebnis oder einen echten Blocker. Eine Frage beantworte, wenn du etwas beitragen kannst. Sende eine nötige Antwort mit dem telegram reply-Tool an chat_id=${meta.chat_id}, ohne dieselbe Annahme zu wiederholen.]` : addressed ? `[REPLY-PFLICHT: Der Absender liest NUR Telegram. Sende deine Antwort mit dem telegram reply-Tool an chat_id=${meta.chat_id}. Sichtbarer Antworttext kann auch automatisch weitergeleitet werden; er ist kein privater Denkbereich.]` : inferAddressing ? `[SEMANTISCHE ADRESSIERUNG: Diese menschliche Gruppennachricht nennt dich nicht explizit. Entscheide anhand von Inhalt und Gesprächskontext, ob sie an dich gerichtet ist oder Arbeit von dir verlangt. Falls ja, antworte kurz mit dem telegram reply-Tool an chat_id=${meta.chat_id}. Falls nein, beende den Turn ohne Ausgabe; sende keine Bestätigung deiner Stille.]` : "[NUR MITLESEN — diese Nachricht ist NICHT an dich gerichtet (kein @King, keine Antwort an dich, sie spricht jemand anderen oder die Gruppe allgemein an). ANTWORTE NICHT: kein reply-Tool, keine Bestätigung, kein \"verstanden\", kein \"halte mich raus\" — beende den Turn einfach ohne Ausgabe. NUR wenn du ein echtes, konkretes Ergebnis oder einen echten Blocker beitragen kannst, antwortest du kurz. Im Zweifel: schweigen.]"}`;
|
|
60197
60201
|
}
|
|
60198
60202
|
/**
|
|
60199
60203
|
* Sent once as the head of the first prompt of every bridged session —
|
|
@@ -60211,6 +60215,8 @@ function buildPreamble() {
|
|
|
60211
60215
|
"useful contribution: a relevant finding, error, or improvement. Otherwise finish",
|
|
60212
60216
|
"with no reply tool call and no assistant text. Do not narrate that decision, cite",
|
|
60213
60217
|
"a silence rule, acknowledge inactivity, or report that you ended without output.",
|
|
60218
|
+
"If your host requires assistant text to finish a silent turn, output exactly",
|
|
60219
|
+
"[[BLUN_TELEGRAM_SILENT]] and nothing else. Never send this marker with reply.",
|
|
60214
60220
|
"When you do have something useful to contribute, send it once without repeating it.",
|
|
60215
60221
|
"",
|
|
60216
60222
|
"Messages arrive wrapped as <channel source=\"telegram\" chat_id=\"...\" message_id=\"...\"",
|
|
@@ -67887,6 +67893,10 @@ function assertTelegramLanguageReleased(text, releaseToken, now) {
|
|
|
67887
67893
|
function isGroupChat(chatId) {
|
|
67888
67894
|
return chatId.startsWith("-");
|
|
67889
67895
|
}
|
|
67896
|
+
const SILENT_TURN_MARKER = "[[BLUN_TELEGRAM_SILENT]]";
|
|
67897
|
+
function isSilentTurnMarker(text) {
|
|
67898
|
+
return text.trim() === SILENT_TURN_MARKER;
|
|
67899
|
+
}
|
|
67890
67900
|
function normalize(s) {
|
|
67891
67901
|
return s.toLowerCase().replace(/ä/g, "a").replace(/ö/g, "o").replace(/ü/g, "u").replace(/ß/g, "ss").replace(/[‐-―]/g, " ").replace(/[^\p{L}\p{N}\s]/gu, " ").replace(/\s+/g, " ").trim();
|
|
67892
67902
|
}
|
|
@@ -67896,6 +67906,7 @@ const EMPTY_CLAUSES = [
|
|
|
67896
67906
|
/^(keine (gruppen ?)?antwort|gar keine antwort|keine reaktion|kein wort|no ?action|no (response|reply|answer|comment))( (notig|erforderlich|needed|required))?$/,
|
|
67897
67907
|
/^(ich )?(halte mich raus|(bin|bleibe) (jetzt )?still|schweige|habe nichts zu melden)$/,
|
|
67898
67908
|
/^(stille|still|silence|nichts (zu tun|zu melden|fur mich|fuer mich)|nicht meine lane)$/,
|
|
67909
|
+
/^(kein handlungsbedarf|es gibt keinen handlungsbedarf|hier besteht kein handlungsbedarf)( (fur|fuer) mich)?$/,
|
|
67899
67910
|
/^(i (am |will be )?)?(staying (silent|out|quiet)|keeping (quiet|out)|holding back)$/,
|
|
67900
67911
|
/^(i have )?nothing (to (add|do|report)|for me)$/,
|
|
67901
67912
|
/^no need to (respond|reply)$/,
|
|
@@ -67909,11 +67920,16 @@ const EMPTY_CLAUSES = [
|
|
|
67909
67920
|
/^(nach|gemaess) der leerlauf regel( \d+)? gibt es nichts zu melden$/
|
|
67910
67921
|
];
|
|
67911
67922
|
const NOTIFICATION_CONTEXT = /^die(se)? nachricht ist eine (konfigurations )?information( [\p{L}\p{N} _-]{1,64} wird ab jetzt nur noch bei erwahnung benachrichtigt)?( an die gruppe gerichtet)?$/u;
|
|
67923
|
+
function isNarratedSilentTurn(text) {
|
|
67924
|
+
const words = normalize(text);
|
|
67925
|
+
return /^(kein neuer auftrag|kein handlungsbedarf|keine neue anweisung keine frage an mich nichts zu berichten)\b/u.test(words) && /\b(nichts weiter zu tun|stille|stille ohne jede ausgabe)$/u.test(words);
|
|
67926
|
+
}
|
|
67912
67927
|
/**
|
|
67913
67928
|
* True only when all clauses are acknowledgements or silence commentary.
|
|
67914
67929
|
* One substantive or unrecognised clause preserves the entire message.
|
|
67915
67930
|
*/
|
|
67916
67931
|
function isGroupNoiseReply(text) {
|
|
67932
|
+
if (isNarratedSilentTurn(text)) return true;
|
|
67917
67933
|
return text.split(/[.!?;,\r\n]+|\s+[-\u2013\u2014]\s+/u).map(normalize).filter(Boolean).every((clause) => EMPTY_CLAUSES.some((pattern) => pattern.test(clause)) || NOTIFICATION_CONTEXT.test(clause));
|
|
67918
67934
|
}
|
|
67919
67935
|
//#endregion
|
|
@@ -68037,6 +68053,7 @@ function scrubSecrets(text) {
|
|
|
68037
68053
|
* log it to outbox.jsonl as kind "reply-fallback". Returns true on success.
|
|
68038
68054
|
*/
|
|
68039
68055
|
async function sendReplyFallback(chatId, rawText, threadId) {
|
|
68056
|
+
if (rawText.trim().length === 0 || isSilentTurnMarker(rawText)) return true;
|
|
68040
68057
|
let topic;
|
|
68041
68058
|
try {
|
|
68042
68059
|
topic = threadId === void 0 ? {} : { message_thread_id: telegramThreadId(threadId) };
|
|
@@ -73470,6 +73470,7 @@ const EMPTY_CLAUSES = [
|
|
|
73470
73470
|
/^(keine (gruppen ?)?antwort|gar keine antwort|keine reaktion|kein wort|no ?action|no (response|reply|answer|comment))( (notig|erforderlich|needed|required))?$/,
|
|
73471
73471
|
/^(ich )?(halte mich raus|(bin|bleibe) (jetzt )?still|schweige|habe nichts zu melden)$/,
|
|
73472
73472
|
/^(stille|still|silence|nichts (zu tun|zu melden|fur mich|fuer mich)|nicht meine lane)$/,
|
|
73473
|
+
/^(kein handlungsbedarf|es gibt keinen handlungsbedarf|hier besteht kein handlungsbedarf)( (fur|fuer) mich)?$/,
|
|
73473
73474
|
/^(i (am |will be )?)?(staying (silent|out|quiet)|keeping (quiet|out)|holding back)$/,
|
|
73474
73475
|
/^(i have )?nothing (to (add|do|report)|for me)$/,
|
|
73475
73476
|
/^no need to (respond|reply)$/,
|
|
@@ -73483,11 +73484,16 @@ const EMPTY_CLAUSES = [
|
|
|
73483
73484
|
/^(nach|gemaess) der leerlauf regel( \d+)? gibt es nichts zu melden$/
|
|
73484
73485
|
];
|
|
73485
73486
|
const NOTIFICATION_CONTEXT = /^die(se)? nachricht ist eine (konfigurations )?information( [\p{L}\p{N} _-]{1,64} wird ab jetzt nur noch bei erwahnung benachrichtigt)?( an die gruppe gerichtet)?$/u;
|
|
73487
|
+
function isNarratedSilentTurn(text) {
|
|
73488
|
+
const words = normalize(text);
|
|
73489
|
+
return /^(kein neuer auftrag|kein handlungsbedarf|keine neue anweisung keine frage an mich nichts zu berichten)\b/u.test(words) && /\b(nichts weiter zu tun|stille|stille ohne jede ausgabe)$/u.test(words);
|
|
73490
|
+
}
|
|
73486
73491
|
/**
|
|
73487
73492
|
* True only when all clauses are acknowledgements or silence commentary.
|
|
73488
73493
|
* One substantive or unrecognised clause preserves the entire message.
|
|
73489
73494
|
*/
|
|
73490
73495
|
function isGroupNoiseReply(text) {
|
|
73496
|
+
if (isNarratedSilentTurn(text)) return true;
|
|
73491
73497
|
return text.split(/[.!?;,\r\n]+|\s+[-\u2013\u2014]\s+/u).map(normalize).filter(Boolean).every((clause) => EMPTY_CLAUSES.some((pattern) => pattern.test(clause)) || NOTIFICATION_CONTEXT.test(clause));
|
|
73492
73498
|
}
|
|
73493
73499
|
//#endregion
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
function isGroupChat(chatId) {
|
|
10
10
|
return chatId.startsWith("-");
|
|
11
11
|
}
|
|
12
|
+
const SILENT_TURN_MARKER = "[[BLUN_TELEGRAM_SILENT]]";
|
|
13
|
+
function isSilentTurnMarker(text) {
|
|
14
|
+
return text.trim() === SILENT_TURN_MARKER;
|
|
15
|
+
}
|
|
12
16
|
function normalize(s) {
|
|
13
17
|
return s.toLowerCase().replace(/ä/g, "a").replace(/ö/g, "o").replace(/ü/g, "u").replace(/ß/g, "ss").replace(/[‐-―]/g, " ").replace(/[^\p{L}\p{N}\s]/gu, " ").replace(/\s+/g, " ").trim();
|
|
14
18
|
}
|
|
@@ -18,6 +22,7 @@ const EMPTY_CLAUSES = [
|
|
|
18
22
|
/^(keine (gruppen ?)?antwort|gar keine antwort|keine reaktion|kein wort|no ?action|no (response|reply|answer|comment))( (notig|erforderlich|needed|required))?$/,
|
|
19
23
|
/^(ich )?(halte mich raus|(bin|bleibe) (jetzt )?still|schweige|habe nichts zu melden)$/,
|
|
20
24
|
/^(stille|still|silence|nichts (zu tun|zu melden|fur mich|fuer mich)|nicht meine lane)$/,
|
|
25
|
+
/^(kein handlungsbedarf|es gibt keinen handlungsbedarf|hier besteht kein handlungsbedarf)( (fur|fuer) mich)?$/,
|
|
21
26
|
/^(i (am |will be )?)?(staying (silent|out|quiet)|keeping (quiet|out)|holding back)$/,
|
|
22
27
|
/^(i have )?nothing (to (add|do|report)|for me)$/,
|
|
23
28
|
/^no need to (respond|reply)$/,
|
|
@@ -31,12 +36,17 @@ const EMPTY_CLAUSES = [
|
|
|
31
36
|
/^(nach|gemaess) der leerlauf regel( \d+)? gibt es nichts zu melden$/
|
|
32
37
|
];
|
|
33
38
|
const NOTIFICATION_CONTEXT = /^die(se)? nachricht ist eine (konfigurations )?information( [\p{L}\p{N} _-]{1,64} wird ab jetzt nur noch bei erwahnung benachrichtigt)?( an die gruppe gerichtet)?$/u;
|
|
39
|
+
function isNarratedSilentTurn(text) {
|
|
40
|
+
const words = normalize(text);
|
|
41
|
+
return /^(kein neuer auftrag|kein handlungsbedarf|keine neue anweisung keine frage an mich nichts zu berichten)\b/u.test(words) && /\b(nichts weiter zu tun|stille|stille ohne jede ausgabe)$/u.test(words);
|
|
42
|
+
}
|
|
34
43
|
/**
|
|
35
44
|
* True only when all clauses are acknowledgements or silence commentary.
|
|
36
45
|
* One substantive or unrecognised clause preserves the entire message.
|
|
37
46
|
*/
|
|
38
47
|
function isGroupNoiseReply(text) {
|
|
48
|
+
if (isNarratedSilentTurn(text)) return true;
|
|
39
49
|
return text.split(/[.!?;,\r\n]+|\s+[-\u2013\u2014]\s+/u).map(normalize).filter(Boolean).every((clause) => EMPTY_CLAUSES.some((pattern) => pattern.test(clause)) || NOTIFICATION_CONTEXT.test(clause));
|
|
40
50
|
}
|
|
41
51
|
//#endregion
|
|
42
|
-
export { isGroupChat, isGroupNoiseReply };
|
|
52
|
+
export { SILENT_TURN_MARKER, isGroupChat, isGroupNoiseReply, isSilentTurnMarker };
|
package/worker-host.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// BLUN_BUILD_INPUT_SHA256:
|
|
2
|
+
// BLUN_BUILD_INPUT_SHA256:8c6e16ea727bc28d31e8a784ec7ab7cc656e35f70f996bb1e64df97776875360
|
|
3
3
|
import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
|
|
4
4
|
import { dirname as __cjsShimDirname } from 'node:path';
|
|
5
5
|
const __filename = __cjsShimFileURLToPath(import.meta.url);
|
|
@@ -347625,6 +347625,7 @@ var BlunAuthTransitionError = class extends Error {
|
|
|
347625
347625
|
this.warnings = warnings;
|
|
347626
347626
|
}
|
|
347627
347627
|
};
|
|
347628
|
+
const LEGACY_PROFILE_MODEL = "blun/king";
|
|
347628
347629
|
var BlunAuthFacade = class {
|
|
347629
347630
|
options;
|
|
347630
347631
|
toolkit;
|
|
@@ -347651,6 +347652,57 @@ var BlunAuthFacade = class {
|
|
|
347651
347652
|
}
|
|
347652
347653
|
});
|
|
347653
347654
|
}
|
|
347655
|
+
async restoreMissingProfileModel() {
|
|
347656
|
+
const needsRepair = (config) => config.defaultModel === LEGACY_PROFILE_MODEL && config.models?.[LEGACY_PROFILE_MODEL] === void 0 && config.providers["managed:blun"] === void 0;
|
|
347657
|
+
if (!needsRepair(readConfigFileForUpdate(this.options.configPath))) return false;
|
|
347658
|
+
const runtime = this.resolveRuntimeManagedAuth(BLUN_PROVIDER_NAME$1);
|
|
347659
|
+
if (!(await this.toolkit.status("managed:blun", runtime.oauthRef)).providers.some((provider) => provider.hasToken)) return false;
|
|
347660
|
+
let accessToken;
|
|
347661
|
+
try {
|
|
347662
|
+
accessToken = await this.toolkit.ensureFresh(BLUN_PROVIDER_NAME$1, { oauthRef: runtime.oauthRef });
|
|
347663
|
+
} catch (error) {
|
|
347664
|
+
if (error instanceof OAuthError$1) return false;
|
|
347665
|
+
throw error;
|
|
347666
|
+
}
|
|
347667
|
+
let models;
|
|
347668
|
+
try {
|
|
347669
|
+
models = requireManagedBlunKingCatalog(await fetchManagedBlunCodeModels({
|
|
347670
|
+
accessToken,
|
|
347671
|
+
baseUrl: runtime.baseUrl,
|
|
347672
|
+
headers: this.options.identity === void 0 ? void 0 : createBlunDefaultHeaders({
|
|
347673
|
+
homeDir: this.options.homeDir,
|
|
347674
|
+
...this.options.identity
|
|
347675
|
+
})
|
|
347676
|
+
}));
|
|
347677
|
+
} catch (error) {
|
|
347678
|
+
if (error instanceof ManagedBlunCodeModelsAuthError) return false;
|
|
347679
|
+
throw error;
|
|
347680
|
+
}
|
|
347681
|
+
const repaired = await mutateConfigFile(this.options.configPath, (config) => {
|
|
347682
|
+
if (!needsRepair(config)) return {
|
|
347683
|
+
config,
|
|
347684
|
+
result: false
|
|
347685
|
+
};
|
|
347686
|
+
const existingServices = config.services;
|
|
347687
|
+
applyManagedBlunCodeConfig(config, {
|
|
347688
|
+
models,
|
|
347689
|
+
baseUrl: runtime.baseUrl,
|
|
347690
|
+
oauthKey: runtime.oauthRef.key,
|
|
347691
|
+
oauthHost: runtime.oauthRef.oauthHost,
|
|
347692
|
+
preserveDefaultModel: true
|
|
347693
|
+
});
|
|
347694
|
+
config.services = {
|
|
347695
|
+
...config.services,
|
|
347696
|
+
...existingServices
|
|
347697
|
+
};
|
|
347698
|
+
return {
|
|
347699
|
+
config,
|
|
347700
|
+
result: true
|
|
347701
|
+
};
|
|
347702
|
+
});
|
|
347703
|
+
if (repaired) this.options.onConfigUpdated?.(readConfigFile(this.options.configPath));
|
|
347704
|
+
return repaired;
|
|
347705
|
+
}
|
|
347654
347706
|
async status(providerName) {
|
|
347655
347707
|
const auth = this.resolveManagedAuth(providerName);
|
|
347656
347708
|
const apiKey = nonEmpty(auth.apiKey);
|
|
@@ -348683,6 +348735,15 @@ function errorMessage(error) {
|
|
|
348683
348735
|
}
|
|
348684
348736
|
//#endregion
|
|
348685
348737
|
//#region ../../packages/node-sdk/src/sdk-rpc-client.ts
|
|
348738
|
+
function authHomeForProfile(homeDir) {
|
|
348739
|
+
const sharedHome = process.env["BLUN_SHARED_HOME"]?.trim();
|
|
348740
|
+
const profile = process.env["BLUN_PROFILE"]?.trim();
|
|
348741
|
+
if (!sharedHome || !profile || !/^[a-z0-9][a-z0-9_-]{0,31}$/u.test(profile)) return homeDir;
|
|
348742
|
+
const expected = resolve(join(sharedHome, "profile", profile));
|
|
348743
|
+
const actual = resolve(homeDir);
|
|
348744
|
+
if ((process.platform === "win32" ? expected.toLowerCase() : expected) !== (process.platform === "win32" ? actual.toLowerCase() : actual)) return homeDir;
|
|
348745
|
+
return resolve(sharedHome);
|
|
348746
|
+
}
|
|
348686
348747
|
var SDKRpcClient = class extends SDKRpcClientBase {
|
|
348687
348748
|
homeDir;
|
|
348688
348749
|
configPath;
|
|
@@ -348691,6 +348752,7 @@ var SDKRpcClient = class extends SDKRpcClientBase {
|
|
|
348691
348752
|
auth;
|
|
348692
348753
|
core;
|
|
348693
348754
|
ready;
|
|
348755
|
+
usesDefaultConfigPath;
|
|
348694
348756
|
constructor(options = {}) {
|
|
348695
348757
|
super();
|
|
348696
348758
|
this.identity = options.identity === void 0 ? void 0 : assertBlunHostIdentity(options.identity);
|
|
@@ -348699,9 +348761,10 @@ var SDKRpcClient = class extends SDKRpcClientBase {
|
|
|
348699
348761
|
homeDir: this.homeDir,
|
|
348700
348762
|
configPath: options.configPath
|
|
348701
348763
|
});
|
|
348764
|
+
this.usesDefaultConfigPath = options.configPath === void 0;
|
|
348702
348765
|
this.telemetry = options.telemetry ?? noopTelemetryClient;
|
|
348703
348766
|
this.auth = new BlunAuthFacade({
|
|
348704
|
-
homeDir: this.homeDir,
|
|
348767
|
+
homeDir: authHomeForProfile(this.homeDir),
|
|
348705
348768
|
configPath: this.configPath,
|
|
348706
348769
|
identity: this.identity,
|
|
348707
348770
|
onRefresh: options.onOAuthRefresh
|
|
@@ -348721,6 +348784,7 @@ var SDKRpcClient = class extends SDKRpcClientBase {
|
|
|
348721
348784
|
}
|
|
348722
348785
|
async ensureConfigFile() {
|
|
348723
348786
|
await ensureConfigFile(this.configPath);
|
|
348787
|
+
if (this.usesDefaultConfigPath && authHomeForProfile(this.homeDir) !== this.homeDir) await this.auth.restoreMissingProfileModel();
|
|
348724
348788
|
}
|
|
348725
348789
|
async close() {
|
|
348726
348790
|
try {
|