blun-king-cli 9.1.207 → 9.1.208

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.
@@ -37,6 +37,7 @@ const {
37
37
  handoffArgsForMode,
38
38
  handoffRuntime,
39
39
  prepareRunningUpdate,
40
+ pruneRunningUpdateReleases,
40
41
  readActiveRuntime,
41
42
  } = require('./running-update.cjs');
42
43
  const {
@@ -418,7 +419,16 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
418
419
  }
419
420
  return { ...nextCore, ready };
420
421
  },
421
- activateTarget: (target) => (options.activateRuntime || activateRuntime)(env.BLUN_SHARED_HOME, target),
422
+ activateTarget: async (target) => {
423
+ await (options.activateRuntime || activateRuntime)(env.BLUN_SHARED_HOME, target);
424
+ try {
425
+ await (options.pruneRunningUpdateReleases || pruneRunningUpdateReleases)(env.BLUN_SHARED_HOME, {
426
+ activePackageRoot: target.packageRoot,
427
+ });
428
+ } catch (error) {
429
+ options.onRunningUpdateError?.(error);
430
+ }
431
+ },
422
432
  });
423
433
  const resumed = handoff.core;
424
434
  if (resumed === undefined || resumed.ready !== true) return 1;
@@ -7,6 +7,7 @@ const path = require('node:path');
7
7
  const { spawn } = require('node:child_process');
8
8
 
9
9
  const { compareSemver, requestTrustedJson } = require('./update-notice');
10
+ const { tryAcquireUpdateLease } = require('./update-lease');
10
11
  const { ensurePrivateDirectory, securePrivateFile, writePrivateFile } = require('./private-paths');
11
12
 
12
13
  const PACKAGE_NAME = 'blun-king-cli';
@@ -19,6 +20,8 @@ const RUNNING_UPDATE_HANDOFF_MESSAGE = 'blun-running-update-handoff';
19
20
  const RUNNING_UPDATE_MODE_MESSAGE = 'blun-running-update-mode';
20
21
  const RUNTIME_READY_MESSAGE = 'blun-runtime-session-ready';
21
22
  const MAX_TARBALL_BYTES = 128 * 1024 * 1024;
23
+ const AUTO_UPDATE_SETTLE_MS = 30 * 60 * 1000;
24
+ const DEFAULT_RETAINED_RELEASE_VERSIONS = 3;
22
25
 
23
26
  function ownData(value, key) {
24
27
  if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;
@@ -37,22 +40,110 @@ function validIntegrity(value) {
37
40
  }
38
41
  }
39
42
 
