blun-king-cli 9.1.206 → 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.
- package/bin/launcher-runtime.js +11 -1
- package/bin/running-update.cjs +96 -3
- package/bin/telemetry-spool-policy.cjs +57 -0
- package/blun.mjs +58 -17
- package/package.json +1 -1
package/bin/launcher-runtime.js
CHANGED
|
@@ -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) =>
|
|
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;
|
package/bin/running-update.cjs
CHANGED
|
@@ -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(
|
|
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,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const TELEMETRY_RETRY_MAX_FILES = 16;
|
|
4
|
+
const TELEMETRY_SPOOL_MAX_FILES = 512;
|
|
5
|
+
const TELEMETRY_SPOOL_MAX_BYTES = 16 * 1024 * 1024;
|
|
6
|
+
const TELEMETRY_SPOOL_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
7
|
+
|
|
8
|
+
function isFailedTelemetryEntry(entry) {
|
|
9
|
+
return entry !== null
|
|
10
|
+
&& typeof entry === 'object'
|
|
11
|
+
&& /^failed_[^.]+\.jsonl$/u.test(entry.name ?? '')
|
|
12
|
+
&& Number.isFinite(entry.mtimeMs)
|
|
13
|
+
&& Number.isFinite(entry.size)
|
|
14
|
+
&& entry.size >= 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function oldestFirst(left, right) {
|
|
18
|
+
return left.mtimeMs - right.mtimeMs || left.name.localeCompare(right.name);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function newestFirst(left, right) {
|
|
22
|
+
return right.mtimeMs - left.mtimeMs || right.name.localeCompare(left.name);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function selectTelemetryRetryEntries(entries, maxFiles = TELEMETRY_RETRY_MAX_FILES) {
|
|
26
|
+
if (!Number.isSafeInteger(maxFiles) || maxFiles < 0) return [];
|
|
27
|
+
return entries.filter(isFailedTelemetryEntry).sort(oldestFirst).slice(0, maxFiles);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function selectTelemetrySpoolPruneEntries(entries, now = Date.now()) {
|
|
31
|
+
const failed = entries.filter(isFailedTelemetryEntry);
|
|
32
|
+
const expired = failed.filter((entry) => now - entry.mtimeMs > TELEMETRY_SPOOL_MAX_AGE_MS);
|
|
33
|
+
const expiredNames = new Set(expired.map((entry) => entry.name));
|
|
34
|
+
const live = failed.filter((entry) => !expiredNames.has(entry.name)).sort(newestFirst);
|
|
35
|
+
const keptNames = new Set();
|
|
36
|
+
let keptBytes = 0;
|
|
37
|
+
|
|
38
|
+
for (const entry of live) {
|
|
39
|
+
if (keptNames.size >= TELEMETRY_SPOOL_MAX_FILES) continue;
|
|
40
|
+
if (keptBytes + entry.size > TELEMETRY_SPOOL_MAX_BYTES) continue;
|
|
41
|
+
keptNames.add(entry.name);
|
|
42
|
+
keptBytes += entry.size;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return failed
|
|
46
|
+
.filter((entry) => expiredNames.has(entry.name) || !keptNames.has(entry.name))
|
|
47
|
+
.sort(oldestFirst);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = {
|
|
51
|
+
TELEMETRY_RETRY_MAX_FILES,
|
|
52
|
+
TELEMETRY_SPOOL_MAX_AGE_MS,
|
|
53
|
+
TELEMETRY_SPOOL_MAX_BYTES,
|
|
54
|
+
TELEMETRY_SPOOL_MAX_FILES,
|
|
55
|
+
selectTelemetryRetryEntries,
|
|
56
|
+
selectTelemetrySpoolPruneEntries,
|
|
57
|
+
};
|
package/blun.mjs
CHANGED
|
@@ -328163,6 +328163,10 @@ const RETRY_BACKOFFS_MS = [
|
|
|
328163
328163
|
16e3
|
|
328164
328164
|
];
|
|
328165
328165
|
const DEFAULT_REQUEST_TIMEOUT_MS = 1e4;
|
|
328166
|
+
const {
|
|
328167
|
+
selectTelemetryRetryEntries,
|
|
328168
|
+
selectTelemetrySpoolPruneEntries
|
|
328169
|
+
} = createRequire(import.meta.url)("./bin/telemetry-spool-policy.cjs");
|
|
328166
328170
|
var AsyncTransport = class {
|
|
328167
328171
|
homeDir;
|
|
328168
328172
|
deviceId;
|
|
@@ -328173,6 +328177,7 @@ var AsyncTransport = class {
|
|
|
328173
328177
|
requestTimeoutMs;
|
|
328174
328178
|
sleepImpl;
|
|
328175
328179
|
now;
|
|
328180
|
+
spoolWritesSincePrune = 0;
|
|
328176
328181
|
constructor(options) {
|
|
328177
328182
|
this.homeDir = options.homeDir;
|
|
328178
328183
|
this.deviceId = options.deviceId;
|
|
@@ -328235,26 +328240,44 @@ var AsyncTransport = class {
|
|
|
328235
328240
|
try {
|
|
328236
328241
|
chmodSync(path, 384);
|
|
328237
328242
|
} catch {}
|
|
328243
|
+
this.spoolWritesSincePrune += 1;
|
|
328244
|
+
if (this.spoolWritesSincePrune === 1 || this.spoolWritesSincePrune >= 64) {
|
|
328245
|
+
this.pruneDiskSpool();
|
|
328246
|
+
this.spoolWritesSincePrune = 0;
|
|
328247
|
+
}
|
|
328238
328248
|
}
|
|
328239
|
-
|
|
328240
|
-
let
|
|
328249
|
+
readSpoolEntries() {
|
|
328250
|
+
let names;
|
|
328241
328251
|
try {
|
|
328242
|
-
|
|
328252
|
+
names = readdirSync(this.telemetryDir());
|
|
328243
328253
|
} catch {
|
|
328244
|
-
return;
|
|
328254
|
+
return [];
|
|
328245
328255
|
}
|
|
328246
|
-
const
|
|
328247
|
-
for (const
|
|
328248
|
-
if (!
|
|
328249
|
-
const path = join(this.telemetryDir(),
|
|
328256
|
+
const entries = [];
|
|
328257
|
+
for (const name of names) {
|
|
328258
|
+
if (!name.startsWith("failed_") || !name.endsWith(".jsonl")) continue;
|
|
328259
|
+
const path = join(this.telemetryDir(), name);
|
|
328250
328260
|
try {
|
|
328251
|
-
|
|
328252
|
-
|
|
328253
|
-
|
|
328254
|
-
|
|
328255
|
-
|
|
328256
|
-
|
|
328257
|
-
|
|
328261
|
+
const stats = statSync(path);
|
|
328262
|
+
entries.push({ name, mtimeMs: stats.mtimeMs, size: stats.size });
|
|
328263
|
+
} catch {}
|
|
328264
|
+
}
|
|
328265
|
+
return entries;
|
|
328266
|
+
}
|
|
328267
|
+
pruneDiskSpool(spoolEntries = this.readSpoolEntries()) {
|
|
328268
|
+
const pruned = selectTelemetrySpoolPruneEntries(spoolEntries, this.now());
|
|
328269
|
+
for (const entry of pruned) try {
|
|
328270
|
+
unlinkSync(join(this.telemetryDir(), entry.name));
|
|
328271
|
+
} catch {}
|
|
328272
|
+
return new Set(pruned.map((entry) => entry.name));
|
|
328273
|
+
}
|
|
328274
|
+
async retryDiskEvents() {
|
|
328275
|
+
const spoolEntries = this.readSpoolEntries();
|
|
328276
|
+
const prunedNames = this.pruneDiskSpool(spoolEntries);
|
|
328277
|
+
const retryEntries = selectTelemetryRetryEntries(spoolEntries
|
|
328278
|
+
.filter((entry) => !prunedNames.has(entry.name)));
|
|
328279
|
+
for (const entry of retryEntries) {
|
|
328280
|
+
const path = join(this.telemetryDir(), entry.name);
|
|
328258
328281
|
let events;
|
|
328259
328282
|
let payload;
|
|
328260
328283
|
try {
|
|
@@ -328270,7 +328293,7 @@ var AsyncTransport = class {
|
|
|
328270
328293
|
await this.sendHttp(payload);
|
|
328271
328294
|
unlinkSync(path);
|
|
328272
328295
|
} catch (error) {
|
|
328273
|
-
if (error instanceof TransientTelemetryError)
|
|
328296
|
+
if (error instanceof TransientTelemetryError) break;
|
|
328274
328297
|
}
|
|
328275
328298
|
}
|
|
328276
328299
|
}
|
|
@@ -515013,12 +515036,14 @@ function turnsToTrim(turns, maxTurns, hysteresis) {
|
|
|
515013
515036
|
//#endregion
|
|
515014
515037
|
//#region src/tui/blun-tui.ts
|
|
515015
515038
|
const {
|
|
515039
|
+
AUTO_UPDATE_SETTLE_MS,
|
|
515016
515040
|
RUNNING_UPDATE_HANDOFF_EXIT_CODE,
|
|
515017
515041
|
RUNNING_UPDATE_HANDOFF_MESSAGE,
|
|
515018
515042
|
RUNNING_UPDATE_MODE_MESSAGE,
|
|
515019
515043
|
RUNNING_UPDATE_PREPARED_MESSAGE,
|
|
515020
515044
|
RUNTIME_READY_MESSAGE,
|
|
515021
|
-
isSafeRuntimeBoundary
|
|
515045
|
+
isSafeRuntimeBoundary,
|
|
515046
|
+
pruneRunningUpdateReleases
|
|
515022
515047
|
} = __require("./bin/running-update.cjs");
|
|
515023
515048
|
const {
|
|
515024
515049
|
RUNNING_UPDATE_MODES,
|
|
@@ -515026,6 +515051,9 @@ const {
|
|
|
515026
515051
|
writeRunningUpdateMode
|
|
515027
515052
|
} = __require("./bin/running-update-preference.cjs");
|
|
515028
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(() => {});
|
|
515029
515057
|
if (!process.connected) return;
|
|
515030
515058
|
const sessionId = tui.getCurrentSessionId();
|
|
515031
515059
|
if (sessionId.length === 0) return;
|
|
@@ -515038,6 +515066,17 @@ function notifyRunningRuntimeReady(tui) {
|
|
|
515038
515066
|
}
|
|
515039
515067
|
function requestRunningUpdateAtSafeBoundary(tui) {
|
|
515040
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
|
+
}
|
|
515041
515080
|
const boundaryState = () => ({
|
|
515042
515081
|
isShuttingDown: tui.isShuttingDown,
|
|
515043
515082
|
streamingPhase: tui.state.appState.streamingPhase,
|
|
@@ -515168,6 +515207,8 @@ var BlunTUI = class {
|
|
|
515168
515207
|
runningUpdatePreparedVersion;
|
|
515169
515208
|
runningUpdatePreparedMode;
|
|
515170
515209
|
runningUpdateHandoffStarted = false;
|
|
515210
|
+
runningUpdateNotBeforeMs = Date.now() + AUTO_UPDATE_SETTLE_MS;
|
|
515211
|
+
runningUpdateDelayTimer;
|
|
515171
515212
|
runningUpdateMessageDispose;
|
|
515172
515213
|
startupPhaseMs = {};
|
|
515173
515214
|
lastActivityMode;
|