usebeeline 0.1.57 → 0.1.59

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.
Files changed (2) hide show
  1. package/dist/usebeeline.mjs +283 -84
  2. package/package.json +1 -1
@@ -126,6 +126,152 @@ var init_lifecycle_test_guard = __esm({
126
126
  }
127
127
  });
128
128
 
129
+ // apps/body/dist/trusty-squire-storage.js
130
+ import { homedir as homedir2 } from "node:os";
131
+ import { isAbsolute, resolve as resolve4 } from "node:path";
132
+ function trustySquireLegacyStorePaths(operatorHome = homedir2(), env = process.env) {
133
+ const roots = /* @__PURE__ */ new Set([resolve4(operatorHome, ".config")]);
134
+ const xdgConfig = env.XDG_CONFIG_HOME?.trim();
135
+ if (xdgConfig && isAbsolute(xdgConfig))
136
+ roots.add(resolve4(xdgConfig));
137
+ return [...roots].map((root) => resolve4(root, "trusty-squire"));
138
+ }
139
+ var init_trusty_squire_storage = __esm({
140
+ "apps/body/dist/trusty-squire-storage.js"() {
141
+ "use strict";
142
+ }
143
+ });
144
+
145
+ // apps/body/dist/storage-filesystem.js
146
+ import { chmod, lstat as lstat2, readdir as readdir2, realpath, rm as rm2 } from "node:fs/promises";
147
+ import { resolve as resolve5, sep } from "node:path";
148
+ import { spawn } from "node:child_process";
149
+ function canonicalStoragePath(path) {
150
+ const absolute = resolve5(path);
151
+ return process.platform === "darwin" ? absolute.replace(/^\/(var|tmp|etc)(?=\/|$)/, "/private/$1") : absolute;
152
+ }
153
+ async function ordinaryDirectory(path) {
154
+ const info = await lstat2(path).catch(() => void 0);
155
+ return Boolean(info?.isDirectory() && canonicalStoragePath(await realpath(path)) === canonicalStoragePath(path));
156
+ }
157
+ async function storageTree(root) {
158
+ if (process.platform === "darwin") {
159
+ const tree2 = /* @__PURE__ */ new Map();
160
+ const visit = async (path) => {
161
+ const info = await lstat2(path);
162
+ tree2.set(path, {
163
+ dev: info.dev,
164
+ ino: info.ino,
165
+ blocks: info.blocks * 512,
166
+ links: info.nlink,
167
+ directory: info.isDirectory(),
168
+ lastUsed: Math.max(info.atimeMs, info.mtimeMs)
169
+ });
170
+ if (info.isDirectory() && !isSquirePath(path))
171
+ for (const name of await readdir2(path))
172
+ await visit(resolve5(path, name));
173
+ };
174
+ await visit(root);
175
+ return tree2;
176
+ }
177
+ const child = spawn("/usr/bin/python3", ["-c", TREE_INVENTORY, root], { stdio: ["ignore", "pipe", "pipe"] });
178
+ const tree = /* @__PURE__ */ new Map();
179
+ let pending = "", stderr = "";
180
+ child.stderr.setEncoding("utf8");
181
+ child.stderr.on("data", (chunk) => {
182
+ stderr = (stderr + chunk).slice(-8192);
183
+ });
184
+ const completed = new Promise((done, fail) => {
185
+ child.once("error", fail);
186
+ child.once("close", (code) => code === 0 ? done() : fail(new Error(`storage inventory failed (${code}): ${stderr}`)));
187
+ });
188
+ void completed.catch(() => void 0);
189
+ const add = (line) => {
190
+ const [path, dev, ino, blocks, links, directory, lastUsed] = JSON.parse(line);
191
+ tree.set(path, { dev, ino, blocks, links, directory, lastUsed });
192
+ };
193
+ child.stdout.setEncoding("utf8");
194
+ for await (const chunk of child.stdout) {
195
+ pending += chunk;
196
+ let newline;
197
+ while ((newline = pending.indexOf("\n")) >= 0) {
198
+ add(pending.slice(0, newline));
199
+ pending = pending.slice(newline + 1);
200
+ }
201
+ }
202
+ await completed;
203
+ if (pending)
204
+ add(pending);
205
+ return tree;
206
+ }
207
+ function isSquirePath(path) {
208
+ const absolute = resolve5(path);
209
+ return absolute.split(sep).some((part, index, parts) => [".trusty-squire", "squire-host-config"].includes(part) || part === "trusty-squire" && parts[index - 1] !== "skills") || trustySquireLegacyStorePaths().some((root) => absolute === root || absolute.startsWith(`${root}${sep}`));
210
+ }
211
+ async function removeStoragePath(root, knownTree) {
212
+ const original = knownTree ?? await storageTree(root);
213
+ let index = removalIndexes.get(original);
214
+ if (!index) {
215
+ index = {
216
+ excluded: [...original.keys()].filter(isSquirePath),
217
+ directories: [...original].filter(([, item]) => item.directory).map(([path]) => path).sort()
218
+ };
219
+ removalIndexes.set(original, index);
220
+ }
221
+ const { excluded, directories } = index;
222
+ if (excluded.some((path) => path === root || path.startsWith(`${root}/`)))
223
+ throw new Error("refusing removal of Trusty Squire storage");
224
+ if (isSquirePath(root))
225
+ throw new Error("refusing removal of Trusty Squire storage");
226
+ const prefix = `${root}/`;
227
+ let low = 0, high = directories.length;
228
+ while (low < high) {
229
+ const middle = low + high >>> 1;
230
+ if (directories[middle] < prefix)
231
+ low = middle + 1;
232
+ else
233
+ high = middle;
234
+ }
235
+ const ownedDirectories = original.get(root)?.directory ? [root] : [];
236
+ for (let cursor = low; cursor < directories.length && directories[cursor].startsWith(prefix); cursor++)
237
+ ownedDirectories.push(directories[cursor]);
238
+ for (const path of ownedDirectories) {
239
+ const info = await lstat2(path).catch((error) => {
240
+ if (error.code === "ENOENT")
241
+ return void 0;
242
+ throw error;
243
+ });
244
+ if (!info)
245
+ continue;
246
+ if (info.isDirectory() && process.getuid?.() === info.uid && (info.mode & 448) !== 448)
247
+ await chmod(path, info.mode | 448);
248
+ }
249
+ await rm2(root, { recursive: true, force: true, maxRetries: 3 });
250
+ }
251
+ var TREE_INVENTORY, removalIndexes;
252
+ var init_storage_filesystem = __esm({
253
+ "apps/body/dist/storage-filesystem.js"() {
254
+ "use strict";
255
+ init_trusty_squire_storage();
256
+ TREE_INVENTORY = String.raw`
257
+ import os, sys, json, stat
258
+ root=sys.argv[1]
259
+ def visit(path):
260
+ s=os.lstat(path)
261
+ directory=stat.S_ISDIR(s.st_mode)
262
+ print(json.dumps([path,s.st_dev,s.st_ino,s.st_blocks*512,s.st_nlink,directory,max(s.st_atime_ns,s.st_mtime_ns)/1000000]))
263
+ if directory and not (os.path.basename(path) in ['.trusty-squire','squire-host-config'] or (os.path.basename(path)=='trusty-squire' and os.path.basename(os.path.dirname(path))!='skills')):
264
+ fd=os.open(path,os.O_RDONLY|os.O_DIRECTORY|os.O_NOATIME|os.O_NOFOLLOW)
265
+ try: names=os.listdir(fd)
266
+ finally: os.close(fd)
267
+ for name in names: visit(os.path.join(path,name))
268
+ visit(root)
269
+
270
+ `;
271
+ removalIndexes = /* @__PURE__ */ new WeakMap();
272
+ }
273
+ });
274
+
129
275
  // apps/body/dist/self-update-manifest.js