40
- function selectRunningUpdateTarget(currentVersion, manifest) {
43
+ function selectRunningUpdateTarget(currentVersion, manifest, options = {}) {
41
44
  const name = ownData(manifest, 'name');
42
45
  const version = ownData(manifest, 'latest');
43
46
  const integrity = ownData(manifest, 'integrity');
44
47
  const releasedAt = ownData(manifest, 'releasedAt');
48
+ const releasedAtMs = Date.parse(releasedAt);
49
+ const now = options.now || Date.now;
45
50
  if (name !== PACKAGE_NAME
46
51
  || typeof version !== 'string'
47
52
  || compareSemver(currentVersion, version) !== -1
48
53
  || !validIntegrity(integrity)
49
54
  || typeof releasedAt !== 'string'
50
- || !Number.isFinite(Date.parse(releasedAt))) {
55
+ || !Number.isFinite(releasedAtMs)
56
+ || now() - releasedAtMs < AUTO_UPDATE_SETTLE_MS) {
51
57
  return null;
52
58
  }
53
59
  return Object.freeze({ version, integrity });
54
60
  }
55
61
 
62
+ function releaseVersionEntries(sharedHome) {
63
+ const root = releasesRoot(sharedHome);
64
+ let entries;
65
+ try {
66
+ entries = fs.readdirSync(root, { withFileTypes: true });
67
+ } catch {
68
+ return [];
69
+ }
70
+ const releases = [];
71
+ for (const entry of entries) {
72
+ if (!entry.isDirectory() || entry.isSymbolicLink()) continue;
73
+ const version = entry.name;
74
+ try {
75
+ if (compareSemver('0.0.0', version) !== -1) continue;
76
+ } catch {
77
+ continue;
78
+ }
79
+ const versionRoot = path.join(root, version);
80
+ const digestEntries = fs.readdirSync(versionRoot, { withFileTypes: true });
81
+ const packageRoots = [];
82
+ let valid = digestEntries.length > 0;
83
+ for (const digestEntry of digestEntries) {
84
+ if (!digestEntry.isDirectory() || digestEntry.isSymbolicLink()) {
85
+ valid = false;
86
+ break;
87
+ }
88
+ const packageRoot = path.join(versionRoot, digestEntry.name, 'node_modules', PACKAGE_NAME);
89
+ if (!verifyRuntimePackage(packageRoot, version, { allowedRoot: versionRoot })) {
90
+ valid = false;
91
+ break;
92
+ }
93
+ packageRoots.push(path.resolve(packageRoot));
94
+ }
95
+ if (valid && packageRoots.length > 0) releases.push({ packageRoots, version, versionRoot });
96
+ }
97
+ releases.sort((left, right) => compareSemver(right.version, left.version));
98
+ return releases;
99
+ }
100
+
101
+ async function pruneRunningUpdateReleases(sharedHome, options = {}) {
102
+ const keepVersions = Number.isInteger(options.keepVersions) && options.keepVersions >= 1
103
+ ? options.keepVersions
104
+ : DEFAULT_RETAINED_RELEASE_VERSIONS;
105
+ const activePackageRoot = typeof options.activePackageRoot === 'string'
106
+ ? path.resolve(options.activePackageRoot)
107
+ : readActiveRuntime(sharedHome)?.packageRoot;
108
+ const acquire = options.tryAcquireLease
109
+ || (({ packageRoot }) => tryAcquireUpdateLease({ packageRoot, role: 'install' }));
110
+ const releases = releaseVersionEntries(sharedHome);
111
+ const kept = new Set(releases.slice(0, keepVersions).map((release) => release.version));
112
+ for (const release of releases) {
113
+ if (release.packageRoots.includes(activePackageRoot)) kept.add(release.version);
114
+ }
115
+ const removedVersions = [];
116
+ const busyVersions = [];
117
+ for (const release of releases) {
118
+ if (kept.has(release.version)) continue;
119
+ const leases = [];
120
+ let blocked = false;
121
+ try {
122
+ for (const packageRoot of release.packageRoots) {
123
+ const result = await acquire({ packageRoot });
124
+ if (!result?.acquired || !result.lease) {
125
+ blocked = true;
126
+ break;
127
+ }
128
+ leases.push(result.lease);
129
+ }
130
+ if (blocked) {
131
+ busyVersions.push(release.version);
132
+ continue;
133
+ }
134
+ for (const lease of leases) lease.assertOwned();
135
+ fs.rmSync(release.versionRoot, { recursive: true, force: true });
136
+ removedVersions.push(release.version);
137
+ } finally {
138
+ await Promise.allSettled(leases.map((lease) => lease.release()));
139
+ }
140
+ }
141
+ return Object.freeze({
142
+ busyVersions: Object.freeze(busyVersions),
143
+ removedVersions: Object.freeze(removedVersions),
144
+ });
145
+ }
146
+
56
147
  function isSafeRuntimeBoundary(state) {
57
148
  return state?.isShuttingDown === false
58
149
  && state.streamingPhase === 'idle'
@@ -325,7 +416,7 @@ async function prepareRunningUpdate(options) {
325
416
  const manifest = await (options.loadManifest
326
417
  ? options.loadManifest()
327
418
  : requestTrustedJson(MANIFEST_URL));
328
- const target = selectRunningUpdateTarget(options.currentVersion, manifest);
419
+ const target = selectRunningUpdateTarget(options.currentVersion, manifest, { now: options.now });
329
420
  if (target === null) return null;
330
421
  const registry = await (options.loadRegistry
331
422
  ? options.loadRegistry()
@@ -378,6 +469,7 @@ async function prepareRunningUpdate(options) {
378
469
 
379
470
  module.exports = {
380
471
  ACTIVE_RUNTIME_FILE,
472
+ AUTO_UPDATE_SETTLE_MS,
381
473
  RUNNING_UPDATE_HANDOFF_EXIT_CODE,
382
474
  RUNNING_UPDATE_HANDOFF_MESSAGE,
383
475
  RUNNING_UPDATE_MODE_MESSAGE,
@@ -390,6 +482,7 @@ module.exports = {
390
482
  handoffRuntime,
391
483
  isSafeRuntimeBoundary,
392
484
  prepareRunningUpdate,
485
+ pruneRunningUpdateReleases,
393
486
  readActiveRuntime,
394
487
  resumeArgsForHandoff,
395
488
  selectRunningUpdateTarget,
package/blun.mjs CHANGED
@@ -515036,12 +515036,14 @@ function turnsToTrim(turns, maxTurns, hysteresis) {
515036
515036
  //#endregion
515037
515037
  //#region src/tui/blun-tui.ts
515038
515038
  const {
515039
+ AUTO_UPDATE_SETTLE_MS,
515039
515040
  RUNNING_UPDATE_HANDOFF_EXIT_CODE,
515040
515041
  RUNNING_UPDATE_HANDOFF_MESSAGE,
515041
515042
  RUNNING_UPDATE_MODE_MESSAGE,
515042
515043
  RUNNING_UPDATE_PREPARED_MESSAGE,
515043
515044
  RUNTIME_READY_MESSAGE,
515044
- isSafeRuntimeBoundary
515045
+ isSafeRuntimeBoundary,
515046
+ pruneRunningUpdateReleases
515045
515047
  } = __require("./bin/running-update.cjs");
515046
515048
  const {
515047
515049
  RUNNING_UPDATE_MODES,
@@ -515049,6 +515051,9 @@ const {
515049
515051
  writeRunningUpdateMode
515050
515052
  } = __require("./bin/running-update-preference.cjs");
515051
515053
  function notifyRunningRuntimeReady(tui) {
515054
+ if (typeof process.env["BLUN_SHARED_HOME"] === "string" && process.env["BLUN_SHARED_HOME"].length > 0) void pruneRunningUpdateReleases(process.env["BLUN_SHARED_HOME"], {
515055
+ activePackageRoot: __dirname
515056
+ }).catch(() => {});
515052
515057
  if (!process.connected) return;
515053
515058
  const sessionId = tui.getCurrentSessionId();
515054
515059
  if (sessionId.length === 0) return;
@@ -515061,6 +515066,17 @@ function notifyRunningRuntimeReady(tui) {
515061
515066
  }
515062
515067
  function requestRunningUpdateAtSafeBoundary(tui) {
515063
515068
  if (tui.runningUpdatePreparedVersion === void 0 || tui.runningUpdatePreparedMode === void 0 || tui.runningUpdateHandoffStarted || !process.connected) return false;
515069
+ const waitMs = tui.runningUpdateNotBeforeMs - Date.now();
515070
+ if (Number.isFinite(waitMs) && waitMs > 0) {
515071
+ if (tui.runningUpdateDelayTimer === void 0) {
515072
+ tui.runningUpdateDelayTimer = setTimeout(() => {
515073
+ tui.runningUpdateDelayTimer = void 0;
515074
+ requestRunningUpdateAtSafeBoundary(tui);
515075
+ }, waitMs);
515076
+ tui.runningUpdateDelayTimer.unref?.();
515077
+ }
515078
+ return false;
515079
+ }
515064
515080
  const boundaryState = () => ({
515065
515081
  isShuttingDown: tui.isShuttingDown,
515066
515082
  streamingPhase: tui.state.appState.streamingPhase,
@@ -515191,6 +515207,8 @@ var BlunTUI = class {
515191
515207
  runningUpdatePreparedVersion;
515192
515208
  runningUpdatePreparedMode;
515193
515209
  runningUpdateHandoffStarted = false;
515210
+ runningUpdateNotBeforeMs = Date.now() + AUTO_UPDATE_SETTLE_MS;
515211
+ runningUpdateDelayTimer;
515194
515212
  runningUpdateMessageDispose;
515195
515213
  startupPhaseMs = {};
515196
515214
  lastActivityMode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.207",
3
+ "version": "9.1.208",
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": {