blun-king-cli 9.1.628-pruefpaket.modules.r7 → 9.1.629
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/blun.js +0 -0
- package/bin/core-bootstrap.js +0 -0
- package/bin/king.js +0 -0
- package/bin/launcher-mode.js +0 -0
- package/bin/launcher-runtime.js +184 -9
- package/bin/managed-node.js +0 -0
- package/bin/managed-plugin-selection.cjs +116 -115
- package/bin/native-command-runner.js +0 -0
- package/bin/native-module-repair.js +0 -0
- package/bin/node-runtime.js +0 -0
- package/bin/node-version.js +0 -0
- package/bin/plugin-bootstrap.js +163 -162
- package/bin/private-paths.js +0 -0
- package/bin/profile-handoff.cjs +152 -0
- package/bin/profile-launch.cjs +120 -0
- package/bin/rename-private-staging.cjs +82 -0
- package/bin/standard-tools-bootstrap.js +0 -0
- package/bin/update-copy.js +0 -0
- package/bin/update-lease.js +0 -0
- package/bin/update-notice.js +0 -0
- package/blun.mjs +2 -2
- package/package.json +1 -1
- package/worker-host.mjs +2 -2
package/bin/blun.js
CHANGED
|
File without changes
|
package/bin/core-bootstrap.js
CHANGED
|
File without changes
|
package/bin/king.js
CHANGED
|
File without changes
|
package/bin/launcher-mode.js
CHANGED
|
File without changes
|
package/bin/launcher-runtime.js
CHANGED
|
@@ -40,8 +40,142 @@ const {
|
|
|
40
40
|
writePrivateFile,
|
|
41
41
|
} = require('./private-paths');
|
|
42
42
|
|
|
43
|
+
const { writeTelegramTemplate } = require('./rename-private-staging.cjs');
|
|
43
44
|
const PKG = path.resolve(__dirname, '..');
|
|
44
|
-
const
|
|
45
|
+
const { parseProfileLaunchArgs, resolveSelectedProfile } = require('./profile-launch.cjs');
|
|
46
|
+
|
|
47
|
+
// Generated from actual 628 Commander registrations; see GRAMMAR-628.json.
|
|
48
|
+
// No subcommand option arity table: subcommand tails belong to Core.
|
|
49
|
+
const ROOT_OPTION_ARITY = Object.freeze({
|
|
50
|
+
"-V": "flag",
|
|
51
|
+
"--version": "flag",
|
|
52
|
+
"--local": "flag",
|
|
53
|
+
"--message": "required",
|
|
54
|
+
"--history-limit": "required",
|
|
55
|
+
"--timeout-ms": "required",
|
|
56
|
+
"--thinking": "required",
|
|
57
|
+
"--url": "required",
|
|
58
|
+
"--token": "required",
|
|
59
|
+
"--password": "required",
|
|
60
|
+
"--tls-fingerprint": "required",
|
|
61
|
+
"-S": "optional",
|
|
62
|
+
"--session": "optional",
|
|
63
|
+
"-r": "optional",
|
|
64
|
+
"--resume": "optional",
|
|
65
|
+
"-c": "flag",
|
|
66
|
+
"--continue": "flag",
|
|
67
|
+
"-C": "flag",
|
|
68
|
+
"-y": "flag",
|
|
69
|
+
"--yolo": "flag",
|
|
70
|
+
"--auto": "flag",
|
|
71
|
+
"-p": "required",
|
|
72
|
+
"--prompt": "required",
|
|
73
|
+
"--output-format": "required",
|
|
74
|
+
"--skills-dir": "required",
|
|
75
|
+
"--add-dir": "required",
|
|
76
|
+
"--effort": "required",
|
|
77
|
+
"--permission-mode": "required",
|
|
78
|
+
"--fork-session": "required",
|
|
79
|
+
"--allowed-tools": "required",
|
|
80
|
+
"--settings": "required",
|
|
81
|
+
"--yes": "flag",
|
|
82
|
+
"--auto-approve": "flag",
|
|
83
|
+
"--plan": "flag",
|
|
84
|
+
"-h": "flag",
|
|
85
|
+
"--help": "flag"
|
|
86
|
+
});
|
|
87
|
+
const ROOT_COMMAND_NAMES = new Set(["export","proof","brief","handoff","replay","guard","demo","workspace","acp","server","web","login","mistakes","memory","doctor","config","agents","sessions","status","models","logs","vis","setup","docs","upgrade","update","__plugin_run_node","tui","chat","terminal"]);
|
|
88
|
+
|
|
89
|
+
function parseExplicitProfileArgs(args, mode) {
|
|
90
|
+
if (mode !== LAUNCHER_MODES.KING) return { args: [...args], name: undefined };
|
|
91
|
+
const remaining = [];
|
|
92
|
+
let name;
|
|
93
|
+
let firstOperandSeen = false;
|
|
94
|
+
let unknownDestination = false;
|
|
95
|
+
for (let i = 0; i < args.length; i++) {
|
|
96
|
+
const arg = args[i];
|
|
97
|
+
if (arg === '--') {
|
|
98
|
+
remaining.push(...args.slice(i));
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
if (arg === '--profile' || arg.startsWith('--profile=')) {
|
|
102
|
+
if (name !== undefined) throw new Error('--profile darf nur einmal angegeben werden.');
|
|
103
|
+
if (arg !== '--profile') throw new Error('--profile braucht einen getrennten Profilnamen.');
|
|
104
|
+
const pair = i + 1 < args.length ? [arg, args[++i]] : [arg];
|
|
105
|
+
name = parseProfileLaunchArgs(pair, mode).name;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
remaining.push(arg);
|
|
109
|
+
let token = arg;
|
|
110
|
+
let arity;
|
|
111
|
+
let recognized = false;
|
|
112
|
+
let attached = false;
|
|
113
|
+
let separator = false;
|
|
114
|
+
// Replay Commander's virtual tokens after each BOOLEAN short option.
|
|
115
|
+
// The remainder can itself become --, a long option, or a value option:
|
|
116
|
+
// -cy- -> -c -y --; -cy-prompt -> -c -y --prompt.
|
|
117
|
+
for (;;) {
|
|
118
|
+
if (token === '--') { separator = true; break; }
|
|
119
|
+
arity = Object.hasOwn(ROOT_OPTION_ARITY, token) ? ROOT_OPTION_ARITY[token] : undefined;
|
|
120
|
+
recognized = arity !== undefined;
|
|
121
|
+
if (recognized) break;
|
|
122
|
+
if (token.length > 2 && token[0] === '-' && token[1] !== '-') {
|
|
123
|
+
const short = '-' + token[1];
|
|
124
|
+
arity = ROOT_OPTION_ARITY[short];
|
|
125
|
+
if (arity !== undefined) {
|
|
126
|
+
if (arity !== 'flag') { recognized = true; attached = true; break; }
|
|
127
|
+
token = '-' + token.slice(2);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (/^--[^=]+=/u.test(token)) {
|
|
132
|
+
arity = ROOT_OPTION_ARITY[token.slice(0, token.indexOf('='))];
|
|
133
|
+
recognized = arity === 'required' || arity === 'optional';
|
|
134
|
+
attached = recognized;
|
|
135
|
+
}
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
if (separator) {
|
|
139
|
+
remaining.push(...args.slice(i + 1));
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
if (recognized) {
|
|
143
|
+
if (!attached && i + 1 < args.length
|
|
144
|
+
&& (arity === 'required'
|
|
145
|
+
|| (arity === 'optional' && !(args[i + 1].length > 1 && args[i + 1][0] === '-')))) {
|
|
146
|
+
remaining.push(args[++i]);
|
|
147
|
+
}
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const maybeOption = token.length > 1 && token[0] === '-';
|
|
151
|
+
if (maybeOption) unknownDestination = true;
|
|
152
|
+
// Match Commander's first operand/unknown split. A command-looking value
|
|
153
|
+
// behind -p/-S, a previous positional target or an unknown option is NOT
|
|
154
|
+
// a new command boundary.
|
|
155
|
+
if (!unknownDestination && !firstOperandSeen) {
|
|
156
|
+
if (ROOT_COMMAND_NAMES.has(arg)) {
|
|
157
|
+
remaining.push(...args.slice(i + 1));
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
firstOperandSeen = true;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return { args: remaining, name };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const PROFILE = parseExplicitProfileArgs(process.argv.slice(2), launcherModeFromArgv(process.argv));
|
|
167
|
+
const ARGS = PROFILE.args;
|
|
168
|
+
|
|
169
|
+
function applyProfileEnvironment(env, paths) {
|
|
170
|
+
env.BLUN_HOME = paths.blunHome;
|
|
171
|
+
if (PROFILE.name !== undefined) {
|
|
172
|
+
env.BLUN_SHARED_HOME = paths.sharedHome;
|
|
173
|
+
env.BLUN_PROFILE = PROFILE.name;
|
|
174
|
+
} else if (String(env.BLUN_STATE_DIR || '').trim()) {
|
|
175
|
+
env.BLUN_SHARED_HOME = paths.blunHome;
|
|
176
|
+
}
|
|
177
|
+
return env;
|
|
178
|
+
}
|
|
45
179
|
|
|
46
180
|
function normalizeWindowsPath(value) {
|
|
47
181
|
return path.win32.normalize(value).replace(/\\+$/u, '').toLowerCase();
|
|
@@ -51,6 +185,7 @@ function resolveLauncherPrivatePaths(options = {}) {
|
|
|
51
185
|
const env = options.env || process.env;
|
|
52
186
|
const platform = options.platform || process.platform;
|
|
53
187
|
const configuredHome = String(env.BLUN_HOME || '').trim();
|
|
188
|
+
const selectedProfile = options.profileName ?? PROFILE.name;
|
|
54
189
|
const stateDir = String(env.BLUN_STATE_DIR || '').trim();
|
|
55
190
|
if (stateDir) {
|
|
56
191
|
const paths = platform === 'win32' ? path.win32 : path.posix;
|
|
@@ -77,10 +212,35 @@ function resolveLauncherPrivatePaths(options = {}) {
|
|
|
77
212
|
ancestor = parent;
|
|
78
213
|
}
|
|
79
214
|
}
|
|
215
|
+
if (selectedProfile !== undefined) {
|
|
216
|
+
// A state override cannot bypass the canonical OS-home/profile guards.
|
|
217
|
+
const profileEnv = { ...env };
|
|
218
|
+
delete profileEnv.BLUN_STATE_DIR;
|
|
219
|
+
const selected = resolveLauncherPrivatePaths({ ...options, env: profileEnv, profileName: selectedProfile });
|
|
220
|
+
const normalize = platform === 'win32' ? normalizeWindowsPath : paths.normalize;
|
|
221
|
+
if (normalize(blunHome) !== normalize(selected.blunHome)) {
|
|
222
|
+
throw new Error('BLUN_STATE_DIR widerspricht dem ausgewaehlten Profil.');
|
|
223
|
+
}
|
|
224
|
+
return selected;
|
|
225
|
+
}
|
|
80
226
|
return { blunHome, userHome: (options.homedir || os.homedir)() };
|
|
81
227
|
}
|
|
82
228
|
if (platform !== 'win32') {
|
|
229
|
+
const posix = path.posix;
|
|
83
230
|
const userHome = (options.homedir || os.homedir)();
|
|
231
|
+
if (selectedProfile !== undefined) {
|
|
232
|
+
const configuredShared = String(env.BLUN_SHARED_HOME || '').trim();
|
|
233
|
+
const sharedHome = configuredShared
|
|
234
|
+
? posix.resolve(configuredShared)
|
|
235
|
+
: configuredHome && posix.basename(posix.dirname(configuredHome)) === 'profile'
|
|
236
|
+
? posix.resolve(configuredHome, '..', '..')
|
|
237
|
+
: configuredHome ? posix.resolve(configuredHome) : posix.join(userHome, '.blun');
|
|
238
|
+
const blunHome = resolveSelectedProfile(sharedHome, selectedProfile, posix);
|
|
239
|
+
if (configuredShared && configuredHome && posix.resolve(configuredHome) !== blunHome) {
|
|
240
|
+
throw new Error(`BLUN_HOME passt nicht zum Profil ${selectedProfile}: ${blunHome}`);
|
|
241
|
+
}
|
|
242
|
+
return { blunHome, sharedHome, userHome };
|
|
243
|
+
}
|
|
84
244
|
return {
|
|
85
245
|
blunHome: configuredHome ? path.resolve(configuredHome) : path.join(userHome, '.blun'),
|
|
86
246
|
userHome,
|
|
@@ -107,6 +267,19 @@ function resolveLauncherPrivatePaths(options = {}) {
|
|
|
107
267
|
}
|
|
108
268
|
|
|
109
269
|
const blunHome = path.win32.join(userHome, '.blun');
|
|
270
|
+
if (selectedProfile !== undefined) {
|
|
271
|
+
const profileHome = resolveSelectedProfile(blunHome, selectedProfile, path.win32);
|
|
272
|
+
const configuredShared = String(env.BLUN_SHARED_HOME || '').trim();
|
|
273
|
+
if (configuredShared && normalizeWindowsPath(path.win32.resolve(configuredShared)) !== normalizeWindowsPath(blunHome)) {
|
|
274
|
+
throw new Error(`BLUN_SHARED_HOME muss unter Windows exakt im Windows-Benutzerprofil liegen: ${blunHome}`);
|
|
275
|
+
}
|
|
276
|
+
if (configuredHome && normalizeWindowsPath(path.win32.resolve(configuredHome)) !== normalizeWindowsPath(profileHome)
|
|
277
|
+
&& normalizeWindowsPath(path.win32.resolve(configuredHome)) !== normalizeWindowsPath(blunHome)) {
|
|
278
|
+
throw new Error(`BLUN_HOME passt nicht zum Profil ${selectedProfile}: ${profileHome}`);
|
|
279
|
+
}
|
|
280
|
+
return { blunHome: profileHome, sharedHome: blunHome, userHome };
|
|
281
|
+
}
|
|
282
|
+
|
|
110
283
|
if (configuredHome
|
|
111
284
|
&& normalizeWindowsPath(path.win32.resolve(configuredHome)) !== normalizeWindowsPath(blunHome)) {
|
|
112
285
|
throw new Error(`BLUN_HOME muss unter Windows exakt im Windows-Benutzerprofil liegen: ${blunHome}`);
|
|
@@ -253,7 +426,7 @@ function installTelegramForProfile(packageRoot, blunDir) {
|
|
|
253
426
|
ensurePrivateDirectory(channelDir);
|
|
254
427
|
const envFile = path.join(channelDir, '.env');
|
|
255
428
|
if (!fs.existsSync(envFile)) {
|
|
256
|
-
|
|
429
|
+
writeTelegramTemplate(envFile, 'BLUN_TELEGRAM_BOT_TOKEN=HIER_DEIN_BOT_TOKEN_EINFUEGEN\n');
|
|
257
430
|
} else {
|
|
258
431
|
securePrivateFile(envFile);
|
|
259
432
|
}
|
|
@@ -289,6 +462,9 @@ function spawnManagedLauncher(binary, cwd) {
|
|
|
289
462
|
async function runLauncher(options = {}) {
|
|
290
463
|
const callerCwd = process.cwd();
|
|
291
464
|
const mode = options.mode || launcherModeFromArgv(process.argv);
|
|
465
|
+
if (PROFILE.name !== undefined) {
|
|
466
|
+
applyProfileEnvironment(process.env, resolveLauncherPrivatePaths());
|
|
467
|
+
}
|
|
292
468
|
const explicitUpdateRequest = isNpmPackageUpdateRequest(ARGS);
|
|
293
469
|
const explicitToolsRequest = String(ARGS[0] || '').toLowerCase() === 'tools';
|
|
294
470
|
const noticeResult = options.skipUpdateNotice === true
|
|
@@ -393,7 +569,7 @@ async function runLauncher(options = {}) {
|
|
|
393
569
|
const env = createLauncherEnvironment(process.env, mode, readPackageVersion());
|
|
394
570
|
const informationalEnv = { ...process.env };
|
|
395
571
|
delete informationalEnv.BLUN_HOME;
|
|
396
|
-
env
|
|
572
|
+
applyProfileEnvironment(env, resolveLauncherPrivatePaths({ env: informationalEnv }));
|
|
397
573
|
process.exitCode = await runInformationalCore(ARGS, env, callerCwd, releaseNotice);
|
|
398
574
|
return;
|
|
399
575
|
}
|
|
@@ -405,11 +581,11 @@ async function runLauncher(options = {}) {
|
|
|
405
581
|
process.exitCode = 1;
|
|
406
582
|
return;
|
|
407
583
|
}
|
|
408
|
-
const
|
|
584
|
+
const inspectionPaths = resolveLauncherPrivatePaths();
|
|
585
|
+
const blunDir = inspectionPaths.blunHome;
|
|
409
586
|
require('./verify-bundled-agent-sources.cjs').verifyBundledAgentSources(PKG);
|
|
410
587
|
const env = createLauncherEnvironment(process.env, LAUNCHER_MODES.BLUN, readPackageVersion(), blunDir);
|
|
411
|
-
env
|
|
412
|
-
if (String(process.env.BLUN_STATE_DIR || '').trim()) env.BLUN_SHARED_HOME = blunDir;
|
|
588
|
+
applyProfileEnvironment(env, inspectionPaths);
|
|
413
589
|
process.exitCode = await runInformationalCore(ARGS, env, callerCwd, releaseNotice);
|
|
414
590
|
return;
|
|
415
591
|
}
|
|
@@ -439,7 +615,7 @@ async function runLauncher(options = {}) {
|
|
|
439
615
|
const env = createLauncherEnvironment(process.env, mode, readPackageVersion());
|
|
440
616
|
const informationalEnv = { ...process.env };
|
|
441
617
|
delete informationalEnv.BLUN_HOME;
|
|
442
|
-
env
|
|
618
|
+
applyProfileEnvironment(env, resolveLauncherPrivatePaths({ env: informationalEnv }));
|
|
443
619
|
process.exitCode = await superviseProtectedCore(ARGS, env, callerCwd, releaseNotice);
|
|
444
620
|
return;
|
|
445
621
|
}
|
|
@@ -529,8 +705,7 @@ async function runLauncher(options = {}) {
|
|
|
529
705
|
|
|
530
706
|
// --- 6. Start -----------------------------------------------------------
|
|
531
707
|
const env = createLauncherEnvironment(process.env, mode, readPackageVersion(), blunDir);
|
|
532
|
-
env
|
|
533
|
-
if (String(process.env.BLUN_STATE_DIR || '').trim()) env.BLUN_SHARED_HOME = blunDir;
|
|
708
|
+
applyProfileEnvironment(env, getPrivatePaths());
|
|
534
709
|
process.exitCode = await superviseProtectedCore(
|
|
535
710
|
ARGS,
|
|
536
711
|
env,
|
package/bin/managed-node.js
CHANGED
|
File without changes
|
|
@@ -1,115 +1,116 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const { createHash, randomUUID } = require('node:crypto');
|
|
3
|
-
const fs = require('node:fs');
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
const {
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
if (stat.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
length.
|
|
39
|
-
|
|
40
|
-
hash.update(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (records.
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
ensurePrivateDirectory(
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
const { createHash, randomUUID } = require('node:crypto');
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { renamePrivateStaging } = require('./rename-private-staging.cjs');
|
|
6
|
+
const { ensurePrivateDirectory, securePrivateTree } = require('./private-paths');
|
|
7
|
+
const SCHEMA = 'blun.managed-plugin-selection/v1';
|
|
8
|
+
|
|
9
|
+
function snapshotPluginTree(root) {
|
|
10
|
+
const entries = [];
|
|
11
|
+
const rootStat = fs.lstatSync(root);
|
|
12
|
+
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) return null;
|
|
13
|
+
const visit = (directory, relativeDirectory = '') => {
|
|
14
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })
|
|
15
|
+
.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0)) {
|
|
16
|
+
const relativePath = path.join(relativeDirectory, entry.name);
|
|
17
|
+
const absolutePath = path.join(directory, entry.name);
|
|
18
|
+
const stat = fs.lstatSync(absolutePath);
|
|
19
|
+
if (stat.isSymbolicLink()) return false;
|
|
20
|
+
if (stat.isDirectory()) {
|
|
21
|
+
entries.push({ kind: 'directory', path: relativePath });
|
|
22
|
+
if (!visit(absolutePath, relativePath)) return false;
|
|
23
|
+
} else if (stat.isFile()) {
|
|
24
|
+
entries.push({ kind: 'file', path: relativePath, content: fs.readFileSync(absolutePath) });
|
|
25
|
+
} else return false;
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
};
|
|
29
|
+
return visit(root) ? entries : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function digestPluginEntries(entries) {
|
|
33
|
+
if (entries === null) throw new Error('Invalid managed plugin tree');
|
|
34
|
+
const hash = createHash('sha256');
|
|
35
|
+
for (const entry of entries) {
|
|
36
|
+
hash.update(Buffer.from(`${entry.kind}\0${entry.path}\0`, 'utf8'));
|
|
37
|
+
if (entry.kind === 'file') {
|
|
38
|
+
const length = Buffer.allocUnsafe(8);
|
|
39
|
+
length.writeBigUInt64BE(BigInt(entry.content.length));
|
|
40
|
+
hash.update(length);
|
|
41
|
+
hash.update(entry.content);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return hash.digest('hex');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function samePath(left, right) {
|
|
48
|
+
const normalize = value => process.platform === 'win32' ? path.resolve(value).toLowerCase() : path.resolve(value);
|
|
49
|
+
return normalize(left) === normalize(right);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function scopedPluginRoot(blunDir, pluginRoot) {
|
|
53
|
+
const managed = path.resolve(blunDir, 'plugins', 'managed');
|
|
54
|
+
const root = path.resolve(pluginRoot);
|
|
55
|
+
if (!samePath(path.dirname(root), managed)) throw new Error('Selected AgentSpine root is outside this profile');
|
|
56
|
+
for (const directory of [blunDir, path.dirname(managed), managed, root]) {
|
|
57
|
+
const stat = fs.lstatSync(directory);
|
|
58
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error('Selected AgentSpine root contains a linked directory');
|
|
59
|
+
}
|
|
60
|
+
if (!samePath(path.dirname(fs.realpathSync(root)), fs.realpathSync(managed))) {
|
|
61
|
+
throw new Error('Selected AgentSpine root resolves outside this profile');
|
|
62
|
+
}
|
|
63
|
+
return root;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function describeAgentSpineSelection({ blunDir, pluginRoot }) {
|
|
67
|
+
const root = scopedPluginRoot(blunDir, pluginRoot);
|
|
68
|
+
const digest = digestPluginEntries(snapshotPluginTree(root));
|
|
69
|
+
if (path.basename(root) !== `agent-spine-${digest}`) throw new Error('Selected AgentSpine digest mismatch');
|
|
70
|
+
return { schema: SCHEMA, digest };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function selectedAgentSpineRoot({ installed, blunDir }) {
|
|
74
|
+
const records = installed.plugins.filter(record => record.id === 'agent-spine');
|
|
75
|
+
if (!records.some(record => Object.hasOwn(record, 'managedSelection'))) return null;
|
|
76
|
+
if (records.length !== 1) throw new Error('Ambiguous AgentSpine selection');
|
|
77
|
+
const [record] = records;
|
|
78
|
+
const selection = record.managedSelection;
|
|
79
|
+
if (!selection || selection.schema !== SCHEMA || !/^[a-f0-9]{64}$/.test(selection.digest || '')
|
|
80
|
+
|| Object.keys(selection).sort().join(',') !== 'digest,schema' || typeof record.root !== 'string') {
|
|
81
|
+
throw new Error('Invalid AgentSpine selection');
|
|
82
|
+
}
|
|
83
|
+
const actual = describeAgentSpineSelection({ blunDir, pluginRoot: record.root });
|
|
84
|
+
if (actual.digest !== selection.digest) throw new Error('Selected AgentSpine digest mismatch');
|
|
85
|
+
return record.root;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function copySelectedAgentSpinePlugin({ blunDir, sourceRoot }) {
|
|
89
|
+
const plugins = path.resolve(blunDir, 'plugins');
|
|
90
|
+
const managed = path.join(plugins, 'managed');
|
|
91
|
+
ensurePrivateDirectory(plugins);
|
|
92
|
+
ensurePrivateDirectory(managed);
|
|
93
|
+
const digest = digestPluginEntries(snapshotPluginTree(sourceRoot));
|
|
94
|
+
const pluginRoot = path.join(managed, `agent-spine-${digest}`);
|
|
95
|
+
if (fs.existsSync(pluginRoot)) {
|
|
96
|
+
describeAgentSpineSelection({ blunDir, pluginRoot });
|
|
97
|
+
return pluginRoot;
|
|
98
|
+
}
|
|
99
|
+
const staging = path.join(managed, `.agent-spine-selected-${process.pid}-${randomUUID()}.tmp`);
|
|
100
|
+
try {
|
|
101
|
+
fs.cpSync(sourceRoot, staging, { recursive: true, errorOnExist: true, force: false });
|
|
102
|
+
securePrivateTree(staging);
|
|
103
|
+
if (digestPluginEntries(snapshotPluginTree(staging)) !== digest) throw new Error('AgentSpine source changed during installation');
|
|
104
|
+
renamePrivateStaging(staging, pluginRoot);
|
|
105
|
+
describeAgentSpineSelection({ blunDir, pluginRoot });
|
|
106
|
+
return pluginRoot;
|
|
107
|
+
} finally {
|
|
108
|
+
if (!samePath(path.dirname(staging), managed) || !path.basename(staging).startsWith('.agent-spine-selected-')) {
|
|
109
|
+
throw new Error('Invalid AgentSpine staging cleanup path');
|
|
110
|
+
}
|
|
111
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = { snapshotPluginTree, digestPluginEntries, describeAgentSpineSelection,
|
|
116
|
+
selectedAgentSpineRoot, copySelectedAgentSpinePlugin };
|
|
File without changes
|
|
File without changes
|
package/bin/node-runtime.js
CHANGED
|
File without changes
|
package/bin/node-version.js
CHANGED
|
File without changes
|