blun-king-cli 9.1.608 → 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 +15 -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 +508 -368
- 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,20 @@
|
|
|
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
|
+
|
|
12
|
+
## 9.1.609
|
|
13
|
+
|
|
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.
|
|
15
|
+
- Preserve the existing busy-session rule: an effort change during active work is rejected, not queued for later. Ignore the shortcut during replay or editor takeover.
|
|
16
|
+
- These changes do not establish faster live model responses, lower billed token usage, or complete OpenClaw parity.
|
|
17
|
+
|
|
3
18
|
## 9.1.608
|
|
4
19
|
|
|
5
20
|
- Apply Telegram mention admission before unaddressed group messages enter the session context; preserve the explicit opt-out.
|
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 };
|