blun-king-cli 9.1.392 → 9.1.394

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.
@@ -1,8 +1,10 @@
1
1
  'use strict';
2
+
2
3
  /*
3
- * BLUN CLI Launcher (npm) Erststart richtet alles ein:
4
- * 1. King richtet das Telegram-Plugin im BLUN_HOME ein
5
- * Danach: TUI starten. Anmeldung und Modellkatalog kommen ueber BLUN OAuth.
4
+ * BLUN CLI launcher. It prepares the local profile and then starts the bundled
5
+ * core directly. Updates are offered only through the interactive startup
6
+ * picker; no background download or deferred activation runs here. A user-
7
+ * selected update may relaunch only an explicit `king -c` resume request.
6
8
  */
7
9
  const fs = require('node:fs');
8
10
  const path = require('node:path');
@@ -23,36 +25,12 @@ const {
23
25
  seedStandardSkills,
24
26
  seedStandardTools,
25
27
  } = require('./standard-tools-bootstrap');
26
- const { CORE_LOADED_MESSAGE, RUNTIME_READY_MESSAGE } = require('./core-bootstrap');
28
+ const { CORE_LOADED_MESSAGE } = require('./core-bootstrap');
27
29
  const { prepareManagedNodeRuntime } = require('./node-runtime');
28
30
  const { repairConfiguredNativeModules } = require('./native-module-repair');
29
31
  const { startMnemoConnectHeartbeat } = require('./mnemo-connect-heartbeat.cjs');