130
276
  function resolveManifestUrl(env = process.env) {
131
277
  const override = env.BEELINE_UPDATE_MANIFEST_URL?.trim();
@@ -252,6 +398,7 @@ __export(self_update_exports, {
252
398
  nodeVersionAtLeast: () => nodeVersionAtLeast,
253
399
  normalizeLegacyBundleShape: () => normalizeLegacyBundleShape,
254
400
  prepareLatestRelease: () => prepareLatestRelease,
401
+ pruneHelperReleases: () => pruneHelperReleases,
255
402
  readBundleEngineMinimum: () => readBundleEngineMinimum,
256
403
  readInstalledBundleIdentity: () => readInstalledBundleIdentity,
257
404
  readUpdateState: () => readUpdateState,
@@ -264,12 +411,12 @@ __export(self_update_exports, {
264
411
  });
265
412
  import { createHash as createHash3 } from "node:crypto";
266
413
  import { constants as fsConstants } from "node:fs";
267
- import { access, chmod, copyFile, lstat as lstat2, mkdir as mkdir3, open, readFile as readFile2, rename, rm as rm2, symlink, writeFile as writeFile2 } from "node:fs/promises";
268
- import { spawn } from "node:child_process";
269
- import { homedir as homedir2 } from "node:os";
270
- import { dirname as dirname4, join, resolve as resolve4 } from "node:path";
414
+ import { access, chmod as chmod2, copyFile, lstat as lstat3, mkdir as mkdir3, open, readFile as readFile2, readdir as readdir3, rename as rename2, rm as rm3, symlink, writeFile as writeFile2 } from "node:fs/promises";
415
+ import { spawn as spawn2 } from "node:child_process";
416
+ import { homedir as homedir3 } from "node:os";
417
+ import { dirname as dirname4, join, resolve as resolve6 } from "node:path";
271
418
  function anchorLayout(rawLibDir) {
272
- const libDir = resolve4(rawLibDir);
419
+ const libDir = resolve6(rawLibDir);
273
420
  const segments = libDir.split(/[/\\]/);
274
421
  const idx = segments.lastIndexOf(RELEASES_SEGMENT);
275
422
  if (idx >= 2 && segments[idx - 1] === "lib") {
@@ -285,9 +432,9 @@ function anchorLayout(rawLibDir) {
285
432
  // prefix's bin, NOT <prefix>/lib/bin — deriving it one level up was the
286
433
  // defect that made activateRelease write its stable forwarders where
287
434
  // nothing executed them, leaving stale raw wrappers in <prefix>/bin.
288
- binDir: resolve4(libDir, "../../bin"),
435
+ binDir: resolve6(libDir, "../../bin"),
289
436
  libDir,
290
- releasesRoot: resolve4(libDir, `../${RELEASES_SEGMENT}`)
437
+ releasesRoot: resolve6(libDir, `../${RELEASES_SEGMENT}`)
291
438
  };
292
439
  }
293
440
  function beelineInstallLayout(env = process.env) {
@@ -299,18 +446,18 @@ function beelineInstallLayout(env = process.env) {
299
446
  function releaseIdFromPath(path) {
300
447
  if (!path?.trim())
301
448
  return void 0;
302
- const segments = resolve4(path.trim()).split(/[/\\]/);
449
+ const segments = resolve6(path.trim()).split(/[/\\]/);
303
450
  const idx = segments.lastIndexOf(RELEASES_SEGMENT);
304
451
  const id = idx >= 0 ? segments[idx + 1] : void 0;
305
452
  return id ? sanitizeReleaseId(id) : void 0;
306
453
  }
307
454
  function runningReleaseId(layout, env = process.env, invocationPath = process.argv[1]) {
308
- const releasesRoot = resolve4(layout.releasesRoot);
309
- const anchor = resolve4(layout.libDir);
455
+ const releasesRoot = resolve6(layout.releasesRoot);
456
+ const anchor = resolve6(layout.libDir);
310
457
  for (const raw of [env.BEELINE_LIB_DIR, invocationPath]) {
311
458
  if (!raw?.trim())
312
459
  continue;
313
- const resolved = resolve4(raw.trim());
460
+ const resolved = resolve6(raw.trim());
314
461
  if (resolved !== anchor && !resolved.startsWith(`${releasesRoot}/`))
315
462
  continue;
316
463
  const id = releaseIdFromPath(resolved);
@@ -320,8 +467,8 @@ function runningReleaseId(layout, env = process.env, invocationPath = process.ar
320
467
  return void 0;
321
468
  }
322
469
  function defaultBeelineInstallLayout(env = process.env) {
323
- const home = env.HOME?.trim() || homedir2();
324
- return anchorLayout(resolve4(home, ".local", "lib", "beeline"));
470
+ const home = env.HOME?.trim() || homedir3();
471
+ return anchorLayout(resolve6(home, ".local", "lib", "beeline"));
325
472
  }
326
473
  function discoveredBeelineInstallLayout(env = process.env) {
327
474
  const explicitAnchor = env.BEELINE_INSTALL_LIB_DIR?.trim();
@@ -329,7 +476,7 @@ function discoveredBeelineInstallLayout(env = process.env) {
329
476
  return anchorLayout(explicitAnchor);
330
477
  const explicitBinDir = env.BEELINE_INSTALL_DIR?.trim();
331
478
  if (explicitBinDir)
332
- return anchorLayout(resolve4(dirname4(resolve4(explicitBinDir)), "lib", "beeline"));
479
+ return anchorLayout(resolve6(dirname4(resolve6(explicitBinDir)), "lib", "beeline"));
333
480
  return defaultBeelineInstallLayout(env);
334
481
  }
335
482
  function hostPlatformKey() {
@@ -407,7 +554,7 @@ async function writeUpdateState(layout, state) {
407
554
  } finally {
408
555
  await handle.close();
409
556
  }
410
- await rename(temporary, path);
557
+ await rename2(temporary, path);
411
558
  await fsyncDir(dirname4(path));
412
559
  }
413
560
  async function readInstalledBundleIdentity(layout, _state = {}) {
@@ -422,7 +569,7 @@ function sanitizeReleaseId(id) {
422
569
  }
423
570
  async function pathKind(path) {
424
571
  try {
425
- const info = await lstat2(path);
572
+ const info = await lstat3(path);
426
573
  if (info.isSymbolicLink())
427
574
  return "symlink";
428
575
  return info.isDirectory() ? "directory" : "missing";
@@ -470,7 +617,7 @@ async function fetchText(url, fetchImpl) {
470
617
  }
471
618
  function run(command, args, timeoutMs) {
472
619
  return new Promise((resolveRun) => {
473
- const child = spawn(command, args, { stdio: ["ignore", "ignore", "pipe"] });
620
+ const child = spawn2(command, args, { stdio: ["ignore", "ignore", "pipe"] });
474
621
  let stderr = "";
475
622
  child.stderr?.setEncoding("utf8");
476
623
  child.stderr?.on("data", (chunk) => {
@@ -566,7 +713,7 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
566
713
  }
567
714
  await writeFile2(tempArchive, Buffer.concat(chunks), { mode: 384 });
568
715
  const entries = (await new Promise((resolveList, rejectList) => {
569
- const child = spawn("tar", ["-tzf", tempArchive], { stdio: ["ignore", "pipe", "inherit"] });
716
+ const child = spawn2("tar", ["-tzf", tempArchive], { stdio: ["ignore", "pipe", "inherit"] });
570
717
  const timer = setTimeout(() => child.kill("SIGKILL"), Math.min(3e4, remaining()));
571
718
  let out = "";
572
719
  child.stdout?.on("data", (chunk) => {
@@ -619,10 +766,10 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
619
766
  return releaseId;
620
767
  } catch (error) {
621
768
  if (!previouslyVerified)
622
- await rm2(releaseDir, { recursive: true, force: true });
769
+ await rm3(releaseDir, { recursive: true, force: true });
623
770
  throw error;
624
771
  } finally {
625
- await rm2(tempArchive, { force: true });
772
+ await rm3(tempArchive, { force: true });
626
773
  }
627
774
  }
628
775
  function forwarderScript(tool) {
@@ -656,12 +803,13 @@ function forwarderScript(tool) {
656
803
  async function replaceFile(path, contents, mode) {
657
804
  const temp = `${path}.new-${process.pid}`;
658
805
  await writeFile2(temp, contents, { mode });
659
- await chmod(temp, mode);
660
- await rename(temp, path);
806
+ await chmod2(temp, mode);
807
+ await rename2(temp, path);
661
808
  }
662
- async function activateRelease(layout, releaseId) {
809
+ async function activateRelease(layout, releaseId, options = {}) {
663
810
  assertInstallTestIsolation(layout);
664
811
  let releaseDir = join(layout.releasesRoot, releaseId);
812
+ let previousReleaseId = await activeReleaseId(layout);
665
813
  await access(join(releaseDir, BUNDLE_ENTRYPOINT), fsConstants.F_OK);
666
814
  await mkdir3(layout.releasesRoot, { recursive: true });
667
815
  await mkdir3(layout.binDir, { recursive: true });
@@ -673,29 +821,39 @@ async function activateRelease(layout, releaseId) {
673
821
  try {
674
822
  await access(legacyDir, fsConstants.F_OK);
675
823
  const preservedId = `${legacyId}-${Date.now()}`;
676
- await rename(layout.libDir, join(layout.releasesRoot, preservedId));
824
+ await rename2(layout.libDir, join(layout.releasesRoot, preservedId));
677
825
  await normalizeLegacyBundleShape(join(layout.releasesRoot, preservedId));
826
+ previousReleaseId = preservedId;
678
827
  } catch {
679
- await rename(layout.libDir, legacyDir);
828
+ await rename2(layout.libDir, legacyDir);
680
829
  await normalizeLegacyBundleShape(legacyDir);
830
+ previousReleaseId = legacyId;
681
831
  }
682
832
  }
683
833
  const tempLink = `${layout.libDir}.new-${process.pid}`;
684
- await rm2(tempLink, { force: true });
834
+ await rm3(tempLink, { force: true });
685
835
  await symlink(join("beeline-releases", releaseId), tempLink);
686
- await rename(tempLink, layout.libDir);
836
+ await rename2(tempLink, layout.libDir);
687
837
  await fsyncDir(dirname4(layout.libDir));
688
838
  await installRecoveryLauncher(layout, releaseDir);
689
839
  await writeBinForwarders(layout, releaseDir);
840
+ const state = await readUpdateState(layout);
841
+ await writeUpdateState(layout, {
842
+ ...state,
843
+ stagedReleaseId: void 0,
844
+ previousReleaseId: previousReleaseId && previousReleaseId !== releaseId ? previousReleaseId : state.previousReleaseId
845
+ });
846
+ if (!options.deferPruning)
847
+ await pruneHelperReleases(layout);
690
848
  }
691
849
  async function installRecoveryLauncher(layout, releaseDir) {
692
850
  const bootstrap = join(releaseDir, "lib/beeline/recovery-cli.mjs");
693
851
  if (await access(bootstrap).then(() => true, () => false)) {
694
- const stable = resolve4(layout.libDir, "../beeline-bootstrap.mjs");
852
+ const stable = resolve6(layout.libDir, "../beeline-bootstrap.mjs");
695
853
  const temporary = `${stable}.${process.pid}.tmp`;
696
854
  await copyFile(bootstrap, temporary);
697
- await rename(temporary, stable);
698
- await replaceFile(resolve4(layout.libDir, "../beeline-node-path"), `${process.execPath}
855
+ await rename2(temporary, stable);
856
+ await replaceFile(resolve6(layout.libDir, "../beeline-node-path"), `${process.execPath}
699
857
  `, 384);
700
858
  }
701
859
  }
@@ -719,7 +877,7 @@ async function normalizeLegacyBundleShape(bundleDir) {
719
877
  continue;
720
878
  } catch {
721
879
  }
722
- await rename(join(bundleDir, name), join(innerLib, name)).catch(() => void 0);
880
+ await rename2(join(bundleDir, name), join(innerLib, name)).catch(() => void 0);
723
881
  }
724
882
  }
725
883
  async function writeBinForwarders(layout, activeBundleRoot) {
@@ -734,7 +892,7 @@ async function writeBinForwarders(layout, activeBundleRoot) {
734
892
  } catch {
735
893
  continue;
736
894
  }
737
- const independent = await access(resolve4(layout.libDir, "../beeline-bootstrap.mjs")).then(() => true, () => false);
895
+ const independent = await access(resolve6(layout.libDir, "../beeline-bootstrap.mjs")).then(() => true, () => false);
738
896
  await replaceFile(join(layout.binDir, name), independent || tool !== "beeline" ? forwarderScript(tool) : forwarderScript("legacy-beeline").replace("/bin/legacy-beeline", "/bin/beeline"), 493);
739
897
  }
740
898
  }
@@ -750,7 +908,7 @@ async function repairInstallForwarders(layout, opts = {}) {
750
908
  } catch {
751
909
  current = void 0;
752
910
  }
753
- const independent = await access(resolve4(layout.libDir, "../beeline-bootstrap.mjs")).then(() => true, () => false);
911
+ const independent = await access(resolve6(layout.libDir, "../beeline-bootstrap.mjs")).then(() => true, () => false);
754
912
  const expected = independent || tool !== "beeline" ? forwarderScript(tool) : forwarderScript("legacy-beeline").replace("/bin/legacy-beeline", "/bin/beeline");
755
913
  return current === expected;
756
914
  };
@@ -789,6 +947,7 @@ async function prepareLatestRelease(layout, options = {}) {
789
947
  deadlineAt: options.deadlineAt
790
948
  });
791
949
  await writeUpdateState(layout, {
950
+ ...await readUpdateState(layout),
792
951
  lastCheckAt: Date.now(),
793
952
  lastCheckResult: "verified",
794
953
  stagedReleaseId: releaseId
@@ -804,10 +963,53 @@ async function prepareLatestRelease(layout, options = {}) {
804
963
  throw error;
805
964
  }
806
965
  }
966
+ async function pruneHelperReleases(layout, loadedRelease) {
967
+ assertInstallTestIsolation(layout);
968
+ if (!await ordinaryDirectory(layout.releasesRoot))
969
+ return [];
970
+ const active = await activeReleaseId(layout);
971
+ if (!active)
972
+ return [];
973
+ const state = await readUpdateState(layout);
974
+ const candidates = [];
975
+ for (const entry of await readdir3(layout.releasesRoot, { withFileTypes: true })) {
976
+ if (entry.name.startsWith("."))
977
+ continue;
978
+ if (entry.isSymbolicLink()) {
979
+ if (![active, state.previousReleaseId, state.stagedReleaseId, loadedRelease].includes(entry.name))
980
+ await rm3(join(layout.releasesRoot, entry.name), { force: true });
981
+ continue;
982
+ }
983
+ if (!entry.isDirectory())
984
+ continue;
985
+ const path = join(layout.releasesRoot, entry.name);
986
+ if (!await ordinaryDirectory(path))
987
+ continue;
988
+ candidates.push({
989
+ id: entry.name,
990
+ modified: (await lstat3(path)).mtimeMs,
991
+ bootable: await access(join(path, BUNDLE_ENTRYPOINT)).then(() => true, () => false)
992
+ });
993
+ }
994
+ const recordedPrevious = candidates.find((entry) => entry.id === state.previousReleaseId && entry.id !== active && entry.bootable)?.id;
995
+ const previous = recordedPrevious ?? candidates.filter((entry) => entry.bootable && entry.id !== active && entry.id !== state.stagedReleaseId).sort((a, b) => b.modified - a.modified)[0]?.id;
996
+ const keep = /* @__PURE__ */ new Set([active, previous, loadedRelease, state.stagedReleaseId]);
997
+ const removed = [];
998
+ for (const entry of candidates) {
999
+ if (keep.has(entry.id))
1000
+ continue;
1001
+ await removeStoragePath(join(layout.releasesRoot, entry.id));
1002
+ removed.push(entry.id);
1003
+ }
1004
+ if (previous !== state.previousReleaseId)
1005
+ await writeUpdateState(layout, { ...state, previousReleaseId: previous });
1006
+ return removed;
1007
+ }
807
1008
  var RELEASES_SEGMENT, STAGED_NODE_PATH_FILE, BUNDLE_ENTRYPOINT, PI_MCP_ADAPTER_ENTRYPOINT, CODEGRAPH_ENTRYPOINT, FORWARDER_TOOLS, FORWARDER_ALIASES, LEGACY_FLAT_BUNDLE_FILES, SelfUpdateManager;
808
1009
  var init_self_update = __esm({
809
1010
  "apps/body/dist/self-update.js"() {
810
1011
  "use strict";
1012
+ init_storage_filesystem();
811
1013
  init_lifecycle_test_guard();
812
1014
  init_self_update_manifest();
813
1015
  RELEASES_SEGMENT = "beeline-releases";
@@ -859,10 +1061,10 @@ __export(managed_update_exports, {
859
1061
  versionAtLeast: () => versionAtLeast,
860
1062
  withInstallLock: () => withInstallLock
861
1063
  });
862
- import { spawn as spawn2 } from "node:child_process";
863
- import { resolve as resolve5 } from "node:path";
1064
+ import { spawn as spawn3 } from "node:child_process";
1065
+ import { resolve as resolve7 } from "node:path";
864
1066
  function withInstallLock(layout, work, options = {}) {
865
- return withKernelLock(`beeline-install:${resolve5(layout.libDir)}`, work, options.waitMs);
1067
+ return withKernelLock(`beeline-install:${resolve7(layout.libDir)}`, work, options.waitMs);
866
1068
  }
867
1069
  function versionAtLeast(actual, minimum) {
868
1070
  const parse = (value) => /^v?(\d+)\.(\d+)\.(\d+)$/.exec(value)?.slice(1).map(Number);
@@ -887,9 +1089,9 @@ async function runManagedUpdateWorker() {
887
1089
  }
888
1090
  async function prepareReleaseInWorker(env, deadlineAt, entrypoint) {
889
1091
  const layout = beelineInstallLayout(env);
890
- const bootstrap = entrypoint ?? resolve5(layout.libDir, "../beeline-bootstrap.mjs");
1092
+ const bootstrap = entrypoint ?? resolve7(layout.libDir, "../beeline-bootstrap.mjs");
891
1093
  return new Promise((done, reject) => {
892
- const child = spawn2(process.execPath, [bootstrap, "managed-update-worker"], {
1094
+ const child = spawn3(process.execPath, [bootstrap, "managed-update-worker"], {
893
1095
  detached: true,
894
1096
  env: {
895
1097
  ...env,
@@ -993,7 +1195,7 @@ var init_managed_update = __esm({
993
1195
  if (this.#minimum) {
994
1196
  const identity = await readInstalledBundleIdentity({
995
1197
  ...this.options.layout,
996
- libDir: resolve5(this.options.layout.releasesRoot, releaseId)
1198
+ libDir: resolve7(this.options.layout.releasesRoot, releaseId)
997
1199
  });
998
1200
  if (!identity?.version || !versionAtLeast(identity.version, this.#minimum)) {
999
1201
  this.#prepared = void 0;
@@ -1075,13 +1277,13 @@ var init_managed_update = __esm({
1075
1277
  });
1076
1278
 
1077
1279
  // apps/body/dist/recovery-cli.js
1078
- import { spawn as spawn3 } from "node:child_process";
1079
- import { readFile as readFile4, readdir as readdir3, stat as stat2, unlink } from "node:fs/promises";
1080
- import { resolve as resolve7 } from "node:path";
1280
+ import { spawn as spawn4 } from "node:child_process";
1281
+ import { readFile as readFile4, readdir as readdir5, stat as stat2, unlink } from "node:fs/promises";
1282
+ import { resolve as resolve9 } from "node:path";
1081
1283
 
1082
1284
  // apps/body/dist/recovery.js
1083
- import { readFile as readFile3, readdir as readdir2, stat } from "node:fs/promises";
1084
- import { resolve as resolve6 } from "node:path";
1285
+ import { readFile as readFile3, readdir as readdir4, stat } from "node:fs/promises";
1286
+ import { resolve as resolve8 } from "node:path";
1085
1287
 
1086
1288
  // apps/body/dist/machine-control.js
1087
1289
  import { watch } from "node:fs";
@@ -1193,7 +1395,7 @@ async function requestMachine(stateRoot, request, accept, timeoutMs = 6e5) {
1193
1395
  init_kernel_lock();
1194
1396
  init_lifecycle_test_guard();
1195
1397
  import { execFile } from "node:child_process";
1196
- import { mkdir as mkdir2, readFile, readdir, rm, writeFile } from "node:fs/promises";
1398
+ import { mkdir as mkdir2, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
1197
1399
  import { homedir } from "node:os";
1198
1400
  import { resolve as resolve3 } from "node:path";
1199
1401
  import { promisify } from "node:util";
@@ -1215,9 +1417,7 @@ Environment=PATH=%h/.local/bin:/usr/local/bin:/usr/bin:/bin
1215
1417
  ExecStart=%h/.local/bin/beeline helper-bootstrap
1216
1418
  ExecReload=/bin/kill -HUP $MAINPID
1217
1419
  Restart=always
1218
- RestartSec=5s
1219
- RestartSteps=5
1220
- RestartMaxDelaySec=60s
1420
+ RestartSec=1s
1221
1421
  WatchdogSec=180s
1222
1422
  CPUWeight=1000
1223
1423
  IOWeight=1000
@@ -1449,7 +1649,7 @@ async function prepareMachineRelease(layout, options = {}) {
1449
1649
  return withInstallLock(layout, () => prepareLatestRelease(layout, { ...options, deadlineAt }), {
1450
1650
  waitMs: Math.max(0, deadlineAt - Date.now())
1451
1651
  });
1452
- const stable = resolve6(layout.libDir, "../beeline-bootstrap.mjs");
1652
+ const stable = resolve8(layout.libDir, "../beeline-bootstrap.mjs");
1453
1653
  const entrypoint = env.BEELINE_RECOVERY_ENTRY || await readFile3(stable).then(() => stable, () => process.argv[1]);
1454
1654
  return prepareReleaseInWorker({ ...env, ...options?.force ? { BEELINE_FORCE_REPAIR: "1" } : {} }, deadlineAt, entrypoint);
1455
1655
  }
@@ -1458,9 +1658,9 @@ async function convergeMachine(options = {}) {
1458
1658
  assertLifecycleTestIsolation(env, Boolean(options.run));
1459
1659
  const layout = options.layout ?? beelineInstallLayout(env) ?? discoveredBeelineInstallLayout(env);
1460
1660
  const deadlineAt = options.deadlineAt ?? Date.now() + (options.timeoutMs ?? 6e5);
1461
- const stateRoot = env.XDG_STATE_HOME || resolve6(env.HOME || "", ".local/state");
1462
- const root = resolve6(stateRoot, "beeline/agents");
1463
- const configured = await Promise.all((await readdir2(root).catch(() => [])).filter((id) => /^[0-9a-f]{64}$/i.test(id)).map(async (id) => await stat(resolve6(root, id, "runtime.json")).then(() => true, () => false) && !await stat(resolve6(root, id, "stopped")).then(() => true, () => false) ? id : void 0));
1661
+ const stateRoot = env.XDG_STATE_HOME || resolve8(env.HOME || "", ".local/state");
1662
+ const root = resolve8(stateRoot, "beeline/agents");
1663
+ const configured = await Promise.all((await readdir4(root).catch(() => [])).filter((id) => /^[0-9a-f]{64}$/i.test(id)).map(async (id) => await stat(resolve8(root, id, "runtime.json")).then(() => true, () => false) && !await stat(resolve8(root, id, "stopped")).then(() => true, () => false) ? id : void 0));
1464
1664
  const required = /* @__PURE__ */ new Set([
1465
1665
  ...configured.filter((id) => Boolean(id)),
1466
1666
  ...options.agentIds ?? []
@@ -1481,7 +1681,7 @@ async function convergeMachine(options = {}) {
1481
1681
  let expectedRelease = options.releaseId;
1482
1682
  const targetIdentity = options.releaseId ? await readInstalledBundleIdentity({
1483
1683
  ...layout,
1484
- libDir: resolve6(layout.releasesRoot, options.releaseId)
1684
+ libDir: resolve8(layout.releasesRoot, options.releaseId)
1485
1685
  }) : void 0;
1486
1686
  const request = options.releaseId && hasControl ? {
1487
1687
  operation: "update",
@@ -1512,33 +1712,32 @@ async function bootstrapMachine(env = process.env) {
1512
1712
  if (!pid || ![Number(env.BEELINE_BOOT_LAUNCHER_PID), Number(env.BEELINE_BOOT_LAUNCHER_PARENT_PID)].includes(pid))
1513
1713
  throw new Error("helper bootstrap requires native service ownership; run beeline start");
1514
1714
  const layout = beelineInstallLayout(env) ?? discoveredBeelineInstallLayout(env);
1715
+ const state = await readUpdateState(layout);
1716
+ const active = await activeReleaseId(layout);
1515
1717
  let releaseId;
1516
- try {
1718
+ for (const id of [state.stagedReleaseId, active]) {
1719
+ if (!id || !/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(id))
1720
+ continue;
1721
+ const digest = await readFile3(resolve8(layout.releasesRoot, id, ".stage-ok"), "utf8").catch(() => "");
1722
+ if (/^[0-9a-f]{64}$/i.test(digest.trim()) && await resolveBundleEntrypoint(resolve8(layout.releasesRoot, id))) {
1723
+ releaseId = id;
1724
+ break;
1725
+ }
1726
+ }
1727
+ if (!releaseId) {
1517
1728
  if (env.BEELINE_UPDATE_DISABLE === "1")
1518
- throw new Error("automatic release selection disabled by BEELINE_UPDATE_DISABLE");
1519
- const prepared = await prepareMachineRelease(layout, {
1729
+ throw new Error("automatic release selection disabled; no verified runnable release");
1730
+ releaseId = (await prepareMachineRelease(layout, {
1520
1731
  env,
1521
1732
  deadlineAt: Date.now() + 18e4,
1522
1733
  logger: (line) => console.error(line)
1523
- });
1524
- releaseId = prepared.releaseId;
1525
- await withInstallLock(layout, () => activateRelease(layout, releaseId), {
1526
- waitMs: Math.max(0, bootDeadlineAt - Date.now())
1527
- });
1528
- } catch (error) {
1529
- const state = await readUpdateState(layout);
1530
- const candidates = [state.stagedReleaseId, await activeReleaseId(layout)];
1531
- const verified = await Promise.all(candidates.map(async (id) => id && /^[0-9a-f]{64}$/i.test((await readFile3(resolve6(layout.releasesRoot, id, ".stage-ok"), "utf8").catch(() => "")).trim()) ? id : void 0));
1532
- const candidate = verified.find(Boolean);
1533
- if (!candidate)
1534
- throw new Error(`startup release selection failed; no verified runnable release: ${String(error)}`);
1535
- releaseId = candidate;
1536
- console.error(`[beeline] serving verified ${releaseId}; release freshness unconfirmed: ${String(error)}`);
1537
- await withInstallLock(layout, () => activateRelease(layout, releaseId), {
1734
+ })).releaseId;
1735
+ }
1736
+ if (releaseId !== active)
1737
+ await withInstallLock(layout, () => activateRelease(layout, releaseId, { deferPruning: true }), {
1538
1738
  waitMs: Math.max(0, bootDeadlineAt - Date.now())
1539
1739
  });
1540
- }
1541
- const entry = await resolveBundleEntrypoint(resolve6(layout.releasesRoot, releaseId));
1740
+ const entry = await resolveBundleEntrypoint(resolve8(layout.releasesRoot, releaseId));
1542
1741
  if (!entry)
1543
1742
  throw new Error(`selected release ${releaseId} has no body entry`);
1544
1743
  return entry;
@@ -1599,18 +1798,18 @@ async function main() {
1599
1798
  if (args[i] === "--manifest-url" && !args[++i])
1600
1799
  throw new Error("--manifest-url requires a URL");
1601
1800
  }
1602
- const stateRoot2 = process.env.XDG_STATE_HOME || resolve7(process.env.HOME || "", ".local/state");
1801
+ const stateRoot2 = process.env.XDG_STATE_HOME || resolve9(process.env.HOME || "", ".local/state");
1603
1802
  if (args.includes("--status")) {
1604
1803
  const [native, helper] = await Promise.all([
1605
1804
  nativeHelperStatus(),
1606
1805
  requestMachine(stateRoot2, { operation: "status" }, () => true, 2e3).catch(() => void 0)
1607
1806
  ]);
1608
- const root = resolve7(stateRoot2, "beeline/agents");
1609
- const agents = await Promise.all((await readdir3(root).catch(() => [])).filter((id) => /^[0-9a-f]{64}$/.test(id)).map(async (id) => ({
1807
+ const root = resolve9(stateRoot2, "beeline/agents");
1808
+ const agents = await Promise.all((await readdir5(root).catch(() => [])).filter((id) => /^[0-9a-f]{64}$/.test(id)).map(async (id) => ({
1610
1809
  agentId: id,
1611
- enabled: !await stat2(resolve7(root, id, "stopped")).then(() => true, () => false),
1810
+ enabled: !await stat2(resolve9(root, id, "stopped")).then(() => true, () => false),
1612
1811
  ...helper?.agents[id] ?? { state: "not hosted" },
1613
- validRecord: await readFile4(resolve7(root, id, "runtime.json"), "utf8").then((raw) => Boolean(JSON.parse(raw).agent)).catch(() => false)
1812
+ validRecord: await readFile4(resolve9(root, id, "runtime.json"), "utf8").then((raw) => Boolean(JSON.parse(raw).agent)).catch(() => false)
1614
1813
  })));
1615
1814
  console.log(JSON.stringify({
1616
1815
  native,
@@ -1660,7 +1859,7 @@ async function main() {
1660
1859
  console.log(`[beeline] executing release ${status.loadedRelease}; helper ${status.pid}; every enabled agent serving`);
1661
1860
  return;
1662
1861
  }
1663
- const stateRoot = process.env.XDG_STATE_HOME || resolve7(process.env.HOME || "", ".local/state");
1862
+ const stateRoot = process.env.XDG_STATE_HOME || resolve9(process.env.HOME || "", ".local/state");
1664
1863
  let selectedRelease;
1665
1864
  if (command === "start") {
1666
1865
  for (let i = 1; i < args.length; i++) {
@@ -1675,13 +1874,13 @@ async function main() {
1675
1874
  const requested = agentFlag >= 0 ? args[agentFlag + 1] : args[1] && !args[1].startsWith("--") ? args[1] : void 0;
1676
1875
  if (agentFlag >= 0 && !requested)
1677
1876
  throw new Error("--agent requires an agent public key");
1678
- const agentRoot = resolve7(stateRoot, "beeline/agents");
1679
- const ids = (await readdir3(agentRoot).catch(() => [])).filter((id) => /^[0-9a-f]{64}$/.test(id));
1877
+ const agentRoot = resolve9(stateRoot, "beeline/agents");
1878
+ const ids = (await readdir5(agentRoot).catch(() => [])).filter((id) => /^[0-9a-f]{64}$/.test(id));
1680
1879
  const selected = requested ? ids.filter((id) => id.startsWith(requested)) : args.includes("--all") ? ids : [];
1681
1880
  if (requested && selected.length !== 1)
1682
1881
  throw new Error(`no unique paired agent matches ${requested}`);
1683
1882
  for (const id of selected)
1684
- await unlink(resolve7(agentRoot, id, "stopped")).catch((error) => {
1883
+ await unlink(resolve9(agentRoot, id, "stopped")).catch((error) => {
1685
1884
  if (error.code !== "ENOENT")
1686
1885
  throw error;
1687
1886
  });
@@ -1705,7 +1904,7 @@ async function main() {
1705
1904
  const entry = await resolveBundleEntrypoint(layout.libDir);
1706
1905
  if (!entry)
1707
1906
  throw new Error("no installed Beeline body; run npx usebeeline connect");
1708
- const child = spawn3(process.execPath, [entry, ...args], {
1907
+ const child = spawn4(process.execPath, [entry, ...args], {
1709
1908
  env: {
1710
1909
  ...process.env,
1711
1910
  BEELINE_LIB_DIR: layout.libDir,
@@ -1723,7 +1922,7 @@ async function convergeDependencies(layout, deadlineAt) {
1723
1922
  if (!entry)
1724
1923
  throw new Error("dependency phase has no selected body");
1725
1924
  await new Promise((done, reject) => {
1726
- const child = spawn3(process.execPath, [entry, "converge-dependencies"], {
1925
+ const child = spawn4(process.execPath, [entry, "converge-dependencies"], {
1727
1926
  env: { ...process.env, BEELINE_LIB_DIR: layout.libDir, BEELINE_INTERNAL_RECOVERY: "1" },
1728
1927
  stdio: "inherit"
1729
1928
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usebeeline",
3
- "version": "0.1.57",
3
+ "version": "0.1.59",
4
4
  "description": "Connect an AI coding agent to Beeline with one command.",
5
5
  "homepage": "https://usebeeline.app",
6
6
  "bugs": {