blun-king-cli 9.1.208 → 9.1.209

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.
@@ -33,12 +33,15 @@ const {
33
33
  RUNNING_UPDATE_HANDOFF_MESSAGE,
34
34
  RUNNING_UPDATE_MODE_MESSAGE,
35
35
  RUNNING_UPDATE_PREPARED_MESSAGE,
36
+ RUNTIME_EXIT_INTENT_MESSAGE,
36
37
  activateRuntime,
37
38
  handoffArgsForMode,
38
39
  handoffRuntime,
39
40
  prepareRunningUpdate,
40
41
  pruneRunningUpdateReleases,
42
+ recordRunningUpdateEvent,
41
43
  readActiveRuntime,
44
+ restoreActiveRuntime,
42
45
  } = require('./running-update.cjs');
43
46
  const {
44
47
  RUNNING_UPDATE_MODES,
@@ -65,6 +68,7 @@ const RUNTIME_READY_TIMEOUT_MS = 60_000;
65
68
  const RUNNING_UPDATE_RECHECK_MS = 60_000;
66
69
  const RUNNING_UPDATE_RECHECK_JITTER_MS = 30_000;
67
70
  const RUNNING_UPDATE_HANDOFF_NOTICE = 'Ein Update wird geladen. Die TUI wird anschließend neu gestartet.';
71
+ const RUNNING_UPDATE_STABILIZATION_MS = 2 * 60 * 1000;
68
72
 
69
73
  function runningUpdateRecheckDelay(randomValue = Math.random()) {
70
74
  const normalized = Number.isFinite(randomValue)
@@ -268,6 +272,14 @@ function resolveHandoffCwd(candidate, fallback) {
268
272
  }
269
273
  }
270
274
 
275
+ function shouldRecoverActivatedRuntime(result, recovery, options = {}) {
276
+ if (recovery === undefined || options.runtimeExitIntent === true) return false;
277
+ const signal = result?.signal;
278
+ if (signal === 'SIGINT' || signal === 'SIGTERM' || signal === 'SIGHUP') return false;
279
+ const now = options.now ?? Date.now();
280
+ return now - recovery.activatedAt <= RUNNING_UPDATE_STABILIZATION_MS;
281
+ }
282
+
271
283
  async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {}) {
272
284
  const packageRoot = path.resolve(options.packageRoot || PKG);
273
285
  let preparedTarget;
@@ -276,6 +288,7 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
276
288
  let handoffCwd = cwd;
277
289
  let updateStarted = false;
278
290
  let runtimeReady = false;
291
+ let runtimeExitIntent = false;
279
292
  let runningUpdatePollTimer;
280
293
  let runningUpdateMode = readRunningUpdateMode(env.BLUN_HOME);
281
294
  const automaticMode = () => runningUpdateMode === RUNNING_UPDATE_MODES.RESUME
@@ -306,11 +319,17 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
306
319
  };
307
320
  const announcePrepared = (child) => {
308
321
  if (preparedTarget === undefined || !automaticMode() || child.connected !== true) return;
309
- child.send({
310
- type: RUNNING_UPDATE_PREPARED_MESSAGE,
311
- version: preparedTarget.version,
312
- mode: runningUpdateMode,
313
- });
322
+ try {
323
+ child.send({
324
+ type: RUNNING_UPDATE_PREPARED_MESSAGE,
325
+ version: preparedTarget.version,
326
+ mode: runningUpdateMode,
327
+ }, (error) => {
328
+ if (error) options.onRunningUpdateError?.(error);
329
+ });
330
+ } catch (error) {
331
+ options.onRunningUpdateError?.(error);
332
+ }
314
333
  };
315
334
  const startPreparation = (child) => {
316
335
  if (updateStarted || options.runningUpdate === false || !automaticMode()) return;
@@ -341,6 +360,7 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
341
360
  });
342
361
  };
