usebeeline 0.1.3 → 0.1.5

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 +1433 -662
  2. package/package.json +1 -1
@@ -9554,11 +9554,11 @@ var init_squire_broker_link = __esm({
9554
9554
 
9555
9555
  // apps/body/dist/squire-host.js
9556
9556
  import { spawn as spawn3 } from "node:child_process";
9557
- import { accessSync, constants as fsConstants, existsSync as existsSync2, mkdirSync } from "node:fs";
9557
+ import { accessSync, constants as fsConstants, existsSync as existsSync2, mkdirSync, realpathSync, statSync, writeFileSync } from "node:fs";
9558
9558
  import { createRequire as createRequire2 } from "node:module";
9559
9559
  import { createConnection as createConnection2 } from "node:net";
9560
9560
  import { homedir as homedir2 } from "node:os";
9561
- import { dirname as dirname2, join as join2, resolve as resolve2 } from "node:path";
9561
+ import { basename, dirname as dirname2, join as join2, resolve as resolve2 } from "node:path";
9562
9562
  import { fileURLToPath as fileURLToPath2 } from "node:url";
9563
9563
  function squireHostPaths(home) {
9564
9564
  const dir = join2(resolve2(home), ".trusty-squire");
@@ -9584,6 +9584,22 @@ function ensureSquireHostDir(home) {
9584
9584
  mkdirSync(paths.profileDir, { recursive: true, mode: 448 });
9585
9585
  return paths;
9586
9586
  }
9587
+ function writeSquireBrokerUnitMarker(home) {
9588
+ const paths = ensureSquireHostDir(home);
9589
+ const profile = realpathSync.native(paths.profileDir);
9590
+ const parent = dirname2(profile);
9591
+ const { dev, ino } = statSync(parent);
9592
+ const path = join2(parent, SQUIRE_BROKER_UNIT_MARKER_FILE);
9593
+ const marker = {
9594
+ version: 1,
9595
+ socket: paths.brokerSocket,
9596
+ profile: { dev, ino, name: basename(profile) },
9597
+ accountBinding: null
9598
+ };
9599
+ writeFileSync(path, `${JSON.stringify(marker, null, 2)}
9600
+ `, { mode: 384 });
9601
+ return path;
9602
+ }
9587
9603
  function squireHostDirWritable(socketPath) {
9588
9604
  try {
9589
9605
  accessSync(dirname2(socketPath), fsConstants.W_OK);
@@ -9678,7 +9694,14 @@ function spawnSquireServer(env, locateNpx, args) {
9678
9694
  function runSquireBroker(env = process.env) {
9679
9695
  const home = env.HOME?.trim() || homedir2();
9680
9696
  const paths = ensureSquireHostDir(home);
9681
- spawnSquireServer({ ...env, ...squireHostRewriteEnv(home), TRUSTY_SQUIRE_BROKER_SOCKET: paths.brokerSocket }, true, SQUIRE_BROKER_ARGS);
9697
+ spawnSquireServer({
9698
+ ...env,
9699
+ ...squireHostRewriteEnv(home),
9700
+ TRUSTY_SQUIRE_BROKER_SOCKET: paths.brokerSocket,
9701
+ // Squire's daemon refuses to start under the managed marker unless the
9702
+ // unit started it; launchd sets no INVOCATION_ID, so say so explicitly.
9703
+ TRUSTY_SQUIRE_BROKER_UNIT: "1"
9704
+ }, true, SQUIRE_BROKER_ARGS);
9682
9705
  }
9683
9706
  async function runSquireFacade(env = process.env) {
9684
9707
  const socket = env.TRUSTY_SQUIRE_BROKER_SOCKET ?? squireHostPaths(env.HOME?.trim() || homedir2()).brokerSocket;
@@ -9695,7 +9718,7 @@ async function runSquireFacade(env = process.env) {
9695
9718
  });
9696
9719
  process.exit(ok ? 0 : 1);
9697
9720
  }
9698
- var SQUIRE_BROKER_UNAVAILABLE, TRUSTY_SQUIRE_BROKER_UNIT_NAME, SQUIRE_FACADE_FLAG, SQUIRE_BROKER_FLAG, SQUIRE_SERVER_ARGS, SQUIRE_BROKER_ARGS;
9721
+ var SQUIRE_BROKER_UNAVAILABLE, TRUSTY_SQUIRE_BROKER_UNIT_NAME, SQUIRE_FACADE_FLAG, SQUIRE_BROKER_FLAG, SQUIRE_SERVER_ARGS, SQUIRE_BROKER_ARGS, SQUIRE_BROKER_UNIT_MARKER_FILE;
9699
9722
  var init_squire_host = __esm({
9700
9723
  "apps/body/dist/squire-host.js"() {
9701
9724
  "use strict";
@@ -9706,12 +9729,13 @@ var init_squire_host = __esm({
9706
9729
  SQUIRE_BROKER_FLAG = "--squire-broker";
9707
9730
  SQUIRE_SERVER_ARGS = ["-y", "@trusty-squire/mcp@latest", "server"];
9708
9731
  SQUIRE_BROKER_ARGS = ["-y", "@trusty-squire/mcp@latest", "broker"];
9732
+ SQUIRE_BROKER_UNIT_MARKER_FILE = ".trusty-squire-broker-unit.json";
9709
9733
  }
9710
9734
  });
9711
9735
 
9712
9736
  // apps/body/dist/systemd.js
9713
9737
  import { execFile as execFile2 } from "node:child_process";
9714
- import { mkdir as mkdir2, readFile as readFile3, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
9738
+ import { mkdir as mkdir2, readFile as readFile3, rename as rename2, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
9715
9739
  import { homedir as homedir3 } from "node:os";
9716
9740
  import { dirname as dirname3, resolve as resolve3 } from "node:path";
9717
9741
  import { setTimeout as sleep } from "node:timers/promises";
@@ -9740,6 +9764,7 @@ CPUWeight=1000
9740
9764
  IOWeight=1000
9741
9765
  Nice=-5
9742
9766
  OOMScoreAdjust=-1000
9767
+ OOMPolicy=continue
9743
9768
  TimeoutStartSec=90s
9744
9769
  TimeoutStopSec=90s
9745
9770
  KillMode=control-group
@@ -9779,7 +9804,7 @@ async function installTrustySquireBrokerService(options = {}) {
9779
9804
  const env = options.env ?? process.env;
9780
9805
  assertCanonicalInstalledLauncher(env, options.invocationPath);
9781
9806
  const home = env.HOME?.trim() || homedir3();
9782
- ensureSquireHostDir(home);
9807
+ writeSquireBrokerUnitMarker(home);
9783
9808
  const path = systemdBrokerUnitPath(env);
9784
9809
  const content = trustySquireBrokerUnit();
9785
9810
  const existing = await readFile3(path, "utf8").catch(() => "");
@@ -9812,6 +9837,26 @@ async function convergeTrustySquireBrokerService(options) {
9812
9837
  return false;
9813
9838
  }
9814
9839
  }
9840
+ async function convergeAgentServiceUnit(options) {
9841
+ const env = options.env ?? process.env;
9842
+ try {
9843
+ assertCanonicalInstalledLauncher(env, resolve3(options.libDir, "lib", "beeline", "beeline-cli.mjs"));
9844
+ const path = systemdUserUnitPath(env);
9845
+ const content = agentServiceUnit();
9846
+ const existing = await readFile3(path, "utf8").catch(() => "");
9847
+ if (existing === content)
9848
+ return false;
9849
+ await mkdir2(dirname3(path), { recursive: true, mode: 448 });
9850
+ await writeFile2(path, content, { mode: 384 });
9851
+ const run2 = options.run ?? runSystemctl;
9852
+ await run2(["daemon-reload"]);
9853
+ options.log?.("[beeline] agent systemd unit updated; it applies on the next daemon restart");
9854
+ return true;
9855
+ } catch (error) {
9856
+ options.log?.(`[beeline] agent systemd unit not converged: ${error instanceof Error ? error.message : String(error)}`);
9857
+ return false;
9858
+ }
9859
+ }
9815
9860
  async function installAgentService(publicKey, options = {}) {
9816
9861
  if (!/^[0-9a-f]{64}$/i.test(publicKey))
9817
9862
  throw new Error("agent public key must be 64 hex");
@@ -9844,6 +9889,23 @@ async function installAgentService(publicKey, options = {}) {
9844
9889
  } while (Date.now() < deadline);
9845
9890
  throw new Error(`systemd did not replace ${service}'s MainPID before the restart deadline`);
9846
9891
  }
9892
+ async function rewriteAgentServiceUnit(options) {
9893
+ const env = options.env ?? process.env;
9894
+ assertCanonicalInstalledLauncher(env, options.invocationPath);
9895
+ const path = systemdUserUnitPath(env);
9896
+ const content = agentServiceUnit();
9897
+ const run2 = options.run ?? runSystemctl;
9898
+ return options.lock(async () => {
9899
+ if (await readFile3(path, "utf8").catch(() => "") === content)
9900
+ return false;
9901
+ await mkdir2(dirname3(path), { recursive: true, mode: 448 });
9902
+ const staged = `${path}.${process.pid}.tmp`;
9903
+ await writeFile2(staged, content, { mode: 384 });
9904
+ await rename2(staged, path);
9905
+ await run2(["daemon-reload"]);
9906
+ return true;
9907
+ });
9908
+ }
9847
9909
  async function serviceStatus(run2, service) {
9848
9910
  const status = await run2([
9849
9911
  "show",
@@ -9947,8 +10009,10 @@ async function notify(fields) {
9947
10009
  async function extendSystemdStartTimeout(ms) {
9948
10010
  await notify([`EXTEND_TIMEOUT_USEC=${Math.max(0, Math.round(ms)) * 1e3}`]).catch(() => void 0);
9949
10011
  }
9950
- function startLocalWatchdog(notifier, status, intervalMs = 6e4) {
9951
- const timer = setInterval(() => void notifier.progress(status()), intervalMs);
10012
+ function startLocalWatchdog(notifier, status, intervalMs = 6e4, env = process.env) {
10013
+ if (!env.WATCHDOG_USEC)
10014
+ return () => void 0;
10015
+ const timer = setInterval(() => void notifier.progress(status()).catch((error) => console.error("[thin-core] watchdog notification failed:", error)), intervalMs);
9952
10016
  timer.unref?.();
9953
10017
  return () => clearInterval(timer);
9954
10018
  }
@@ -9989,8 +10053,8 @@ var init_systemd = __esm({
9989
10053
 
9990
10054
  // apps/body/dist/launchd.js
9991
10055
  import { execFile as execFile3 } from "node:child_process";
9992
- import { realpathSync } from "node:fs";
9993
- import { chmod as chmod2, mkdir as mkdir3, readFile as readFile4, readdir as readdir2, rename as rename2, rm, stat as stat3, writeFile as writeFile3 } from "node:fs/promises";
10056
+ import { realpathSync as realpathSync2 } from "node:fs";
10057
+ import { chmod as chmod2, mkdir as mkdir3, readFile as readFile4, readdir as readdir2, rename as rename3, rm, stat as stat3, writeFile as writeFile3 } from "node:fs/promises";
9994
10058
  import { homedir as homedir4 } from "node:os";
9995
10059
  import { dirname as dirname4, resolve as resolve4 } from "node:path";
9996
10060
  import { setTimeout as sleep2 } from "node:timers/promises";
@@ -10003,7 +10067,7 @@ function launchdHome(env) {
10003
10067
  }
10004
10068
  function nodeBinDirectory() {
10005
10069
  try {
10006
- return dirname4(realpathSync(process.execPath));
10070
+ return dirname4(realpathSync2(process.execPath));
10007
10071
  } catch {
10008
10072
  return dirname4(process.execPath);
10009
10073
  }
@@ -10158,7 +10222,7 @@ async function writeManagedFile(path, content, mode) {
10158
10222
  const pending = `${path}.${process.pid}.pending`;
10159
10223
  await writeFile3(pending, content, { mode });
10160
10224
  await chmod2(pending, mode);
10161
- await rename2(pending, path);
10225
+ await rename3(pending, path);
10162
10226
  return true;
10163
10227
  }
10164
10228
  function launchctlFailureText(error) {
@@ -10320,7 +10384,7 @@ async function installLaunchdTrustySquireBrokerService(options = {}) {
10320
10384
  const env = options.env ?? process.env;
10321
10385
  assertCanonicalInstalledLauncher2(env, options.invocationPath);
10322
10386
  const home = launchdHome(env);
10323
- ensureSquireHostDir(home);
10387
+ writeSquireBrokerUnitMarker(home);
10324
10388
  await mkdir3(resolve4(home, "Library", "Logs", "Beeline"), { recursive: true, mode: 448 });
10325
10389
  const plistPath = launchdBrokerPlistPath(env);
10326
10390
  const changed = await writeManagedFile(plistPath, launchdBrokerPlist(env), 384);
@@ -10409,7 +10473,7 @@ __export(self_update_exports, {
10409
10473
  });
10410
10474
  import { createHash } from "node:crypto";
10411
10475
  import { constants as fsConstants2 } from "node:fs";
10412
- import { access, chmod as chmod3, lstat, mkdir as mkdir4, open, readFile as readFile5, rename as rename3, rm as rm2, symlink, writeFile as writeFile4 } from "node:fs/promises";
10476
+ import { access, chmod as chmod3, lstat, mkdir as mkdir4, open, readFile as readFile5, rename as rename4, rm as rm2, symlink, writeFile as writeFile4 } from "node:fs/promises";
10413
10477
  import { spawn as spawn4 } from "node:child_process";
10414
10478
  import { homedir as homedir5 } from "node:os";
10415
10479
  import { dirname as dirname5, join as join3, resolve as resolve5 } from "node:path";
@@ -10712,7 +10776,7 @@ async function replaceFile(path, contents, mode) {
10712
10776
  const temp = `${path}.new-${process.pid}`;
10713
10777
  await writeFile4(temp, contents, { mode });
10714
10778
  await chmod3(temp, mode);
10715
- await rename3(temp, path);
10779
+ await rename4(temp, path);
10716
10780
  }
10717
10781
  async function activateRelease(layout, releaseId) {
10718
10782
  const releaseDir = join3(layout.releasesRoot, releaseId);
@@ -10728,10 +10792,10 @@ async function activateRelease(layout, releaseId) {
10728
10792
  try {
10729
10793
  await access(legacyDir, fsConstants2.F_OK);
10730
10794
  previousReleaseId = `${legacyId}-${Date.now()}`;
10731
- await rename3(layout.libDir, join3(layout.releasesRoot, previousReleaseId));
10795
+ await rename4(layout.libDir, join3(layout.releasesRoot, previousReleaseId));
10732
10796
  await normalizeLegacyBundleShape(join3(layout.releasesRoot, previousReleaseId));
10733
10797
  } catch {
10734
- await rename3(layout.libDir, legacyDir);
10798
+ await rename4(layout.libDir, legacyDir);
10735
10799
  await normalizeLegacyBundleShape(legacyDir);
10736
10800
  previousReleaseId = legacyId;
10737
10801
  }
@@ -10739,7 +10803,7 @@ async function activateRelease(layout, releaseId) {
10739
10803
  const tempLink = `${layout.libDir}.new-${process.pid}`;
10740
10804
  await rm2(tempLink, { force: true });
10741
10805
  await symlink(join3("beeline-releases", releaseId), tempLink);
10742
- await rename3(tempLink, layout.libDir);
10806
+ await rename4(tempLink, layout.libDir);
10743
10807
  await fsyncDir(dirname5(layout.libDir));
10744
10808
  await writeBinForwarders(layout, releaseDir);
10745
10809
  return { previousReleaseId };
@@ -10764,7 +10828,7 @@ async function normalizeLegacyBundleShape(bundleDir) {
10764
10828
  continue;
10765
10829
  } catch {
10766
10830
  }
10767
- await rename3(join3(bundleDir, name), join3(innerLib, name)).catch(() => void 0);
10831
+ await rename4(join3(bundleDir, name), join3(innerLib, name)).catch(() => void 0);
10768
10832
  }
10769
10833
  }
10770
10834
  async function writeBinForwarders(layout, activeBundleRoot) {
@@ -10815,7 +10879,7 @@ async function rollbackToPreviousRelease(layout, previousReleaseId) {
10815
10879
  const tempLink = `${layout.libDir}.rollback-${process.pid}`;
10816
10880
  await rm2(tempLink, { force: true });
10817
10881
  await symlink(join3("beeline-releases", previousReleaseId), tempLink);
10818
- await rename3(tempLink, layout.libDir);
10882
+ await rename4(tempLink, layout.libDir);
10819
10883
  await fsyncDir(dirname5(layout.libDir));
10820
10884
  }
10821
10885
  function updateAttemptPath(layout) {
@@ -10838,7 +10902,7 @@ async function writeUpdateAttempt(layout, record6) {
10838
10902
  const staged = `${path}.${process.pid}.tmp`;
10839
10903
  await writeFile4(staged, `${JSON.stringify(record6, null, 2)}
10840
10904
  `, { mode: 384 });
10841
- await rename3(staged, path);
10905
+ await rename4(staged, path);
10842
10906
  }
10843
10907
  async function replaceUpdateAttempt(layout, record6) {
10844
10908
  await writeUpdateAttempt(layout, record6);
@@ -10925,7 +10989,6 @@ var init_self_update = __esm({
10925
10989
  fetchImpl;
10926
10990
  log;
10927
10991
  stageOnly;
10928
- timer;
10929
10992
  nextCheckAllowedAt = 0;
10930
10993
  disposed = false;
10931
10994
  applying = false;
@@ -10942,9 +11005,7 @@ var init_self_update = __esm({
10942
11005
  };
10943
11006
  this.options = {
10944
11007
  layout: options.layout,
10945
- tickMs: options.tickMs ?? numberEnv2("BEELINE_UPDATE_TICK_MS", 6e4),
10946
11008
  checkIntervalMs: options.checkIntervalMs ?? numberEnv2("BEELINE_UPDATE_INTERVAL_MS", 6 * 60 * 6e4),
10947
- initialDelayMs: options.initialDelayMs ?? numberEnv2("BEELINE_UPDATE_INITIAL_DELAY_MS", 2 * 6e4),
10948
11009
  idleTimeoutMs: options.idleTimeoutMs ?? numberEnv2("BEELINE_UPDATE_IDLE_TIMEOUT_MS", 30 * 6e4),
10949
11010
  idlePollMs: options.idlePollMs ?? 5e3,
10950
11011
  env,
@@ -10956,20 +11017,8 @@ var init_self_update = __esm({
10956
11017
  this.log = options.logger ?? ((line) => console.log(line));
10957
11018
  this.stageOnly = options.stageOnly ?? false;
10958
11019
  }
10959
- start() {
10960
- if (this.timer || this.disposed)
10961
- return;
10962
- this.nextCheckAllowedAt = this.options.now() + this.options.initialDelayMs;
10963
- this.timer = setInterval(() => {
10964
- void this.tick();
10965
- }, this.options.tickMs);
10966
- this.timer.unref?.();
10967
- }
10968
11020
  dispose() {
10969
11021
  this.disposed = true;
10970
- if (this.timer)
10971
- clearInterval(this.timer);
10972
- this.timer = void 0;
10973
11022
  }
10974
11023
  busy() {
10975
11024
  return !this.isIdle();
@@ -11087,6 +11136,11 @@ var init_self_update = __esm({
11087
11136
  env: this.options.env,
11088
11137
  log: this.log
11089
11138
  });
11139
+ await convergeAgentServiceUnit({
11140
+ libDir: this.options.layout.libDir,
11141
+ env: this.options.env,
11142
+ log: this.log
11143
+ });
11090
11144
  } else if (process.platform === "darwin" && this.options.env.BEELINE_LAUNCHD_USER !== "0") {
11091
11145
  await convergeLaunchdTrustySquireBrokerService({
11092
11146
  libDir: this.options.layout.libDir,
@@ -11251,10 +11305,10 @@ var init_cursor_models = __esm({
11251
11305
  // apps/body/dist/cursor-acp-bridge.js
11252
11306
  import { spawn as spawn6 } from "node:child_process";
11253
11307
  import { existsSync as existsSync3 } from "node:fs";
11254
- import { chmod as chmod4, lstat as lstat2, mkdir as mkdir6, rename as rename4, unlink as unlink3, writeFile as writeFile5 } from "node:fs/promises";
11308
+ import { chmod as chmod4, lstat as lstat2, mkdir as mkdir6, rename as rename5, unlink as unlink3, writeFile as writeFile5 } from "node:fs/promises";
11255
11309
  import { createRequire as createRequire3 } from "node:module";
11256
11310
  import { createInterface as createInterface3 } from "node:readline";
11257
- import { basename, dirname as dirname7, resolve as resolvePath } from "node:path";
11311
+ import { basename as basename2, dirname as dirname7, resolve as resolvePath } from "node:path";
11258
11312
  import { fileURLToPath as fileURLToPath3 } from "node:url";
11259
11313
  import { randomUUID as randomUUID2 } from "node:crypto";
11260
11314
  function harnessIdentityLabel(agent) {
@@ -11320,11 +11374,11 @@ async function writeIsolatedCursorFile(path, content) {
11320
11374
  if (!parentStats.isDirectory() || parentStats.isSymbolicLink()) {
11321
11375
  throw new Error(`isolated cursor parent is not a real directory: ${parent}`);
11322
11376
  }
11323
- const temporary = resolvePath(parent, `.${basename(path)}.${process.pid}.${randomUUID2()}.tmp`);
11377
+ const temporary = resolvePath(parent, `.${basename2(path)}.${process.pid}.${randomUUID2()}.tmp`);
11324
11378
  try {
11325
11379
  await writeFile5(temporary, content, { mode: 384, flag: "wx" });
11326
11380
  await chmod4(temporary, 384);
11327
- await rename4(temporary, path);
11381
+ await rename5(temporary, path);
11328
11382
  } finally {
11329
11383
  await unlink3(temporary).catch(() => void 0);
11330
11384
  }
@@ -25219,7 +25273,7 @@ var import_picocolors3 = __toESM(require_picocolors(), 1);
25219
25273
  // apps/body/dist/config.js
25220
25274
  init_agent_command();
25221
25275
  import { accessSync as accessSync3, constants as constants2, existsSync as existsSync5, readFileSync as readFileSync2 } from "node:fs";
25222
- import { basename as basename2, dirname as dirname8, resolve as resolve8 } from "node:path";
25276
+ import { basename as basename3, dirname as dirname8, resolve as resolve8 } from "node:path";
25223
25277
  function firstExisting2(paths) {
25224
25278
  for (const p of paths) {
25225
25279
  if (p && existsSync5(p))
@@ -25229,7 +25283,7 @@ function firstExisting2(paths) {
25229
25283
  }
25230
25284
  function commandInRunningBundle(command) {
25231
25285
  const entrypoint = process.argv[1];
25232
- if (!entrypoint || basename2(entrypoint) !== "beeline-cli.mjs")
25286
+ if (!entrypoint || basename3(entrypoint) !== "beeline-cli.mjs")
25233
25287
  return void 0;
25234
25288
  const entrypointDir = dirname8(resolve8(entrypoint));
25235
25289
  const candidates = [
@@ -26630,7 +26684,7 @@ function isAllowedAgentModelConfigCategory(category, id) {
26630
26684
 
26631
26685
  // apps/body/dist/model-config.js
26632
26686
  import { lstatSync, readFileSync as readFileSync3 } from "node:fs";
26633
- import { basename as basename3, resolve as resolve9 } from "node:path";
26687
+ import { basename as basename4, resolve as resolve9 } from "node:path";
26634
26688
 
26635
26689
  // packages/api-contract/dist/daemon-operations.js
26636
26690
  function isAgentCommand(value) {
@@ -26668,6 +26722,12 @@ var ARTIFACT_EXTENSIONS_BY_MIME = {
26668
26722
  "application/json": [".json"],
26669
26723
  "text/csv": [".csv"],
26670
26724
  "application/zip": [".zip"],
26725
+ "video/mp4": [".mp4", ".m4v"],
26726
+ "video/quicktime": [".mov"],
26727
+ "video/webm": [".webm"],
26728
+ "audio/mpeg": [".mp3"],
26729
+ "audio/wav": [".wav"],
26730
+ "audio/mp4": [".m4a"],
26671
26731
  "application/octet-stream": []
26672
26732
  };
26673
26733
  var ARTIFACT_MIME_BY_EXTENSION = Object.fromEntries(Object.entries(ARTIFACT_EXTENSIONS_BY_MIME).flatMap(([mime, extensions]) => extensions.map((extension2) => [extension2, mime])));
@@ -27244,7 +27304,7 @@ function isGrokAgentCommand(agent) {
27244
27304
  return true;
27245
27305
  if (agent.kind && agent.kind !== "custom")
27246
27306
  return false;
27247
- return /^grok(?:\.[a-z0-9]+)?$/i.test(basename3(agent.command));
27307
+ return /^grok(?:\.[a-z0-9]+)?$/i.test(basename4(agent.command));
27248
27308
  }
27249
27309
  function agentArgsWithModelSelection(agent, selection) {
27250
27310
  const original = [...agent.args];
@@ -27787,6 +27847,72 @@ function matchProfile(agentCommand) {
27787
27847
  return void 0;
27788
27848
  }
27789
27849
 
27850
+ // apps/body/dist/oom-kill.js
27851
+ import { readFile as readFile9 } from "node:fs/promises";
27852
+ import { join as join5 } from "node:path";
27853
+ var CGROUP_V2_ROOT = "/sys/fs/cgroup";
27854
+ function parseOomKillCount(contents) {
27855
+ for (const line of contents.split("\n")) {
27856
+ const [key, raw] = line.trim().split(/\s+/, 2);
27857
+ if (key !== "oom_kill")
27858
+ continue;
27859
+ const value = Number(raw);
27860
+ return Number.isSafeInteger(value) && value >= 0 ? value : null;
27861
+ }
27862
+ return null;
27863
+ }
27864
+ function unifiedCgroupPath(procSelfCgroup) {
27865
+ const line = procSelfCgroup.split("\n").map((value) => value.trim()).find((value) => value.startsWith("0::"));
27866
+ const path = line?.slice(3).trim();
27867
+ return path && path.startsWith("/") ? path : null;
27868
+ }
27869
+ function cgroupOomKillProbe(options = {}) {
27870
+ const readProc = options.procSelfCgroup ?? (() => readFile9("/proc/self/cgroup", "utf8"));
27871
+ const root = options.cgroupRoot ?? CGROUP_V2_ROOT;
27872
+ let directory;
27873
+ return {
27874
+ async read() {
27875
+ try {
27876
+ if (directory === void 0) {
27877
+ const path = unifiedCgroupPath(await readProc());
27878
+ directory = path ? join5(root, path) : null;
27879
+ }
27880
+ if (!directory)
27881
+ return null;
27882
+ return parseOomKillCount(await readFile9(join5(directory, "memory.events"), "utf8"));
27883
+ } catch {
27884
+ return null;
27885
+ }
27886
+ }
27887
+ };
27888
+ }
27889
+ var OomKillTracker = class {
27890
+ probe;
27891
+ #last = null;
27892
+ #banked = 0;
27893
+ constructor(probe) {
27894
+ this.probe = probe;
27895
+ }
27896
+ /** Record the current count so the next kill is measured against it. */
27897
+ async prime() {
27898
+ const current = await this.probe.read();
27899
+ if (current !== null)
27900
+ this.#last = current;
27901
+ }
27902
+ async consume() {
27903
+ const current = await this.probe.read();
27904
+ if (current === null)
27905
+ return false;
27906
+ if (this.#last !== null && current > this.#last)
27907
+ this.#banked += current - this.#last;
27908
+ this.#last = current;
27909
+ if (this.#banked === 0)
27910
+ return false;
27911
+ this.#banked -= 1;
27912
+ return true;
27913
+ }
27914
+ };
27915
+
27790
27916
  // apps/body/dist/acp.js
27791
27917
  var STDERR_TAIL_MAX_CHARS2 = 2e3;
27792
27918
  function braceDelta(line) {
@@ -28122,6 +28248,9 @@ var AcpClient = class extends EventEmitter {
28122
28248
  permissionHandler;
28123
28249
  permissionAllowlist;
28124
28250
  commandsHandler;
28251
+ oomKills;
28252
+ /** A deliberate stop owns its SIGKILL; that exit is never labelled OOM. */
28253
+ stopping = false;
28125
28254
  constructor(opts) {
28126
28255
  super();
28127
28256
  const command = opts.agentCommand ?? opts.agentBinary;
@@ -28139,10 +28268,13 @@ var AcpClient = class extends EventEmitter {
28139
28268
  this.permissionHandler = opts.permissionHandler;
28140
28269
  this.permissionAllowlist = opts.permissionAllowlist;
28141
28270
  this.commandsHandler = opts.onCommands;
28271
+ this.oomKills = opts.oomKills ?? new OomKillTracker(cgroupOomKillProbe());
28142
28272
  }
28143
28273
  async start(timeoutMs = 6e4) {
28144
28274
  if (this.alive)
28145
28275
  return;
28276
+ await this.oomKills.prime().catch(() => void 0);
28277
+ this.stopping = false;
28146
28278
  this.child = await withAdapterInstallLock(() => spawn10(this.agentCommand, this.agentArgs, {
28147
28279
  // agentEnv is the child's whole environment: buildAgentEnv's allowlist is
28148
28280
  // a real boundary, not a decorative one layered over a full inherit.
@@ -28164,19 +28296,7 @@ var AcpClient = class extends EventEmitter {
28164
28296
  this.child.stdout.setEncoding("utf8");
28165
28297
  this.child.stdout.on("data", (chunk) => this.onData(chunk));
28166
28298
  this.child.on("exit", (code, signal) => {
28167
- this.alive = false;
28168
- const meaningful = flattenHarnessCause(this.stderrTail);
28169
- const stderrSuffix = meaningful ? `: ${meaningful}` : "";
28170
- for (const [, p] of this.pending) {
28171
- this.clearTimer(p);
28172
- p.reject(new Error(`ACP agent ${this.agentLabel} exited code=${code} signal=${signal}${stderrSuffix}`));
28173
- }
28174
- this.pending.clear();
28175
- this.activeRunIds.clear();
28176
- this.activePromptSessions.clear();
28177
- this.toolCallMetadata.clear();
28178
- this.sessionCommands.clear();
28179
- this.emit("exit", { code, signal });
28299
+ void this.handleChildExit(code, signal);
28180
28300
  });
28181
28301
  this.child.on("error", (err) => {
28182
28302
  this.alive = false;
@@ -28202,12 +28322,36 @@ var AcpClient = class extends EventEmitter {
28202
28322
  if (!isPiAcpHarness(this.agentLabel))
28203
28323
  this.notify("notifications/initialized", {});
28204
28324
  }
28325
+ /**
28326
+ * One child exit: reject every in-flight request so the turn fails and is
28327
+ * reported instead of hanging, then publish the exit. A SIGKILL is checked
28328
+ * against the daemon cgroup's OOM counter so the rejection can name the real
28329
+ * cause instead of a bare signal.
28330
+ */
28331
+ async handleChildExit(code, signal) {
28332
+ this.alive = false;
28333
+ const oomKilled = signal === "SIGKILL" && !this.stopping && await this.oomKills.consume().catch(() => false);
28334
+ const exitText = oomKilled ? `ACP agent ${this.agentLabel} was killed by the kernel OOM killer (signal=SIGKILL)` : `ACP agent ${this.agentLabel} exited code=${code} signal=${signal}`;
28335
+ const meaningful = flattenHarnessCause(this.stderrTail);
28336
+ const stderrSuffix = meaningful ? `: ${meaningful}` : "";
28337
+ for (const [, p] of this.pending) {
28338
+ this.clearTimer(p);
28339
+ p.reject(new Error(`${exitText}${stderrSuffix}`));
28340
+ }
28341
+ this.pending.clear();
28342
+ this.activeRunIds.clear();
28343
+ this.activePromptSessions.clear();
28344
+ this.toolCallMetadata.clear();
28345
+ this.sessionCommands.clear();
28346
+ this.emit("exit", { code, signal });
28347
+ }
28205
28348
  async stop() {
28206
28349
  if (!this.alive)
28207
28350
  return;
28208
28351
  const child = this.child;
28209
28352
  if (!child)
28210
28353
  return;
28354
+ this.stopping = true;
28211
28355
  try {
28212
28356
  if (this.child?.stdin.writable) {
28213
28357
  this.notify("shutdown", {});
@@ -28852,7 +28996,7 @@ async function applyRuntimeModelPreflight(config, agent, selection, validate = v
28852
28996
 
28853
28997
  // apps/body/dist/model-catalog-sync.js
28854
28998
  import { createHash as createHash3 } from "node:crypto";
28855
- import { readFile as readFile9, writeFile as writeFile6 } from "node:fs/promises";
28999
+ import { readFile as readFile10, writeFile as writeFile6 } from "node:fs/promises";
28856
29000
  import { resolve as resolve11 } from "node:path";
28857
29001
  var MODEL_CATALOG_PROBE_TIMEOUT_MS = 3e4;
28858
29002
  var MODEL_CATALOG_HASH_FILE = "model-catalog.sha256";
@@ -28890,7 +29034,7 @@ async function syncAgentModelCatalog(input) {
28890
29034
  const { catalog } = await withTimeout(fetchCatalog(input.agent, input.agentEnv, input.startupUnavailable ? void 0 : selection), input.timeoutMs ?? MODEL_CATALOG_PROBE_TIMEOUT_MS, "model catalog probe");
28891
29035
  const effectiveCatalog = input.startupUnavailable ? catalog : withEffectiveCurrentValues(catalog, selection);
28892
29036
  const hash = modelCatalogHash(effectiveCatalog, selection, input.startupUnavailable);
28893
- const previous = await readFile9(hashPath, "utf8").catch(() => "");
29037
+ const previous = await readFile10(hashPath, "utf8").catch(() => "");
28894
29038
  if (!input.force && previous.trim() === hash)
28895
29039
  return "unchanged";
28896
29040
  await input.api.execute("postAgentModelCatalog", {
@@ -28917,7 +29061,7 @@ import { execFile as execFile6 } from "node:child_process";
28917
29061
  import { randomUUID as randomUUID4 } from "node:crypto";
28918
29062
  import { rm as rm4 } from "node:fs/promises";
28919
29063
  import { tmpdir as tmpdir2, userInfo } from "node:os";
28920
- import { join as join5 } from "node:path";
29064
+ import { join as join6 } from "node:path";
28921
29065
  var COMMAND_TIMEOUT_MS = 2 * 6e4;
28922
29066
  var OUTPUT_LIMIT = 32 * 1024;
28923
29067
  var LOGIN_URL = /https:\/\/login\.tailscale\.com\/[A-Za-z0-9_/-]+/u;
@@ -29004,7 +29148,7 @@ async function ensureInstalled(run2, onProgress, platform) {
29004
29148
  }
29005
29149
  };
29006
29150
  }
29007
- const script = join5(tmpdir2(), `beeline-tailscale-install-${process.pid}-${randomUUID4()}.sh`);
29151
+ const script = join6(tmpdir2(), `beeline-tailscale-install-${process.pid}-${randomUUID4()}.sh`);
29008
29152
  const steps2 = [
29009
29153
  step2("Tailscale installed", "running", {
29010
29154
  command: "curl -fsSL https://tailscale.com/install.sh"
@@ -29124,10 +29268,10 @@ ${up.stderr}`;
29124
29268
  import { createHash as createHash4, randomBytes as randomBytes6 } from "node:crypto";
29125
29269
  import { lookup } from "node:dns/promises";
29126
29270
  import { createRequire as createRequire4 } from "node:module";
29127
- import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync, writeFileSync } from "node:fs";
29271
+ import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
29128
29272
  import { createConnection as createConnection3, createServer as createServer2, isIPv4, isIPv6 } from "node:net";
29129
29273
  import { homedir as homedir10 } from "node:os";
29130
- import { dirname as dirname9, join as join6 } from "node:path";
29274
+ import { dirname as dirname9, join as join7 } from "node:path";
29131
29275
  import { createInterface as createInterface5 } from "node:readline";
29132
29276
  import { fileURLToPath as fileURLToPath4 } from "node:url";
29133
29277
 
@@ -29262,12 +29406,12 @@ async function assertNotOwnMachine(url, remoteIsOwnMachine, resolveHost) {
29262
29406
  return url;
29263
29407
  }
29264
29408
  function registryMcpStateRoot(home = homedir10()) {
29265
- return join6(home, ".beeline", "registry-mcp");
29409
+ return join7(home, ".beeline", "registry-mcp");
29266
29410
  }
29267
29411
  function registryMcpStatePath(connectorId, home = homedir10()) {
29268
29412
  if (!/^[0-9a-f-]{20,}$/i.test(connectorId))
29269
29413
  throw new Error("Registry connector id is invalid");
29270
- return join6(registryMcpStateRoot(home), `${connectorId}.json`);
29414
+ return join7(registryMcpStateRoot(home), `${connectorId}.json`);
29271
29415
  }
29272
29416
  function readState(path) {
29273
29417
  try {
@@ -29279,7 +29423,7 @@ function readState(path) {
29279
29423
  }
29280
29424
  function writeState(path, state) {
29281
29425
  mkdirSync2(dirname9(path), { recursive: true, mode: 448 });
29282
- writeFileSync(path, `${JSON.stringify(state)}
29426
+ writeFileSync2(path, `${JSON.stringify(state)}
29283
29427
  `, { mode: 384 });
29284
29428
  }
29285
29429
  function challengeParameter(header, key) {
@@ -29653,7 +29797,7 @@ function jsonRpcToolName(line) {
29653
29797
  }
29654
29798
  }
29655
29799
  function brokerSocketPath(home = homedir10()) {
29656
- return join6(home, ".beeline", "registry-mcp-broker", `${process.pid}.sock`);
29800
+ return join7(home, ".beeline", "registry-mcp-broker", `${process.pid}.sock`);
29657
29801
  }
29658
29802
  var RegistryMcpHostBroker = class {
29659
29803
  home;
@@ -30218,13 +30362,13 @@ function describe(error) {
30218
30362
  }
30219
30363
 
30220
30364
  // apps/body/dist/institutional-memory-shadow-worker.js
30221
- import { mkdtemp as mkdtemp2, readFile as readFile10, rm as rm5 } from "node:fs/promises";
30365
+ import { mkdtemp as mkdtemp2, readFile as readFile11, rm as rm5 } from "node:fs/promises";
30222
30366
  import { tmpdir as tmpdir3 } from "node:os";
30223
- import { join as join8, resolve as resolve12 } from "node:path";
30367
+ import { join as join9, resolve as resolve12 } from "node:path";
30224
30368
 
30225
30369
  // apps/body/dist/attachment-delivery.js
30226
30370
  import { mkdir as mkdir7, writeFile as writeFile7 } from "node:fs/promises";
30227
- import { basename as basename4, extname, join as join7 } from "node:path";
30371
+ import { basename as basename5, extname, join as join8 } from "node:path";
30228
30372
  var MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
30229
30373
  var EXPIRED_REASON = "expired: these attachment bytes are past their retention window";
30230
30374
  var FETCH_TIMEOUT_MS = 3e4;
@@ -30254,7 +30398,7 @@ async function fetchBoundedBytes(url, fetchImpl = fetch) {
30254
30398
  }
30255
30399
  var MAX_INLINE_IMAGE_BYTES = 5 * 1024 * 1024;
30256
30400
  function safeFileName(attachment, index, taken) {
30257
- const raw = basename4(attachment.name ?? "").replace(/[^\w.-]+/g, "_").replace(/^\.+/, "");
30401
+ const raw = basename5(attachment.name ?? "").replace(/[^\w.-]+/g, "_").replace(/^\.+/, "");
30258
30402
  let name = raw || `attachment-${index + 1}`;
30259
30403
  if (taken.has(name)) {
30260
30404
  const ext = extname(name);
@@ -30283,7 +30427,7 @@ async function deliverAttachments(attachments, dir, fetchImpl = fetch) {
30283
30427
  return { attachment, reason: EXPIRED_REASON };
30284
30428
  if (!fetched.ok)
30285
30429
  throw new Error(`HTTP ${fetched.status}`);
30286
- const path = join7(dir, safeFileName(attachment, index, taken));
30430
+ const path = join8(dir, safeFileName(attachment, index, taken));
30287
30431
  await writeFile7(path, fetched.bytes);
30288
30432
  const bytes = fetched.bytes;
30289
30433
  const mimeType = attachment.mimeType ?? fetched.mimeType;
@@ -30422,7 +30566,7 @@ async function readInstitutionalMemoryAttachments(job, dir, acceptsImages, fetch
30422
30566
  for (const message of job.messages) {
30423
30567
  if (!message.attachments?.length)
30424
30568
  continue;
30425
- const delivered = await deliverAttachments(message.attachments, join8(dir, message.id), fetchImpl);
30569
+ const delivered = await deliverAttachments(message.attachments, join9(dir, message.id), fetchImpl);
30426
30570
  for (const entry of delivered) {
30427
30571
  const { attachment } = entry;
30428
30572
  const mimeType = attachment.mimeType ?? entry.image?.mimeType;
@@ -30447,7 +30591,7 @@ async function readInstitutionalMemoryAttachments(job, dir, acceptsImages, fetch
30447
30591
  evidence.push({ ...base, notRead: "the attachment text budget is used up" });
30448
30592
  continue;
30449
30593
  }
30450
- const text2 = clipText(await readFile10(entry.path, "utf8"), textBudget);
30594
+ const text2 = clipText(await readFile11(entry.path, "utf8"), textBudget);
30451
30595
  textBudget -= Buffer.byteLength(text2, "utf8");
30452
30596
  evidence.push({ ...base, text: text2 });
30453
30597
  } else {
@@ -30561,7 +30705,7 @@ async function extractInstitutionalMemoryShadowJob(job, options, signal) {
30561
30705
  if (options.modelSelection) {
30562
30706
  await applyAgentModelSelection(client, opened.sessionId, parseAdvertisedConfigOptions(opened.raw, options.modelSelection.model), options.modelSelection);
30563
30707
  }
30564
- const attachments = await readInstitutionalMemoryAttachments(job, join8(scratch, "attachments"), client.canPromptWithImages(), options.fetch);
30708
+ const attachments = await readInstitutionalMemoryAttachments(job, join9(scratch, "attachments"), client.canPromptWithImages(), options.fetch);
30565
30709
  prompt = institutionalMemoryExtractionPrompt(job, attachments.evidence);
30566
30710
  const result = await client.sessionPrompt(opened.sessionId, promptWithImages(prompt, attachments.images));
30567
30711
  const proposal = parseExtractionText(result.agentText, job.triggerKind);
@@ -30704,9 +30848,9 @@ function helperVersionHeader(value) {
30704
30848
 
30705
30849
  // apps/body/dist/host-read-budget.js
30706
30850
  import { createHash as createHash5, randomUUID as randomUUID5 } from "node:crypto";
30707
- import { mkdir as mkdir8, open as open3, readFile as readFile11, readdir as readdir3, stat as stat5, unlink as unlink4, writeFile as writeFile8 } from "node:fs/promises";
30851
+ import { mkdir as mkdir8, open as open3, readFile as readFile12, readdir as readdir3, stat as stat5, unlink as unlink4, writeFile as writeFile8 } from "node:fs/promises";
30708
30852
  import { tmpdir as tmpdir4 } from "node:os";
30709
- import { join as join9 } from "node:path";
30853
+ import { join as join10 } from "node:path";
30710
30854
  var READ_SLOTS = 6;
30711
30855
  var BACKGROUND_SLOTS = 4;
30712
30856
  var MAX_WAITERS = 256;
@@ -30737,7 +30881,7 @@ var HostReadBudget = class {
30737
30881
  const uid = process.getuid?.() ?? "unknown";
30738
30882
  const origin = new URL(baseUrl).origin;
30739
30883
  const key = createHash5("sha256").update(origin).digest("hex").slice(0, 16);
30740
- this.directory = join9(root, `beeline-read-budget-${uid}-${key}`);
30884
+ this.directory = join10(root, `beeline-read-budget-${uid}-${key}`);
30741
30885
  }
30742
30886
  metrics() {
30743
30887
  return { waiting: this.waiting, active: this.active, totalWaitMs: this.totalWaitMs };
@@ -30745,7 +30889,7 @@ var HostReadBudget = class {
30745
30889
  async acquire(priority, deadlineAt) {
30746
30890
  await mkdir8(this.directory, { recursive: true, mode: 448 });
30747
30891
  const ticket = `wait-${priority === "urgent" ? "0" : "1"}-${String(Date.now()).padStart(13, "0")}-${randomUUID5()}`;
30748
- const ticketPath = join9(this.directory, ticket);
30892
+ const ticketPath = join10(this.directory, ticket);
30749
30893
  const tickets = (await readdir3(this.directory)).filter((name) => name.startsWith("wait-"));
30750
30894
  if (tickets.length >= MAX_WAITERS)
30751
30895
  throw new ReadBudgetFullError();
@@ -30757,14 +30901,14 @@ var HostReadBudget = class {
30757
30901
  const queue = (await readdir3(this.directory)).filter((name) => name.startsWith("wait-")).sort();
30758
30902
  for (const name of queue.slice(0, READ_SLOTS)) {
30759
30903
  if (name !== ticket)
30760
- await this.reapStaleTicket(join9(this.directory, name));
30904
+ await this.reapStaleTicket(join10(this.directory, name));
30761
30905
  }
30762
30906
  const liveQueue = (await readdir3(this.directory)).filter((name) => name.startsWith("wait-")).sort();
30763
30907
  const rank = liveQueue.indexOf(ticket);
30764
30908
  const available = priority === "urgent" ? READ_SLOTS : BACKGROUND_SLOTS;
30765
30909
  if (rank >= 0 && rank < available) {
30766
30910
  for (let slot = 0; slot < available; slot += 1) {
30767
- const slotPath = join9(this.directory, `slot-${slot}`);
30911
+ const slotPath = join10(this.directory, `slot-${slot}`);
30768
30912
  try {
30769
30913
  const handle2 = await open3(slotPath, "wx", 384);
30770
30914
  try {
@@ -30814,7 +30958,7 @@ var HostReadBudget = class {
30814
30958
  const details = await stat5(path).catch(() => void 0);
30815
30959
  if (!details || Date.now() - details.mtimeMs < 2e3)
30816
30960
  return;
30817
- const owner = await readFile11(path, "utf8").then((value) => {
30961
+ const owner = await readFile12(path, "utf8").then((value) => {
30818
30962
  try {
30819
30963
  return JSON.parse(value);
30820
30964
  } catch {
@@ -30829,13 +30973,13 @@ var HostReadBudget = class {
30829
30973
  const details = await stat5(path).catch(() => void 0);
30830
30974
  if (!details || Date.now() - details.mtimeMs < 2e3)
30831
30975
  return;
30832
- const pid = Number(await readFile11(path, "utf8").catch(() => "0"));
30976
+ const pid = Number(await readFile12(path, "utf8").catch(() => "0"));
30833
30977
  if (pid > 0 && ownerAlive(pid) && Date.now() - details.mtimeMs < STALE_SLOT_MS)
30834
30978
  return;
30835
30979
  await unlink4(path).catch(() => void 0);
30836
30980
  }
30837
30981
  async takeRateToken(deadlineAt) {
30838
- const lock = join9(this.directory, "rate.lock");
30982
+ const lock = join10(this.directory, "rate.lock");
30839
30983
  let handle2;
30840
30984
  try {
30841
30985
  handle2 = await open3(lock, "wx", 384);
@@ -30846,8 +30990,8 @@ var HostReadBudget = class {
30846
30990
  return false;
30847
30991
  }
30848
30992
  try {
30849
- const path = join9(this.directory, "next-admission");
30850
- const next = Number(await readFile11(path, "utf8").catch(() => "0")) || 0;
30993
+ const path = join10(this.directory, "next-admission");
30994
+ const next = Number(await readFile12(path, "utf8").catch(() => "0")) || 0;
30851
30995
  const now2 = Date.now();
30852
30996
  if (next > now2 || now2 >= deadlineAt)
30853
30997
  return false;
@@ -30860,6 +31004,251 @@ var HostReadBudget = class {
30860
31004
  }
30861
31005
  };
30862
31006
 
31007
+ // apps/body/dist/live-link.js
31008
+ var LIVE_LINK_TIMING = Object.freeze({
31009
+ handshakeTimeoutMs: 3e4,
31010
+ pongTimeoutMs: 15e3,
31011
+ backoffBaseMs: 1e3,
31012
+ backoffMaxMs: 3e4,
31013
+ stableSocketMs: 3e4,
31014
+ stableCloseJitterMs: 1e4,
31015
+ keepAliveDelayMs: 5 * 6e4
31016
+ });
31017
+ var NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
31018
+ "ECONNRESET",
31019
+ "ECONNREFUSED",
31020
+ "ENOTFOUND",
31021
+ "EAI_AGAIN",
31022
+ "ETIMEDOUT",
31023
+ "EHOSTUNREACH",
31024
+ "ENETUNREACH",
31025
+ "EPIPE",
31026
+ "ECONNABORTED"
31027
+ ]);
31028
+ function isNetworkFailure(error) {
31029
+ const seen = /* @__PURE__ */ new Set();
31030
+ const visit = (value) => {
31031
+ if (!value || typeof value !== "object" || seen.has(value))
31032
+ return false;
31033
+ seen.add(value);
31034
+ const code = value.code;
31035
+ if (typeof code === "string" && (NETWORK_ERROR_CODES.has(code) || code.startsWith("UND_ERR_")))
31036
+ return true;
31037
+ if (value instanceof AggregateError && value.errors.length > 0 && value.errors.every(visit))
31038
+ return true;
31039
+ return visit(value.cause);
31040
+ };
31041
+ return visit(error);
31042
+ }
31043
+ function retryAfterMs(value) {
31044
+ if (!value)
31045
+ return void 0;
31046
+ const seconds = Number(value);
31047
+ const delay = Number.isFinite(seconds) && seconds >= 0 ? seconds * 1e3 : Date.parse(value) - Date.now();
31048
+ return Number.isFinite(delay) && delay >= 0 ? Math.min(delay, 6e4) : void 0;
31049
+ }
31050
+ var LiveLink = class {
31051
+ options;
31052
+ #state = "disconnected";
31053
+ #socket;
31054
+ #openedAt;
31055
+ #failedConnects = 0;
31056
+ #retryAfterMs = 0;
31057
+ #reconnect;
31058
+ #pongDeadline;
31059
+ #wanted = false;
31060
+ #updateAnnounced = false;
31061
+ #reconnects = 0;
31062
+ #openListeners = /* @__PURE__ */ new Set();
31063
+ #timing;
31064
+ #random;
31065
+ #now;
31066
+ constructor(options) {
31067
+ this.options = options;
31068
+ this.#timing = { ...LIVE_LINK_TIMING, ...options.timing };
31069
+ this.#random = options.random ?? Math.random;
31070
+ this.#now = options.now ?? Date.now;
31071
+ }
31072
+ get state() {
31073
+ return this.#state;
31074
+ }
31075
+ get reconnects() {
31076
+ return this.#reconnects;
31077
+ }
31078
+ /** A suspect socket is still open for writes until its pong deadline. */
31079
+ isOpen() {
31080
+ return this.#state === "open" || this.#state === "suspect";
31081
+ }
31082
+ /** Called on every open, after the link's own open handling. */
31083
+ onOpen(listener) {
31084
+ this.#openListeners.add(listener);
31085
+ return () => this.#openListeners.delete(listener);
31086
+ }
31087
+ start() {
31088
+ if (this.#state === "update-required")
31089
+ return;
31090
+ this.#wanted = true;
31091
+ if (!this.#socket && !this.#reconnect)
31092
+ this.#connect();
31093
+ }
31094
+ stop() {
31095
+ this.#wanted = false;
31096
+ this.#clearTimers();
31097
+ const socket = this.#socket;
31098
+ this.#socket = void 0;
31099
+ if (this.#state !== "update-required")
31100
+ this.#state = "disconnected";
31101
+ socket?.close();
31102
+ }
31103
+ send(data) {
31104
+ if (!this.isOpen() || this.#socket?.readyState !== wrapper_default.OPEN)
31105
+ return false;
31106
+ this.#socket.send(data);
31107
+ return true;
31108
+ }
31109
+ /**
31110
+ * Something suggests the socket may be dead (a network-class HTTP failure,
31111
+ * an admitted fetch past its deadline, a failed reconcile). Ask the socket
31112
+ * itself: one ping, and no pong within the deadline drops it. A socket that
31113
+ * is not open is already on its way to a new one.
31114
+ */
31115
+ suspect() {
31116
+ const socket = this.#socket;
31117
+ if (this.#state !== "open" || !socket)
31118
+ return;
31119
+ this.#state = "suspect";
31120
+ try {
31121
+ socket.ping?.();
31122
+ } catch {
31123
+ }
31124
+ this.#pongDeadline = setTimeout(() => {
31125
+ this.#pongDeadline = void 0;
31126
+ if (this.#socket === socket && this.#state === "suspect")
31127
+ socket.terminate();
31128
+ }, this.#timing.pongTimeoutMs);
31129
+ this.#pongDeadline.unref?.();
31130
+ }
31131
+ /** The server requires a newer helper: stop for good and say so once. */
31132
+ requireUpdate(minVersion) {
31133
+ this.#markUpdateRequired();
31134
+ if (this.#updateAnnounced)
31135
+ return;
31136
+ this.#updateAnnounced = true;
31137
+ this.options.onUpdateRequired(minVersion);
31138
+ }
31139
+ #markUpdateRequired() {
31140
+ this.#state = "update-required";
31141
+ this.#clearTimers();
31142
+ const socket = this.#socket;
31143
+ this.#socket = void 0;
31144
+ socket?.close();
31145
+ }
31146
+ #clearTimers() {
31147
+ clearTimeout(this.#reconnect);
31148
+ this.#reconnect = void 0;
31149
+ clearTimeout(this.#pongDeadline);
31150
+ this.#pongDeadline = void 0;
31151
+ }
31152
+ #connect() {
31153
+ const socket = this.options.factory(this.options.url(), this.options.protocols(), {
31154
+ handshakeTimeout: this.#timing.handshakeTimeoutMs
31155
+ });
31156
+ this.#socket = socket;
31157
+ this.#state = "connecting";
31158
+ this.#openedAt = void 0;
31159
+ socket.on?.("upgrade", (response) => {
31160
+ response.socket?.setKeepAlive?.(true, this.#timing.keepAliveDelayMs);
31161
+ });
31162
+ socket.on?.("unexpected-response", (_request, response) => {
31163
+ if (response.statusCode !== 426) {
31164
+ this.#retryAfterMs = retryAfterMs(headerValue(response.headers?.["retry-after"])) ?? 0;
31165
+ socket.terminate();
31166
+ return;
31167
+ }
31168
+ this.#state = "update-required";
31169
+ this.#clearTimers();
31170
+ let body = "";
31171
+ response.on("data", (chunk) => {
31172
+ if (body.length < 1024)
31173
+ body += chunk.toString("utf8").slice(0, 1024 - body.length);
31174
+ });
31175
+ response.on("end", () => {
31176
+ try {
31177
+ const refusal = JSON.parse(body);
31178
+ if (refusal.error === "update_required" && typeof refusal.minVersion === "string")
31179
+ this.requireUpdate(refusal.minVersion);
31180
+ } catch {
31181
+ }
31182
+ });
31183
+ response.on("close", () => socket.terminate());
31184
+ });
31185
+ socket.on?.("pong", () => {
31186
+ if (this.#socket !== socket || this.#state !== "suspect")
31187
+ return;
31188
+ clearTimeout(this.#pongDeadline);
31189
+ this.#pongDeadline = void 0;
31190
+ this.#state = "open";
31191
+ });
31192
+ socket.onopen = () => {
31193
+ if (this.#socket !== socket)
31194
+ return;
31195
+ this.#state = "open";
31196
+ this.#openedAt = this.#now();
31197
+ this.options.onOpen();
31198
+ for (const listener of [...this.#openListeners]) {
31199
+ try {
31200
+ listener();
31201
+ } catch (error) {
31202
+ console.error("[live-link] open listener failed", error);
31203
+ }
31204
+ }
31205
+ };
31206
+ socket.onmessage = (message) => {
31207
+ if (this.#socket === socket)
31208
+ this.options.onMessage(String(message.data));
31209
+ };
31210
+ socket.onerror = () => void 0;
31211
+ socket.onclose = () => {
31212
+ if (this.#socket !== socket)
31213
+ return;
31214
+ this.#socket = void 0;
31215
+ clearTimeout(this.#pongDeadline);
31216
+ this.#pongDeadline = void 0;
31217
+ const openedAt = this.#openedAt;
31218
+ this.#openedAt = void 0;
31219
+ if (this.#state !== "update-required")
31220
+ this.#state = "disconnected";
31221
+ this.options.onClose();
31222
+ if (this.#state === "update-required" || !this.#wanted)
31223
+ return;
31224
+ this.#scheduleReconnect(openedAt);
31225
+ };
31226
+ }
31227
+ #scheduleReconnect(openedAt) {
31228
+ let delay;
31229
+ if (openedAt !== void 0 && this.#now() - openedAt >= this.#timing.stableSocketMs) {
31230
+ this.#failedConnects = 0;
31231
+ delay = this.#random() * this.#timing.stableCloseJitterMs;
31232
+ } else {
31233
+ const ceiling = Math.min(this.#timing.backoffMaxMs, this.#timing.backoffBaseMs * 2 ** this.#failedConnects);
31234
+ this.#failedConnects += 1;
31235
+ delay = this.#random() * ceiling;
31236
+ }
31237
+ delay = Math.floor(Math.max(delay, this.#retryAfterMs));
31238
+ this.#retryAfterMs = 0;
31239
+ this.#reconnects += 1;
31240
+ this.#reconnect = setTimeout(() => {
31241
+ this.#reconnect = void 0;
31242
+ if (this.#wanted && !this.#socket && this.#state !== "update-required")
31243
+ this.#connect();
31244
+ }, delay);
31245
+ this.#reconnect.unref?.();
31246
+ }
31247
+ };
31248
+ function headerValue(value) {
31249
+ return Array.isArray(value) ? value[0] : value;
31250
+ }
31251
+
30863
31252
  // apps/body/dist/daemon-api-client.js
30864
31253
  init_runtime();
30865
31254
  var DaemonApiError = class extends Error {
@@ -30908,32 +31297,19 @@ async function responseError(response) {
30908
31297
  }
30909
31298
  return new DaemonApiError(`monolith daemon request failed (${response.status}: ${code})`, response.status, response.status === 408 || response.status === 429 || response.status >= 500, code, retryAfterMs(response.headers.get("retry-after")), minVersion);
30910
31299
  }
30911
- function retryAfterMs(value) {
30912
- if (!value)
30913
- return void 0;
30914
- const seconds = Number(value);
30915
- const delay = Number.isFinite(seconds) && seconds >= 0 ? seconds * 1e3 : Date.parse(value) - Date.now();
30916
- return Number.isFinite(delay) && delay >= 0 ? Math.min(delay, 6e4) : void 0;
30917
- }
30918
31300
  var REQUEST_DEADLINE_MS = 3e4;
30919
- var STABLE_SOCKET_MS = 3e4;
30920
31301
  var DaemonApiClient = class {
30921
31302
  baseUrl;
30922
31303
  daemonToken;
30923
31304
  agentId;
30924
31305
  fetchImpl;
30925
- webSocketFactory;
30926
31306
  presenceLifecycleId = randomUUID6();
30927
- liveSocket;
30928
- liveReconnect;
30929
- liveReconnectDelayMs = 1e3;
30930
- liveOpenedAt = 0;
31307
+ /** The one live socket; it reconnects by itself for as long as it is wanted. */
31308
+ link;
30931
31309
  inFlight = 0;
30932
31310
  requestCount = 0;
30933
31311
  timeoutCount = 0;
30934
- reconnectCount = 0;
30935
31312
  subscriptionCount = 0;
30936
- discoveryWakeSupported = false;
30937
31313
  readBudget;
30938
31314
  liveRooms = /* @__PURE__ */ new Map();
30939
31315
  roomsChangedListener;
@@ -30944,17 +31320,28 @@ var DaemonApiClient = class {
30944
31320
  cornerRestartListener;
30945
31321
  helperReleaseListener;
30946
31322
  forceUpdateListener;
30947
- forceUpdatePending = false;
30948
31323
  helperIdentity = {
30949
31324
  releaseVersion: "v0.0.0"
30950
31325
  };
30951
- constructor(baseUrl, daemonToken, agentId, fetchImpl = fetch, webSocketFactory = (url, protocols) => new wrapper_default(url, protocols), readBudget) {
31326
+ constructor(baseUrl, daemonToken, agentId, fetchImpl = fetch, webSocketFactory = (url, protocols, options) => new wrapper_default(url, protocols, options), readBudget, linkTiming) {
30952
31327
  this.baseUrl = baseUrl;
30953
31328
  this.daemonToken = daemonToken;
30954
31329
  this.agentId = agentId;
30955
31330
  this.fetchImpl = fetchImpl;
30956
- this.webSocketFactory = webSocketFactory;
30957
31331
  this.readBudget = readBudget ?? (fetchImpl === fetch ? new HostReadBudget(baseUrl) : void 0);
31332
+ this.link = new LiveLink({
31333
+ url: () => this.liveUrl(),
31334
+ protocols: () => [`bearer.${this.daemonToken}`],
31335
+ factory: webSocketFactory,
31336
+ onOpen: () => this.liveOpened(),
31337
+ onMessage: (data) => this.liveMessage(data),
31338
+ onClose: () => {
31339
+ for (const room of this.liveRooms.values())
31340
+ room.onState?.(false);
31341
+ },
31342
+ onUpdateRequired: (minVersion) => this.forceUpdateListener?.(minVersion),
31343
+ ...linkTiming ? { timing: linkTiming } : {}
31344
+ });
30958
31345
  }
30959
31346
  /** Connection material for the daemon-owned MCP proxy and corner credentials. */
30960
31347
  connection() {
@@ -30980,24 +31367,15 @@ var DaemonApiClient = class {
30980
31367
  inFlight: this.inFlight,
30981
31368
  requests: this.requestCount,
30982
31369
  timeouts: this.timeoutCount,
30983
- reconnects: this.reconnectCount,
31370
+ reconnects: this.link.reconnects,
30984
31371
  subscriptionsSent: this.subscriptionCount,
30985
31372
  readQueueDepth: budget?.waiting ?? 0,
30986
31373
  readQueueWaitMs: budget?.totalWaitMs ?? 0
30987
31374
  };
30988
31375
  }
30989
- /** A new server can wake discovery after its DB listener recovers. */
30990
- supportsDiscoveryWake() {
30991
- return this.discoveryWakeSupported;
30992
- }
30993
- requestForceUpdate(minVersion) {
30994
- if (this.forceUpdatePending)
30995
- return;
30996
- this.forceUpdatePending = true;
30997
- clearTimeout(this.liveReconnect);
30998
- this.liveReconnect = void 0;
30999
- this.forceUpdateListener?.(minVersion);
31000
- this.liveSocket?.close();
31376
+ /** Called on every live socket open, after resubscription and the wakes. */
31377
+ onLiveOpen(listener) {
31378
+ return this.link.onOpen(listener);
31001
31379
  }
31002
31380
  /** Add one Room to this agent's shared live socket. */
31003
31381
  liveSubscribe(roomId, cursor3, onItems, onState, presence, onCommands) {
@@ -31020,18 +31398,12 @@ var DaemonApiClient = class {
31020
31398
  });
31021
31399
  }
31022
31400
  this.ensureLiveSocket();
31023
- if (this.liveSocket?.readyState === wrapper_default.OPEN)
31024
- this.sendLiveSubscription(roomId);
31401
+ this.sendLiveSubscription(roomId);
31025
31402
  return () => {
31026
31403
  this.liveRooms.delete(roomId);
31027
- if (this.liveSocket?.readyState === wrapper_default.OPEN) {
31028
- this.liveSocket.send(JSON.stringify({ type: "unsubscribe", roomId }));
31029
- }
31030
- if (!this.liveRooms.size && !this.roomsChangedListener) {
31031
- clearTimeout(this.liveReconnect);
31032
- this.liveSocket?.close();
31033
- this.liveSocket = void 0;
31034
- }
31404
+ this.link.send(JSON.stringify({ type: "unsubscribe", roomId }));
31405
+ if (!this.liveRooms.size && !this.roomsChangedListener)
31406
+ this.link.stop();
31035
31407
  };
31036
31408
  }
31037
31409
  /** Register the one listener invoked when the server reports this agent's
@@ -31072,17 +31444,9 @@ var DaemonApiClient = class {
31072
31444
  if (room && cursor3)
31073
31445
  room.cursor = cursor3;
31074
31446
  }
31075
- /** Retry an uncertain discovery read through socket reconnect backoff. */
31076
- reconnectLive() {
31077
- this.liveSocket?.close();
31078
- }
31079
31447
  /** Release the long-lived socket when the helper itself is shutting down. */
31080
31448
  closeLive() {
31081
- clearTimeout(this.liveReconnect);
31082
- this.liveReconnect = void 0;
31083
- const socket = this.liveSocket;
31084
- this.liveSocket = void 0;
31085
- socket?.close();
31449
+ this.link.stop();
31086
31450
  }
31087
31451
  async execute(name, input) {
31088
31452
  const candidate = input;
@@ -31116,7 +31480,7 @@ var DaemonApiClient = class {
31116
31480
  if (!response.ok) {
31117
31481
  const error = await responseError(response);
31118
31482
  if (error.status === 426 && error.code === "update_required" && error.minVersion)
31119
- this.requestForceUpdate(error.minVersion);
31483
+ this.link.requireUpdate(error.minVersion);
31120
31484
  throw error;
31121
31485
  }
31122
31486
  output = await response.json();
@@ -31126,8 +31490,12 @@ var DaemonApiClient = class {
31126
31490
  }
31127
31491
  if (controller.signal.aborted) {
31128
31492
  this.timeoutCount += 1;
31493
+ if (admitted)
31494
+ this.link.suspect();
31129
31495
  throw new DaemonApiError(`monolith daemon ${String(name)} timed out`, 408, true, "deadline_exceeded");
31130
31496
  }
31497
+ if (isNetworkFailure(error))
31498
+ this.link.suspect();
31131
31499
  throw error;
31132
31500
  } finally {
31133
31501
  clearTimeout(deadline);
@@ -31138,171 +31506,121 @@ var DaemonApiClient = class {
31138
31506
  return output;
31139
31507
  }
31140
31508
  ensureLiveSocket() {
31141
- if (this.forceUpdatePending || this.liveSocket || !this.liveRooms.size && !this.roomsChangedListener)
31142
- return;
31509
+ if (this.liveRooms.size || this.roomsChangedListener)
31510
+ this.link.start();
31511
+ }
31512
+ liveUrl() {
31143
31513
  const liveUrl = new URL("/v1/phone/live", this.baseUrl);
31144
31514
  liveUrl.protocol = liveUrl.protocol === "https:" ? "wss:" : "ws:";
31145
31515
  liveUrl.searchParams.set("helperVersion", this.helperIdentity.releaseVersion);
31146
31516
  if (this.helperIdentity.sourceSha)
31147
31517
  liveUrl.searchParams.set("sourceSha", this.helperIdentity.sourceSha);
31148
- const socket = this.webSocketFactory(liveUrl.toString(), [
31149
- `bearer.${this.daemonToken}`
31150
- ]);
31151
- this.liveSocket = socket;
31152
- socket.on?.("unexpected-response", (_request, response) => {
31153
- if (response.statusCode !== 426)
31154
- return;
31155
- this.forceUpdatePending = true;
31156
- clearTimeout(this.liveReconnect);
31157
- this.liveReconnect = void 0;
31158
- let body = "";
31159
- response.on("data", (chunk) => {
31160
- if (body.length < 1024)
31161
- body += chunk.toString("utf8").slice(0, 1024 - body.length);
31162
- });
31163
- response.on("end", () => {
31164
- try {
31165
- const refusal = JSON.parse(body);
31166
- if (refusal.error === "update_required" && typeof refusal.minVersion === "string")
31167
- this.forceUpdateListener?.(refusal.minVersion);
31168
- } catch {
31169
- }
31170
- });
31171
- });
31172
- socket.onopen = () => {
31173
- this.liveOpenedAt = Date.now();
31174
- this.discoveryWakeSupported = false;
31175
- for (const roomId of this.liveRooms.keys())
31176
- this.sendLiveSubscription(roomId);
31518
+ return liveUrl.toString();
31519
+ }
31520
+ liveOpened() {
31521
+ for (const roomId of this.liveRooms.keys())
31522
+ this.sendLiveSubscription(roomId);
31523
+ this.roomsChangedListener?.();
31524
+ this.connectorAssignmentListener?.();
31525
+ this.memoryJobListener?.();
31526
+ }
31527
+ liveMessage(data) {
31528
+ let value;
31529
+ try {
31530
+ value = JSON.parse(data);
31531
+ } catch {
31532
+ return;
31533
+ }
31534
+ if (!value || typeof value !== "object")
31535
+ return;
31536
+ const event = value;
31537
+ if (event.type === "force-update" && typeof event.minVersion === "string") {
31538
+ this.link.requireUpdate(event.minVersion);
31539
+ return;
31540
+ }
31541
+ if (event.type === "helper-release" && typeof event.version === "string" && typeof event.sha === "string") {
31542
+ this.helperReleaseListener?.({ version: event.version, sha: event.sha });
31543
+ return;
31544
+ }
31545
+ if (event.type === "rooms-changed") {
31546
+ this.roomsChangedListener?.(membershipChange(event));
31547
+ return;
31548
+ }
31549
+ if (event.type === "discovery-wake" && (event.reason === "listener-resync" || event.reason === "database-recovered")) {
31177
31550
  this.roomsChangedListener?.();
31551
+ return;
31552
+ }
31553
+ if (event.type === "corner-complete" && typeof event.roomId === "string") {
31554
+ this.cornerCompleteListener?.(event.roomId);
31555
+ return;
31556
+ }
31557
+ if (event.type === "corner-restart" && typeof event.roomId === "string") {
31558
+ this.cornerRestartListener?.(event.roomId);
31559
+ return;
31560
+ }
31561
+ if (event.type === "connector-assignment") {
31178
31562
  this.connectorAssignmentListener?.();
31563
+ return;
31564
+ }
31565
+ if (event.type === "memory-job") {
31179
31566
  this.memoryJobListener?.();
31180
- };
31181
- socket.onmessage = (message) => {
31182
- let value;
31183
- try {
31184
- value = JSON.parse(String(message.data));
31185
- } catch {
31186
- return;
31187
- }
31188
- if (!value || typeof value !== "object")
31189
- return;
31190
- const event = value;
31191
- if (event.type === "force-update" && typeof event.minVersion === "string") {
31192
- this.requestForceUpdate(event.minVersion);
31193
- return;
31194
- }
31195
- if (event.type === "hello" && event.protocolMin === 1 && event.protocolMax === 1) {
31196
- const capabilities = event.capabilities;
31197
- if (capabilities?.discoveryWake === true)
31198
- this.discoveryWakeSupported = true;
31199
- return;
31200
- }
31201
- if (event.type === "helper-release" && typeof event.version === "string" && typeof event.sha === "string") {
31202
- this.helperReleaseListener?.({ version: event.version, sha: event.sha });
31203
- return;
31204
- }
31205
- if (event.type === "rooms-changed") {
31206
- this.roomsChangedListener?.(membershipChange(event));
31207
- return;
31208
- }
31209
- if (event.type === "discovery-wake" && (event.reason === "listener-resync" || event.reason === "database-recovered")) {
31210
- this.roomsChangedListener?.();
31211
- return;
31212
- }
31213
- if (event.type === "corner-complete" && typeof event.roomId === "string") {
31214
- this.cornerCompleteListener?.(event.roomId);
31215
- return;
31216
- }
31217
- if (event.type === "corner-restart" && typeof event.roomId === "string") {
31218
- this.cornerRestartListener?.(event.roomId);
31219
- return;
31220
- }
31221
- if (event.type === "connector-assignment") {
31222
- this.connectorAssignmentListener?.();
31223
- return;
31224
- }
31225
- if (event.type === "memory-job") {
31226
- this.memoryJobListener?.();
31227
- return;
31228
- }
31229
- if (event.type === "config-changed") {
31230
- this.configChangedListener?.();
31231
- return;
31232
- }
31233
- if (event.type === "subscribed" && typeof event.roomId === "string") {
31234
- const capabilities = event.capabilities;
31235
- if (capabilities?.discoveryWake === true)
31236
- this.discoveryWakeSupported = true;
31237
- this.liveRooms.get(event.roomId)?.onState?.(true, {
31238
- pushIntake: capabilities?.pushIntake === true,
31239
- connectionPresence: capabilities?.connectionPresence === true
31240
- });
31241
- return;
31242
- }
31243
- if (event.type === "commands" && typeof event.roomId === "string" && event.commandProtocol === 1 && Array.isArray(event.commands)) {
31244
- const room2 = this.liveRooms.get(event.roomId);
31245
- if (!room2)
31246
- return;
31247
- const commands = event.commands.filter((command) => {
31248
- if (!isAgentCommand(command) || command.roomId !== event.roomId || command.agentId !== this.agentId || room2.pushedCommandIds.has(command.id))
31249
- return false;
31250
- room2.pushedCommandIds.add(command.id);
31251
- return true;
31252
- });
31253
- while (room2.pushedCommandIds.size > 1e4)
31254
- room2.pushedCommandIds.delete(room2.pushedCommandIds.values().next().value);
31255
- if (commands.length)
31256
- room2.onCommands?.(commands);
31257
- return;
31258
- }
31259
- if (event.type !== "inbox" || typeof event.roomId !== "string" || !Array.isArray(event.items))
31260
- return;
31261
- const room = this.liveRooms.get(event.roomId);
31262
- if (!room)
31567
+ return;
31568
+ }
31569
+ if (event.type === "config-changed") {
31570
+ this.configChangedListener?.();
31571
+ return;
31572
+ }
31573
+ if (event.type === "subscribed" && typeof event.roomId === "string") {
31574
+ const capabilities = event.capabilities;
31575
+ this.liveRooms.get(event.roomId)?.onState?.(true, {
31576
+ pushIntake: capabilities?.pushIntake === true,
31577
+ connectionPresence: capabilities?.connectionPresence === true
31578
+ });
31579
+ return;
31580
+ }
31581
+ if (event.type === "commands" && typeof event.roomId === "string" && event.commandProtocol === 1 && Array.isArray(event.commands)) {
31582
+ const room2 = this.liveRooms.get(event.roomId);
31583
+ if (!room2)
31263
31584
  return;
31264
- const items = [];
31265
- for (const candidate of event.items) {
31266
- if (!candidate || typeof candidate !== "object")
31267
- continue;
31268
- const id = candidate.id;
31269
- if (typeof id === "string") {
31270
- if (!room.pushedIds.has(id))
31271
- items.push(candidate);
31272
- room.pushedIds.add(id);
31273
- }
31585
+ const commands = event.commands.filter((command) => {
31586
+ if (!isAgentCommand(command) || command.roomId !== event.roomId || command.agentId !== this.agentId || room2.pushedCommandIds.has(command.id))
31587
+ return false;
31588
+ room2.pushedCommandIds.add(command.id);
31589
+ return true;
31590
+ });
31591
+ while (room2.pushedCommandIds.size > 1e4)
31592
+ room2.pushedCommandIds.delete(room2.pushedCommandIds.values().next().value);
31593
+ if (commands.length)
31594
+ room2.onCommands?.(commands);
31595
+ return;
31596
+ }
31597
+ if (event.type !== "inbox" || typeof event.roomId !== "string" || !Array.isArray(event.items))
31598
+ return;
31599
+ const room = this.liveRooms.get(event.roomId);
31600
+ if (!room)
31601
+ return;
31602
+ const items = [];
31603
+ for (const candidate of event.items) {
31604
+ if (!candidate || typeof candidate !== "object")
31605
+ continue;
31606
+ const id = candidate.id;
31607
+ if (typeof id === "string") {
31608
+ if (!room.pushedIds.has(id))
31609
+ items.push(candidate);
31610
+ room.pushedIds.add(id);
31274
31611
  }
31275
- while (room.pushedIds.size > 1e4)
31276
- room.pushedIds.delete(room.pushedIds.values().next().value);
31277
- if (items.length)
31278
- room.onItems?.(items, typeof event.cursor === "string" ? event.cursor : void 0);
31279
- };
31280
- const reconnect = () => {
31281
- if (this.liveSocket !== socket)
31282
- return;
31283
- this.liveSocket = void 0;
31284
- this.discoveryWakeSupported = false;
31285
- for (const room of this.liveRooms.values())
31286
- room.onState?.(false);
31287
- if (this.forceUpdatePending)
31288
- return;
31289
- if (!this.liveRooms.size && !this.roomsChangedListener)
31290
- return;
31291
- const delay = this.liveReconnectDelayMs;
31292
- this.liveReconnectDelayMs = Date.now() - this.liveOpenedAt >= STABLE_SOCKET_MS ? 1e3 : Math.min(delay * 2, 3e4);
31293
- this.reconnectCount += 1;
31294
- this.liveReconnect = setTimeout(() => this.ensureLiveSocket(), Math.floor(Math.random() * delay));
31295
- this.liveReconnect.unref?.();
31296
- };
31297
- socket.onerror = () => void 0;
31298
- socket.onclose = reconnect;
31612
+ }
31613
+ while (room.pushedIds.size > 1e4)
31614
+ room.pushedIds.delete(room.pushedIds.values().next().value);
31615
+ if (items.length)
31616
+ room.onItems?.(items, typeof event.cursor === "string" ? event.cursor : void 0);
31299
31617
  }
31300
31618
  sendLiveSubscription(roomId) {
31301
31619
  const room = this.liveRooms.get(roomId);
31302
- if (!room || this.liveSocket?.readyState !== wrapper_default.OPEN)
31620
+ if (!room || !this.link.isOpen())
31303
31621
  return;
31304
31622
  this.subscriptionCount += 1;
31305
- this.liveSocket.send(JSON.stringify({
31623
+ this.link.send(JSON.stringify({
31306
31624
  type: "subscribe",
31307
31625
  roomId,
31308
31626
  lifecycleId: this.presenceLifecycleId,
@@ -31363,7 +31681,7 @@ import { promisify as promisify8 } from "node:util";
31363
31681
  // apps/body/dist/grant-runner.js
31364
31682
  import { execFile as execFile8 } from "node:child_process";
31365
31683
  import { createHash as createHash6, randomBytes as randomBytes7, randomUUID as randomUUID7 } from "node:crypto";
31366
- import { chmod as chmod6, mkdir as mkdir10, readFile as readFile13, readdir as readdir4, rename as rename5, rm as rm6, rmdir, stat as stat6, writeFile as writeFile10 } from "node:fs/promises";
31684
+ import { chmod as chmod6, mkdir as mkdir10, readFile as readFile14, readdir as readdir4, rename as rename6, rm as rm6, rmdir, stat as stat6, writeFile as writeFile10 } from "node:fs/promises";
31367
31685
  import { createServer as createServer3 } from "node:http";
31368
31686
  import { dirname as dirname12, resolve as resolve16 } from "node:path";
31369
31687
 
@@ -31612,9 +31930,9 @@ function surfaceAllows(surface, capability) {
31612
31930
  init_agent_command();
31613
31931
  init_cursor_acp_bridge();
31614
31932
  import { execFile as execFile7, spawnSync } from "node:child_process";
31615
- import { lstatSync as lstatSync2, readdirSync as readdirSync2, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
31933
+ import { lstatSync as lstatSync2, readdirSync as readdirSync2, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
31616
31934
  import { homedir as homedir11 } from "node:os";
31617
- import { basename as basename5, dirname as dirname10, isAbsolute as isAbsolute2, relative, resolve as resolve14 } from "node:path";
31935
+ import { basename as basename6, dirname as dirname10, isAbsolute as isAbsolute2, relative, resolve as resolve14 } from "node:path";
31618
31936
  var DEFAULT_SANDBOX_POLICY = "bwrap";
31619
31937
  function isSandboxPolicy(value) {
31620
31938
  return value === "bwrap" || value === "off";
@@ -31709,7 +32027,7 @@ function siblingAgentMaskPaths(ownRuntimeDir, listAgentDirs = (agentsRoot) => {
31709
32027
  }) {
31710
32028
  const own = resolve14(ownRuntimeDir);
31711
32029
  const agentsRoot = dirname10(own);
31712
- const ownName = basename5(own).toLowerCase();
32030
+ const ownName = basename6(own).toLowerCase();
31713
32031
  return listAgentDirs(agentsRoot).filter((name) => name.toLowerCase() !== ownName).map((name) => ({ path: resolve14(agentsRoot, name), kind: "dir" })).sort((a2, b) => a2.path.localeCompare(b.path));
31714
32032
  }
31715
32033
  var DEFAULT_SANDBOX_DEVICES = ["/dev/kvm"];
@@ -31935,7 +32253,7 @@ function recordInstallFailure(stateDir, at) {
31935
32253
  if (!stateDir)
31936
32254
  return;
31937
32255
  try {
31938
- writeFileSync2(installFailureMarker(stateDir), `${at}
32256
+ writeFileSync3(installFailureMarker(stateDir), `${at}
31939
32257
  `, "utf8");
31940
32258
  } catch {
31941
32259
  }
@@ -32058,7 +32376,7 @@ async function ensureBwrapSandbox(options = {}) {
32058
32376
  }
32059
32377
 
32060
32378
  // apps/body/dist/provider-key-store.js
32061
- import { chmod as chmod5, mkdir as mkdir9, readFile as readFile12, writeFile as writeFile9 } from "node:fs/promises";
32379
+ import { chmod as chmod5, mkdir as mkdir9, readFile as readFile13, writeFile as writeFile9 } from "node:fs/promises";
32062
32380
  import { homedir as homedir12 } from "node:os";
32063
32381
  import { dirname as dirname11, resolve as resolve15 } from "node:path";
32064
32382
  var PROVIDER_KEY_ENV_VARS = {
@@ -32075,7 +32393,7 @@ function providerKeyStorePath(env = process.env) {
32075
32393
  }
32076
32394
  async function readProviderKeyStore(env = process.env) {
32077
32395
  const path = providerKeyStorePath(env);
32078
- const raw = await readFile12(path, "utf8").catch(() => void 0);
32396
+ const raw = await readFile13(path, "utf8").catch(() => void 0);
32079
32397
  if (!raw)
32080
32398
  return {};
32081
32399
  try {
@@ -32127,7 +32445,7 @@ function operatorSecretsPath(env = process.env) {
32127
32445
  return resolve16(dirname12(providerKeyStorePath(env)), "secrets.json");
32128
32446
  }
32129
32447
  async function readOperatorSecrets(env = process.env) {
32130
- const raw = await readFile13(operatorSecretsPath(env), "utf8").catch(() => void 0);
32448
+ const raw = await readFile14(operatorSecretsPath(env), "utf8").catch(() => void 0);
32131
32449
  if (!raw)
32132
32450
  return {};
32133
32451
  try {
@@ -32339,7 +32657,7 @@ ${ROOM_WRITE_REFUSED_NOTE}` : outcome.output, secrets), cap);
32339
32657
  return;
32340
32658
  let bytes;
32341
32659
  for (const candidate of candidates) {
32342
- bytes = await readFile13(candidate).catch(() => void 0);
32660
+ bytes = await readFile14(candidate).catch(() => void 0);
32343
32661
  if (bytes)
32344
32662
  break;
32345
32663
  }
@@ -32541,10 +32859,10 @@ function captureConnectionUsage(recorder, turn, calls) {
32541
32859
  }
32542
32860
 
32543
32861
  // apps/body/dist/turn-usage.js
32544
- import { readFile as readFile15 } from "node:fs/promises";
32862
+ import { readFile as readFile16 } from "node:fs/promises";
32545
32863
 
32546
32864
  // apps/body/dist/pi-turn-record.js
32547
- import { readdir as readdir5, readFile as readFile14 } from "node:fs/promises";
32865
+ import { readdir as readdir5, readFile as readFile15 } from "node:fs/promises";
32548
32866
  import { resolve as resolve17 } from "node:path";
32549
32867
  function summarizeProviderError(errorMessage2) {
32550
32868
  const trimmed = errorMessage2.trim();
@@ -32572,7 +32890,7 @@ async function piSessionFilePath(agentEnv, sessionId) {
32572
32890
  }
32573
32891
  async function sessionFileFromMap(home, sessionId) {
32574
32892
  try {
32575
- const raw = await readFile14(resolve17(home, ".pi", "pi-acp", "session-map.json"), "utf8");
32893
+ const raw = await readFile15(resolve17(home, ".pi", "pi-acp", "session-map.json"), "utf8");
32576
32894
  const map = JSON.parse(raw);
32577
32895
  const file = map.sessions?.[sessionId]?.sessionFile;
32578
32896
  return typeof file === "string" && file ? file : void 0;
@@ -32614,7 +32932,7 @@ async function readPiTurnRecord(input) {
32614
32932
  return void 0;
32615
32933
  let raw;
32616
32934
  try {
32617
- raw = await readFile14(file, "utf8");
32935
+ raw = await readFile15(file, "utf8");
32618
32936
  } catch {
32619
32937
  return void 0;
32620
32938
  }
@@ -32675,7 +32993,7 @@ async function readHarnessTurnUsage(input) {
32675
32993
  return void 0;
32676
32994
  let raw;
32677
32995
  try {
32678
- raw = await readFile15(file, "utf8");
32996
+ raw = await readFile16(file, "utf8");
32679
32997
  } catch {
32680
32998
  return void 0;
32681
32999
  }
@@ -32715,15 +33033,15 @@ async function readHarnessTurnUsage(input) {
32715
33033
  // apps/body/dist/server-command-intake.js
32716
33034
  import { randomUUID as randomUUID8 } from "node:crypto";
32717
33035
  import { mkdir as mkdir11, writeFile as writeFile11 } from "node:fs/promises";
32718
- import { writeFileSync as writeFileSync3 } from "node:fs";
33036
+ import { writeFileSync as writeFileSync4 } from "node:fs";
32719
33037
  import { tmpdir as tmpdir5 } from "node:os";
32720
- import { dirname as dirname13, join as join10 } from "node:path";
33038
+ import { dirname as dirname13, join as join11 } from "node:path";
32721
33039
  var CommandExecutionContext = class {
32722
33040
  generationId = randomUUID8();
32723
33041
  path;
32724
33042
  current;
32725
33043
  constructor(root) {
32726
- this.path = join10(root ?? tmpdir5(), `beeline-command-${this.generationId}.json`);
33044
+ this.path = join11(root ?? tmpdir5(), `beeline-command-${this.generationId}.json`);
32727
33045
  }
32728
33046
  async enter(command) {
32729
33047
  this.current = command;
@@ -32744,7 +33062,7 @@ var CommandExecutionContext = class {
32744
33062
  const command = this.current;
32745
33063
  if (!command)
32746
33064
  return;
32747
- writeFileSync3(this.path, JSON.stringify({
33065
+ writeFileSync4(this.path, JSON.stringify({
32748
33066
  roomId: command.roomId,
32749
33067
  requestId: command.turnRequestId,
32750
33068
  taskId: command.rootCommandId,
@@ -32831,6 +33149,7 @@ async function runServerCommandIntake(options) {
32831
33149
  requestReconciliation();
32832
33150
  hasSubscribed = true;
32833
33151
  }, options.presence, notify2);
33152
+ let failed = false;
32834
33153
  try {
32835
33154
  while (!signal?.aborted) {
32836
33155
  if (reconciliationError)
@@ -32908,22 +33227,36 @@ async function runServerCommandIntake(options) {
32908
33227
  if (reconcile)
32909
33228
  requestReconciliation();
32910
33229
  }
33230
+ } catch (error) {
33231
+ failed = true;
33232
+ throw error;
32911
33233
  } finally {
32912
33234
  stopped = true;
32913
33235
  off?.();
32914
33236
  options.onWake?.(void 0);
32915
- if (context.current)
32916
- options.stop(context.current.turnRequestId);
32917
- await busy;
33237
+ const current = context.current;
33238
+ const stopCurrent = () => {
33239
+ if (current)
33240
+ options.stop(current.turnRequestId);
33241
+ };
33242
+ if (!failed || signal?.aborted)
33243
+ stopCurrent();
33244
+ else
33245
+ signal?.addEventListener("abort", stopCurrent, { once: true });
33246
+ try {
33247
+ await busy;
33248
+ } finally {
33249
+ signal?.removeEventListener("abort", stopCurrent);
33250
+ }
32918
33251
  }
32919
33252
  }
32920
33253
 
32921
33254
  // apps/body/dist/monolith-corner-turn.js
32922
33255
  import { execFile as execFile11 } from "node:child_process";
32923
33256
  import { createHash as createHash10 } from "node:crypto";
32924
- import { mkdir as mkdir20, readFile as readFile21, writeFile as writeFile16 } from "node:fs/promises";
33257
+ import { mkdir as mkdir20, readFile as readFile22, writeFile as writeFile16 } from "node:fs/promises";
32925
33258
  import { homedir as homedir15, hostname as hostname2 } from "node:os";
32926
- import { dirname as dirname18, join as join16, resolve as resolve28 } from "node:path";
33259
+ import { dirname as dirname18, join as join17, resolve as resolve28 } from "node:path";
32927
33260
  import { promisify as promisify7 } from "node:util";
32928
33261
 
32929
33262
  // apps/body/dist/agent-home.js
@@ -33726,9 +34059,9 @@ function stringify(obj, { maxDepth = 1e3, numbersAsFloat = false } = {}) {
33726
34059
 
33727
34060
  // apps/body/dist/agent-home.js
33728
34061
  import { createHash as createHash7, randomUUID as randomUUID9 } from "node:crypto";
33729
- import { chmod as chmod7, copyFile, lstat as lstat3, mkdir as mkdir13, readFile as readFile17, readlink, readdir as readdir6, realpath, rename as rename6, rm as rm7, symlink as symlink2, unlink as unlink5, writeFile as writeFile13 } from "node:fs/promises";
34062
+ import { chmod as chmod7, copyFile, lstat as lstat3, mkdir as mkdir13, readFile as readFile18, readlink, readdir as readdir6, realpath, rename as rename7, rm as rm7, symlink as symlink2, unlink as unlink5, writeFile as writeFile13 } from "node:fs/promises";
33730
34063
  import { homedir as homedir13 } from "node:os";
33731
- import { basename as basename6, dirname as dirname14, join as join12, relative as relative2, resolve as resolve21, sep } from "node:path";
34064
+ import { basename as basename7, dirname as dirname14, join as join13, relative as relative2, resolve as resolve21, sep } from "node:path";
33732
34065
 
33733
34066
  // apps/body/dist/draw-avatar-skill.js
33734
34067
  var DRAW_AVATAR_SKILL_NAME = "draw-avatar";
@@ -33852,14 +34185,235 @@ function isAgentPairingCode(value) {
33852
34185
  // packages/api-contract/dist/phone.js
33853
34186
  init_agent_access();
33854
34187
 
34188
+ // packages/api-contract/dist/message-search.js
34189
+ var MESSAGE_SEARCH_FILLER_WORDS = [
34190
+ // Articles and determiners
34191
+ "a",
34192
+ "an",
34193
+ "the",
34194
+ "this",
34195
+ "that",
34196
+ "these",
34197
+ "those",
34198
+ "some",
34199
+ "any",
34200
+ "each",
34201
+ "every",
34202
+ "all",
34203
+ "both",
34204
+ "either",
34205
+ "neither",
34206
+ "much",
34207
+ "many",
34208
+ "more",
34209
+ "most",
34210
+ "such",
34211
+ "other",
34212
+ "another",
34213
+ "own",
34214
+ "same",
34215
+ "no",
34216
+ "not",
34217
+ "only",
34218
+ "very",
34219
+ // Pronouns
34220
+ "i",
34221
+ "me",
34222
+ "my",
34223
+ "mine",
34224
+ "myself",
34225
+ "you",
34226
+ "your",
34227
+ "yours",
34228
+ "yourself",
34229
+ "he",
34230
+ "him",
34231
+ "his",
34232
+ "himself",
34233
+ "she",
34234
+ "her",
34235
+ "hers",
34236
+ "herself",
34237
+ "it",
34238
+ "its",
34239
+ "itself",
34240
+ "we",
34241
+ "us",
34242
+ "our",
34243
+ "ours",
34244
+ "ourselves",
34245
+ "they",
34246
+ "them",
34247
+ "their",
34248
+ "theirs",
34249
+ "themselves",
34250
+ "who",
34251
+ "whom",
34252
+ "whose",
34253
+ "which",
34254
+ "what",
34255
+ "someone",
34256
+ "something",
34257
+ "anyone",
34258
+ "anything",
34259
+ "everyone",
34260
+ "everything",
34261
+ // Auxiliaries
34262
+ "am",
34263
+ "is",
34264
+ "are",
34265
+ "was",
34266
+ "were",
34267
+ "be",
34268
+ "been",
34269
+ "being",
34270
+ "have",
34271
+ "has",
34272
+ "had",
34273
+ "having",
34274
+ "do",
34275
+ "does",
34276
+ "did",
34277
+ "doing",
34278
+ "done",
34279
+ "will",
34280
+ "would",
34281
+ "shall",
34282
+ "should",
34283
+ "can",
34284
+ "could",
34285
+ "might",
34286
+ "must",
34287
+ "get",
34288
+ "gets",
34289
+ "got",
34290
+ // Contraction pieces
34291
+ "s",
34292
+ "t",
34293
+ "d",
34294
+ "ll",
34295
+ "m",
34296
+ "re",
34297
+ "ve",
34298
+ "don",
34299
+ "doesn",
34300
+ "didn",
34301
+ "isn",
34302
+ "aren",
34303
+ "wasn",
34304
+ "weren",
34305
+ "haven",
34306
+ "hasn",
34307
+ "hadn",
34308
+ "won",
34309
+ "wouldn",
34310
+ "couldn",
34311
+ "shouldn",
34312
+ "cannot",
34313
+ // Conjunctions
34314
+ "and",
34315
+ "or",
34316
+ "but",
34317
+ "nor",
34318
+ "so",
34319
+ "yet",
34320
+ "if",
34321
+ "then",
34322
+ "than",
34323
+ "because",
34324
+ "as",
34325
+ "while",
34326
+ "though",
34327
+ "although",
34328
+ "unless",
34329
+ "until",
34330
+ "whether",
34331
+ "also",
34332
+ // Prepositions
34333
+ "of",
34334
+ "to",
34335
+ "in",
34336
+ "on",
34337
+ "at",
34338
+ "by",
34339
+ "for",
34340
+ "with",
34341
+ "from",
34342
+ "into",
34343
+ "onto",
34344
+ "about",
34345
+ "above",
34346
+ "below",
34347
+ "over",
34348
+ "under",
34349
+ "after",
34350
+ "before",
34351
+ "between",
34352
+ "through",
34353
+ "during",
34354
+ "without",
34355
+ "within",
34356
+ "again",
34357
+ "against",
34358
+ "upon",
34359
+ "via",
34360
+ "per",
34361
+ "around",
34362
+ // Adverbs
34363
+ "here",
34364
+ "there",
34365
+ "where",
34366
+ "when",
34367
+ "why",
34368
+ "how",
34369
+ "now",
34370
+ "just",
34371
+ "too",
34372
+ "still",
34373
+ "even",
34374
+ "ever",
34375
+ "never",
34376
+ "always",
34377
+ "really",
34378
+ "maybe",
34379
+ "already",
34380
+ "soon",
34381
+ // Chat filler
34382
+ "ok",
34383
+ "okay",
34384
+ "yes",
34385
+ "yeah",
34386
+ "yep",
34387
+ "yup",
34388
+ "nope",
34389
+ "hi",
34390
+ "hey",
34391
+ "hello",
34392
+ "thanks",
34393
+ "thank",
34394
+ "thx",
34395
+ "please",
34396
+ "pls",
34397
+ "like",
34398
+ "gonna",
34399
+ "wanna",
34400
+ "kinda",
34401
+ "lol",
34402
+ "oh",
34403
+ "ah",
34404
+ "um",
34405
+ "uh",
34406
+ "hmm",
34407
+ "oops",
34408
+ "anyway"
34409
+ ];
34410
+ var FILLER_WORDS = new Set(MESSAGE_SEARCH_FILLER_WORDS);
34411
+
33855
34412
  // apps/body/dist/beeline-skill.js
33856
34413
  var USING_BEELINE_SKILL_NAME = "using-beeline";
33857
34414
  var BEELINE_TRIAGE_SKILL_NAME = "beeline-triage";
33858
34415
  var BEELINE_REVIEW_SKILL_NAME = "beeline-review";
33859
34416
  var BEELINE_SPEC_SKILL_NAME = "beeline-spec";
33860
- function isConfiguredReviewer(agentHandle, reviewerHandle) {
33861
- return Boolean(agentHandle && reviewerHandle && agentHandle.replace(/^@/, "") === reviewerHandle.replace(/^@/, ""));
33862
- }
33863
34417
  var BEELINE_ROOM_MECHANICS = [
33864
34418
  "Files and photos people share are downloaded for you: read them at the local path named in the prompt (photos may also arrive inline); never fetch the reference URL.",
33865
34419
  "To create a file you can send, call beeline-agent write_scratch_file with a relative path and content - text by default, or base64 for bytes you computed; it returns a path in your writable session home. To send a file, call beeline-agent post_artifact with a path inside your checkout or anywhere in your writable session home (wherever a file you or your harness generated actually landed, including one you just wrote), or with html/bytes content directly; it is uploaded and attached to your reply, and title and mime default from the file when you post by path. write_scratch_file produces the file, not a picture. To put a real photograph in an artifact, call beeline-agent fetch_image with the photo URL; it writes the bytes to your session scratch and returns the path, mime, and size \u2014 read them, base64-encode, and embed as a data: URL. The validator still refuses every http(s) image reference, and drawing an SVG stand-in is not a photograph.",
@@ -34024,7 +34578,7 @@ Only the author may report final_authorization as passed, after pr_checks_status
34024
34578
 
34025
34579
  Then take exactly one action:
34026
34580
 
34027
- - FAIL: reply \`@author\` with the confirmed findings to fix.
34581
+ - FAIL: call \`record_validation_stage\` with stage \`review\`, status \`failed\`, and the reviewed head SHA, then reply \`@author\` with the confirmed findings and that reviewed head SHA to fix. The recorded review stage is how an implementer who has already fixed the findings can tell whether the review is stale.
34028
34582
  - PASS: call \`approve_merge\` with the reviewed head SHA and assigned briefRevision (omit the revision only for a legacy corner without a brief), then reply \`approved <reviewed sha>\` without tagging the author. Do not tell the author to merge.
34029
34583
  - Approving is your last step as reviewer. The server squash-merges that exact head once checks are green, the worker's yolo is on, and no human hold stands. Neither you nor the author merges it.
34030
34584
  `;
@@ -34194,7 +34748,7 @@ var SQUIRE_GOVERNED_TOOLS = [
34194
34748
  var SQUIRE_GOVERNED_TOOL_SET = new Set(SQUIRE_GOVERNED_TOOLS);
34195
34749
 
34196
34750
  // apps/body/dist/host-mcp-route.js
34197
- import { join as join11, resolve as resolve19 } from "node:path";
34751
+ import { join as join12, resolve as resolve19 } from "node:path";
34198
34752
  var import_yaml = __toESM(require_dist(), 1);
34199
34753
  init_squire_host();
34200
34754
  function grantedMcpServerNames(grants) {
@@ -34249,7 +34803,7 @@ function grantedSquireHostRouteNames(granted, declarations = {}) {
34249
34803
  });
34250
34804
  }
34251
34805
  function hostRouteEnv(hostHome) {
34252
- return { XDG_CONFIG_HOME: join11(resolve19(hostHome), ".config") };
34806
+ return { XDG_CONFIG_HOME: join12(resolve19(hostHome), ".config") };
34253
34807
  }
34254
34808
  function rewriteHostMcpDeclaration(name, declaration, hostHome, resourceAuthFile, squireScope) {
34255
34809
  const next = { ...declaration };
@@ -34368,7 +34922,7 @@ function mergeGooseHostRoutes(existing, routes) {
34368
34922
  init_squire_host();
34369
34923
 
34370
34924
  // apps/body/dist/openrouter-routing.js
34371
- import { mkdir as mkdir12, readFile as readFile16, writeFile as writeFile12 } from "node:fs/promises";
34925
+ import { mkdir as mkdir12, readFile as readFile17, writeFile as writeFile12 } from "node:fs/promises";
34372
34926
  import { resolve as resolve20 } from "node:path";
34373
34927
  var OPENROUTER_ENDPOINTS_BASE_URL = "https://openrouter.ai/api/v1/models";
34374
34928
  var OPENROUTER_COMPLETIONS_URL = "https://openrouter.ai/api/v1/chat/completions";
@@ -34496,7 +35050,7 @@ function cachePath(cacheDir, model) {
34496
35050
  }
34497
35051
  async function readCache(cacheDir, model) {
34498
35052
  try {
34499
- const parsed = JSON.parse(await readFile16(cachePath(cacheDir, model), "utf8"));
35053
+ const parsed = JSON.parse(await readFile17(cachePath(cacheDir, model), "utf8"));
34500
35054
  if (!parsed || typeof parsed !== "object")
34501
35055
  return void 0;
34502
35056
  const cached = parsed;
@@ -34539,7 +35093,7 @@ function probeCachePath(cacheDir, model) {
34539
35093
  }
34540
35094
  async function readProbeCache(cacheDir, model) {
34541
35095
  try {
34542
- const parsed = JSON.parse(await readFile16(probeCachePath(cacheDir, model), "utf8"));
35096
+ const parsed = JSON.parse(await readFile17(probeCachePath(cacheDir, model), "utf8"));
34543
35097
  if (!parsed || typeof parsed !== "object")
34544
35098
  return void 0;
34545
35099
  const cached = parsed;
@@ -35090,10 +35644,10 @@ function claudeLoginExpiresAt(text2) {
35090
35644
  }
35091
35645
  }
35092
35646
  async function hasNewerDetachedClaudeLogin(detached, source) {
35093
- const detachedExpiresAt = claudeLoginExpiresAt(await readFile17(detached, "utf8"));
35647
+ const detachedExpiresAt = claudeLoginExpiresAt(await readFile18(detached, "utf8"));
35094
35648
  if (detachedExpiresAt === void 0)
35095
35649
  return false;
35096
- const sourceExpiresAt = claudeLoginExpiresAt(await readFile17(source, "utf8"));
35650
+ const sourceExpiresAt = claudeLoginExpiresAt(await readFile18(source, "utf8"));
35097
35651
  return sourceExpiresAt !== void 0 && detachedExpiresAt > sourceExpiresAt;
35098
35652
  }
35099
35653
  async function repairRoomAgentCredentialLinks(input) {
@@ -35120,7 +35674,7 @@ async function repairRoomAgentCredentialLinks(input) {
35120
35674
  continue;
35121
35675
  }
35122
35676
  const quarantine = `${target}.beeline-quarantine-${Date.now()}-${randomUUID9()}`;
35123
- await rename6(target, quarantine);
35677
+ await rename7(target, quarantine);
35124
35678
  }
35125
35679
  await symlink2(source, target);
35126
35680
  }
@@ -35600,7 +36154,7 @@ async function provisionManagedSkillsDir(target, managedSkills, sharedSkills, op
35600
36154
  const existing = await lstat3(target).catch(() => void 0);
35601
36155
  if (existing)
35602
36156
  await rm7(target, { recursive: existing.isDirectory(), force: true });
35603
- await rename6(staged, target);
36157
+ await rename7(staged, target);
35604
36158
  } finally {
35605
36159
  await rm7(staged, { recursive: true, force: true });
35606
36160
  }
@@ -35621,8 +36175,8 @@ async function planManagedSkills(managedSkills, sharedSkills, optionalShares) {
35621
36175
  try {
35622
36176
  const tree = [];
35623
36177
  await walkSafeSkillTree(shared.source, shared.source, {
35624
- directory: async (rel) => void tree.push(`d ${join12(shared.name, rel)}`),
35625
- file: async (rel, realPath) => void tree.push(`f ${join12(shared.name, rel)} ${sha2563(await readFile17(realPath))}`)
36178
+ directory: async (rel) => void tree.push(`d ${join13(shared.name, rel)}`),
36179
+ file: async (rel, realPath) => void tree.push(`f ${join13(shared.name, rel)} ${sha2563(await readFile18(realPath))}`)
35626
36180
  });
35627
36181
  entries.push({ kind: "shared", name: shared.name, source: shared.source });
35628
36182
  lines.push(...tree);
@@ -35642,7 +36196,7 @@ async function materializedSkillManifest(target) {
35642
36196
  const visit = async (directory, prefix) => {
35643
36197
  for (const entry of await readdir6(directory)) {
35644
36198
  const path = resolve21(directory, entry);
35645
- const rel = prefix ? join12(prefix, entry) : entry;
36199
+ const rel = prefix ? join13(prefix, entry) : entry;
35646
36200
  const entryStats = await lstat3(path);
35647
36201
  if (entryStats.isSymbolicLink())
35648
36202
  return false;
@@ -35654,7 +36208,7 @@ async function materializedSkillManifest(target) {
35654
36208
  }
35655
36209
  if (!entryStats.isFile() || entryStats.nlink !== 1)
35656
36210
  return false;
35657
- lines.push(`f ${rel} ${sha2563(await readFile17(path))}`);
36211
+ lines.push(`f ${rel} ${sha2563(await readFile18(path))}`);
35658
36212
  }
35659
36213
  return true;
35660
36214
  };
@@ -35802,7 +36356,7 @@ async function walkSafeSkillTree(source, sourceRoot, visitor, rel = "") {
35802
36356
  const stats = await lstat3(resolvedSource);
35803
36357
  if (stats.isSymbolicLink())
35804
36358
  throw new Error(`shared skill contains a symlink: ${source}`);
35805
- if (BLOCKED_SHARED_FILENAMES.test(basename6(source))) {
36359
+ if (BLOCKED_SHARED_FILENAMES.test(basename7(source))) {
35806
36360
  throw new Error(`shared skill contains credential or configuration material: ${source}`);
35807
36361
  }
35808
36362
  if (stats.isDirectory()) {
@@ -35810,7 +36364,7 @@ async function walkSafeSkillTree(source, sourceRoot, visitor, rel = "") {
35810
36364
  for (const entry of await readdir6(resolvedSource)) {
35811
36365
  if (entry === "." || entry === "..")
35812
36366
  throw new Error("invalid shared skill entry");
35813
- await walkSafeSkillTree(resolve21(source, entry), sourceRoot, visitor, rel ? join12(rel, entry) : entry);
36367
+ await walkSafeSkillTree(resolve21(source, entry), sourceRoot, visitor, rel ? join13(rel, entry) : entry);
35814
36368
  }
35815
36369
  return;
35816
36370
  }
@@ -35837,11 +36391,11 @@ async function writeIsolatedHarnessFile(path, content) {
35837
36391
  if (!parentStats.isDirectory() || parentStats.isSymbolicLink()) {
35838
36392
  throw new Error(`isolated harness parent is not a real directory: ${parent}`);
35839
36393
  }
35840
- const temporary = resolve21(parent, `.${basename6(path)}.${process.pid}.${randomUUID9()}.tmp`);
36394
+ const temporary = resolve21(parent, `.${basename7(path)}.${process.pid}.${randomUUID9()}.tmp`);
35841
36395
  try {
35842
36396
  await writeFile13(temporary, content, { mode: 384, flag: "wx" });
35843
36397
  await chmod7(temporary, 384);
35844
- await rename6(temporary, path);
36398
+ await rename7(temporary, path);
35845
36399
  } finally {
35846
36400
  await unlink5(temporary).catch(() => void 0);
35847
36401
  }
@@ -36832,7 +37386,7 @@ function readOnlyMcpServer(config, cwd, agentMemoryDir) {
36832
37386
 
36833
37387
  // apps/body/dist/codegraph.js
36834
37388
  import { execFile as execFile10 } from "node:child_process";
36835
- import { access as access2, appendFile, mkdir as mkdir15, readFile as readFile18 } from "node:fs/promises";
37389
+ import { access as access2, appendFile, mkdir as mkdir15, readFile as readFile19 } from "node:fs/promises";
36836
37390
  import { dirname as dirname15, isAbsolute as isAbsolute3, resolve as resolve24 } from "node:path";
36837
37391
  import { promisify as promisify6 } from "node:util";
36838
37392
  var execFileAsync6 = promisify6(execFile10);
@@ -36882,7 +37436,7 @@ async function excludeIndexFromGitStatus(cwd) {
36882
37436
  return;
36883
37437
  const path = isAbsolute3(gitPath) ? gitPath : resolve24(cwd, gitPath);
36884
37438
  await mkdir15(dirname15(path), { recursive: true });
36885
- const existing = await readFile18(path, "utf8").catch(() => "");
37439
+ const existing = await readFile19(path, "utf8").catch(() => "");
36886
37440
  if (existing.split("\n").includes(".codegraph/"))
36887
37441
  return;
36888
37442
  await appendFile(path, `${existing && !existing.endsWith("\n") ? "\n" : ""}.codegraph/
@@ -36918,20 +37472,20 @@ init_cursor_acp_bridge();
36918
37472
  // apps/body/dist/monolith-room-turn.js
36919
37473
  import { mkdir as mkdir17, writeFile as writeFile14 } from "node:fs/promises";
36920
37474
  import { homedir as homedir14, hostname } from "node:os";
36921
- import { dirname as dirname16, join as join14 } from "node:path";
37475
+ import { dirname as dirname16, join as join15 } from "node:path";
36922
37476
 
36923
37477
  // packages/api-contract/dist/scheduled-prompts.js
36924
37478
  var SCHEDULE_SCHEDULER_NAME = "Beeline Scheduler";
36925
37479
  var SCHEDULE_RAN_VERB = "ran a schedule for";
36926
37480
 
36927
37481
  // apps/body/dist/model-context-window.js
36928
- import { readFile as readFile19 } from "node:fs/promises";
36929
- import { join as join13 } from "node:path";
37482
+ import { readFile as readFile20 } from "node:fs/promises";
37483
+ import { join as join14 } from "node:path";
36930
37484
  async function modelContextWindowTokens(model, piHome) {
36931
37485
  if (!model || !piHome)
36932
37486
  return void 0;
36933
37487
  try {
36934
- const catalog = JSON.parse(await readFile19(join13(piHome, "models.json"), "utf8"));
37488
+ const catalog = JSON.parse(await readFile20(join14(piHome, "models.json"), "utf8"));
36935
37489
  const parts = model.split("/");
36936
37490
  const preferredProvider = parts.length > 2 ? parts.shift() : void 0;
36937
37491
  const modelId = parts.join("/");
@@ -37082,7 +37636,7 @@ var CORNERS = [
37082
37636
  ];
37083
37637
  var EVERYWHERE = PROMPT_SURFACES;
37084
37638
  var CORNER_AUTHOR_CONTRACT = `The current brief's spec is the scope and its checklist says what done means. Its approval quote is the human's own words and wins any conflict with the spec. The short objective is navigation-only text and cannot add, remove, or narrow a requirement.
37085
- Complete every checklist item in the current spec and use the brief's file manifest. Record relevant validation stages with record_validation_stage against the current revision and head, citing actual commands or observed behavior. Do not call a missing stage passed.
37639
+ Complete every checklist item in the current spec and use the brief's file manifest. Record your stages with record_validation_stage for this revision and head: intent, base, tests, docs, lint_types, publication, ci, final_authorization; the configured reviewer records review. Do not call a missing stage passed.
37086
37640
  When a human correction changes the assignment, read the latest revision and use revise_corner_brief with the complete updated brief and a change description before doing dependent work. A chat reply does not revise the assignment.
37087
37641
  Before any code, write its end-user story in one sentence: "a person who does X sees Y".
37088
37642
  Follow the beeline-triage skill's bugfix execution contract when the spec or its approval quote reports a defect.
@@ -37113,15 +37667,15 @@ function cornerMergeInstruction(yoloMode, reviewerHandle) {
37113
37667
  return yoloMode ? `Once the pull request exists, reply with its full URL and end the turn; do not tag ${reviewer}, check, or wait for CI. Green checks wake ${reviewer}; after its PASS the server merges the pull request itself. Never merge it yourself. You are woken only to fix failing checks, requested changes, or a merge GitHub refused; push the fix, and the new head needs green checks and a fresh PASS.` : `Once the pull request exists, reply with its full URL and end the turn; do not tag ${reviewer}, check, or wait for CI. Yolo is off, so the merge gate stays closed for you: never merge; a person merges it after ${reviewer} approves.`;
37114
37668
  }
37115
37669
  function cornerReviewerInstruction(input) {
37116
- if (!input.reviewerHandle || !input.agentHandle || input.openedByAgent || handle(input.agentHandle) !== handle(input.reviewerHandle))
37670
+ if (!input.isReviewer || input.openedByAgent)
37117
37671
  return void 0;
37118
37672
  const author = input.authorHandle ? handle(input.authorHandle) : "author";
37119
37673
  const number = input.pullRequestNumber ?? "N";
37120
37674
  const headSha = input.headSha ?? "<head sha>";
37121
- return `Checks are green on PR #${number} at ${headSha}${input.briefRevision ? ` with assigned brief revision ${input.briefRevision}` : ""}. Review it now with the beeline-review skill against that exact head and current assigned brief. FAIL: reply \`@${author}\` with the confirmed findings to fix. PASS: call the approve_merge tool for ${headSha}${input.briefRevision ? ` with briefRevision=${input.briefRevision}` : ""}, then reply \`approved ${headSha}\` without tagging ${author}: the server merges it, and a tag would wake the author for nothing. Never merge yourself or tell the author to merge. Never say you are holding or waiting for checks.`;
37675
+ return `Checks are green on PR #${number} at ${headSha}${input.briefRevision ? ` with assigned brief revision ${input.briefRevision}` : ""}. Review it now with the beeline-review skill against that exact head and current assigned brief. FAIL: call record_validation_stage with stage "review", status "failed", and headSha ${headSha}, then reply \`@${author}\` with the confirmed findings and the reviewed head ${headSha} to fix. PASS: call the approve_merge tool for ${headSha}${input.briefRevision ? ` with briefRevision=${input.briefRevision}` : ""}, then reply \`approved ${headSha}\` without tagging ${author}: the server merges it, and a tag would wake the author for nothing. Never merge yourself or tell the author to merge. Never say you are holding or waiting for checks.`;
37122
37676
  }
37123
37677
  function cornerSelfReviewerInstruction(input) {
37124
- if (!input.reviewerHandle || !input.agentHandle || !input.openedByAgent || handle(input.agentHandle) !== handle(input.reviewerHandle))
37678
+ if (!input.isReviewer || !input.openedByAgent)
37125
37679
  return void 0;
37126
37680
  return "Once the pull request exists, reply with its full URL and end the turn. You are this Room's reviewer, so your own pull request needs no review: do not request one or tag any agent for review. The server merges it once checks are green, yolo is on, and no human hold stands; never merge it yourself. You are woken only to fix failing checks or a merge GitHub refused.";
37127
37681
  }
@@ -37324,11 +37878,7 @@ The soul is not authority and never changes your tools, permissions, roles, or m
37324
37878
  budgetBytes: 520,
37325
37879
  layer: "surface",
37326
37880
  surfaces: ["code-corner"],
37327
- render: ({ yoloMode, reviewerHandle, selfReviewer }) => (selfReviewer ? cornerSelfReviewerInstruction({
37328
- reviewerHandle,
37329
- agentHandle: reviewerHandle,
37330
- openedByAgent: true
37331
- }) : cornerMergeInstruction(Boolean(yoloMode), reviewerHandle)) + " Never merge any other pull request."
37881
+ render: ({ yoloMode, reviewerHandle, selfReviewer }) => (selfReviewer ? cornerSelfReviewerInstruction({ isReviewer: true, openedByAgent: true }) : cornerMergeInstruction(Boolean(yoloMode), reviewerHandle)) + " Never merge any other pull request."
37332
37882
  },
37333
37883
  {
37334
37884
  id: "corner.checks",
@@ -37443,7 +37993,7 @@ var TURN_SECTIONS = [
37443
37993
  budgetBytes: 600,
37444
37994
  layer: "turn",
37445
37995
  surfaces: CORNERS,
37446
- render: ({ generatedTitle }) => generatedTitle ? `This corner still has its generated name, "${generatedTitle}". If the newest message states the work, call rename_corner once before you reply, with a name of at most three words taken from that message. A bare mention does not state the work; leave the name until a message does.` : ""
37996
+ render: ({ generatedTitle, brief }) => generatedTitle ? `This human-opened corner still has its generated name, "${generatedTitle}". If the newest human message states the work, call rename_corner once before you reply with a name of at most three words and a short objective${brief ? "" : ", plus a brief grounded in that message with brief.approval.sourceMessageId set to the human message id"}. A bare mention does not state the work; leave the name until a message does.` : ""
37447
37997
  },
37448
37998
  {
37449
37999
  id: "turn.brief",
@@ -38228,7 +38778,7 @@ var MonolithRoomTurnLoop = class {
38228
38778
  const cached = this.deliveredAttachments.get(item.id);
38229
38779
  if (cached)
38230
38780
  return cached;
38231
- const delivered = await deliverAttachments(item.attachments, join14(this.attachmentDir, item.id.replace(/[^\w-]/g, "_")), this.options.fetchImpl);
38781
+ const delivered = await deliverAttachments(item.attachments, join15(this.attachmentDir, item.id.replace(/[^\w-]/g, "_")), this.options.fetchImpl);
38232
38782
  this.deliveredAttachments.set(item.id, withoutImageData(delivered));
38233
38783
  return delivered;
38234
38784
  }
@@ -38415,7 +38965,7 @@ var MonolithRoomTurnLoop = class {
38415
38965
  root: this.options.config.agentHomeRoot,
38416
38966
  squireScope,
38417
38967
  sharedSkills: this.options.config.sharedSkills ?? [],
38418
- isReviewer: isConfiguredReviewer(self?.handle, configuration.reviewerHandle),
38968
+ isReviewer: configuration.isReviewer === true,
38419
38969
  grantedHostRoutes: mountedHostRoutes,
38420
38970
  extraHostRoutes: registryHostDeclarations,
38421
38971
  resourceAuthFile,
@@ -38444,7 +38994,7 @@ var MonolithRoomTurnLoop = class {
38444
38994
  args: this.options.config.agentArgs ?? []
38445
38995
  }, selection);
38446
38996
  const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
38447
- this.attachmentDir = tmpDir ? join14(tmpDir, "beeline-attachments") : void 0;
38997
+ this.attachmentDir = tmpDir ? join15(tmpDir, "beeline-attachments") : void 0;
38448
38998
  this.sessionScratchDir = tmpDir;
38449
38999
  this.sessionStateDirs = stateDirs;
38450
39000
  const homeStateDirs = harnessHomeStateDirs(harnessLabel, agentEnv.HOME ?? operatorHome);
@@ -38693,7 +39243,6 @@ var MonolithRoomTurnLoop = class {
38693
39243
  };
38694
39244
  this.activeTurn = active;
38695
39245
  active.promise = this.prompt(active).catch((error) => {
38696
- this.options.health.failure(1e3);
38697
39246
  console.error(`[thin-core] monolith Room ${this.options.roomId} turn failed:`, error);
38698
39247
  }).finally(() => {
38699
39248
  if (this.activeTurn === active) {
@@ -39076,56 +39625,60 @@ var MonolithRoomTurnLoop = class {
39076
39625
  }
39077
39626
  }
39078
39627
  async run() {
39079
- const { api, roomId, signal } = this.options;
39628
+ const { api, roomId, signal, supervisor } = this.options;
39629
+ const intake = (progress) => runServerCommandIntake({
39630
+ api,
39631
+ roomId,
39632
+ agentId: this.agent.publicKey,
39633
+ context: this.commandContext,
39634
+ signal,
39635
+ pollMs: this.options.pollMs,
39636
+ presence: {
39637
+ ...this.options.config.daemonReleaseVersion ? { releaseVersion: this.options.config.daemonReleaseVersion } : {},
39638
+ ...this.options.config.daemonSourceSha ? { sourceSha: this.options.config.daemonSourceSha } : {},
39639
+ available: !this.options.config.modelUnavailable
39640
+ },
39641
+ onWake: (wake) => {
39642
+ this.wakeIntake = wake;
39643
+ },
39644
+ onPoll: () => {
39645
+ progress();
39646
+ this.options.health.poll();
39647
+ },
39648
+ onSubscriptionState: this.options.onSubscriptionState,
39649
+ onError: (error) => {
39650
+ this.options.onIntakeError?.(error);
39651
+ console.error("[thin-core] Room command failed", error);
39652
+ },
39653
+ // Marks this loop busy the instant it attempts to claim a real
39654
+ // turn, before the claim's own network round trip and before
39655
+ // context.enter()'s own file I/O - closing the window where
39656
+ // RoomRuntimeCoordinator.reconcile's repository-revision restart
39657
+ // could see a claiming-but-not-yet-`busy` Room as idle and stop it,
39658
+ // cancelling a turn that had already begun (or was about to) (C112).
39659
+ onClaiming: () => {
39660
+ this.busy = true;
39661
+ },
39662
+ onClaimFailed: () => {
39663
+ this.busy = false;
39664
+ },
39665
+ onEnter: (command) => this.squireRelay.activate(command, this.commandContext.generationId),
39666
+ onLeave: (command) => this.squireRelay.deactivate(command.turnRequestId, this.pausedOnGrantRequestId),
39667
+ stop: (requestId) => this.stopTurn(requestId),
39668
+ restart: () => this.options.onRestartRequested?.(),
39669
+ canStartTurn: this.options.canStartTurn,
39670
+ run: async (command) => {
39671
+ const item = {
39672
+ ...command.source,
39673
+ id: command.turnRequestId,
39674
+ body: command.action === "resume" ? `Resume the paused turn. The server supplied this answer: ${command.source.body}` : command.source.body
39675
+ };
39676
+ this.startPrompt(item);
39677
+ await this.activeTurn?.promise;
39678
+ }
39679
+ });
39080
39680
  try {
39081
- await runServerCommandIntake({
39082
- api,
39083
- roomId,
39084
- agentId: this.agent.publicKey,
39085
- context: this.commandContext,
39086
- signal,
39087
- pollMs: this.options.pollMs,
39088
- presence: {
39089
- ...this.options.config.daemonReleaseVersion ? { releaseVersion: this.options.config.daemonReleaseVersion } : {},
39090
- ...this.options.config.daemonSourceSha ? { sourceSha: this.options.config.daemonSourceSha } : {},
39091
- available: !this.options.config.modelUnavailable
39092
- },
39093
- onWake: (wake) => {
39094
- this.wakeIntake = wake;
39095
- },
39096
- onPoll: () => this.options.health.poll(),
39097
- onSubscriptionState: this.options.onSubscriptionState,
39098
- onError: (error) => {
39099
- this.options.onIntakeError?.(error);
39100
- console.error("[thin-core] Room command failed", error);
39101
- },
39102
- // Marks this loop busy the instant it attempts to claim a real
39103
- // turn, before the claim's own network round trip and before
39104
- // context.enter()'s own file I/O - closing the window where
39105
- // RoomRuntimeCoordinator.reconcile's repository-revision restart
39106
- // could see a claiming-but-not-yet-`busy` Room as idle and stop it,
39107
- // cancelling a turn that had already begun (or was about to) (C112).
39108
- onClaiming: () => {
39109
- this.busy = true;
39110
- },
39111
- onClaimFailed: () => {
39112
- this.busy = false;
39113
- },
39114
- onEnter: (command) => this.squireRelay.activate(command, this.commandContext.generationId),
39115
- onLeave: (command) => this.squireRelay.deactivate(command.turnRequestId, this.pausedOnGrantRequestId),
39116
- stop: (requestId) => this.stopTurn(requestId),
39117
- restart: () => this.options.onRestartRequested?.(),
39118
- canStartTurn: this.options.canStartTurn,
39119
- run: async (command) => {
39120
- const item = {
39121
- ...command.source,
39122
- id: command.turnRequestId,
39123
- body: command.action === "resume" ? `Resume the paused turn. The server supplied this answer: ${command.source.body}` : command.source.body
39124
- };
39125
- this.startPrompt(item);
39126
- await this.activeTurn?.promise;
39127
- }
39128
- });
39681
+ await (supervisor ? supervisor.supervise(`Room ${roomId}`, signal, intake) : intake(() => void 0));
39129
39682
  } finally {
39130
39683
  this.squireRelay.close();
39131
39684
  this.options.grantRunner?.unregister(roomId);
@@ -39311,8 +39864,8 @@ process.exit(result.status ?? 1);
39311
39864
  // apps/body/dist/warm-node-modules.js
39312
39865
  import { createHash as createHash9, randomUUID as randomUUID10 } from "node:crypto";
39313
39866
  import { constants as fsConstants3 } from "node:fs";
39314
- import { chmod as chmod9, copyFile as copyFile2, link, lstat as lstat4, mkdir as mkdir19, readdir as readdir8, readFile as readFile20, readlink as readlink2, rename as rename7, rm as rm9, stat as stat7, symlink as symlink3, utimes } from "node:fs/promises";
39315
- import { dirname as dirname17, join as join15, resolve as resolve27 } from "node:path";
39867
+ import { chmod as chmod9, copyFile as copyFile2, link, lstat as lstat4, mkdir as mkdir19, readdir as readdir8, readFile as readFile21, readlink as readlink2, rename as rename8, rm as rm9, stat as stat7, symlink as symlink3, utimes } from "node:fs/promises";
39868
+ import { dirname as dirname17, join as join16, resolve as resolve27 } from "node:path";
39316
39869
  var STORE_FORMAT = "v1";
39317
39870
  var STAGING_PREFIX = ".beeline-warm-";
39318
39871
  var STAGING_SWEEP_MS = 24 * 60 * 60 * 1e3;
@@ -39333,7 +39886,7 @@ function isPlanRefusal(value) {
39333
39886
  return "failure" in value;
39334
39887
  }
39335
39888
  async function readWarmPlan(worktreePath) {
39336
- const lockfile = await readFile20(resolve27(worktreePath, "package-lock.json")).catch(() => void 0);
39889
+ const lockfile = await readFile21(resolve27(worktreePath, "package-lock.json")).catch(() => void 0);
39337
39890
  if (!lockfile)
39338
39891
  return { failure: "no-lockfile" };
39339
39892
  const packages = parseLockfilePackages(lockfile);
@@ -39398,7 +39951,7 @@ async function seedWarmNodeModules(input) {
39398
39951
  for (const tree of plan.trees) {
39399
39952
  const target = resolve27(input.worktreePath, tree);
39400
39953
  await mkdir19(dirname17(target), { recursive: true });
39401
- await rename7(resolve27(staging, tree), target);
39954
+ await rename8(resolve27(staging, tree), target);
39402
39955
  placed.push(target);
39403
39956
  }
39404
39957
  return { reason: "seeded", key: plan.key };
@@ -39443,7 +39996,7 @@ async function harvestWarmNodeModules(input) {
39443
39996
  if (!await stagedTreeIsPublishable(input.worktreePath, staging, plan.key)) {
39444
39997
  return { reason: "changed", key: plan.key };
39445
39998
  }
39446
- await rename7(staging, entry);
39999
+ await rename8(staging, entry);
39447
40000
  await pruneWarmStore(input.storeRoot, WARM_STORE_MAX_ENTRIES);
39448
40001
  return { reason: "stored", key: plan.key };
39449
40002
  } catch (error) {
@@ -39458,18 +40011,18 @@ async function stagedTreeIsPublishable(worktreePath, staging, key) {
39458
40011
  const settled = await readWarmPlan(worktreePath);
39459
40012
  if (isPlanRefusal(settled) || settled.key !== key)
39460
40013
  return false;
39461
- const hidden = join15("node_modules", ".package-lock.json");
40014
+ const hidden = join16("node_modules", ".package-lock.json");
39462
40015
  const [copied, current] = await Promise.all([
39463
- readFile20(resolve27(staging, hidden)).catch(() => void 0),
39464
- readFile20(resolve27(worktreePath, hidden)).catch(() => void 0)
40016
+ readFile21(resolve27(staging, hidden)).catch(() => void 0),
40017
+ readFile21(resolve27(worktreePath, hidden)).catch(() => void 0)
39465
40018
  ]);
39466
40019
  if (!copied || !current || !copied.equals(current))
39467
40020
  return false;
39468
40021
  return (await missingInstalledPackages(worktreePath, staging)).length === 0;
39469
40022
  }
39470
40023
  async function missingInstalledPackages(worktreePath, treeRoot = worktreePath) {
39471
- const wanted = parseLockfilePackages(await readFile20(resolve27(worktreePath, "package-lock.json")).catch(() => void 0));
39472
- const installed = parseLockfilePackages(await readFile20(resolve27(treeRoot, "node_modules", ".package-lock.json")).catch(() => void 0));
40024
+ const wanted = parseLockfilePackages(await readFile21(resolve27(worktreePath, "package-lock.json")).catch(() => void 0));
40025
+ const installed = parseLockfilePackages(await readFile21(resolve27(treeRoot, "node_modules", ".package-lock.json")).catch(() => void 0));
39473
40026
  if (!wanted)
39474
40027
  return ["package-lock.json is unreadable"];
39475
40028
  if (!installed)
@@ -39496,7 +40049,7 @@ async function missingInstalledPackages(worktreePath, treeRoot = worktreePath) {
39496
40049
  return missing.sort();
39497
40050
  }
39498
40051
  async function isInstalledPackage(path) {
39499
- return stat7(join15(path, "package.json")).then((info) => info.isFile(), () => false);
40052
+ return stat7(join16(path, "package.json")).then((info) => info.isFile(), () => false);
39500
40053
  }
39501
40054
  var INTEGRITY_READ_CONCURRENCY = 64;
39502
40055
  async function mapWithLimit(values, limit, visit) {
@@ -39530,8 +40083,8 @@ function isContainedTreePath(value) {
39530
40083
  async function cloneTree(source, target, file, topLevel = true) {
39531
40084
  await mkdir19(target, { recursive: true, mode: 493 });
39532
40085
  for (const entry of await readdir8(source, { withFileTypes: true })) {
39533
- const from = join15(source, entry.name);
39534
- const to = join15(target, entry.name);
40086
+ const from = join16(source, entry.name);
40087
+ const to = join16(target, entry.name);
39535
40088
  if (entry.isSymbolicLink()) {
39536
40089
  await symlink3(await readlink2(from), to);
39537
40090
  continue;
@@ -39561,13 +40114,13 @@ async function pruneWarmStore(storeRoot, keep) {
39561
40114
  const names = (await readdir8(storeRoot).catch(() => [])).filter((name) => !name.startsWith(STAGING_PREFIX));
39562
40115
  const entries = [];
39563
40116
  for (const name of names) {
39564
- const info = await lstat4(join15(storeRoot, name)).catch(() => void 0);
40117
+ const info = await lstat4(join16(storeRoot, name)).catch(() => void 0);
39565
40118
  if (info?.isDirectory())
39566
40119
  entries.push({ name, usedAt: info.mtimeMs });
39567
40120
  }
39568
40121
  const dropped = entries.sort((a2, b) => b.usedAt - a2.usedAt || a2.name.localeCompare(b.name)).slice(keep);
39569
40122
  for (const entry of dropped) {
39570
- await rm9(join15(storeRoot, entry.name), { recursive: true, force: true }).catch(() => void 0);
40123
+ await rm9(join16(storeRoot, entry.name), { recursive: true, force: true }).catch(() => void 0);
39571
40124
  }
39572
40125
  return dropped.map((entry) => entry.name);
39573
40126
  }
@@ -39576,7 +40129,7 @@ async function sweepStaleStaging(storeRoot, now2) {
39576
40129
  for (const name of entries) {
39577
40130
  if (!name.startsWith(STAGING_PREFIX))
39578
40131
  continue;
39579
- const path = join15(storeRoot, name);
40132
+ const path = join16(storeRoot, name);
39580
40133
  const info = await lstat4(path).catch(() => void 0);
39581
40134
  if (!info || now2 - info.mtimeMs < STAGING_SWEEP_MS)
39582
40135
  continue;
@@ -40092,8 +40645,7 @@ var MonolithCornerTurnLoop = class {
40092
40645
  this.yoloMode = configuration.yoloMode;
40093
40646
  const opener = this.options.openedBy ? roster.members.find((member) => member.identityId === this.options.openedBy) : void 0;
40094
40647
  const reviewerInput = {
40095
- reviewerHandle: configuration.reviewerHandle,
40096
- agentHandle: self?.handle,
40648
+ isReviewer: configuration.isReviewer === true,
40097
40649
  authorHandle: opener?.handle,
40098
40650
  openedByAgent: !this.options.openedBy || this.options.openedBy === this.agent.publicKey
40099
40651
  };
@@ -40135,7 +40687,7 @@ var MonolithCornerTurnLoop = class {
40135
40687
  root: this.options.config.agentHomeRoot,
40136
40688
  squireScope,
40137
40689
  sharedSkills: this.options.config.sharedSkills ?? [],
40138
- isReviewer: isConfiguredReviewer(self?.handle, configuration.reviewerHandle),
40690
+ isReviewer: Boolean(reviewerInstruction),
40139
40691
  grantedHostRoutes: mountedHostRoutes,
40140
40692
  extraHostRoutes: registryHostDeclarations,
40141
40693
  resourceAuthFile,
@@ -40193,7 +40745,7 @@ var MonolithCornerTurnLoop = class {
40193
40745
  args: this.options.config.agentArgs ?? []
40194
40746
  }, selection);
40195
40747
  const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
40196
- this.attachmentDir = tmpDir ? join16(tmpDir, "beeline-attachments") : void 0;
40748
+ this.attachmentDir = tmpDir ? join17(tmpDir, "beeline-attachments") : void 0;
40197
40749
  this.sessionScratchDir = tmpDir;
40198
40750
  const homeStateDirs = harnessHomeStateDirs(harnessLabel, agentEnv.HOME ?? operatorHome);
40199
40751
  await Promise.all(homeStateDirs.map((dir) => mkdir20(dir, { recursive: true })));
@@ -40594,10 +41146,10 @@ var MonolithCornerTurnLoop = class {
40594
41146
  size: file.size
40595
41147
  }));
40596
41148
  const allAttachments = [...attachments, ...briefAttachments];
40597
- const delivered = this.attachmentDir && allAttachments.length ? await deliverAttachments(allAttachments, join16(this.attachmentDir, requestId.replace(/[^\w-]/g, "_")), this.options.fetchImpl) : [];
41149
+ const delivered = this.attachmentDir && allAttachments.length ? await deliverAttachments(allAttachments, join17(this.attachmentDir, requestId.replace(/[^\w-]/g, "_")), this.options.fetchImpl) : [];
40598
41150
  const briefFileLines = await Promise.all((restored.brief?.attachments ?? []).map(async (file, index) => {
40599
41151
  const entry = delivered.find((item) => item.attachment === briefAttachments[index]);
40600
- const verified = entry?.path && createHash10("sha256").update(await readFile21(entry.path)).digest("hex") === file.sha256;
41152
+ const verified = entry?.path && createHash10("sha256").update(await readFile22(entry.path)).digest("hex") === file.sha256;
40601
41153
  return `- ${file.title} (${file.purpose}; ${file.required ? "required" : "optional"}; sha256 ${file.sha256}): ${verified ? entry.path : "UNAVAILABLE OR CONTENT MISMATCH"}`;
40602
41154
  }));
40603
41155
  const missingRequiredBriefFile = (restored.brief?.attachments ?? []).some((file, index) => file.required && briefFileLines[index]?.includes("UNAVAILABLE OR CONTENT MISMATCH"));
@@ -41037,68 +41589,72 @@ ${deviceGrantResumePrompt(grantedDevice, result.agentText)}`);
41037
41589
  });
41038
41590
  }
41039
41591
  async run() {
41040
- const { api, cornerId, signal } = this.options;
41041
- try {
41042
- await runServerCommandIntake({
41043
- api,
41044
- roomId: cornerId,
41045
- agentId: this.agent.publicKey,
41046
- context: this.commandContext,
41047
- signal,
41048
- pollMs: this.options.pollMs,
41049
- presence: {
41050
- ...this.options.config.daemonReleaseVersion ? { releaseVersion: this.options.config.daemonReleaseVersion } : {},
41051
- ...this.options.config.daemonSourceSha ? { sourceSha: this.options.config.daemonSourceSha } : {},
41052
- available: !this.options.config.modelUnavailable
41053
- },
41054
- onWake: (wake) => {
41055
- this.wakeIntake = wake;
41056
- },
41057
- onPoll: () => this.options.onPoll(),
41058
- onSubscriptionState: this.options.onSubscriptionState,
41059
- onError: (error) => {
41060
- this.options.onIntakeError?.(error);
41061
- console.error("[thin-core] corner command failed", error);
41062
- },
41063
- // See monolith-room-turn.ts (C112): mark busy the instant a claim
41064
- // attempt starts, before its own network round trip and before
41065
- // context.enter()'s own file I/O, so a reconcile busy-check landing
41066
- // anywhere in that window can never see this corner as idle when a
41067
- // turn was already claimed (or about to be).
41068
- onClaiming: () => {
41069
- this.busy = true;
41070
- },
41071
- onClaimFailed: () => {
41072
- this.busy = false;
41073
- },
41074
- onEnter: (command) => this.squireRelay.activate(command, this.commandContext.generationId),
41075
- onLeave: (command) => this.squireRelay.deactivate(command.turnRequestId),
41076
- stop: (requestId) => this.stopTurn(requestId),
41077
- restart: () => this.options.onRestartRequested?.(),
41078
- canStartTurn: this.options.canStartTurn,
41079
- closed: async () => {
41080
- if (this.closePushed) {
41081
- this.closePushed = false;
41082
- await this.harvest;
41083
- await this.options.onCloseRequested();
41084
- return true;
41085
- }
41086
- if (!this.reconcileCloseRequested)
41087
- return false;
41088
- this.reconcileCloseRequested = false;
41089
- const state = await api.execute("getCornerRestoreState", { cornerId });
41090
- if (this.options.lane === "no_code" && state.lane !== "no_code") {
41091
- this.options.onLaneChanged?.();
41092
- return true;
41093
- }
41094
- if (!state.closeRequested)
41095
- return false;
41592
+ const { api, cornerId, signal, supervisor } = this.options;
41593
+ const intake = (progress) => runServerCommandIntake({
41594
+ api,
41595
+ roomId: cornerId,
41596
+ agentId: this.agent.publicKey,
41597
+ context: this.commandContext,
41598
+ signal,
41599
+ pollMs: this.options.pollMs,
41600
+ presence: {
41601
+ ...this.options.config.daemonReleaseVersion ? { releaseVersion: this.options.config.daemonReleaseVersion } : {},
41602
+ ...this.options.config.daemonSourceSha ? { sourceSha: this.options.config.daemonSourceSha } : {},
41603
+ available: !this.options.config.modelUnavailable
41604
+ },
41605
+ onWake: (wake) => {
41606
+ this.wakeIntake = wake;
41607
+ },
41608
+ onPoll: () => {
41609
+ progress();
41610
+ this.options.onPoll();
41611
+ },
41612
+ onSubscriptionState: this.options.onSubscriptionState,
41613
+ onError: (error) => {
41614
+ this.options.onIntakeError?.(error);
41615
+ console.error("[thin-core] corner command failed", error);
41616
+ },
41617
+ // See monolith-room-turn.ts (C112): mark busy the instant a claim
41618
+ // attempt starts, before its own network round trip and before
41619
+ // context.enter()'s own file I/O, so a reconcile busy-check landing
41620
+ // anywhere in that window can never see this corner as idle when a
41621
+ // turn was already claimed (or about to be).
41622
+ onClaiming: () => {
41623
+ this.busy = true;
41624
+ },
41625
+ onClaimFailed: () => {
41626
+ this.busy = false;
41627
+ },
41628
+ onEnter: (command) => this.squireRelay.activate(command, this.commandContext.generationId),
41629
+ onLeave: (command) => this.squireRelay.deactivate(command.turnRequestId),
41630
+ stop: (requestId) => this.stopTurn(requestId),
41631
+ restart: () => this.options.onRestartRequested?.(),
41632
+ canStartTurn: this.options.canStartTurn,
41633
+ closed: async () => {
41634
+ if (this.closePushed) {
41635
+ this.closePushed = false;
41096
41636
  await this.harvest;
41097
41637
  await this.options.onCloseRequested();
41098
41638
  return true;
41099
- },
41100
- run: (command) => this.prompt(command.turnRequestId, command.source.body, command.source.attachments, command.source.authorId, command.reason === "corner_check" ? [command.source.body] : void 0, command.source.type === "message" ? command.sourceMessageId : void 0)
41101
- });
41639
+ }
41640
+ if (!this.reconcileCloseRequested)
41641
+ return false;
41642
+ this.reconcileCloseRequested = false;
41643
+ const state = await api.execute("getCornerRestoreState", { cornerId });
41644
+ if (this.options.lane === "no_code" && state.lane !== "no_code") {
41645
+ this.options.onLaneChanged?.();
41646
+ return true;
41647
+ }
41648
+ if (!state.closeRequested)
41649
+ return false;
41650
+ await this.harvest;
41651
+ await this.options.onCloseRequested();
41652
+ return true;
41653
+ },
41654
+ run: (command) => this.prompt(command.turnRequestId, command.source.body, command.source.attachments, command.source.authorId, command.reason === "corner_check" ? [command.source.body] : void 0, command.source.type === "message" ? command.sourceMessageId : void 0)
41655
+ });
41656
+ try {
41657
+ await (supervisor ? supervisor.supervise(`corner ${cornerId}`, signal, intake) : intake(() => void 0));
41102
41658
  } finally {
41103
41659
  this.squireRelay.close();
41104
41660
  this.options.grantRunner?.unregister(cornerId);
@@ -41170,6 +41726,89 @@ var SurfaceHealth = class {
41170
41726
  }
41171
41727
  };
41172
41728
 
41729
+ // apps/body/dist/room-supervisor.js
41730
+ var ROOM_RETRY_DELAYS_MS = [1e3, 5e3, 3e4];
41731
+ var RoomSupervisor = class {
41732
+ delaysMs;
41733
+ #parked = /* @__PURE__ */ new Set();
41734
+ #wakes = 0;
41735
+ constructor(delaysMs = ROOM_RETRY_DELAYS_MS) {
41736
+ this.delaysMs = delaysMs;
41737
+ }
41738
+ /** The live socket opened or discovery was woken: every waiting loop retries now. */
41739
+ wake() {
41740
+ this.#wakes += 1;
41741
+ for (const resume of [...this.#parked])
41742
+ resume();
41743
+ }
41744
+ /** Wakes so far; a wait that started from an older count does not wait. */
41745
+ get wakes() {
41746
+ return this.#wakes;
41747
+ }
41748
+ /** How many timed retries follow one failure before a loop parks. */
41749
+ get retries() {
41750
+ return this.delaysMs.length;
41751
+ }
41752
+ /**
41753
+ * Wait before retrying failure number `failures` (1-based). Within the
41754
+ * bound the fixed delay applies, and a wake may cut it short; past the bound
41755
+ * only a wake (or the abort) ends the wait. A wake that already arrived
41756
+ * after `since` (a `wakes` count) ends it at once, so none is lost.
41757
+ */
41758
+ retryDelay(failures, signal, since = this.#wakes) {
41759
+ const delay = this.delaysMs[failures - 1];
41760
+ return new Promise((resolve40) => {
41761
+ let timer;
41762
+ const done = () => {
41763
+ clearTimeout(timer);
41764
+ this.#parked.delete(done);
41765
+ signal?.removeEventListener("abort", done);
41766
+ resolve40();
41767
+ };
41768
+ this.#parked.add(done);
41769
+ signal?.addEventListener("abort", done, { once: true });
41770
+ if (signal?.aborted || this.#wakes !== since) {
41771
+ done();
41772
+ return;
41773
+ }
41774
+ if (delay !== void 0) {
41775
+ timer = setTimeout(done, delay);
41776
+ timer.unref?.();
41777
+ }
41778
+ });
41779
+ }
41780
+ /**
41781
+ * Run one Room or corner intake under supervision: a failed read never ends
41782
+ * it. `run` resolves only when the intake itself is finished (aborted or
41783
+ * closed); it calls `progress` once its reads succeed again, which restores
41784
+ * the full retry budget. The intake rejects only after its active turn has
41785
+ * settled, so re-entering cannot claim the same command twice.
41786
+ */
41787
+ async supervise(label, signal, run2) {
41788
+ let failures = 0;
41789
+ for (; ; ) {
41790
+ const since = this.#wakes;
41791
+ try {
41792
+ await run2(() => {
41793
+ failures = 0;
41794
+ });
41795
+ return;
41796
+ } catch (error) {
41797
+ if (signal?.aborted)
41798
+ return;
41799
+ failures += 1;
41800
+ const parked = failures > this.delaysMs.length;
41801
+ console.error(`[thin-core] ${label} intake failed; ${parked ? "waiting for the next live open or discovery wake" : `retry ${failures} of ${this.delaysMs.length} in ${this.delaysMs[failures - 1]}ms`}:`, error);
41802
+ await this.retryDelay(failures, signal, since);
41803
+ if (signal?.aborted)
41804
+ return;
41805
+ if (parked)
41806
+ failures = 0;
41807
+ }
41808
+ }
41809
+ }
41810
+ };
41811
+
41173
41812
  // apps/body/dist/room-runtime.js
41174
41813
  init_runtime();
41175
41814
 
@@ -41597,11 +42236,6 @@ var SessionScheduler = class {
41597
42236
  // apps/body/dist/room-runtime.js
41598
42237
  var REMOVAL_CONFIRMATION_READS = 2;
41599
42238
  var ROOM_JOIN_CONCURRENCY = 4;
41600
- var DEFAULT_ROOM_WATCHDOG_STALE_MS = 9e4;
41601
- var DEFAULT_RECONCILE_HEARTBEAT_MS = 10 * 6e4;
41602
- function defaultReconcileHeartbeatMs(random = Math.random) {
41603
- return DEFAULT_RECONCILE_HEARTBEAT_MS + Math.floor(random() * 5 * 6e4);
41604
- }
41605
42239
  var DEFAULT_DRAIN_DEADLINE_MS = 30 * 6e4;
41606
42240
  var CORNER_BRANCH_DELETE_ATTEMPTS = 3;
41607
42241
  var CornerCredentialLookupError = class extends Error {
@@ -41609,25 +42243,6 @@ var CornerCredentialLookupError = class extends Error {
41609
42243
  super("corner repository credential lookup failed", { cause });
41610
42244
  }
41611
42245
  };
41612
- var DiscoveryWakes = class {
41613
- arrived = 0;
41614
- served = 0;
41615
- /** Called for every agent-directed `rooms-changed` wake. */
41616
- wake() {
41617
- this.arrived += 1;
41618
- }
41619
- needsFastReconcile() {
41620
- return this.arrived !== this.served;
41621
- }
41622
- /** Snapshot at reconcile entry: the wake count its reads will cover. */
41623
- beginReconcile() {
41624
- return this.arrived;
41625
- }
41626
- /** Clear only wakes the completed start pass actually served. */
41627
- completeReconcile(covered) {
41628
- this.served = Math.max(this.served, covered);
41629
- }
41630
- };
41631
42246
  function shouldPostInitialCornerWorkingState(restore) {
41632
42247
  return !restore.lifecycle?.branch && !restore.lifecycle?.pr;
41633
42248
  }
@@ -41908,8 +42523,6 @@ var RoomRuntimeCoordinator = class {
41908
42523
  /** Parent ownership retained so a failed corner listing never authorizes removal. */
41909
42524
  monolithCornerParents = /* @__PURE__ */ new Map();
41910
42525
  now;
41911
- watchdogStaleMs;
41912
- reconcileHeartbeatMs;
41913
42526
  drainDeadlineMs;
41914
42527
  drainDeadlineAt;
41915
42528
  workspaceRemovalConfirmations = 0;
@@ -41921,14 +42534,9 @@ var RoomRuntimeCoordinator = class {
41921
42534
  repositoryStateFlights = /* @__PURE__ */ new Map();
41922
42535
  tokenCache = /* @__PURE__ */ new Map();
41923
42536
  tokenFlights = /* @__PURE__ */ new Map();
41924
- confirmationPending = false;
41925
42537
  restartRequested = false;
41926
- /** Unscoped agent-directed discovery wakes (#1369), counted instead of
41927
- * flagged: a wake that arrives while a reconcile is already running must
41928
- * survive its start-pass clearing, or a reconnect mid-reconcile waits a
41929
- * heartbeat for a wake the daemon already received. A scoped membership
41930
- * event applies incrementally and never touches this latch. */
41931
- discoveryWakes = new DiscoveryWakes();
42538
+ /** Keeps Room and corner intakes alive across failed reads, without polling. */
42539
+ supervisor = new RoomSupervisor();
41932
42540
  discoveryWakeListener;
41933
42541
  interactiveIdleListener;
41934
42542
  /** One command-grant runner per daemon; Rooms and corners register their checkouts on it. */
@@ -41975,8 +42583,6 @@ var RoomRuntimeCoordinator = class {
41975
42583
  void this.scheduler.suspendIdle().catch((error) => console.error("[body] config-change session restart failed", error));
41976
42584
  void Promise.resolve(this.options.onConfigChanged?.()).catch((error) => console.error("[body] config-change catalog refresh failed", error));
41977
42585
  });
41978
- this.watchdogStaleMs = options.watchdogStaleMs ?? DEFAULT_ROOM_WATCHDOG_STALE_MS;
41979
- this.reconcileHeartbeatMs = options.reconcileHeartbeatMs ?? defaultReconcileHeartbeatMs();
41980
42586
  this.drainDeadlineMs = options.drainDeadlineMs ?? DEFAULT_DRAIN_DEADLINE_MS;
41981
42587
  const fixedWorkspaceCeiling = process.env.BUZZY_BODY_MAX_SESSIONS;
41982
42588
  this.scheduler = new SessionScheduler({
@@ -41998,12 +42604,18 @@ var RoomRuntimeCoordinator = class {
41998
42604
  setInteractiveIdleListener(listener) {
41999
42605
  this.interactiveIdleListener = listener;
42000
42606
  }
42001
- reconnectAfterFailure() {
42002
- if (!this.options.daemonApi.supportsDiscoveryWake?.())
42003
- this.options.daemonApi.reconnectLive?.();
42607
+ /**
42608
+ * A failed reconcile makes the live socket suspect: it is pinged, and a
42609
+ * silent one is replaced, which works with any server version. A response
42610
+ * the server actually sent (or a full local read budget) says nothing about
42611
+ * the socket.
42612
+ */
42613
+ suspectLink(error) {
42614
+ if (!(error instanceof DaemonApiError))
42615
+ this.options.daemonApi.link?.suspect();
42004
42616
  }
42005
42617
  wakeDiscovery() {
42006
- this.discoveryWakes.wake();
42618
+ this.supervisor.wake();
42007
42619
  this.discoveryWakeListener?.();
42008
42620
  }
42009
42621
  activeRoomCount() {
@@ -42027,12 +42639,6 @@ var RoomRuntimeCoordinator = class {
42027
42639
  schedulerSnapshot() {
42028
42640
  return this.scheduler.snapshot();
42029
42641
  }
42030
- needsFastReconcile() {
42031
- return this.confirmationPending || this.discoveryWakes.needsFastReconcile();
42032
- }
42033
- reconcileHeartbeatIntervalMs() {
42034
- return this.reconcileHeartbeatMs;
42035
- }
42036
42642
  isWorkspaceIdle() {
42037
42643
  return this.activeTurnCount() === 0;
42038
42644
  }
@@ -42058,6 +42664,16 @@ var RoomRuntimeCoordinator = class {
42058
42664
  room.controller.abort();
42059
42665
  return true;
42060
42666
  }
42667
+ /**
42668
+ * Undo `quiesceForUpdateIfIdle` after a managed restart failed: clear the
42669
+ * latch and let reconciliation start the Room loops the quiesce stopped.
42670
+ */
42671
+ resumeServing() {
42672
+ if (this.stopped)
42673
+ return;
42674
+ this.restartRequested = false;
42675
+ this.wakeDiscovery();
42676
+ }
42061
42677
  setDrainDeadlineAt(deadlineAt) {
42062
42678
  if (Number.isFinite(deadlineAt)) {
42063
42679
  this.drainDeadlineAt = Math.min(this.drainDeadlineAt ?? Number.POSITIVE_INFINITY, deadlineAt);
@@ -42080,9 +42696,7 @@ var RoomRuntimeCoordinator = class {
42080
42696
  async reconcile() {
42081
42697
  if (this.stopped)
42082
42698
  return "member";
42083
- this.confirmationPending = false;
42084
42699
  const existingRooms = new Set(this.running.keys());
42085
- const coveredWakes = this.discoveryWakes.beginReconcile();
42086
42700
  const bootstrap = await this.options.daemonApi.execute("getDaemonBootstrap", {
42087
42701
  agentId: this.agent.publicKey
42088
42702
  });
@@ -42091,7 +42705,6 @@ var RoomRuntimeCoordinator = class {
42091
42705
  if (!bootstrap.workspaceIds.includes(this.runtime.communityId)) {
42092
42706
  this.workspaceRemovalConfirmations += 1;
42093
42707
  if (this.workspaceRemovalConfirmations < REMOVAL_CONFIRMATION_READS) {
42094
- this.confirmationPending = true;
42095
42708
  this.wakeDiscovery();
42096
42709
  return "unknown";
42097
42710
  }
@@ -42179,10 +42792,8 @@ var RoomRuntimeCoordinator = class {
42179
42792
  continue;
42180
42793
  const confirmations = (this.roomRemovalConfirmations.get(channelId) ?? 0) + 1;
42181
42794
  this.roomRemovalConfirmations.set(channelId, confirmations);
42182
- if (confirmations < REMOVAL_CONFIRMATION_READS) {
42183
- this.confirmationPending = true;
42795
+ if (confirmations < REMOVAL_CONFIRMATION_READS)
42184
42796
  continue;
42185
- }
42186
42797
  await this.stopRunning(channelId, running);
42187
42798
  this.roomRemovalConfirmations.delete(channelId);
42188
42799
  }
@@ -42207,7 +42818,6 @@ var RoomRuntimeCoordinator = class {
42207
42818
  for (const [roomId, running] of this.running)
42208
42819
  if (existingRooms.has(roomId))
42209
42820
  running.body.requestReconciliation();
42210
- this.discoveryWakes.completeReconcile(coveredWakes);
42211
42821
  if (this.roomRemovalConfirmations.size)
42212
42822
  this.wakeDiscovery();
42213
42823
  return "member";
@@ -42310,7 +42920,6 @@ var RoomRuntimeCoordinator = class {
42310
42920
  await this.reapCornerScratch(running.scratch);
42311
42921
  } catch (error) {
42312
42922
  console.error(`[thin-core] corner ${channelId} cleanup failed; will retry:`, error);
42313
- this.confirmationPending = true;
42314
42923
  }
42315
42924
  }
42316
42925
  roomRecord(roomId) {
@@ -42373,7 +42982,6 @@ var RoomRuntimeCoordinator = class {
42373
42982
  const controller = new AbortController();
42374
42983
  const cwd = this.roomRoot(roomId);
42375
42984
  const grantRunnerEndpoint = await this.grantRunnerEndpoint();
42376
- const startedAt = this.now();
42377
42985
  const loop = new MonolithRoomTurnLoop({
42378
42986
  roomId,
42379
42987
  workspaceId: this.runtime.communityId,
@@ -42386,18 +42994,12 @@ var RoomRuntimeCoordinator = class {
42386
42994
  api: this.options.daemonApi,
42387
42995
  scheduler: this.scheduler,
42388
42996
  signal: controller.signal,
42389
- health: {
42390
- poll: () => this.notePoll(roomId),
42391
- failure: (retryInMs) => this.noteFailure(roomId, retryInMs),
42392
- presence: () => void 0
42393
- },
42997
+ health: { poll: () => this.notePoll(roomId) },
42394
42998
  onSubscriptionState: (connected) => this.surfaceHealth.subscribed(roomId, connected),
42395
42999
  onIntakeError: () => this.surfaceHealth.degraded(roomId, "Room command intake failed"),
42396
- onCornerOpened: () => {
42397
- this.confirmationPending = true;
42398
- },
42399
43000
  onRestartRequested: () => this.requestLifecycleRestart(),
42400
- canStartTurn: () => !this.restartRequested
43001
+ canStartTurn: () => !this.restartRequested,
43002
+ supervisor: this.supervisor
42401
43003
  });
42402
43004
  const promise = loop.run().catch((error) => {
42403
43005
  if (!controller.signal.aborted) {
@@ -42413,14 +43015,7 @@ var RoomRuntimeCoordinator = class {
42413
43015
  await promise;
42414
43016
  return;
42415
43017
  }
42416
- this.running.set(roomId, {
42417
- body: loop,
42418
- controller,
42419
- promise,
42420
- lastPollAt: startedAt,
42421
- backoffUntil: 0,
42422
- recovering: false
42423
- });
43018
+ this.running.set(roomId, { body: loop, controller, promise });
42424
43019
  console.log(`[thin-core] serving monolith Room ${roomId}`);
42425
43020
  }
42426
43021
  /**
@@ -42546,7 +43141,6 @@ var RoomRuntimeCoordinator = class {
42546
43141
  }
42547
43142
  const controller = new AbortController();
42548
43143
  const grantRunnerEndpoint = await this.grantRunnerEndpoint();
42549
- const startedAt = this.now();
42550
43144
  const loop = new MonolithCornerTurnLoop({
42551
43145
  cornerId: corner.cornerId,
42552
43146
  grantRunner: this.grantRunner,
@@ -42574,7 +43168,6 @@ var RoomRuntimeCoordinator = class {
42574
43168
  scheduler: this.scheduler,
42575
43169
  signal: controller.signal,
42576
43170
  onPoll: () => this.notePoll(corner.cornerId),
42577
- onFailure: (retryInMs) => this.noteFailure(corner.cornerId, retryInMs),
42578
43171
  onSubscriptionState: (connected) => this.surfaceHealth.subscribed(corner.cornerId, connected),
42579
43172
  onIntakeError: () => this.surfaceHealth.degraded(corner.cornerId, "corner command intake failed"),
42580
43173
  onCloseRequested: () => worktree ? this.reapCornerWorktree({
@@ -42586,7 +43179,8 @@ var RoomRuntimeCoordinator = class {
42586
43179
  }) : this.reapCornerScratch({ path: workspacePath, cornerId: corner.cornerId }),
42587
43180
  onLaneChanged: () => void this.applyCornerRestart(corner.cornerId).catch((error) => console.error("[thin-core] corner lane-change restart failed", error)),
42588
43181
  onRestartRequested: () => this.requestLifecycleRestart(),
42589
- canStartTurn: () => !this.restartRequested
43182
+ canStartTurn: () => !this.restartRequested,
43183
+ supervisor: this.supervisor
42590
43184
  });
42591
43185
  const promise = loop.run().catch((error) => {
42592
43186
  if (!controller.signal.aborted) {
@@ -42607,9 +43201,6 @@ var RoomRuntimeCoordinator = class {
42607
43201
  body: loop,
42608
43202
  controller,
42609
43203
  promise,
42610
- lastPollAt: startedAt,
42611
- backoffUntil: 0,
42612
- recovering: false,
42613
43204
  ...worktree ? {
42614
43205
  worktree: {
42615
43206
  ...worktree,
@@ -42937,8 +43528,6 @@ var RoomRuntimeCoordinator = class {
42937
43528
  const room = this.running.get(roomId);
42938
43529
  if (!room)
42939
43530
  return;
42940
- room.lastPollAt = this.now();
42941
- room.backoffUntil = 0;
42942
43531
  if (this.deferredRepositoryRestarts.has(roomId) && !room.body.isBusy()) {
42943
43532
  this.deferredRepositoryRestarts.delete(roomId);
42944
43533
  void this.stopRunning(roomId, room).then(() => this.stopped ? void 0 : this.startRoom(roomId)).catch((error) => {
@@ -42949,29 +43538,6 @@ var RoomRuntimeCoordinator = class {
42949
43538
  if (this.isWorkspaceIdle())
42950
43539
  this.interactiveIdleListener?.();
42951
43540
  }
42952
- noteFailure(roomId, retryInMs) {
42953
- const room = this.running.get(roomId);
42954
- if (room)
42955
- room.backoffUntil = Math.max(room.backoffUntil, this.now() + retryInMs);
42956
- }
42957
- async watchdogTick() {
42958
- if (this.stopped)
42959
- return;
42960
- for (const [roomId, room] of [...this.running]) {
42961
- if (room.recovering || room.body.isBusy())
42962
- continue;
42963
- if (this.now() <= Math.max(room.lastPollAt + this.watchdogStaleMs, room.backoffUntil))
42964
- continue;
42965
- room.recovering = true;
42966
- room.controller.abort();
42967
- await room.promise.catch(() => void 0);
42968
- if (room.worktree) {
42969
- this.confirmationPending = true;
42970
- } else if (!this.stopped && !this.running.has(roomId)) {
42971
- this.startRoom(roomId);
42972
- }
42973
- }
42974
- }
42975
43541
  beginShutdown() {
42976
43542
  if (this.stopped)
42977
43543
  return;
@@ -43160,6 +43726,10 @@ var ThinDaemonCore = class {
43160
43726
  quiesceForUpdateIfIdle() {
43161
43727
  return this.roomRuntime.quiesceForUpdateIfIdle();
43162
43728
  }
43729
+ /** A managed restart failed after intake closed: take turns again. */
43730
+ resumeServing() {
43731
+ this.roomRuntime.resumeServing();
43732
+ }
43163
43733
  async prepareForForcedUpdateRestart() {
43164
43734
  await this.roomRuntime.prepareForForcedUpdateRestart();
43165
43735
  }
@@ -43192,25 +43762,35 @@ var ThinDaemonCore = class {
43192
43762
  try {
43193
43763
  if (!opts.signal?.aborted)
43194
43764
  await opts.onEstablished?.();
43765
+ const supervisor = this.roomRuntime.supervisor;
43766
+ let failures = 0;
43195
43767
  while (!opts.signal?.aborted) {
43196
43768
  this.pendingDiscovery = false;
43769
+ const wakesBefore = supervisor.wakes;
43197
43770
  try {
43198
43771
  const membership = await this.roomRuntime.reconcile();
43199
43772
  if (membership === "not-member")
43200
43773
  return "agent-removed";
43201
43774
  this.discoveryStatus = membership === "unknown" ? "monolith membership degraded" : "";
43775
+ failures = 0;
43202
43776
  } catch (error) {
43203
43777
  if (isAgentRemovedError(error))
43204
43778
  return "agent-removed";
43205
- console.error("[thin-core] discovery failed; waiting for live recovery:", error);
43779
+ failures += 1;
43780
+ console.error(failures > supervisor.retries ? "[thin-core] discovery failed; waiting for the next live open or discovery wake:" : `[thin-core] discovery failed; retry ${failures} of ${supervisor.retries}:`, error);
43206
43781
  this.discoveryStatus = `monolith discovery degraded: ${error instanceof Error ? error.message : String(error)}`;
43207
- this.roomRuntime.reconnectAfterFailure();
43782
+ this.roomRuntime.suspectLink(error);
43208
43783
  }
43209
43784
  if (opts.signal?.aborted)
43210
43785
  break;
43211
43786
  await opts.onProgress?.(this.healthStatus());
43212
- if (!this.pendingDiscovery)
43787
+ if (failures > 0) {
43788
+ await supervisor.retryDelay(failures, opts.signal, wakesBefore);
43789
+ if (failures > supervisor.retries)
43790
+ failures = 0;
43791
+ } else if (!this.pendingDiscovery) {
43213
43792
  await this.waitForDiscovery(opts.signal);
43793
+ }
43214
43794
  }
43215
43795
  return "aborted";
43216
43796
  } finally {
@@ -43220,32 +43800,173 @@ var ThinDaemonCore = class {
43220
43800
  }
43221
43801
  };
43222
43802
 
43223
- // apps/body/dist/daemon-shutdown.js
43224
- var DAEMON_HARD_STOP_MS = 75e3;
43225
- function installDaemonStopSignals(controller, options = {}) {
43226
- const emitter = options.emitter ?? process;
43227
- let timer;
43228
- const stop = () => {
43229
- if (controller.signal.aborted)
43803
+ // apps/body/dist/helper-lifecycle.js
43804
+ init_systemd();
43805
+ var HELPER_HARD_STOP_MS = 8e4;
43806
+ var HELPER_EXIT_CODES = {
43807
+ stopped: 0,
43808
+ "restart-requested": 0,
43809
+ update: 0,
43810
+ failed: 75,
43811
+ "rolled-back": 75,
43812
+ "force-update-failed": 75,
43813
+ distress: DAEMON_DISTRESS_EXIT_STATUS,
43814
+ "agent-removed": DELIBERATE_REMOVAL_EXIT_STATUS,
43815
+ "unknown-agent": UNKNOWN_AGENT_EXIT_STATUS
43816
+ };
43817
+ var HelperLifecycle = class {
43818
+ options;
43819
+ #state = { kind: "serving" };
43820
+ #hardStop;
43821
+ #exitProcess;
43822
+ constructor(options) {
43823
+ this.options = options;
43824
+ this.#exitProcess = options.exitProcess ?? ((code) => process.exit(code));
43825
+ }
43826
+ get state() {
43827
+ return this.#state;
43828
+ }
43829
+ get serving() {
43830
+ return this.#state.kind === "serving";
43831
+ }
43832
+ /** The reason the final stop was asked for, once one was. */
43833
+ get stopReason() {
43834
+ return this.#state.kind === "stopping" ? this.#state.reason : void 0;
43835
+ }
43836
+ /**
43837
+ * Close intake. An update may only start from serving; `/restart` and a
43838
+ * forced update also take over a waiting update. Returns whether this call
43839
+ * moved the helper to quiescing.
43840
+ */
43841
+ quiesce(reason) {
43842
+ const state = this.#state;
43843
+ if (state.kind === "serving" || state.kind === "quiescing" && state.reason === "update" && reason !== "update") {
43844
+ this.#state = { kind: "quiescing", reason };
43845
+ return true;
43846
+ }
43847
+ return false;
43848
+ }
43849
+ /** A managed restart failed after intake closed: serve again. */
43850
+ resume() {
43851
+ if (this.#state.kind !== "quiescing")
43852
+ return false;
43853
+ this.#state = { kind: "serving" };
43854
+ return true;
43855
+ }
43856
+ /**
43857
+ * The managed update's idle gate: quiesce only while serving and only if
43858
+ * `closeIntakeIfIdle` proves no turn is running (and closes intake in the
43859
+ * same step). A busy helper stays serving, so the update waits.
43860
+ */
43861
+ quiesceUpdateIfIdle(closeIntakeIfIdle) {
43862
+ return this.serving && closeIntakeIfIdle() && this.quiesce("update");
43863
+ }
43864
+ /** A managed restart failed after its quiesce: serve again and reopen intake. */
43865
+ resumeAfterFailedUpdate(reopenIntake) {
43866
+ const state = this.#state;
43867
+ if (state.kind !== "quiescing" || state.reason !== "update" || !this.resume())
43868
+ return false;
43869
+ reopenIntake();
43870
+ return true;
43871
+ }
43872
+ /** The final stop: abort the core, and exit no later than the hard limit. */
43873
+ stop(reason) {
43874
+ if (this.#state.kind === "stopping" || this.#state.kind === "exited")
43230
43875
  return;
43231
- timer = setTimeout(() => {
43232
- console.error("[thin-core] shutdown deadline reached; exiting old helper");
43233
- (options.hardExit ?? (() => process.exit(0)))();
43234
- }, options.timeoutMs ?? DAEMON_HARD_STOP_MS);
43235
- controller.abort();
43236
- };
43237
- emitter.once("SIGINT", stop);
43238
- emitter.once("SIGTERM", stop);
43239
- return () => {
43240
- if (timer)
43241
- clearTimeout(timer);
43242
- emitter.removeListener("SIGINT", stop);
43243
- emitter.removeListener("SIGTERM", stop);
43244
- };
43876
+ this.#state = { kind: "stopping", reason };
43877
+ this.#hardStop = setTimeout(() => {
43878
+ console.error("[thin-core] shutdown deadline reached; exiting helper");
43879
+ this.exit(reason);
43880
+ }, this.options.hardStopMs ?? HELPER_HARD_STOP_MS);
43881
+ this.#hardStop.unref?.();
43882
+ this.options.controller.abort();
43883
+ }
43884
+ /** The one exit funnel: each reason maps to exactly one code. */
43885
+ exit(reason) {
43886
+ if (this.#state.kind === "exited")
43887
+ return;
43888
+ clearTimeout(this.#hardStop);
43889
+ const code = HELPER_EXIT_CODES[reason];
43890
+ this.#state = { kind: "exited", code };
43891
+ this.#exitProcess(code);
43892
+ }
43893
+ /** SIGTERM and SIGINT are a final stop. */
43894
+ installSignals(emitter = process) {
43895
+ const stop = () => this.stop("stopped");
43896
+ emitter.once("SIGINT", stop);
43897
+ emitter.once("SIGTERM", stop);
43898
+ return () => {
43899
+ emitter.removeListener("SIGINT", stop);
43900
+ emitter.removeListener("SIGTERM", stop);
43901
+ };
43902
+ }
43903
+ };
43904
+ function installUnhandledRejectionGuard(emitter = process) {
43905
+ const log2 = (reason) => console.error("[thin-core] unhandled rejection (kept running):", reason);
43906
+ emitter.on("unhandledRejection", log2);
43907
+ return () => emitter.removeListener("unhandledRejection", log2);
43908
+ }
43909
+ function isTransientServerFailure(error) {
43910
+ return isNetworkFailure(error) || error instanceof DaemonApiError && error.retryable;
43911
+ }
43912
+ function successorRollbackAllowed(error, deadlineAt, now2 = Date.now()) {
43913
+ return !isTransientServerFailure(error) || deadlineAt !== void 0 && now2 >= deadlineAt;
43914
+ }
43915
+ async function retryBeforeReady(work, options) {
43916
+ const now2 = options.now ?? Date.now;
43917
+ const slice = options.waitSliceMs ?? 6e4;
43918
+ let failures = 0;
43919
+ for (; ; ) {
43920
+ try {
43921
+ return await work();
43922
+ } catch (error) {
43923
+ if (!isTransientServerFailure(error) || options.signal?.aborted)
43924
+ throw error;
43925
+ if (options.deadlineAt !== void 0 && now2() >= options.deadlineAt)
43926
+ throw error;
43927
+ failures += 1;
43928
+ const timedRetry = ROOM_RETRY_DELAYS_MS[failures - 1];
43929
+ console.warn(`[thin-core] server unreachable before READY; ${timedRetry === void 0 ? "waiting for the live socket to open" : `retrying in ${timedRetry}ms or on open`}:`, error);
43930
+ let opened = false;
43931
+ let wake = () => void 0;
43932
+ const off = options.onLinkOpen(() => {
43933
+ opened = true;
43934
+ wake();
43935
+ });
43936
+ try {
43937
+ const waitStarted = now2();
43938
+ while (!opened && !options.signal?.aborted) {
43939
+ const remaining = [
43940
+ slice,
43941
+ options.deadlineAt === void 0 ? Infinity : options.deadlineAt - now2(),
43942
+ timedRetry === void 0 ? Infinity : timedRetry - (now2() - waitStarted)
43943
+ ];
43944
+ const wait2 = Math.min(...remaining);
43945
+ if (wait2 <= 0)
43946
+ break;
43947
+ await options.extendStartTimeout(wait2 + 15e3);
43948
+ await new Promise((resolve40) => {
43949
+ const timer = setTimeout(done, wait2);
43950
+ function done() {
43951
+ clearTimeout(timer);
43952
+ options.signal?.removeEventListener("abort", done);
43953
+ resolve40();
43954
+ }
43955
+ wake = done;
43956
+ options.signal?.addEventListener("abort", done, { once: true });
43957
+ if (opened || options.signal?.aborted)
43958
+ done();
43959
+ });
43960
+ }
43961
+ } finally {
43962
+ off();
43963
+ }
43964
+ }
43965
+ }
43245
43966
  }
43246
43967
 
43247
43968
  // apps/body/dist/force-update-journal.js
43248
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync8, renameSync, writeFileSync as writeFileSync4 } from "node:fs";
43969
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync8, renameSync, writeFileSync as writeFileSync5 } from "node:fs";
43249
43970
  import { resolve as resolve30 } from "node:path";
43250
43971
  var NAME = "force-update-interruptions.json";
43251
43972
  function pathFor(runtimeDir) {
@@ -43274,7 +43995,7 @@ function write(runtimeDir, rows) {
43274
43995
  mkdirSync4(runtimeDir, { recursive: true, mode: 448 });
43275
43996
  const target = pathFor(runtimeDir);
43276
43997
  const temporary = `${target}.${process.pid}.tmp`;
43277
- writeFileSync4(temporary, `${JSON.stringify(rows)}
43998
+ writeFileSync5(temporary, `${JSON.stringify(rows)}
43278
43999
  `, { mode: 384 });
43279
44000
  renameSync(temporary, target);
43280
44001
  }
@@ -43667,7 +44388,7 @@ async function runUpdateFunctionalProbe(input) {
43667
44388
  }
43668
44389
 
43669
44390
  // apps/body/dist/update-rollback-alert.js
43670
- import { mkdir as mkdir23, readFile as readFile22, rename as rename8, unlink as unlink6, writeFile as writeFile17 } from "node:fs/promises";
44391
+ import { mkdir as mkdir23, readFile as readFile23, rename as rename9, unlink as unlink6, writeFile as writeFile17 } from "node:fs/promises";
43671
44392
  import { dirname as dirname20, resolve as resolve32 } from "node:path";
43672
44393
  var REPORT_INTERVAL_MS = 60 * 60 * 1e3;
43673
44394
  var lastLogged = /* @__PURE__ */ new Map();
@@ -43680,7 +44401,7 @@ async function writeAlert(runtimeDir, alert) {
43680
44401
  await mkdir23(dirname20(path), { recursive: true });
43681
44402
  await writeFile17(staged, `${JSON.stringify(alert, null, 2)}
43682
44403
  `, { mode: 384 });
43683
- await rename8(staged, path);
44404
+ await rename9(staged, path);
43684
44405
  }
43685
44406
  async function queueUpdateRollbackAlert(runtimeDir, releaseId, now2 = Date.now()) {
43686
44407
  const existing = await readUpdateRollbackAlert(runtimeDir);
@@ -43690,7 +44411,7 @@ async function queueUpdateRollbackAlert(runtimeDir, releaseId, now2 = Date.now()
43690
44411
  }
43691
44412
  async function readUpdateRollbackAlert(runtimeDir) {
43692
44413
  try {
43693
- const value = JSON.parse(await readFile22(updateRollbackAlertPath(runtimeDir), "utf8"));
44414
+ const value = JSON.parse(await readFile23(updateRollbackAlertPath(runtimeDir), "utf8"));
43694
44415
  if (value.version !== 1 || typeof value.releaseId !== "string")
43695
44416
  return void 0;
43696
44417
  return value;
@@ -44024,6 +44745,11 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
44024
44745
  env: this.#env,
44025
44746
  log: (line) => console.log(line)
44026
44747
  });
44748
+ await convergeAgentServiceUnit({
44749
+ libDir: this.#layout.libDir,
44750
+ env: this.#env,
44751
+ log: (line) => console.log(line)
44752
+ });
44027
44753
  } else if (process.platform === "darwin" && this.#env.BEELINE_LAUNCHD_USER !== "0") {
44028
44754
  await convergeLaunchdTrustySquireBrokerService({
44029
44755
  libDir: this.#layout.libDir,
@@ -44375,7 +45101,7 @@ init_runtime();
44375
45101
  init_launchd();
44376
45102
  init_systemd();
44377
45103
  init_runtime();
44378
- import { mkdir as mkdir25, rename as rename9 } from "node:fs/promises";
45104
+ import { mkdir as mkdir25, rename as rename10 } from "node:fs/promises";
44379
45105
  import { dirname as dirname22, resolve as resolve34 } from "node:path";
44380
45106
  async function retireRemovedAgent(runtime, options = {}) {
44381
45107
  const deletedRoot = resolve34(runtime.supervisorRoot, "beeline", "deleted-runtimes");
@@ -44400,12 +45126,12 @@ async function relocateAgentRuntime(runtime, target, options = {}) {
44400
45126
  });
44401
45127
  }
44402
45128
  await mkdir25(dirname22(destination), { recursive: true, mode: 448 });
44403
- await rename9(source, destination);
45129
+ await rename10(source, destination);
44404
45130
  return destination;
44405
45131
  }
44406
45132
 
44407
45133
  // apps/body/dist/start-command.js
44408
- import { basename as basename7, dirname as dirname23 } from "node:path";
45134
+ import { basename as basename8, dirname as dirname23 } from "node:path";
44409
45135
  var import_picocolors2 = __toESM(require_picocolors(), 1);
44410
45136
  init_agent_command();
44411
45137
  init_runtime();
@@ -44620,7 +45346,7 @@ async function startStoredRuntime(configPath, opts = {}, dependencyOverrides = {
44620
45346
  return { status: "started", pid };
44621
45347
  }
44622
45348
  function agentStartId(configPath) {
44623
- return basename7(dirname23(configPath));
45349
+ return basename8(dirname23(configPath));
44624
45350
  }
44625
45351
  async function startRuntime(configPath, spinnerHandle) {
44626
45352
  const report = (text2) => spinnerHandle ? spinnerHandle.message(text2) : console.log(text2);
@@ -44735,7 +45461,7 @@ async function runStartCommand(args, interactiveUi, dependencyOverrides = {}) {
44735
45461
  // apps/body/dist/connect-command.js
44736
45462
  import { spawn as spawn12 } from "node:child_process";
44737
45463
  import { createHash as createHash13, randomUUID as randomUUID11 } from "node:crypto";
44738
- import { chmod as chmod10, mkdir as mkdir26, readFile as readFile23, unlink as unlink7, writeFile as writeFile19 } from "node:fs/promises";
45464
+ import { chmod as chmod10, mkdir as mkdir26, readFile as readFile24, unlink as unlink7, writeFile as writeFile19 } from "node:fs/promises";
44739
45465
  import { homedir as homedir17, hostname as hostname3 } from "node:os";
44740
45466
  import { dirname as dirname24, resolve as resolve35 } from "node:path";
44741
45467
  import { stdin as stdin3, stdout as stdout4 } from "node:process";
@@ -45413,7 +46139,7 @@ async function readMachineId(env = process.env) {
45413
46139
  let machineId;
45414
46140
  let machineName = hostname3();
45415
46141
  try {
45416
- const existing = await readFile23(machineIdPath, "utf8");
46142
+ const existing = await readFile24(machineIdPath, "utf8");
45417
46143
  machineId = existing.trim();
45418
46144
  if (!/^[0-9a-f-]{32,}$/.test(machineId))
45419
46145
  throw new Error("invalid persisted machine id");
@@ -45669,12 +46395,12 @@ async function runConnectFinishCommand(path) {
45669
46395
  throw new Error("connect-finish may run only from the canonical installed Beeline launcher");
45670
46396
  }
45671
46397
  try {
45672
- const grant = JSON.parse(await readFile23(resolve35(path), "utf8"));
46398
+ const grant = JSON.parse(await readFile24(resolve35(path), "utf8"));
45673
46399
  if (!isDevicePairingGrant(grant))
45674
46400
  throw new Error("device connection grant is invalid");
45675
46401
  const connected = await completeDevicePairing(grant);
45676
46402
  await unlink7(resolve35(path));
45677
- const providerEnv = grant.llmEnvFile ? await readFile23(grant.llmEnvFile, "utf8").catch(() => "") : "";
46403
+ const providerEnv = grant.llmEnvFile ? await readFile24(grant.llmEnvFile, "utf8").catch(() => "") : "";
45678
46404
  const apiKey = (/^OPENROUTER_API_KEY=(\S+)/m.exec(providerEnv)?.[1] ?? "").replace(/^["']|["']$/g, "");
45679
46405
  const model = openRouterModelId(grant.model, { OPENROUTER_API_KEY: apiKey });
45680
46406
  if (model) {
@@ -45698,7 +46424,7 @@ async function runConnectFinishCommand(path) {
45698
46424
  init_self_update();
45699
46425
 
45700
46426
  // apps/body/dist/daemon-failure.js
45701
- import { mkdir as mkdir27, readFile as readFile24, rename as rename10, rm as rm13, writeFile as writeFile20 } from "node:fs/promises";
46427
+ import { mkdir as mkdir27, readFile as readFile25, rename as rename11, rm as rm13, writeFile as writeFile20 } from "node:fs/promises";
45702
46428
  import { dirname as dirname25, resolve as resolve36 } from "node:path";
45703
46429
  var DAEMON_FAILURE_LIMIT = 3;
45704
46430
  var DAEMON_FAILURE_WINDOW_MS = 5 * 6e4;
@@ -45707,7 +46433,7 @@ function daemonFailurePath(runtimeDir) {
45707
46433
  }
45708
46434
  async function readFailureRecord(runtimeDir) {
45709
46435
  try {
45710
- const value = JSON.parse(await readFile24(daemonFailurePath(runtimeDir), "utf8"));
46436
+ const value = JSON.parse(await readFile25(daemonFailurePath(runtimeDir), "utf8"));
45711
46437
  if (value.version !== 1 || !Array.isArray(value.failures) || value.failures.some((failure) => typeof failure !== "number") || typeof value.lastError !== "string") {
45712
46438
  return void 0;
45713
46439
  }
@@ -45722,7 +46448,14 @@ async function writeFailureRecord(runtimeDir, record6) {
45722
46448
  await mkdir27(dirname25(path), { recursive: true, mode: 448 });
45723
46449
  await writeFile20(staged, `${JSON.stringify(record6, null, 2)}
45724
46450
  `, { mode: 384 });
45725
- await rename10(staged, path);
46451
+ await rename11(staged, path);
46452
+ }
46453
+ function countsTowardDistress(error) {
46454
+ if (isNetworkFailure(error))
46455
+ return false;
46456
+ if (error instanceof DaemonApiError && (error.retryable || error.status === 401))
46457
+ return false;
46458
+ return true;
45726
46459
  }
45727
46460
  async function recordDaemonStartFailure(runtimeDir, error, now2 = Date.now()) {
45728
46461
  const prior = await readFailureRecord(runtimeDir);
@@ -45737,6 +46470,11 @@ async function recordDaemonStartFailure(runtimeDir, error, now2 = Date.now()) {
45737
46470
  await writeFailureRecord(runtimeDir, record6);
45738
46471
  return { distressed, count: failures.length, path: daemonFailurePath(runtimeDir) };
45739
46472
  }
46473
+ async function settleDaemonStartFailure(runtimeDir, error, now2 = Date.now()) {
46474
+ if (!countsTowardDistress(error))
46475
+ return { distressed: false };
46476
+ return recordDaemonStartFailure(runtimeDir, error, now2);
46477
+ }
45740
46478
  async function clearDaemonStartFailures(runtimeDir) {
45741
46479
  await rm13(daemonFailurePath(runtimeDir), { force: true });
45742
46480
  }
@@ -45746,7 +46484,7 @@ init_systemd();
45746
46484
 
45747
46485
  // apps/body/dist/current-release-probe.js
45748
46486
  import { spawn as spawn13 } from "node:child_process";
45749
- import { dirname as dirname26, join as join17 } from "node:path";
46487
+ import { dirname as dirname26, join as join18 } from "node:path";
45750
46488
  init_runtime();
45751
46489
  init_self_update();
45752
46490
  var CURRENT_RELEASE_PROBE_TIMEOUT_MS = 12e4;
@@ -45790,7 +46528,7 @@ function outcomeFromReport(report) {
45790
46528
  }
45791
46529
  }
45792
46530
  async function probeReleaseInSubprocess(input) {
45793
- const bundleDir = join17(input.layout.releasesRoot, input.releaseId);
46531
+ const bundleDir = join18(input.layout.releasesRoot, input.releaseId);
45794
46532
  const entrypoint = await resolveBundleEntrypoint(bundleDir);
45795
46533
  if (!entrypoint) {
45796
46534
  return { kind: "unavailable", reason: `release ${input.releaseId} has no runnable CLI entrypoint` };
@@ -45853,7 +46591,7 @@ async function runUpdateProbeCommand(args, options = {}) {
45853
46591
  const runtime = await readRuntimeRecord(configPath);
45854
46592
  const agent = runtimeAgentCommand(runtime);
45855
46593
  const config = loadBodyConfig({
45856
- workspaceRoot: join17(dirname26(configPath), "workspace"),
46594
+ workspaceRoot: join18(dirname26(configPath), "workspace"),
45857
46595
  llmEnvFile: runtime.llmEnvFile,
45858
46596
  env: { ...env, BUZZ_AGENT_BIN: agent.command, BUZZ_DEV_MCP_BIN: runtime.mcpBinary },
45859
46597
  agent
@@ -45879,14 +46617,14 @@ async function runUpdateProbeCommand(args, options = {}) {
45879
46617
  sandboxRequired: runtime.sandbox !== "off",
45880
46618
  sandboxUnavailableDetail: sandbox.advisory,
45881
46619
  // The successor's probe still holds `<runtimeDir>/update-functional-probe`.
45882
- probeRoot: join17(runtimeDir, "current-release-probe")
46620
+ probeRoot: join18(runtimeDir, "current-release-probe")
45883
46621
  }));
45884
46622
  const report = outcome.kind === "served" ? { probe: "served" } : outcome.kind === "refused" ? { probe: "refused", status: outcome.status, reason: outcome.reason } : outcome.kind === "sandbox-unavailable" ? { probe: "sandbox-unavailable", reason: outcome.reason } : { probe: "failed", reason: outcome.reason };
45885
46623
  write2(JSON.stringify(report));
45886
46624
  }
45887
46625
 
45888
46626
  // apps/body/dist/release-status.js
45889
- import { readFile as readFile25, readdir as readdir10, rename as rename11, writeFile as writeFile21 } from "node:fs/promises";
46627
+ import { readFile as readFile26, readdir as readdir10, rename as rename12, writeFile as writeFile21 } from "node:fs/promises";
45890
46628
  import { resolve as resolve37 } from "node:path";
45891
46629
  var DAEMON_RELEASE_STATUS_FILE = "release-status.json";
45892
46630
  var RELEASE_VERSION = /^v\d+\.\d+\.\d+$/;
@@ -45908,7 +46646,7 @@ async function writeDaemonReleaseStatus(runtimeDir, agentPubkey, identity, optio
45908
46646
  const temporary = `${target}.${status.pid}.tmp`;
45909
46647
  await writeFile21(temporary, `${JSON.stringify(status, null, 2)}
45910
46648
  `, { mode: 384 });
45911
- await rename11(temporary, target);
46649
+ await rename12(temporary, target);
45912
46650
  return status;
45913
46651
  }
45914
46652
 
@@ -46028,15 +46766,16 @@ All other config via env vars (see config.ts).
46028
46766
  process.exit(exitCode);
46029
46767
  }
46030
46768
  var daemonFailureRuntimeDir;
46769
+ var daemonLifecycle;
46031
46770
  var SCRATCH_SWEEP_INTERVAL_MS = 6 * 60 * 6e4;
46032
46771
  function runScratchSweepLogged(runtimeDir) {
46033
46772
  void runScratchSweep(runtimeDir).catch((error) => console.error("[body] scratch sweep failed:", error));
46034
46773
  }
46035
46774
  var DaemonExitError = class extends Error {
46036
- exitStatus;
46037
- constructor(message, exitStatus) {
46775
+ reason;
46776
+ constructor(message, reason) {
46038
46777
  super(message);
46039
- this.exitStatus = exitStatus;
46778
+ this.reason = reason;
46040
46779
  this.name = "DaemonExitError";
46041
46780
  }
46042
46781
  };
@@ -46117,7 +46856,9 @@ async function runStoredDaemon(pathOrPointer) {
46117
46856
  config.sandboxMaskPaths = [...config.sandboxMaskPaths ?? [], ...runtime.sandboxMaskPaths];
46118
46857
  }
46119
46858
  const controller = new AbortController();
46120
- const disposeStopSignals = installDaemonStopSignals(controller);
46859
+ const lifecycle = new HelperLifecycle({ controller });
46860
+ daemonLifecycle = lifecycle;
46861
+ const disposeStopSignals = lifecycle.installSignals();
46121
46862
  const runtimeDir = dirname27(configPath);
46122
46863
  const layout = beelineInstallLayout(process.env);
46123
46864
  const notifier = new SystemdNotifier();
@@ -46139,15 +46880,17 @@ async function runStoredDaemon(pathOrPointer) {
46139
46880
  let update;
46140
46881
  let pendingSuccessor = false;
46141
46882
  let successorRolledBack = false;
46883
+ let attemptDeadlineAt;
46142
46884
  if (layout) {
46143
46885
  const settle = await settleUpdateAttemptOnStart(layout);
46144
46886
  if (settle.kind === "rolled-back") {
46145
46887
  await queueUpdateRollbackAlert(runtimeDir, settle.record.releaseId);
46146
46888
  await drainRollbackAlert(runtime.rooms[0]?.channelId);
46147
46889
  console.error(`[body] self-update ROLLED BACK: bundle ${describeIdentity(settle.record.to)} never confirmed healthy; restored ${settle.record.previousReleaseId ?? "previous release"}`);
46148
- throw new DaemonExitError("stale unconfirmed release rolled back; supervisor must restart", 75);
46890
+ throw new DaemonExitError("stale unconfirmed release rolled back; supervisor must restart", "rolled-back");
46149
46891
  } else if (settle.kind === "pending") {
46150
46892
  pendingSuccessor = true;
46893
+ attemptDeadlineAt = (await readUpdateAttempt(layout))?.confirmBy;
46151
46894
  }
46152
46895
  loadedRelease = await activeReleaseId(layout);
46153
46896
  await clearUpdateRollbackAlertIfConfirmed(runtimeDir, loadedRelease);
@@ -46162,6 +46905,9 @@ async function runStoredDaemon(pathOrPointer) {
46162
46905
  requiredProbeIds: [...await runningRuntimeProbeIds(process.env), runtime.agent.publicKey]
46163
46906
  });
46164
46907
  if (pendingSuccessor && process.platform === "linux" && process.env.BEELINE_SYSTEMD_USER !== "0") {
46908
+ await rewriteAgentServiceUnit({ lock: (work) => withInstallLock(layout, work) }).catch((error) => {
46909
+ console.error(`[beeline] agent unit not converged: ${error instanceof Error ? error.message : String(error)}`);
46910
+ });
46165
46911
  await installTrustySquireBrokerService().catch((error) => {
46166
46912
  console.error(`[beeline] host Squire broker unit not converged: ${error instanceof Error ? error.message : String(error)}`);
46167
46913
  });
@@ -46201,6 +46947,7 @@ async function runStoredDaemon(pathOrPointer) {
46201
46947
  return catalogRefresh;
46202
46948
  };
46203
46949
  let stoppingStatus = "daemon stopped";
46950
+ let exitReason;
46204
46951
  try {
46205
46952
  registryMcpBroker = new RegistryMcpHostBroker(
46206
46953
  config.operatorHome,
@@ -46224,16 +46971,16 @@ async function runStoredDaemon(pathOrPointer) {
46224
46971
  daemonApi,
46225
46972
  onConfigChanged: refreshCatalog,
46226
46973
  onRestartRequested: () => {
46227
- if (lifecycleRestart)
46974
+ if (lifecycleRestart || !lifecycle.quiesce("restart"))
46228
46975
  return;
46229
46976
  lifecycleRestart = (async () => {
46230
46977
  const cancelled = await core.cancelActiveWorkForRestart();
46231
46978
  stoppingStatus = cancelled ? "restart requested; active work cancelled" : "restart requested; no active work";
46232
- await notifier.stopping(stoppingStatus);
46233
- controller.abort();
46979
+ await notifier.stopping(stoppingStatus).catch((error) => console.error("[thin-core] stopping notification failed:", error));
46980
+ lifecycle.stop("restart-requested");
46234
46981
  })().catch((error) => {
46235
46982
  console.error("[thin-core] requested restart failed:", error);
46236
- controller.abort();
46983
+ lifecycle.stop("restart-requested");
46237
46984
  });
46238
46985
  }
46239
46986
  });
@@ -46242,6 +46989,7 @@ async function runStoredDaemon(pathOrPointer) {
46242
46989
  loadedVersion: loadedReleaseIdentity?.version,
46243
46990
  runtimeDir,
46244
46991
  interrupt: () => {
46992
+ lifecycle.quiesce("force-update");
46245
46993
  core.setDrainDeadlineAt(Date.now() + 6e4);
46246
46994
  return core.interruptForServerMinimum();
46247
46995
  },
@@ -46260,25 +47008,25 @@ async function runStoredDaemon(pathOrPointer) {
46260
47008
  if (staggerMs > 0)
46261
47009
  await new Promise((resolve40) => setTimeout(resolve40, staggerMs));
46262
47010
  stoppingStatus = `server minimum installed; restarting onto ${desiredRelease}`;
46263
- await notifier.stopping(stoppingStatus);
46264
- controller.abort();
47011
+ await notifier.stopping(stoppingStatus).catch((error) => console.error("[thin-core] stopping notification failed:", error));
47012
+ lifecycle.stop("update");
46265
47013
  },
46266
47014
  failed: (error) => {
46267
47015
  console.error("[thin-core] forced helper update failed:", error);
46268
- controller.abort();
47016
+ lifecycle.stop("force-update-failed");
46269
47017
  }
46270
47018
  }) : void 0;
46271
47019
  daemonApi.setForceUpdateListener((minVersion) => {
46272
47020
  if (!forceUpdate) {
46273
47021
  console.error("[thin-core] server requires a published helper, but this process has no install layout");
46274
- controller.abort();
47022
+ lifecycle.stop("stopped");
46275
47023
  return;
46276
47024
  }
46277
47025
  forceUpdate.request(minVersion);
46278
47026
  });
46279
47027
  const updateDrain = update ? new ManagedUpdateDrain({
46280
47028
  update,
46281
- quiesceIfIdle: () => !forceUpdate?.active && core.quiesceForUpdateIfIdle(),
47029
+ quiesceIfIdle: () => !forceUpdate?.active && lifecycle.quiesceUpdateIfIdle(() => core.quiesceForUpdateIfIdle()),
46282
47030
  activeTurnCount: () => core.activeTurnCount(),
46283
47031
  restart: async ({ desiredRelease, drainDeadlineAt }) => {
46284
47032
  core.setDrainDeadlineAt(drainDeadlineAt);
@@ -46286,20 +47034,29 @@ async function runStoredDaemon(pathOrPointer) {
46286
47034
  if (staggerMs > 0)
46287
47035
  await new Promise((resolve40) => setTimeout(resolve40, staggerMs));
46288
47036
  stoppingStatus = `update pending, converging; loaded_release=${loadedRelease ?? "unknown"}; desired_release=${desiredRelease}; active work drained; intake quiesced`;
46289
- await notifier.stopping(stoppingStatus);
46290
- controller.abort();
47037
+ await notifier.stopping(stoppingStatus).catch((error) => console.error("[thin-core] stopping notification failed:", error));
47038
+ lifecycle.stop("update");
46291
47039
  },
46292
47040
  waiting: async ({ desiredRelease }) => {
46293
47041
  await notifier.progress(`loaded_release=${loadedRelease ?? "unknown"}; update ready; active agent work is still running; handoff deferred; desired_release=${desiredRelease}`);
46294
47042
  }
46295
47043
  }) : void 0;
47044
+ const tickUpdate = async () => {
47045
+ try {
47046
+ await updateDrain?.tick();
47047
+ } catch (error) {
47048
+ console.error("[thin-core] managed restart failed; serving again:", error);
47049
+ lifecycle.resumeAfterFailedUpdate(() => core.resumeServing());
47050
+ }
47051
+ };
46296
47052
  daemonApi.setHelperReleaseListener(({ version, sha }) => {
46297
47053
  update?.notifyReleaseAvailable(`${version}:${sha}`);
46298
- void updateDrain?.tick();
47054
+ void tickUpdate();
46299
47055
  });
47056
+ daemonApi.onLiveOpen(() => void tickUpdate());
46300
47057
  core.setInteractiveIdleListener(() => {
46301
47058
  institutionalMemoryWorker?.wake();
46302
- void updateDrain?.tick();
47059
+ void tickUpdate();
46303
47060
  });
46304
47061
  const result = await core.run({
46305
47062
  signal: controller.signal,
@@ -46359,7 +47116,12 @@ async function runStoredDaemon(pathOrPointer) {
46359
47116
  console.log(`[thin-core] successor functional probe passed on exact release ${loadedRelease}: ${functionalProof?.harness ?? "unknown"} session/new + turn` + (functionalProof?.modelAnswer === "unavailable" ? ` (model answer unavailable: ${functionalProof.modelAnswerReason})` : ""));
46360
47117
  }
46361
47118
  try {
46362
- await reportInterruptedTurns(runtimeDir, daemonApi, runtime.agent.publicKey);
47119
+ await retryBeforeReady(() => reportInterruptedTurns(runtimeDir, daemonApi, runtime.agent.publicKey), {
47120
+ onLinkOpen: (listener) => daemonApi.onLiveOpen(listener),
47121
+ extendStartTimeout: extendSystemdStartTimeout,
47122
+ ...attemptDeadlineAt !== void 0 ? { deadlineAt: attemptDeadlineAt } : {},
47123
+ signal: controller.signal
47124
+ });
46363
47125
  } catch (error) {
46364
47126
  if (error instanceof DaemonApiError && error.status === 426 && error.code === "update_required" && forceUpdate?.pending) {
46365
47127
  await forceUpdate.pending;
@@ -46407,18 +47169,18 @@ async function runStoredDaemon(pathOrPointer) {
46407
47169
  onProgress: async (status) => {
46408
47170
  lastCoreStatus = status;
46409
47171
  void drainRollbackAlert(core.activeRoomIds()[0] ?? runtime.rooms[0]?.channelId);
46410
- await notifier.progress(`loaded_release=${loadedRelease ?? "development"}; ${status}`);
46411
- await updateDrain?.tick();
47172
+ await notifier.progress(`loaded_release=${loadedRelease ?? "development"}; ${status}`).catch((error) => console.error("[thin-core] progress notification failed:", error));
47173
+ await tickUpdate();
46412
47174
  }
46413
47175
  });
46414
47176
  if (result === "agent-removed") {
46415
47177
  controller.abort();
46416
47178
  const archivedRuntime = await retireRemovedAgent(runtime);
46417
- process.exitCode = DELIBERATE_REMOVAL_EXIT_STATUS;
47179
+ exitReason = "agent-removed";
46418
47180
  console.log(`[beeline] agent ${runtime.agent.publicKey} removed; runtime archived at ${archivedRuntime}`);
46419
47181
  }
46420
47182
  } catch (error) {
46421
- const rolledBack = successorRolledBack || layout && pendingSuccessor && !ready && // Named, so a sibling daemon's journal can say whose failure reverted
47183
+ const rolledBack = successorRolledBack || layout && pendingSuccessor && !ready && successorRollbackAllowed(error, attemptDeadlineAt) && // Named, so a sibling daemon's journal can say whose failure reverted
46422
47184
  // the attempt it shares with this one.
46423
47185
  await rollbackFailedSuccessor(layout, runtimeDir, {
46424
47186
  probeId: runtime.agent.publicKey,
@@ -46441,6 +47203,7 @@ async function runStoredDaemon(pathOrPointer) {
46441
47203
  await clearDaemonPidRecordIfPid(configPath, process.pid);
46442
47204
  disposeStopSignals();
46443
47205
  }
47206
+ return exitReason ?? lifecycle.stopReason ?? "stopped";
46444
47207
  }
46445
47208
  async function main() {
46446
47209
  const args = process.argv.slice(2);
@@ -46529,6 +47292,7 @@ async function main() {
46529
47292
  return;
46530
47293
  }
46531
47294
  if (command === "daemon") {
47295
+ installUnhandledRejectionGuard();
46532
47296
  const configFlag = args.indexOf("--config");
46533
47297
  const agentFlag = args.indexOf("--agent");
46534
47298
  let configPath = configFlag >= 0 ? args[configFlag + 1] : void 0;
@@ -46547,11 +47311,12 @@ async function main() {
46547
47311
  configPath = configs.find((candidate) => dirname27(candidate).endsWith(agentPubkey));
46548
47312
  }
46549
47313
  if (!configPath && agentPubkey) {
46550
- throw new DaemonExitError(`unknown agent ${agentPubkey}: no durable runtime exists; refusing service restart loop`, UNKNOWN_AGENT_EXIT_STATUS);
47314
+ throw new DaemonExitError(`unknown agent ${agentPubkey}: no durable runtime exists; refusing service restart loop`, "unknown-agent");
46551
47315
  }
46552
47316
  if (!configPath)
46553
47317
  throw new Error("daemon requires --config <runtime.json> or --agent <pubkey>");
46554
- await runStoredDaemon(resolve39(configPath));
47318
+ const reason = await runStoredDaemon(resolve39(configPath));
47319
+ daemonLifecycle?.exit(reason);
46555
47320
  return;
46556
47321
  }
46557
47322
  if (command === "update") {
@@ -46591,7 +47356,8 @@ async function main() {
46591
47356
  main().catch(async (err) => {
46592
47357
  if (process.argv[2] === "daemon") {
46593
47358
  const layout = beelineInstallLayout(process.env);
46594
- if (layout && await rollbackFailedSuccessor(layout).catch(() => false)) {
47359
+ const attempt = layout ? await readUpdateAttempt(layout).catch(() => void 0) : void 0;
47360
+ if (layout && successorRollbackAllowed(err, attempt?.confirmBy) && await rollbackFailedSuccessor(layout).catch(() => false)) {
46595
47361
  console.error("[thin-core] successor failed during startup; previous release restored once");
46596
47362
  }
46597
47363
  }
@@ -46603,19 +47369,24 @@ main().catch(async (err) => {
46603
47369
  } else {
46604
47370
  console.error(import_picocolors3.default.red("[body] fatal:"), err);
46605
47371
  }
46606
- let exitStatus = err instanceof DaemonExitError ? err.exitStatus : 1;
46607
- if (process.argv[2] === "daemon" && daemonFailureRuntimeDir && exitStatus === 1) {
47372
+ if (process.argv[2] !== "daemon")
47373
+ process.exit(1);
47374
+ let reason = err instanceof DaemonExitError ? err.reason : "failed";
47375
+ if (daemonFailureRuntimeDir && reason === "failed") {
46608
47376
  try {
46609
- const failure = await recordDaemonStartFailure(daemonFailureRuntimeDir, err);
47377
+ const failure = await settleDaemonStartFailure(daemonFailureRuntimeDir, err);
46610
47378
  if (failure.distressed) {
46611
- exitStatus = DAEMON_DISTRESS_EXIT_STATUS;
47379
+ reason = "distress";
46612
47380
  console.error(`[thin-core] daemon start failed ${failure.count} times; service restart stopped. operator record: ${failure.path}`);
46613
47381
  }
46614
47382
  } catch (recordError) {
46615
47383
  console.error("[thin-core] could not persist daemon distress record:", recordError);
46616
47384
  }
46617
47385
  }
46618
- process.exit(exitStatus);
47386
+ if (daemonLifecycle)
47387
+ daemonLifecycle.exit(reason);
47388
+ else
47389
+ process.exit(HELPER_EXIT_CODES[reason]);
46619
47390
  });
46620
47391
  /*! Bundled license information:
46621
47392