30
- const { acquireSharedRuntimeLease, tryAcquireUpdateLease } = require('./update-lease');
31
- const { compareSemver, runExplicitUpdate, runUpdateNotice } = require('./update-notice');
32
- const {
33
- RUNNING_UPDATE_HANDOFF_ACK_MESSAGE,
34
- RUNNING_UPDATE_HANDOFF_EXIT_CODE,
35
- RUNNING_UPDATE_HANDOFF_MESSAGE,
36
- RUNNING_UPDATE_MODE_MESSAGE,
37
- RUNNING_UPDATE_PREPARED_MESSAGE,
38
- RUNTIME_EXIT_INTENT_MESSAGE,
39
- activateRuntime,
40
- clearActiveRuntime,
41
- prepareRunningUpdate,
42
- pruneRunningUpdateReleases,
43
- recordRunningUpdateEvent,
44
- readActiveRuntime,
45
- readPendingRuntime,
46
- restoreActiveRuntime,
47
- clearPendingRuntime,
48
- handoffArgsForMode,
49
- stageRuntime,
50
- } = require('./running-update.cjs');
51
- const {
52
- RUNNING_UPDATE_MODES,
53
- normalizeRunningUpdateMode,
54
- readRunningUpdateMode,
55
- } = require('./running-update-preference.cjs');
32
+ const { acquireSharedRuntimeLease } = require('./update-lease');
33
+ const { runExplicitUpdate, runUpdateNotice } = require('./update-notice');
56
34
  const {
57
35
  ensurePrivateDirectory,
58
36
  securePrivateFile,
@@ -69,40 +47,6 @@ const RAW_ARGS = process.argv.slice(2);
69
47
  const PROFILE = parseProfileLaunchArgs(RAW_ARGS, launcherModeFromArgv(process.argv));
70
48
  const ARGS = PROFILE.args;
71
49
  const CORE_LOAD_TIMEOUT_MS = 30_000;
72
- const RUNNING_UPDATE_RESUME_SESSION_ENV = 'BLUN_RUNNING_UPDATE_RESUME_SESSION_ID';
73
- const RUNTIME_READY_TIMEOUT_MS = 60_000;
74
- const RUNNING_UPDATE_RECHECK_MS = 5 * 60_000;
75
- const RUNNING_UPDATE_RECHECK_JITTER_MS = 2 * 60_000;
76
- const RUNNING_UPDATE_ERROR_RETRY_MS = 10_000;
77
- const RUNNING_UPDATE_STABILIZATION_MS = 2 * 60 * 1000;
78
-
79
- function runningUpdateRecheckDelay(randomValue = Math.random()) {
80
- const normalized = Number.isFinite(randomValue)
81
- ? Math.min(Math.max(randomValue, 0), 0.999999999)
82
- : 0;
83
- return RUNNING_UPDATE_RECHECK_MS
84
- + Math.floor(normalized * RUNNING_UPDATE_RECHECK_JITTER_MS);
85
- }
86
-
87
- function scheduleRunningUpdateRecheck(callback, options = {}) {
88
- const timer = (options.setTimeoutImpl || setTimeout)(
89
- callback,
90
- options.delayMs ?? runningUpdateRecheckDelay((options.randomImpl || Math.random)()),
91
- );
92
- timer.unref?.();
93
- return timer;
94
- }
95
-
96
- function runningUpdateFailureDetails(error) {
97
- const safeField = (value, fallback) => typeof value === 'string'
98
- && /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/u.test(value)
99
- ? value
100
- : fallback;
101
- return {
102
- errorName: safeField(error?.name, 'Error'),
103
- errorCode: safeField(error?.code, 'UNKNOWN'),
104
- };
105
- }
106
50
 
107
51
  function normalizeWindowsPath(value) {
108
52
  return path.win32.normalize(value).replace(/\\+$/u, '').toLowerCase();
@@ -209,10 +153,8 @@ function spawnProtectedCore(args, env, cwd, options = {}) {
209
153
  },
210
154
  );
211
155
  let loadedSettled = false;
212
- let readySettled = false;
213
156
  let completedSettled = false;
214
157
  let resolveLoaded;
215
- let resolveReady;
216
158
  let resolveCompleted;
217
159
  const loadTimer = setTimeout(() => {
218
160
  try {
@@ -220,7 +162,6 @@ function spawnProtectedCore(args, env, cwd, options = {}) {
220
162
  } catch {}
221
163
  }, CORE_LOAD_TIMEOUT_MS);
222
164
  const loaded = new Promise((resolve) => { resolveLoaded = resolve; });
223
- const ready = new Promise((resolve) => { resolveReady = resolve; });
224
165
  const completed = new Promise((resolve) => { resolveCompleted = resolve; });
225
166
  const settleLoaded = (value) => {
226
167
  if (loadedSettled) return;
@@ -228,17 +169,11 @@ function spawnProtectedCore(args, env, cwd, options = {}) {
228
169
  clearTimeout(loadTimer);
229
170
  resolveLoaded(value);
230
171
  };
231
- const settleReady = (value) => {
232
- if (readySettled) return;
233
- readySettled = true;
234
- resolveReady(value);
235
- };
236
172
  const settleCompleted = (result) => {
237
173
  if (completedSettled) return;
238
174
  completedSettled = true;
239
175
  for (const [signal, handler] of signalHandlers) process.removeListener(signal, handler);
240
176
  settleLoaded(false);
241
- settleReady(false);
242
177
  resolveCompleted(result);
243
178
  };
244
179
  const signalHandlers = new Map();
@@ -253,416 +188,40 @@ function spawnProtectedCore(args, env, cwd, options = {}) {
253
188
  }
254
189
  child.on('message', (message) => {
255
190
  if (message?.type === CORE_LOADED_MESSAGE) settleLoaded(true);
256
- if (message?.type === RUNTIME_READY_MESSAGE) settleReady(true);
257
191
  options.onMessage?.(message, child);
258
192
  });
259
193
  child.once('error', (error) => settleCompleted({ error }));
260
194
  child.once('exit', (code, signal) => settleCompleted({ code, signal }));
261
- return { child, completed, loaded, packageRoot, ready };
195
+ return { child, completed, loaded, packageRoot };
262
196
  }
263
197
 
264
- async function waitForRuntimeReady(core, timeoutMs = RUNTIME_READY_TIMEOUT_MS) {
265
- let timer;
266
- const timedOut = new Promise((resolve) => {
267
- timer = setTimeout(() => resolve(false), timeoutMs);
268
- timer.unref?.();
269
- });
270
- try {
271
- return await Promise.race([core.ready, timedOut]);
272
- } finally {
273
- clearTimeout(timer);
274
- }
275
- }
276
-
277
- function resolveHandoffCwd(candidate, fallback) {
278
- if (typeof candidate !== 'string' || candidate.trim().length === 0 || !path.isAbsolute(candidate)) {
279
- return fallback;
280
- }
281
- try {
282
- return fs.statSync(candidate).isDirectory() ? path.resolve(candidate) : fallback;
283
- } catch {
284
- return fallback;
285
- }
286
- }
287
-
288
- function isAutomaticRunningUpdateMode(mode) {
289
- return mode === RUNNING_UPDATE_MODES.RESUME || mode === RUNNING_UPDATE_MODES.NEW;
290
- }
291
-
292
- function shouldLaunchPreparedRuntimeAutomatically(runtime, mode) {
293
- return runtime !== undefined
294
- && runtime !== null
295
- && isAutomaticRunningUpdateMode(mode)
296
- && runtime.mode === mode;
297
- }
298
-
299
- async function prepareAutomaticStartupRuntime(options) {
300
- if (options.existingRuntime !== undefined) return options.existingRuntime;
301
- const runningUpdateMode = readRunningUpdateMode(options.profileHome);
302
- if (!isAutomaticRunningUpdateMode(runningUpdateMode)) return undefined;
303
- const target = await (options.prepareRunningUpdate || prepareRunningUpdate)({
304
- currentVersion: options.currentVersion,
305
- sharedHome: options.sharedHome,
306
- env: options.env,
307
- });
308
- if (target === null) return undefined;
309
- (options.stageRuntime || stageRuntime)(options.sharedHome, target, {
310
- mode: runningUpdateMode,
311
- cwd: options.cwd,
312
- });
313
- (options.recordRunningUpdateEvent || recordRunningUpdateEvent)(options.sharedHome, {
314
- event: 'prepared-on-cold-start',
315
- fromVersion: options.currentVersion,
316
- toVersion: target.version,
317
- });
318
- return (options.readPendingRuntime || readPendingRuntime)(options.sharedHome)
319
- || Object.freeze({ ...target, mode: runningUpdateMode, cwd: options.cwd });
198
+ function exitCodeForChild(result, loaded) {
199
+ if (!loaded && result.code === 75) return 0;
200
+ if (Number.isInteger(result.code)) return result.code;
201
+ if (result.signal === 'SIGINT') return 130;
202
+ if (result.signal === 'SIGTERM') return 143;
203
+ return 1;
320
204
  }
321
205
 
322
- function shouldRecoverActivatedRuntime(result, recovery, options = {}) {
323
- if (recovery === undefined || options.runtimeExitIntent === true) return false;
324
- const signal = result?.signal;
325
- if (signal === 'SIGINT' || signal === 'SIGTERM' || signal === 'SIGHUP') return false;
326
- const now = options.now ?? Date.now();
327
- return now - recovery.activatedAt <= RUNNING_UPDATE_STABILIZATION_MS;
206
+ function shouldResumeAfterStartupUpdate(args, updateResult) {
207
+ return updateResult?.kind === 'installed'
208
+ && Array.isArray(args)
209
+ && args.length === 1
210
+ && args[0] === '-c';
328
211
  }
329
212
 
330
- async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {}) {
331
- const packageRoot = path.resolve(options.packageRoot || PKG);
332
- let preparedTarget;
333
- let updateStarted = false;
334
- let runtimeReady = options.initialRuntimeReady === true;
335
- let activeSession = options.initialActiveSession;
336
- let acceptedHandoff;
337
- let runtimeExitIntent = false;
338
- let supervisionCompleted = false;
339
- let runningUpdatePollTimer;
340
- let runningUpdateMode = readRunningUpdateMode(env.BLUN_HOME);
341
- const automaticMode = () => runningUpdateMode === RUNNING_UPDATE_MODES.RESUME
342
- || runningUpdateMode === RUNNING_UPDATE_MODES.NEW;
343
- const refreshRunningUpdateMode = () => {
344
- const persistedMode = readRunningUpdateMode(env.BLUN_HOME);
345
- if (persistedMode !== runningUpdateMode) {
346
- runningUpdateMode = persistedMode;
347
- }
348
- };
349
- const clearRunningUpdatePoll = () => {
350
- if (runningUpdatePollTimer === undefined) return;
351
- (options.clearTimeoutImpl || clearTimeout)(runningUpdatePollTimer);
352
- runningUpdatePollTimer = undefined;
353
- };
354
- const validSessionMessage = (message) => typeof message?.sessionId === 'string'
355
- && message.sessionId.length > 0
356
- && message.sessionId.length <= 256
357
- && !/[\u0000-\u001f\u007f]/u.test(message.sessionId)
358
- && resolveHandoffCwd(message.cwd, '') !== '';
359
- const stagedRuntimeOptions = () => ({
360
- mode: runningUpdateMode,
361
- ...(runningUpdateMode === RUNNING_UPDATE_MODES.RESUME && activeSession !== undefined
362
- ? { cwd: activeSession.cwd, sessionId: activeSession.sessionId }
363
- : {}),
364
- });
365
- const persistPreparedRuntime = () => {
366
- if (preparedTarget === undefined) return;
367
- const sharedHome = env.BLUN_SHARED_HOME;
368
- if (typeof sharedHome !== 'string' || sharedHome.length === 0) return;
369
- (options.stageRuntime || stageRuntime)(sharedHome, preparedTarget, stagedRuntimeOptions());
370
- };
371
- const scheduleNextPreparation = (child, scheduleOptions = {}) => {
372
- clearRunningUpdatePoll();
373
- if (supervisionCompleted || updateStarted) return;
374
- runningUpdatePollTimer = (options.scheduleRunningUpdateRecheck || scheduleRunningUpdateRecheck)(() => {
375
- runningUpdatePollTimer = undefined;
376
- refreshRunningUpdateMode();
377
- if (automaticMode()) startPreparation(child);
378
- else scheduleNextPreparation(child);
379
- }, scheduleOptions);
380
- };
381
- const startPreparation = (child) => {
382
- if (supervisionCompleted || updateStarted
383
- || options.runningUpdate === false || !automaticMode()) return;
384
- updateStarted = true;
385
- const sharedHome = env.BLUN_SHARED_HOME;
386
- if (typeof sharedHome !== 'string' || sharedHome.length === 0) {
387
- updateStarted = false;
388
- scheduleNextPreparation(child);
389
- return;
390
- }
391
- Promise.resolve((options.prepareRunningUpdate || prepareRunningUpdate)({
392
- currentVersion: JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8')).version,
393
- sharedHome,
394
- env,
395
- })).then((target) => {
396
- if (target === null) {
397
- updateStarted = false;
398
- scheduleNextPreparation(child);
399
- return;
400
- }
401
- const previousTarget = preparedTarget;
402
- if (previousTarget !== undefined
403
- && compareSemver(target.version, previousTarget.version) !== 1) {
404
- updateStarted = false;
405
- scheduleNextPreparation(child);
406
- return;
407
- }
408
- preparedTarget = target;
409
- persistPreparedRuntime();
410
- if (child?.connected === true) {
411
- try {
412
- child.send({
413
- type: RUNNING_UPDATE_PREPARED_MESSAGE,
414
- version: target.version,
415
- mode: runningUpdateMode,
416
- });
417
- } catch {}
418
- }
419
- const runningVersion = readPackageVersionAt(packageRoot);
420
- (options.recordRunningUpdateEvent || recordRunningUpdateEvent)(sharedHome, previousTarget === undefined ? {
421
- event: 'prepared-for-next-start',
422
- fromVersion: runningVersion,
423
- toVersion: target.version,
424
- } : {
425
- event: 'prepared-target-refreshed',
426
- fromVersion: previousTarget.version,
427
- toVersion: target.version,
428
- runningVersion,
429
- }, { now: options.nowImpl });
430
- Promise.resolve((options.pruneRunningUpdateReleases || pruneRunningUpdateReleases)(sharedHome, {
431
- activePackageRoot: packageRoot,
432
- })).catch((error) => options.onRunningUpdateError?.(error));
433
- updateStarted = false;
434
- scheduleNextPreparation(child);
435
- }).catch((error) => {
436
- updateStarted = false;
437
- options.onRunningUpdateError?.(error);
438
- try {
439
- (options.recordRunningUpdateEvent || recordRunningUpdateEvent)(sharedHome, {
440
- event: 'prepare-failed',
441
- runningVersion: readPackageVersionAt(packageRoot),
442
- ...runningUpdateFailureDetails(error),
443
- }, { now: options.nowImpl });
444
- } catch (recordError) {
445
- options.onRunningUpdateError?.(recordError);
446
- }
447
- scheduleNextPreparation(child, { delayMs: RUNNING_UPDATE_ERROR_RETRY_MS });
448
- });
449
- };
450
- const handleCoreMessage = (message, child) => {
451
- if (message?.type === RUNTIME_EXIT_INTENT_MESSAGE) runtimeExitIntent = true;
452
- if (message?.type === RUNTIME_READY_MESSAGE) {
453
- if (validSessionMessage(message)) {
454
- activeSession = Object.freeze({
455
- cwd: resolveHandoffCwd(message.cwd, cwd),
456
- sessionId: message.sessionId,
457
- });
458
- try {
459
- persistPreparedRuntime();
460
- } catch (error) {
461
- options.onRunningUpdateError?.(error);
462
- }
463
- }
464
- runtimeReady = true;
465
- refreshRunningUpdateMode();
466
- if (automaticMode()) startPreparation(child);
467
- else scheduleNextPreparation(child);
468
- }
469
- if (message?.type === RUNNING_UPDATE_MODE_MESSAGE && typeof message.mode === 'string') {
470
- runningUpdateMode = normalizeRunningUpdateMode(message.mode);
471
- clearRunningUpdatePoll();
472
- if (automaticMode()) {
473
- if (runtimeReady && !updateStarted) startPreparation(child);
474
- } else if (runtimeReady) scheduleNextPreparation(child);
475
- }
476
- if (message?.type === RUNNING_UPDATE_HANDOFF_MESSAGE) {
477
- refreshRunningUpdateMode();
478
- const requestedCwd = resolveHandoffCwd(message.cwd, '');
479
- const requestMatches = preparedTarget !== undefined
480
- && automaticMode()
481
- && validSessionMessage(message)
482
- && activeSession !== undefined
483
- && message.version === preparedTarget.version
484
- && message.mode === runningUpdateMode
485
- && message.sessionId === activeSession.sessionId
486
- && requestedCwd === activeSession.cwd;
487
- if (!requestMatches) return;
488
- acceptedHandoff = Object.freeze({
489
- cwd: requestedCwd,
490
- mode: runningUpdateMode,
491
- sessionId: message.sessionId,
492
- target: preparedTarget,
493
- });
494
- if (child?.connected === true) {
495
- try {
496
- child.send({
497
- type: RUNNING_UPDATE_HANDOFF_ACK_MESSAGE,
498
- version: preparedTarget.version,
499
- mode: runningUpdateMode,
500
- sessionId: message.sessionId,
501
- });
502
- } catch {}
503
- }
504
- }
505
- };
213
+ async function superviseProtectedCore(args, env, cwd, releaseLauncherLeases, options = {}) {
506
214
  const spawnCore = options.spawnProtectedCore || spawnProtectedCore;
507
215
  const core = options.existingCore || spawnCore(args, env, cwd, {
508
- packageRoot,
509
- onMessage: handleCoreMessage,
216
+ packageRoot: options.packageRoot || PKG,
510
217
  });
511
- let existingMessageHandler;
512
- if (options.existingCore) {
513
- existingMessageHandler = (message) => handleCoreMessage(message, core.child);
514
- core.child.on('message', existingMessageHandler);
515
- }
516
218
  const loaded = await core.loaded;
517
- if (loaded && options.startupPendingRuntime !== undefined) {
518
- await (options.activateRuntime || activateRuntime)(env.BLUN_SHARED_HOME, options.startupPendingRuntime);
519
- (options.clearPendingRuntime || clearPendingRuntime)(env.BLUN_SHARED_HOME, options.startupPendingRuntime);
520
- (options.recordRunningUpdateEvent || recordRunningUpdateEvent)(env.BLUN_SHARED_HOME, {
521
- event: 'activated-on-start',
522
- version: options.startupPendingRuntime.version,
523
- }, { now: options.nowImpl });
524
- } else if (loaded && options.staleActiveRuntime !== undefined) {
525
- (options.clearActiveRuntime || clearActiveRuntime)(env.BLUN_SHARED_HOME, options.staleActiveRuntime);
526
- (options.recordRunningUpdateEvent || recordRunningUpdateEvent)(env.BLUN_SHARED_HOME, {
527
- event: 'stale-active-cleared',
528
- staleVersion: options.staleActiveRuntime.version,
529
- runningVersion: readPackageVersionAt(packageRoot),
530
- }, { now: options.nowImpl });
531
- }
532
- if (loaded && !runtimeReady) {
533
- // Core load is an independent readiness signal. Start the download even
534
- // when the later TUI-ready IPC message is lost; handoff still waits for
535
- // the TUI's fully idle boundary.
536
- runtimeReady = true;
537
- refreshRunningUpdateMode();
538
- if (automaticMode()) startPreparation(core.child);
539
- else scheduleNextPreparation(core.child);
540
- }
541
- await releaseNotice();
219
+ if (loaded) await releaseLauncherLeases();
542
220
  const result = await core.completed;
543
- supervisionCompleted = true;
544
- clearRunningUpdatePoll();
545
- if (existingMessageHandler) core.child.off('message', existingMessageHandler);
546
- if (result.code === RUNNING_UPDATE_HANDOFF_EXIT_CODE && acceptedHandoff !== undefined) {
547
- const sharedHome = env.BLUN_SHARED_HOME;
548
- const previousActiveRuntime = (options.readActiveRuntime || readActiveRuntime)(sharedHome);
549
- const previousVersion = readPackageVersionAt(packageRoot);
550
- const handoffArgs = handoffArgsForMode(args, acceptedHandoff.mode, acceptedHandoff.sessionId);
551
- let targetSession;
552
- const targetCore = spawnCore(handoffArgs, env, acceptedHandoff.cwd, {
553
- packageRoot: acceptedHandoff.target.packageRoot,
554
- onMessage(message) {
555
- if (message?.type === RUNTIME_READY_MESSAGE && validSessionMessage(message)) {
556
- targetSession = Object.freeze({
557
- cwd: resolveHandoffCwd(message.cwd, acceptedHandoff.cwd),
558
- sessionId: message.sessionId,
559
- });
560
- }
561
- },
562
- });
563
- const targetLoaded = await targetCore.loaded;
564
- const targetReady = targetLoaded
565
- && await (options.waitForRuntimeReady || waitForRuntimeReady)(targetCore, options.readyTimeoutMs);
566
- if (targetReady) {
567
- await (options.activateRuntime || activateRuntime)(sharedHome, acceptedHandoff.target);
568
- (options.clearPendingRuntime || clearPendingRuntime)(sharedHome, acceptedHandoff.target);
569
- (options.recordRunningUpdateEvent || recordRunningUpdateEvent)(sharedHome, {
570
- event: 'activated-live-handoff',
571
- fromVersion: previousVersion,
572
- toVersion: acceptedHandoff.target.version,
573
- mode: acceptedHandoff.mode,
574
- sessionId: acceptedHandoff.sessionId,
575
- }, { now: options.nowImpl });
576
- return superviseProtectedCore(handoffArgs, env, acceptedHandoff.cwd, async () => {}, {
577
- ...options,
578
- existingCore: targetCore,
579
- initialActiveSession: targetSession,
580
- initialRuntimeReady: true,
581
- packageRoot: acceptedHandoff.target.packageRoot,
582
- startupPendingRuntime: undefined,
583
- staleActiveRuntime: undefined,
584
- recoveryRuntime: {
585
- activeRuntime: previousActiveRuntime,
586
- activatedAt: (options.nowImpl || Date.now)(),
587
- packageRoot,
588
- version: previousVersion,
589
- },
590
- });
591
- }
592
- try {
593
- targetCore.child.kill();
594
- } catch {}
595
- await (options.restoreActiveRuntime || restoreActiveRuntime)(sharedHome, previousActiveRuntime);
596
- (options.clearPendingRuntime || clearPendingRuntime)(sharedHome, acceptedHandoff.target);
597
- (options.recordRunningUpdateEvent || recordRunningUpdateEvent)(sharedHome, {
598
- event: 'live-handoff-rolled-back',
599
- failedVersion: acceptedHandoff.target.version,
600
- recoveryVersion: previousVersion,
601
- }, { now: options.nowImpl });
602
- const fallback = spawnCore(handoffArgs, env, acceptedHandoff.cwd, { packageRoot });
603
- const fallbackLoaded = await fallback.loaded;
604
- const fallbackReady = fallbackLoaded
605
- && await (options.waitForRuntimeReady || waitForRuntimeReady)(fallback, options.readyTimeoutMs);
606
- if (fallbackReady) {
607
- return superviseProtectedCore(handoffArgs, env, acceptedHandoff.cwd, async () => {}, {
608
- ...options,
609
- existingCore: fallback,
610
- initialActiveSession: activeSession,
611
- initialRuntimeReady: true,
612
- packageRoot,
613
- startupPendingRuntime: undefined,
614
- staleActiveRuntime: undefined,
615
- recoveryRuntime: undefined,
616
- });
617
- }
618
- const fallbackResult = await fallback.completed;
619
- if (fallbackResult.error) throw fallbackResult.error;
620
- return exitCodeForChild(fallbackResult, fallbackLoaded);
621
- }
622
- const recovery = options.recoveryRuntime;
623
- if (shouldRecoverActivatedRuntime(result, recovery, {
624
- now: (options.nowImpl || Date.now)(),
625
- runtimeExitIntent,
626
- })) {
627
- (options.recordRunningUpdateEvent || recordRunningUpdateEvent)(env.BLUN_SHARED_HOME, {
628
- event: 'post-activation-exit',
629
- activeVersion: readPackageVersionAt(packageRoot),
630
- code: Number.isInteger(result.code) ? result.code : null,
631
- error: result.error?.message || null,
632
- signal: result.signal || null,
633
- recoveryVersion: recovery.version,
634
- }, { now: options.nowImpl });
635
- await (options.restoreActiveRuntime || restoreActiveRuntime)(env.BLUN_SHARED_HOME, recovery.activeRuntime);
636
- const fallback = spawnCore(args, env, cwd, { packageRoot: recovery.packageRoot });
637
- const fallbackLoaded = await fallback.loaded;
638
- const fallbackReady = fallbackLoaded && await waitForRuntimeReady(fallback, options.readyTimeoutMs);
639
- (options.recordRunningUpdateEvent || recordRunningUpdateEvent)(env.BLUN_SHARED_HOME, {
640
- event: fallbackReady ? 'recovery-ready' : 'recovery-failed',
641
- version: recovery.version,
642
- }, { now: options.nowImpl });
643
- if (fallbackReady) {
644
- return superviseProtectedCore(args, env, cwd, async () => {}, {
645
- ...options,
646
- existingCore: fallback,
647
- packageRoot: recovery.packageRoot,
648
- startupPendingRuntime: undefined,
649
- staleActiveRuntime: undefined,
650
- recoveryRuntime: undefined,
651
- });
652
- }
653
- }
654
221
  if (result.error) throw result.error;
655
222
  return exitCodeForChild(result, loaded);
656
223
  }
657
224
 
658
- function exitCodeForChild(result, loaded) {
659
- if (!loaded && result.code === 75) return 0;
660
- if (Number.isInteger(result.code)) return result.code;
661
- if (result.signal === 'SIGINT') return 130;
662
- if (result.signal === 'SIGTERM') return 143;
663
- return 1;
664
- }
665
-
666
225
  function installTelegramForProfile(packageRoot, blunDir) {
667
226
  installManagedTelegramPlugin({ packageRoot, blunDir });
668
227
  const channelsDir = path.join(blunDir, 'channels');
@@ -725,101 +284,22 @@ function spawnManagedLauncher(binary, cwd) {
725
284
  });
726
285
  }
727
286
 
728
- function spawnActiveLauncher(packageRoot, cwd, args = RAW_ARGS, options = {}) {
729
- const entryName = path.basename(process.argv[1] || 'king.js').toLowerCase() === 'blun.js'
730
- ? 'blun.js'
731
- : 'king.js';
732
- return new Promise((resolve, reject) => {
733
- const child = spawn(process.execPath, [path.join(packageRoot, 'bin', entryName), ...args], {
734
- cwd,
735
- env: {
736
- ...process.env,
737
- BLUN_ACTIVE_RUNTIME_ROOT: packageRoot,
738
- ...(typeof options.resumeSessionId === 'string' && options.resumeSessionId.length > 0
739
- ? { [RUNNING_UPDATE_RESUME_SESSION_ENV]: options.resumeSessionId }
740
- : {}),
741
- },
742
- stdio: 'inherit',
743
- windowsHide: true,
744
- });
745
- child.once('error', reject);
746
- child.once('exit', (code, signal) => {
747
- if (Number.isInteger(code)) resolve(code);
748
- else if (signal === 'SIGINT') resolve(130);
749
- else if (signal === 'SIGTERM') resolve(143);
750
- else resolve(1);
751
- });
752
- });
753
- }
754
-
755
287
  async function runLauncher(options = {}) {
756
288
  const callerCwd = process.cwd();
757
- let startupPendingRuntime;
758
- let deferredPendingRuntime;
759
- let staleActiveRuntime;
760
- if (options.skipActiveRuntime !== true) {
761
- const activeHome = resolveLauncherPrivatePaths().sharedHome;
762
- const pendingRuntime = readPendingRuntime(activeHome);
763
- const activeRuntime = readActiveRuntime(activeHome);
764
- const currentVersion = readPackageVersion();
765
- if (pendingRuntime !== null
766
- && path.resolve(pendingRuntime.packageRoot) !== PKG
767
- && compareSemver(currentVersion, pendingRuntime.version) === -1) {
768
- deferredPendingRuntime = pendingRuntime;
769
- }
770
- if (pendingRuntime !== null && path.resolve(pendingRuntime.packageRoot) === PKG) {
771
- startupPendingRuntime = pendingRuntime;
772
- }
773
- if (startupPendingRuntime === undefined && deferredPendingRuntime === undefined && activeRuntime !== null
774
- && path.resolve(activeRuntime.packageRoot) !== PKG
775
- && compareSemver(currentVersion, activeRuntime.version) === -1) {
776
- process.exitCode = await spawnActiveLauncher(activeRuntime.packageRoot, callerCwd);
777
- return;
778
- }
779
- if (activeRuntime !== null
780
- && path.resolve(activeRuntime.packageRoot) !== PKG
781
- && compareSemver(currentVersion, activeRuntime.version) >= 0) {
782
- staleActiveRuntime = activeRuntime;
783
- }
784
- }
785
289
  const mode = options.mode || launcherModeFromArgv(process.argv);
786
290
  const explicitUpdateRequest = isNpmPackageUpdateRequest(ARGS);
787
291
  const explicitToolsRequest = String(ARGS[0] || '').toLowerCase() === 'tools';
788
- const noticeResult = options.skipUpdateNotice === true
789
- ? { acquired: true, lease: { assertOwned() {}, async release() {} } }
790
- : options.noticeLease
791
- ? { acquired: true, lease: options.noticeLease }
792
- : await tryAcquireUpdateLease({ packageRoot: PKG, role: 'notice' });
793
- if (!noticeResult.acquired) {
794
- if (explicitUpdateRequest) {
795
- process.stderr.write('Das Update konnte nicht verifiziert werden.\n');
796
- process.exitCode = 1;
797
- }
798
- return;
799
- }
800
- const noticeLease = noticeResult.lease;
292
+ const launcherLease = options.noticeLease;
801
293
  let runtimeLease = options.runtimeLease;
802
- let noticeReleased = false;
803
- const releaseNotice = async () => {
804
- if (noticeReleased) return;
805
- noticeReleased = true;
806
- await noticeLease.release();
294
+ let leasesReleased = false;
295
+ const releaseLauncherLeases = async () => {
296
+ if (leasesReleased) return;
297
+ leasesReleased = true;
298
+ await launcherLease?.release();
807
299
  await runtimeLease?.release();
808
300
  };
809
301
 
810
302
  try {
811
- if (!runtimeLease && !explicitUpdateRequest) {
812
- const installProbe = await tryAcquireUpdateLease({ packageRoot: PKG, role: 'install' });
813
- if (!installProbe.acquired) {
814
- process.stderr.write(explicitUpdateRequest
815
- ? 'Das Update konnte nicht verifiziert werden.\n'
816
- : 'Der geschuetzte BLUN-Start ist gerade nicht verfuegbar.\n');
817
- process.exitCode = 1;
818
- return;
819
- }
820
- await installProbe.lease.release();
821
- }
822
-
823
303
  if (explicitToolsRequest) {
824
304
  const action = String(ARGS[1] || '').toLowerCase();
825
305
  const available = availableStandardToolNames({ packageRoot: PKG });
@@ -855,28 +335,12 @@ async function runLauncher(options = {}) {
855
335
  process.exitCode = 1;
856
336
  return;
857
337
  }
338
+
858
339
  if (ARGS.some((arg) => arg === '--version' || arg === '-V')) {
859
340
  process.stdout.write(`${readPackageVersion()}\n`);
860
341
  return;
861
342
  }
862
343
 
863
- if (explicitUpdateRequest) {
864
- const privatePaths = resolveLauncherPrivatePaths();
865
- const result = await runExplicitUpdate({
866
- currentVersion: readPackageVersion(),
867
- env: process.env,
868
- stdin: process.stdin,
869
- stdout: process.stdout,
870
- stderr: process.stderr,
871
- blunDir: privatePaths.sharedHome,
872
- packageRoot: PKG,
873
- noticeLease,
874
- request: ARGS[0],
875
- });
876
- if (result.kind === 'failed') process.exitCode = result.exitCode || 1;
877
- return;
878
- }
879
-
880
344
  let privatePathsCache;
881
345
  const getPrivatePaths = () => {
882
346
  privatePathsCache ||= resolveLauncherPrivatePaths();
@@ -892,11 +356,28 @@ async function runLauncher(options = {}) {
892
356
  }
893
357
  if (nodeRuntime.kind === 'managed') {
894
358
  process.stdout.write('BLUN hat die passende Node.js-Laufzeit eingerichtet.\n');
895
- await releaseNotice();
359
+ await releaseLauncherLeases();
896
360
  process.exitCode = await spawnManagedLauncher(nodeRuntime.binary, callerCwd);
897
361
  return;
898
362
  }
899
363
 
364
+ if (explicitUpdateRequest) {
365
+ const privatePaths = getPrivatePaths();
366
+ ensurePrivateDirectory(privatePaths.sharedHome);
367
+ const result = await runExplicitUpdate({
368
+ currentVersion: readPackageVersion(),
369
+ env: process.env,
370
+ stdin: process.stdin,
371
+ stdout: process.stdout,
372
+ stderr: process.stderr,
373
+ blunDir: privatePaths.sharedHome,
374
+ packageRoot: PKG,
375
+ noticeLease: launcherLease,
376
+ });
377
+ if (result.kind === 'failed') process.exitCode = result.exitCode || 1;
378
+ return;
379
+ }
380
+
900
381
  if (ARGS.some((arg) => arg === '--help' || arg === '-h')) {
901
382
  process.stdout.write(launcherHelpText());
902
383
  const env = createLauncherEnvironment(process.env, mode, readPackageVersion());
@@ -904,12 +385,25 @@ async function runLauncher(options = {}) {
904
385
  delete informationalEnv.BLUN_HOME;
905
386
  delete informationalEnv.BLUN_SHARED_HOME;
906
387
  const privatePaths = resolveLauncherPrivatePaths({ env: informationalEnv });
907
- const profilePaths = privatePaths.profilePaths;
908
- env.BLUN_HOME = profilePaths.home;
388
+ env.BLUN_HOME = privatePaths.profilePaths.home;
909
389
  env.BLUN_SHARED_HOME = privatePaths.sharedHome;
910
390
  env.BLUN_LOG_HOME = privatePaths.sharedHome;
911
391
  env.BLUN_PROFILE = PROFILE.profileName;
912
- process.exitCode = await superviseProtectedCore(ARGS, env, callerCwd, releaseNotice);
392
+ if (!runtimeLease) {
393
+ const runtimeResult = await acquireSharedRuntimeLease({ packageRoot: PKG });
394
+ if (!runtimeResult.acquired) {
395
+ process.stderr.write('Der geschuetzte BLUN-Start ist gerade nicht verfuegbar.\n');
396
+ process.exitCode = 1;
397
+ return;
398
+ }
399
+ runtimeLease = runtimeResult.lease;
400
+ }
401
+ process.exitCode = await superviseProtectedCore(
402
+ ARGS,
403
+ env,
404
+ callerCwd,
405
+ releaseLauncherLeases,
406
+ );
913
407
  return;
914
408
  }
915
409
 
@@ -922,68 +416,20 @@ async function runLauncher(options = {}) {
922
416
  const blunDir = profilePaths.home;
923
417
  ensurePrivateDirectory(blunDir);
924
418
 
925
- const runningUpdateMode = readRunningUpdateMode(profilePaths.home);
926
- if (deferredPendingRuntime === undefined && isAutomaticRunningUpdateMode(runningUpdateMode)) {
927
- try {
928
- deferredPendingRuntime = await prepareAutomaticStartupRuntime({
929
- currentVersion: readPackageVersion(),
930
- cwd: callerCwd,
931
- env: process.env,
932
- profileHome: profilePaths.home,
933
- sharedHome: privatePaths.sharedHome,
934
- });
935
- } catch {}
936
- }
937
-
938
- if (shouldLaunchPreparedRuntimeAutomatically(deferredPendingRuntime, runningUpdateMode)) {
939
- const pendingArgs = deferredPendingRuntime.mode === RUNNING_UPDATE_MODES.RESUME
940
- && deferredPendingRuntime.sessionId !== undefined
941
- ? handoffArgsForMode(RAW_ARGS, deferredPendingRuntime.mode, deferredPendingRuntime.sessionId)
942
- : RAW_ARGS;
943
- await releaseNotice();
944
- process.exitCode = await spawnActiveLauncher(
945
- deferredPendingRuntime.packageRoot,
946
- deferredPendingRuntime.cwd || callerCwd,
947
- pendingArgs,
948
- deferredPendingRuntime.mode === RUNNING_UPDATE_MODES.RESUME
949
- ? { resumeSessionId: deferredPendingRuntime.sessionId }
950
- : {},
951
- );
952
- return;
953
- }
954
-
955
- // --- 1. Oeffentlicher npm-Update-Dialog. -------------------------------
956
- const updateResult = options.skipUpdateNotice === true
957
- ? { kind: 'continue', reason: 'notice_busy' }
958
- : await runUpdateNotice({
959
- currentVersion: readPackageVersion(),
960
- argv: ARGS,
961
- env: process.env,
962
- stdin: process.stdin,
963
- stdout: process.stdout,
964
- stderr: process.stderr,
965
- blunDir: privatePaths.sharedHome,
966
- packageRoot: PKG,
967
- preparedRuntime: deferredPendingRuntime,
968
- autoInstallPreparedRuntime: isAutomaticRunningUpdateMode(runningUpdateMode),
969
- noticeLease,
970
- });
971
- if (updateResult.kind === 'installed'
972
- && updateResult.source === 'prepared_runtime'
973
- && deferredPendingRuntime !== undefined) {
974
- const pendingArgs = deferredPendingRuntime.mode !== undefined
975
- && deferredPendingRuntime.sessionId !== undefined
976
- ? handoffArgsForMode(RAW_ARGS, deferredPendingRuntime.mode, deferredPendingRuntime.sessionId)
977
- : RAW_ARGS;
978
- await releaseNotice();
979
- process.exitCode = await spawnActiveLauncher(
980
- deferredPendingRuntime.packageRoot,
981
- deferredPendingRuntime.cwd || callerCwd,
982
- pendingArgs,
983
- deferredPendingRuntime.mode === RUNNING_UPDATE_MODES.RESUME
984
- ? { resumeSessionId: deferredPendingRuntime.sessionId }
985
- : {},
986
- );
419
+ const updateResult = await runUpdateNotice({
420
+ currentVersion: readPackageVersion(),
421
+ argv: ARGS,
422
+ env: process.env,
423
+ stdin: process.stdin,
424
+ stdout: process.stdout,
425
+ stderr: process.stderr,
426
+ blunDir: privatePaths.sharedHome,
427
+ packageRoot: PKG,
428
+ noticeLease: launcherLease,
429
+ });
430
+ if (shouldResumeAfterStartupUpdate(ARGS, updateResult)) {
431
+ await releaseLauncherLeases();
432
+ process.exitCode = await spawnManagedLauncher(process.execPath, callerCwd);
987
433
  return;
988
434
  }
989
435
  if (updateResult.kind === 'installed' || updateResult.kind === 'update_in_progress') return;
@@ -998,13 +444,9 @@ async function runLauncher(options = {}) {
998
444
  runtimeLease = runtimeResult.lease;
999
445
  }
1000
446
 
1001
- // --- 2. Bestehende Konfiguration absichern -------------------------------
1002
- // Ein frischer Start bleibt absichtlich ohne statischen Anbieter. /login
1003
- // provisioniert King ueber den BLUN-OAuth-Server.
1004
447
  const cfgFile = path.join(blunDir, 'config.toml');
1005
448
  if (fs.existsSync(cfgFile)) securePrivateFile(cfgFile);
1006
449
 
1007
- // --- 3. BLUN-Standard-MCPs additiv im Benutzerprofil einrichten ---------
1008
450
  const standardMcpNames = availableStandardToolNames({ packageRoot: PKG })
1009
451
  .filter((name) => name !== 'telegram');
1010
452
  seedStandardTools({
@@ -1013,14 +455,8 @@ async function runLauncher(options = {}) {
1013
455
  names: standardMcpNames,
1014
456
  });
1015
457
  seedConfiguredMnemoMcp({ blunDir, env: process.env });
1016
-
1017
- // --- 4. Eigenes BLUN-Grunddesign als einzigen Standard-Skill installieren
1018
458
  seedStandardSkills({ packageRoot: PKG, blunDir: privatePaths.sharedHome });
1019
-
1020
- // --- 5. Telegram-Plugin + Token-Vorlage (nur King) -----------------------
1021
- if (mode === LAUNCHER_MODES.KING) {
1022
- installTelegramForProfile(PKG, blunDir);
1023
- }
459
+ if (mode === LAUNCHER_MODES.KING) installTelegramForProfile(PKG, blunDir);
1024
460
 
1025
461
  const nativeModules = repairConfiguredNativeModules({
1026
462
  blunDir,
@@ -1033,7 +469,6 @@ async function runLauncher(options = {}) {
1033
469
  process.stdout.write('Native Node.js-Module sind wieder einsatzbereit.\n');
1034
470
  }
1035
471
 
1036
- // --- 6. Start -----------------------------------------------------------
1037
472
  const env = createLauncherEnvironment(process.env, mode, readPackageVersion());
1038
473
  env.BLUN_HOME = profilePaths.home;
1039
474
  env.BLUN_SHARED_HOME = privatePaths.sharedHome;
@@ -1051,31 +486,21 @@ async function runLauncher(options = {}) {
1051
486
  ARGS,
1052
487
  env,
1053
488
  callerCwd,
1054
- releaseNotice,
1055
- { startupPendingRuntime, staleActiveRuntime },
489
+ releaseLauncherLeases,
1056
490
  );
1057
491
  } finally {
1058
492
  await mnemoConnect.stop();
1059
493
  }
1060
494
  } finally {
1061
- await releaseNotice();
495
+ await releaseLauncherLeases();
1062
496
  }
1063
497
  }
1064
498
 
1065
499
  module.exports = {
1066
- RUNNING_UPDATE_ERROR_RETRY_MS,
1067
- RUNNING_UPDATE_RECHECK_JITTER_MS,
1068
- RUNNING_UPDATE_RECHECK_MS,
1069
- RUNNING_UPDATE_STABILIZATION_MS,
1070
500
  createManagedNodeEnvironment,
1071
- prepareAutomaticStartupRuntime,
1072
501
  resolveLauncherPrivatePaths,
1073
- resolveHandoffCwd,
1074
- runningUpdateRecheckDelay,
1075
502
  runLauncher,
1076
- scheduleRunningUpdateRecheck,
1077
- shouldLaunchPreparedRuntimeAutomatically,
1078
- shouldRecoverActivatedRuntime,
503
+ shouldResumeAfterStartupUpdate,
1079
504
  shouldDetachProtectedCore,
1080
505
  spawnManagedLauncher,
1081
506
  superviseProtectedCore,