343
362
  const handleCoreMessage = (message, child) => {
363
+ if (message?.type === RUNTIME_EXIT_INTENT_MESSAGE) runtimeExitIntent = true;
344
364
  if (message?.type === RUNTIME_READY_MESSAGE) {
345
365
  runtimeReady = true;
346
366
  refreshRunningUpdateMode();
@@ -395,12 +415,13 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
395
415
  const result = await core.completed;
396
416
  clearRunningUpdatePoll();
397
417
  if (existingMessageHandler) core.child.off('message', existingMessageHandler);
398
- if (result.error) throw result.error;
399
418
  if (result.code === RUNNING_UPDATE_HANDOFF_EXIT_CODE
400
419
  && preparedTarget !== undefined
401
420
  && handoffSessionId !== undefined
402
421
  && handoffMode !== undefined) {
403
422
  writeRunningUpdateHandoffNotice(options.runningUpdateNoticeOutput || process.stdout);
423
+ const previousActiveRuntime = (options.readActiveRuntime || readActiveRuntime)(env.BLUN_SHARED_HOME);
424
+ const activatedAt = (options.nowImpl || Date.now)();
404
425
  const handoff = await handoffRuntime({
405
426
  target: preparedTarget,
406
427
  previous: { packageRoot, version: readPackageVersionAt(packageRoot) },
@@ -421,6 +442,11 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
421
442
  },
422
443
  activateTarget: async (target) => {
423
444
  await (options.activateRuntime || activateRuntime)(env.BLUN_SHARED_HOME, target);
445
+ (options.recordRunningUpdateEvent || recordRunningUpdateEvent)(env.BLUN_SHARED_HOME, {
446
+ event: 'activated',
447
+ fromVersion: readPackageVersionAt(packageRoot),
448
+ toVersion: target.version,
449
+ }, { now: options.nowImpl });
424
450
  try {
425
451
  await (options.pruneRunningUpdateReleases || pruneRunningUpdateReleases)(env.BLUN_SHARED_HOME, {
426
452
  activePackageRoot: target.packageRoot,
@@ -437,8 +463,45 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
437
463
  ...options,
438
464
  existingCore: resumed,
439
465
  packageRoot: handoff.runtime.packageRoot,
466
+ recoveryRuntime: handoff.kind === 'activated' ? {
467
+ activatedAt,
468
+ activeRuntime: previousActiveRuntime,
469
+ packageRoot,
470
+ version: readPackageVersionAt(packageRoot),
471
+ } : undefined,
440
472
  });
441
473
  }
474
+ const recovery = options.recoveryRuntime;
475
+ if (shouldRecoverActivatedRuntime(result, recovery, {
476
+ now: (options.nowImpl || Date.now)(),
477
+ runtimeExitIntent,
478
+ })) {
479
+ (options.recordRunningUpdateEvent || recordRunningUpdateEvent)(env.BLUN_SHARED_HOME, {
480
+ event: 'post-activation-exit',
481
+ activeVersion: readPackageVersionAt(packageRoot),
482
+ code: Number.isInteger(result.code) ? result.code : null,
483
+ error: result.error?.message || null,
484
+ signal: result.signal || null,
485
+ recoveryVersion: recovery.version,
486
+ }, { now: options.nowImpl });
487
+ await (options.restoreActiveRuntime || restoreActiveRuntime)(env.BLUN_SHARED_HOME, recovery.activeRuntime);
488
+ const fallback = spawnCore(args, env, cwd, { packageRoot: recovery.packageRoot });
489
+ const fallbackLoaded = await fallback.loaded;
490
+ const fallbackReady = fallbackLoaded && await waitForRuntimeReady(fallback, options.readyTimeoutMs);
491
+ (options.recordRunningUpdateEvent || recordRunningUpdateEvent)(env.BLUN_SHARED_HOME, {
492
+ event: fallbackReady ? 'recovery-ready' : 'recovery-failed',
493
+ version: recovery.version,
494
+ }, { now: options.nowImpl });
495
+ if (fallbackReady) {
496
+ return superviseProtectedCore(args, env, cwd, async () => {}, {
497
+ ...options,
498
+ existingCore: fallback,
499
+ packageRoot: recovery.packageRoot,
500
+ recoveryRuntime: undefined,
501
+ });
502
+ }
503
+ }
504
+ if (result.error) throw result.error;
442
505
  return exitCodeForChild(result, loaded);
443
506
  }
444
507
 
@@ -768,12 +831,14 @@ async function runLauncher(options = {}) {
768
831
  module.exports = {
769
832
  RUNNING_UPDATE_RECHECK_JITTER_MS,
770
833
  RUNNING_UPDATE_RECHECK_MS,
834
+ RUNNING_UPDATE_STABILIZATION_MS,
771
835
  createManagedNodeEnvironment,
772
836
  resolveLauncherPrivatePaths,
773
837
  resolveHandoffCwd,
774
838
  runningUpdateRecheckDelay,
775
839
  runLauncher,
776
840
  scheduleRunningUpdateRecheck,
841
+ shouldRecoverActivatedRuntime,
777
842
  shouldDetachProtectedCore,
778
843
  spawnManagedLauncher,
779
844
  superviseProtectedCore,
@@ -19,6 +19,7 @@ const RUNNING_UPDATE_PREPARED_MESSAGE = 'blun-running-update-prepared';
19
19
  const RUNNING_UPDATE_HANDOFF_MESSAGE = 'blun-running-update-handoff';
20
20
  const RUNNING_UPDATE_MODE_MESSAGE = 'blun-running-update-mode';
21
21
  const RUNTIME_READY_MESSAGE = 'blun-runtime-session-ready';
22
+ const RUNTIME_EXIT_INTENT_MESSAGE = 'blun-runtime-exit-intent';
22
23
  const MAX_TARBALL_BYTES = 128 * 1024 * 1024;
23
24
  const AUTO_UPDATE_SETTLE_MS = 30 * 60 * 1000;
24
25
  const DEFAULT_RETAINED_RELEASE_VERSIONS = 3;
@@ -276,6 +277,28 @@ function activateRuntime(sharedHome, target) {
276
277
  }
277
278
  }
278
279
 
280
+ function restoreActiveRuntime(sharedHome, runtime) {
281
+ if (runtime === null || runtime === undefined) {
282
+ fs.rmSync(activeRuntimePath(sharedHome), { force: true });
283
+ return;
284
+ }
285
+ activateRuntime(sharedHome, runtime);
286
+ }
287
+
288
+ function recordRunningUpdateEvent(sharedHome, event, options = {}) {
289
+ if (typeof sharedHome !== 'string' || !path.isAbsolute(sharedHome)) return false;
290
+ const updateRoot = path.join(path.resolve(sharedHome), 'updates');
291
+ ensurePrivateDirectory(path.resolve(sharedHome));
292
+ ensurePrivateDirectory(updateRoot);
293
+ const filePath = path.join(updateRoot, 'handoff.jsonl');
294
+ fs.appendFileSync(filePath, `${JSON.stringify({
295
+ at: new Date((options.now || Date.now)()).toISOString(),
296
+ ...event,
297
+ })}\n`, { encoding: 'utf8', mode: 0o600 });
298
+ securePrivateFile(filePath);
299
+ return true;
300
+ }
301
+
279
302
  function registryRelease(registry, version) {
280
303
  const versions = ownData(registry, 'versions');
281
304
  const entry = ownData(versions, version);
@@ -474,6 +497,7 @@ module.exports = {
474
497
  RUNNING_UPDATE_HANDOFF_MESSAGE,
475
498
  RUNNING_UPDATE_MODE_MESSAGE,
476
499
  RUNNING_UPDATE_PREPARED_MESSAGE,
500
+ RUNTIME_EXIT_INTENT_MESSAGE,
477
501
  RUNTIME_READY_MESSAGE,
478
502
  activateRuntime,
479
503
  activeRuntimePath,
@@ -483,7 +507,9 @@ module.exports = {
483
507
  isSafeRuntimeBoundary,
484
508
  prepareRunningUpdate,
485
509
  pruneRunningUpdateReleases,
510
+ recordRunningUpdateEvent,
486
511
  readActiveRuntime,
512
+ restoreActiveRuntime,
487
513
  resumeArgsForHandoff,
488
514
  selectRunningUpdateTarget,
489
515
  verifyRuntimePackage,
package/blun.mjs CHANGED
@@ -515041,6 +515041,7 @@ const {
515041
515041
  RUNNING_UPDATE_HANDOFF_MESSAGE,
515042
515042
  RUNNING_UPDATE_MODE_MESSAGE,
515043
515043
  RUNNING_UPDATE_PREPARED_MESSAGE,
515044
+ RUNTIME_EXIT_INTENT_MESSAGE,
515044
515045
  RUNTIME_READY_MESSAGE,
515045
515046
  isSafeRuntimeBoundary,
515046
515047
  pruneRunningUpdateReleases
@@ -515734,6 +515735,9 @@ var BlunTUI = class {
515734
515735
  async stop(exitCode) {
515735
515736
  if (this.isShuttingDown) return;
515736
515737
  this.isShuttingDown = true;
515738
+ if (exitCode === 0 && process.connected) try {
515739
+ process.send({ type: RUNTIME_EXIT_INTENT_MESSAGE });
515740
+ } catch {}
515737
515741
  this.unregisterSignalHandlers();
515738
515742
  this.aborted = true;
515739
515743
  this.channelQueueDeadline?.dispose();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.208",
3
+ "version": "9.1.209",
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": {