usebeeline 0.1.3 → 0.1.4

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 +1378 -632
  2. package/package.json +1 -1
@@ -9711,7 +9711,7 @@ var init_squire_host = __esm({
9711
9711
 
9712
9712
  // apps/body/dist/systemd.js
9713
9713
  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";
9714
+ import { mkdir as mkdir2, readFile as readFile3, rename as rename2, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
9715
9715
  import { homedir as homedir3 } from "node:os";
9716
9716
  import { dirname as dirname3, resolve as resolve3 } from "node:path";
9717
9717
  import { setTimeout as sleep } from "node:timers/promises";
@@ -9740,6 +9740,7 @@ CPUWeight=1000
9740
9740
  IOWeight=1000
9741
9741
  Nice=-5
9742
9742
  OOMScoreAdjust=-1000
9743
+ OOMPolicy=continue
9743
9744
  TimeoutStartSec=90s
9744
9745
  TimeoutStopSec=90s
9745
9746
  KillMode=control-group
@@ -9812,6 +9813,26 @@ async function convergeTrustySquireBrokerService(options) {
9812
9813
  return false;
9813
9814
  }
9814
9815
  }
9816
+ async function convergeAgentServiceUnit(options) {
9817
+ const env = options.env ?? process.env;
9818
+ try {
9819
+ assertCanonicalInstalledLauncher(env, resolve3(options.libDir, "lib", "beeline", "beeline-cli.mjs"));
9820
+ const path = systemdUserUnitPath(env);
9821
+ const content = agentServiceUnit();
9822
+ const existing = await readFile3(path, "utf8").catch(() => "");
9823
+ if (existing === content)
9824
+ return false;
9825
+ await mkdir2(dirname3(path), { recursive: true, mode: 448 });
9826
+ await writeFile2(path, content, { mode: 384 });
9827
+ const run2 = options.run ?? runSystemctl;
9828
+ await run2(["daemon-reload"]);
9829
+ options.log?.("[beeline] agent systemd unit updated; it applies on the next daemon restart");
9830
+ return true;
9831
+ } catch (error) {
9832
+ options.log?.(`[beeline] agent systemd unit not converged: ${error instanceof Error ? error.message : String(error)}`);
9833
+ return false;
9834
+ }
9835
+ }
9815
9836
  async function installAgentService(publicKey, options = {}) {
9816
9837
  if (!/^[0-9a-f]{64}$/i.test(publicKey))
9817
9838
  throw new Error("agent public key must be 64 hex");
@@ -9844,6 +9865,23 @@ async function installAgentService(publicKey, options = {}) {
9844
9865
  } while (Date.now() < deadline);
9845
9866
  throw new Error(`systemd did not replace ${service}'s MainPID before the restart deadline`);
9846
9867
  }
9868
+ async function rewriteAgentServiceUnit(options) {
9869
+ const env = options.env ?? process.env;
9870
+ assertCanonicalInstalledLauncher(env, options.invocationPath);
9871
+ const path = systemdUserUnitPath(env);
9872
+ const content = agentServiceUnit();
9873
+ const run2 = options.run ?? runSystemctl;
9874
+ return options.lock(async () => {
9875
+ if (await readFile3(path, "utf8").catch(() => "") === content)
9876
+ return false;
9877
+ await mkdir2(dirname3(path), { recursive: true, mode: 448 });
9878
+ const staged = `${path}.${process.pid}.tmp`;
9879
+ await writeFile2(staged, content, { mode: 384 });
9880
+ await rename2(staged, path);
9881
+ await run2(["daemon-reload"]);
9882
+ return true;
9883
+ });
9884
+ }
9847
9885
  async function serviceStatus(run2, service) {
9848
9886
  const status = await run2([
9849
9887
  "show",
@@ -9947,8 +9985,10 @@ async function notify(fields) {
9947
9985
  async function extendSystemdStartTimeout(ms) {
9948
9986
  await notify([`EXTEND_TIMEOUT_USEC=${Math.max(0, Math.round(ms)) * 1e3}`]).catch(() => void 0);
9949
9987
  }
9950
- function startLocalWatchdog(notifier, status, intervalMs = 6e4) {
9951
- const timer = setInterval(() => void notifier.progress(status()), intervalMs);
9988
+ function startLocalWatchdog(notifier, status, intervalMs = 6e4, env = process.env) {
9989
+ if (!env.WATCHDOG_USEC)
9990
+ return () => void 0;
9991
+ const timer = setInterval(() => void notifier.progress(status()).catch((error) => console.error("[thin-core] watchdog notification failed:", error)), intervalMs);
9952
9992
  timer.unref?.();
9953
9993
  return () => clearInterval(timer);
9954
9994
  }
@@ -9990,7 +10030,7 @@ var init_systemd = __esm({
9990
10030
  // apps/body/dist/launchd.js
9991
10031
  import { execFile as execFile3 } from "node:child_process";
9992
10032
  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";
10033
+ 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
10034
  import { homedir as homedir4 } from "node:os";
9995
10035
  import { dirname as dirname4, resolve as resolve4 } from "node:path";
9996
10036
  import { setTimeout as sleep2 } from "node:timers/promises";
@@ -10158,7 +10198,7 @@ async function writeManagedFile(path, content, mode) {
10158
10198
  const pending = `${path}.${process.pid}.pending`;
10159
10199
  await writeFile3(pending, content, { mode });
10160
10200
  await chmod2(pending, mode);
10161
- await rename2(pending, path);
10201
+ await rename3(pending, path);
10162
10202
  return true;
10163
10203
  }
10164
10204
  function launchctlFailureText(error) {
@@ -10409,7 +10449,7 @@ __export(self_update_exports, {
10409
10449
  });
10410
10450
  import { createHash } from "node:crypto";
10411
10451
  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";
10452
+ 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
10453
  import { spawn as spawn4 } from "node:child_process";
10414
10454
  import { homedir as homedir5 } from "node:os";
10415
10455
  import { dirname as dirname5, join as join3, resolve as resolve5 } from "node:path";
@@ -10712,7 +10752,7 @@ async function replaceFile(path, contents, mode) {
10712
10752
  const temp = `${path}.new-${process.pid}`;
10713
10753
  await writeFile4(temp, contents, { mode });
10714
10754
  await chmod3(temp, mode);
10715
- await rename3(temp, path);
10755
+ await rename4(temp, path);
10716
10756
  }
10717
10757
  async function activateRelease(layout, releaseId) {
10718
10758
  const releaseDir = join3(layout.releasesRoot, releaseId);
@@ -10728,10 +10768,10 @@ async function activateRelease(layout, releaseId) {
10728
10768
  try {
10729
10769
  await access(legacyDir, fsConstants2.F_OK);
10730
10770
  previousReleaseId = `${legacyId}-${Date.now()}`;
10731
- await rename3(layout.libDir, join3(layout.releasesRoot, previousReleaseId));
10771
+ await rename4(layout.libDir, join3(layout.releasesRoot, previousReleaseId));
10732
10772
  await normalizeLegacyBundleShape(join3(layout.releasesRoot, previousReleaseId));
10733
10773
  } catch {
10734
- await rename3(layout.libDir, legacyDir);
10774
+ await rename4(layout.libDir, legacyDir);
10735
10775
  await normalizeLegacyBundleShape(legacyDir);
10736
10776
  previousReleaseId = legacyId;
10737
10777
  }
@@ -10739,7 +10779,7 @@ async function activateRelease(layout, releaseId) {
10739
10779
  const tempLink = `${layout.libDir}.new-${process.pid}`;
10740
10780
  await rm2(tempLink, { force: true });
10741
10781
  await symlink(join3("beeline-releases", releaseId), tempLink);
10742
- await rename3(tempLink, layout.libDir);
10782
+ await rename4(tempLink, layout.libDir);
10743
10783
  await fsyncDir(dirname5(layout.libDir));
10744
10784
  await writeBinForwarders(layout, releaseDir);
10745
10785
  return { previousReleaseId };
@@ -10764,7 +10804,7 @@ async function normalizeLegacyBundleShape(bundleDir) {
10764
10804
  continue;
10765
10805
  } catch {
10766
10806
  }
10767
- await rename3(join3(bundleDir, name), join3(innerLib, name)).catch(() => void 0);
10807
+ await rename4(join3(bundleDir, name), join3(innerLib, name)).catch(() => void 0);
10768
10808
  }
10769
10809
  }
10770
10810
  async function writeBinForwarders(layout, activeBundleRoot) {
@@ -10815,7 +10855,7 @@ async function rollbackToPreviousRelease(layout, previousReleaseId) {
10815
10855
  const tempLink = `${layout.libDir}.rollback-${process.pid}`;
10816
10856
  await rm2(tempLink, { force: true });
10817
10857
  await symlink(join3("beeline-releases", previousReleaseId), tempLink);
10818
- await rename3(tempLink, layout.libDir);
10858
+ await rename4(tempLink, layout.libDir);
10819
10859
  await fsyncDir(dirname5(layout.libDir));
10820
10860
  }
10821
10861
  function updateAttemptPath(layout) {
@@ -10838,7 +10878,7 @@ async function writeUpdateAttempt(layout, record6) {
10838
10878
  const staged = `${path}.${process.pid}.tmp`;
10839
10879
  await writeFile4(staged, `${JSON.stringify(record6, null, 2)}
10840
10880
  `, { mode: 384 });
10841
- await rename3(staged, path);
10881
+ await rename4(staged, path);
10842
10882
  }
10843
10883
  async function replaceUpdateAttempt(layout, record6) {
10844
10884
  await writeUpdateAttempt(layout, record6);
@@ -10925,7 +10965,6 @@ var init_self_update = __esm({
10925
10965
  fetchImpl;
10926
10966
  log;
10927
10967
  stageOnly;
10928
- timer;
10929
10968
  nextCheckAllowedAt = 0;
10930
10969
  disposed = false;
10931
10970
  applying = false;
@@ -10942,9 +10981,7 @@ var init_self_update = __esm({
10942
10981
  };
10943
10982
  this.options = {
10944
10983
  layout: options.layout,
10945
- tickMs: options.tickMs ?? numberEnv2("BEELINE_UPDATE_TICK_MS", 6e4),
10946
10984
  checkIntervalMs: options.checkIntervalMs ?? numberEnv2("BEELINE_UPDATE_INTERVAL_MS", 6 * 60 * 6e4),
10947
- initialDelayMs: options.initialDelayMs ?? numberEnv2("BEELINE_UPDATE_INITIAL_DELAY_MS", 2 * 6e4),
10948
10985
  idleTimeoutMs: options.idleTimeoutMs ?? numberEnv2("BEELINE_UPDATE_IDLE_TIMEOUT_MS", 30 * 6e4),
10949
10986
  idlePollMs: options.idlePollMs ?? 5e3,
10950
10987
  env,
@@ -10956,20 +10993,8 @@ var init_self_update = __esm({
10956
10993
  this.log = options.logger ?? ((line) => console.log(line));
10957
10994
  this.stageOnly = options.stageOnly ?? false;
10958
10995
  }
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
10996
  dispose() {
10969
10997
  this.disposed = true;
10970
- if (this.timer)
10971
- clearInterval(this.timer);
10972
- this.timer = void 0;
10973
10998
  }
10974
10999
  busy() {
10975
11000
  return !this.isIdle();
@@ -11087,6 +11112,11 @@ var init_self_update = __esm({
11087
11112
  env: this.options.env,
11088
11113
  log: this.log
11089
11114
  });
11115
+ await convergeAgentServiceUnit({
11116
+ libDir: this.options.layout.libDir,
11117
+ env: this.options.env,
11118
+ log: this.log
11119
+ });
11090
11120
  } else if (process.platform === "darwin" && this.options.env.BEELINE_LAUNCHD_USER !== "0") {
11091
11121
  await convergeLaunchdTrustySquireBrokerService({
11092
11122
  libDir: this.options.layout.libDir,
@@ -11251,7 +11281,7 @@ var init_cursor_models = __esm({
11251
11281
  // apps/body/dist/cursor-acp-bridge.js
11252
11282
  import { spawn as spawn6 } from "node:child_process";
11253
11283
  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";
11284
+ import { chmod as chmod4, lstat as lstat2, mkdir as mkdir6, rename as rename5, unlink as unlink3, writeFile as writeFile5 } from "node:fs/promises";
11255
11285
  import { createRequire as createRequire3 } from "node:module";
11256
11286
  import { createInterface as createInterface3 } from "node:readline";
11257
11287
  import { basename, dirname as dirname7, resolve as resolvePath } from "node:path";
@@ -11324,7 +11354,7 @@ async function writeIsolatedCursorFile(path, content) {
11324
11354
  try {
11325
11355
  await writeFile5(temporary, content, { mode: 384, flag: "wx" });
11326
11356
  await chmod4(temporary, 384);
11327
- await rename4(temporary, path);
11357
+ await rename5(temporary, path);
11328
11358
  } finally {
11329
11359
  await unlink3(temporary).catch(() => void 0);
11330
11360
  }
@@ -26668,6 +26698,11 @@ var ARTIFACT_EXTENSIONS_BY_MIME = {
26668
26698
  "application/json": [".json"],
26669
26699
  "text/csv": [".csv"],
26670
26700
  "application/zip": [".zip"],
26701
+ "video/mp4": [".mp4", ".m4v"],
26702
+ "video/quicktime": [".mov"],
26703
+ "video/webm": [".webm"],
26704
+ "audio/mpeg": [".mp3"],
26705
+ "audio/wav": [".wav"],
26671
26706
  "application/octet-stream": []
26672
26707
  };
26673
26708
  var ARTIFACT_MIME_BY_EXTENSION = Object.fromEntries(Object.entries(ARTIFACT_EXTENSIONS_BY_MIME).flatMap(([mime, extensions]) => extensions.map((extension2) => [extension2, mime])));
@@ -27787,6 +27822,72 @@ function matchProfile(agentCommand) {
27787
27822
  return void 0;
27788
27823
  }
27789
27824
 
27825
+ // apps/body/dist/oom-kill.js
27826
+ import { readFile as readFile9 } from "node:fs/promises";
27827
+ import { join as join5 } from "node:path";
27828
+ var CGROUP_V2_ROOT = "/sys/fs/cgroup";
27829
+ function parseOomKillCount(contents) {
27830
+ for (const line of contents.split("\n")) {
27831
+ const [key, raw] = line.trim().split(/\s+/, 2);
27832
+ if (key !== "oom_kill")
27833
+ continue;
27834
+ const value = Number(raw);
27835
+ return Number.isSafeInteger(value) && value >= 0 ? value : null;
27836
+ }
27837
+ return null;
27838
+ }
27839
+ function unifiedCgroupPath(procSelfCgroup) {
27840
+ const line = procSelfCgroup.split("\n").map((value) => value.trim()).find((value) => value.startsWith("0::"));
27841
+ const path = line?.slice(3).trim();
27842
+ return path && path.startsWith("/") ? path : null;
27843
+ }
27844
+ function cgroupOomKillProbe(options = {}) {
27845
+ const readProc = options.procSelfCgroup ?? (() => readFile9("/proc/self/cgroup", "utf8"));
27846
+ const root = options.cgroupRoot ?? CGROUP_V2_ROOT;
27847
+ let directory;
27848
+ return {
27849
+ async read() {
27850
+ try {
27851
+ if (directory === void 0) {
27852
+ const path = unifiedCgroupPath(await readProc());
27853
+ directory = path ? join5(root, path) : null;
27854
+ }
27855
+ if (!directory)
27856
+ return null;
27857
+ return parseOomKillCount(await readFile9(join5(directory, "memory.events"), "utf8"));
27858
+ } catch {
27859
+ return null;
27860
+ }
27861
+ }
27862
+ };
27863
+ }
27864
+ var OomKillTracker = class {
27865
+ probe;
27866
+ #last = null;
27867
+ #banked = 0;
27868
+ constructor(probe) {
27869
+ this.probe = probe;
27870
+ }
27871
+ /** Record the current count so the next kill is measured against it. */
27872
+ async prime() {
27873
+ const current = await this.probe.read();
27874
+ if (current !== null)
27875
+ this.#last = current;
27876
+ }
27877
+ async consume() {
27878
+ const current = await this.probe.read();
27879
+ if (current === null)
27880
+ return false;
27881
+ if (this.#last !== null && current > this.#last)
27882
+ this.#banked += current - this.#last;
27883
+ this.#last = current;
27884
+ if (this.#banked === 0)
27885
+ return false;
27886
+ this.#banked -= 1;
27887
+ return true;
27888
+ }
27889
+ };
27890
+
27790
27891
  // apps/body/dist/acp.js
27791
27892
  var STDERR_TAIL_MAX_CHARS2 = 2e3;
27792
27893
  function braceDelta(line) {
@@ -28122,6 +28223,9 @@ var AcpClient = class extends EventEmitter {
28122
28223
  permissionHandler;
28123
28224
  permissionAllowlist;
28124
28225
  commandsHandler;
28226
+ oomKills;
28227
+ /** A deliberate stop owns its SIGKILL; that exit is never labelled OOM. */
28228
+ stopping = false;
28125
28229
  constructor(opts) {
28126
28230
  super();
28127
28231
  const command = opts.agentCommand ?? opts.agentBinary;
@@ -28139,10 +28243,13 @@ var AcpClient = class extends EventEmitter {
28139
28243
  this.permissionHandler = opts.permissionHandler;
28140
28244
  this.permissionAllowlist = opts.permissionAllowlist;
28141
28245
  this.commandsHandler = opts.onCommands;
28246
+ this.oomKills = opts.oomKills ?? new OomKillTracker(cgroupOomKillProbe());
28142
28247
  }
28143
28248
  async start(timeoutMs = 6e4) {
28144
28249
  if (this.alive)
28145
28250
  return;
28251
+ await this.oomKills.prime().catch(() => void 0);
28252
+ this.stopping = false;
28146
28253
  this.child = await withAdapterInstallLock(() => spawn10(this.agentCommand, this.agentArgs, {
28147
28254
  // agentEnv is the child's whole environment: buildAgentEnv's allowlist is
28148
28255
  // a real boundary, not a decorative one layered over a full inherit.
@@ -28164,19 +28271,7 @@ var AcpClient = class extends EventEmitter {
28164
28271
  this.child.stdout.setEncoding("utf8");
28165
28272
  this.child.stdout.on("data", (chunk) => this.onData(chunk));
28166
28273
  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 });
28274
+ void this.handleChildExit(code, signal);
28180
28275
  });
28181
28276
  this.child.on("error", (err) => {
28182
28277
  this.alive = false;
@@ -28202,12 +28297,36 @@ var AcpClient = class extends EventEmitter {
28202
28297
  if (!isPiAcpHarness(this.agentLabel))
28203
28298
  this.notify("notifications/initialized", {});
28204
28299
  }
28300
+ /**
28301
+ * One child exit: reject every in-flight request so the turn fails and is
28302
+ * reported instead of hanging, then publish the exit. A SIGKILL is checked
28303
+ * against the daemon cgroup's OOM counter so the rejection can name the real
28304
+ * cause instead of a bare signal.
28305
+ */
28306
+ async handleChildExit(code, signal) {
28307
+ this.alive = false;
28308
+ const oomKilled = signal === "SIGKILL" && !this.stopping && await this.oomKills.consume().catch(() => false);
28309
+ 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}`;
28310
+ const meaningful = flattenHarnessCause(this.stderrTail);
28311
+ const stderrSuffix = meaningful ? `: ${meaningful}` : "";
28312
+ for (const [, p] of this.pending) {
28313
+ this.clearTimer(p);
28314
+ p.reject(new Error(`${exitText}${stderrSuffix}`));
28315
+ }
28316
+ this.pending.clear();
28317
+ this.activeRunIds.clear();
28318
+ this.activePromptSessions.clear();
28319
+ this.toolCallMetadata.clear();
28320
+ this.sessionCommands.clear();
28321
+ this.emit("exit", { code, signal });
28322
+ }
28205
28323
  async stop() {
28206
28324
  if (!this.alive)
28207
28325
  return;
28208
28326
  const child = this.child;
28209
28327
  if (!child)
28210
28328
  return;
28329
+ this.stopping = true;
28211
28330
  try {
28212
28331
  if (this.child?.stdin.writable) {
28213
28332
  this.notify("shutdown", {});
@@ -28852,7 +28971,7 @@ async function applyRuntimeModelPreflight(config, agent, selection, validate = v
28852
28971
 
28853
28972
  // apps/body/dist/model-catalog-sync.js
28854
28973
  import { createHash as createHash3 } from "node:crypto";
28855
- import { readFile as readFile9, writeFile as writeFile6 } from "node:fs/promises";
28974
+ import { readFile as readFile10, writeFile as writeFile6 } from "node:fs/promises";
28856
28975
  import { resolve as resolve11 } from "node:path";
28857
28976
  var MODEL_CATALOG_PROBE_TIMEOUT_MS = 3e4;
28858
28977
  var MODEL_CATALOG_HASH_FILE = "model-catalog.sha256";
@@ -28890,7 +29009,7 @@ async function syncAgentModelCatalog(input) {
28890
29009
  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
29010
  const effectiveCatalog = input.startupUnavailable ? catalog : withEffectiveCurrentValues(catalog, selection);
28892
29011
  const hash = modelCatalogHash(effectiveCatalog, selection, input.startupUnavailable);
28893
- const previous = await readFile9(hashPath, "utf8").catch(() => "");
29012
+ const previous = await readFile10(hashPath, "utf8").catch(() => "");
28894
29013
  if (!input.force && previous.trim() === hash)
28895
29014
  return "unchanged";
28896
29015
  await input.api.execute("postAgentModelCatalog", {
@@ -28917,7 +29036,7 @@ import { execFile as execFile6 } from "node:child_process";
28917
29036
  import { randomUUID as randomUUID4 } from "node:crypto";
28918
29037
  import { rm as rm4 } from "node:fs/promises";
28919
29038
  import { tmpdir as tmpdir2, userInfo } from "node:os";
28920
- import { join as join5 } from "node:path";
29039
+ import { join as join6 } from "node:path";
28921
29040
  var COMMAND_TIMEOUT_MS = 2 * 6e4;
28922
29041
  var OUTPUT_LIMIT = 32 * 1024;
28923
29042
  var LOGIN_URL = /https:\/\/login\.tailscale\.com\/[A-Za-z0-9_/-]+/u;
@@ -29004,7 +29123,7 @@ async function ensureInstalled(run2, onProgress, platform) {
29004
29123
  }
29005
29124
  };
29006
29125
  }
29007
- const script = join5(tmpdir2(), `beeline-tailscale-install-${process.pid}-${randomUUID4()}.sh`);
29126
+ const script = join6(tmpdir2(), `beeline-tailscale-install-${process.pid}-${randomUUID4()}.sh`);
29008
29127
  const steps2 = [
29009
29128
  step2("Tailscale installed", "running", {
29010
29129
  command: "curl -fsSL https://tailscale.com/install.sh"
@@ -29127,7 +29246,7 @@ import { createRequire as createRequire4 } from "node:module";
29127
29246
  import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync, writeFileSync } from "node:fs";
29128
29247
  import { createConnection as createConnection3, createServer as createServer2, isIPv4, isIPv6 } from "node:net";
29129
29248
  import { homedir as homedir10 } from "node:os";
29130
- import { dirname as dirname9, join as join6 } from "node:path";
29249
+ import { dirname as dirname9, join as join7 } from "node:path";
29131
29250
  import { createInterface as createInterface5 } from "node:readline";
29132
29251
  import { fileURLToPath as fileURLToPath4 } from "node:url";
29133
29252
 
@@ -29262,12 +29381,12 @@ async function assertNotOwnMachine(url, remoteIsOwnMachine, resolveHost) {
29262
29381
  return url;
29263
29382
  }
29264
29383
  function registryMcpStateRoot(home = homedir10()) {
29265
- return join6(home, ".beeline", "registry-mcp");
29384
+ return join7(home, ".beeline", "registry-mcp");
29266
29385
  }
29267
29386
  function registryMcpStatePath(connectorId, home = homedir10()) {
29268
29387
  if (!/^[0-9a-f-]{20,}$/i.test(connectorId))
29269
29388
  throw new Error("Registry connector id is invalid");
29270
- return join6(registryMcpStateRoot(home), `${connectorId}.json`);
29389
+ return join7(registryMcpStateRoot(home), `${connectorId}.json`);
29271
29390
  }
29272
29391
  function readState(path) {
29273
29392
  try {
@@ -29653,7 +29772,7 @@ function jsonRpcToolName(line) {
29653
29772
  }
29654
29773
  }
29655
29774
  function brokerSocketPath(home = homedir10()) {
29656
- return join6(home, ".beeline", "registry-mcp-broker", `${process.pid}.sock`);
29775
+ return join7(home, ".beeline", "registry-mcp-broker", `${process.pid}.sock`);
29657
29776
  }
29658
29777
  var RegistryMcpHostBroker = class {
29659
29778
  home;
@@ -30218,13 +30337,13 @@ function describe(error) {
30218
30337
  }
30219
30338
 
30220
30339
  // apps/body/dist/institutional-memory-shadow-worker.js
30221
- import { mkdtemp as mkdtemp2, readFile as readFile10, rm as rm5 } from "node:fs/promises";
30340
+ import { mkdtemp as mkdtemp2, readFile as readFile11, rm as rm5 } from "node:fs/promises";
30222
30341
  import { tmpdir as tmpdir3 } from "node:os";
30223
- import { join as join8, resolve as resolve12 } from "node:path";
30342
+ import { join as join9, resolve as resolve12 } from "node:path";
30224
30343
 
30225
30344
  // apps/body/dist/attachment-delivery.js
30226
30345
  import { mkdir as mkdir7, writeFile as writeFile7 } from "node:fs/promises";
30227
- import { basename as basename4, extname, join as join7 } from "node:path";
30346
+ import { basename as basename4, extname, join as join8 } from "node:path";
30228
30347
  var MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
30229
30348
  var EXPIRED_REASON = "expired: these attachment bytes are past their retention window";
30230
30349
  var FETCH_TIMEOUT_MS = 3e4;
@@ -30283,7 +30402,7 @@ async function deliverAttachments(attachments, dir, fetchImpl = fetch) {
30283
30402
  return { attachment, reason: EXPIRED_REASON };
30284
30403
  if (!fetched.ok)
30285
30404
  throw new Error(`HTTP ${fetched.status}`);
30286
- const path = join7(dir, safeFileName(attachment, index, taken));
30405
+ const path = join8(dir, safeFileName(attachment, index, taken));
30287
30406
  await writeFile7(path, fetched.bytes);
30288
30407
  const bytes = fetched.bytes;
30289
30408
  const mimeType = attachment.mimeType ?? fetched.mimeType;
@@ -30422,7 +30541,7 @@ async function readInstitutionalMemoryAttachments(job, dir, acceptsImages, fetch
30422
30541
  for (const message of job.messages) {
30423
30542
  if (!message.attachments?.length)
30424
30543
  continue;
30425
- const delivered = await deliverAttachments(message.attachments, join8(dir, message.id), fetchImpl);
30544
+ const delivered = await deliverAttachments(message.attachments, join9(dir, message.id), fetchImpl);
30426
30545
  for (const entry of delivered) {
30427
30546
  const { attachment } = entry;
30428
30547
  const mimeType = attachment.mimeType ?? entry.image?.mimeType;
@@ -30447,7 +30566,7 @@ async function readInstitutionalMemoryAttachments(job, dir, acceptsImages, fetch
30447
30566
  evidence.push({ ...base, notRead: "the attachment text budget is used up" });
30448
30567
  continue;
30449
30568
  }
30450
- const text2 = clipText(await readFile10(entry.path, "utf8"), textBudget);
30569
+ const text2 = clipText(await readFile11(entry.path, "utf8"), textBudget);
30451
30570
  textBudget -= Buffer.byteLength(text2, "utf8");
30452
30571
  evidence.push({ ...base, text: text2 });
30453
30572
  } else {
@@ -30561,7 +30680,7 @@ async function extractInstitutionalMemoryShadowJob(job, options, signal) {
30561
30680
  if (options.modelSelection) {
30562
30681
  await applyAgentModelSelection(client, opened.sessionId, parseAdvertisedConfigOptions(opened.raw, options.modelSelection.model), options.modelSelection);
30563
30682
  }
30564
- const attachments = await readInstitutionalMemoryAttachments(job, join8(scratch, "attachments"), client.canPromptWithImages(), options.fetch);
30683
+ const attachments = await readInstitutionalMemoryAttachments(job, join9(scratch, "attachments"), client.canPromptWithImages(), options.fetch);
30565
30684
  prompt = institutionalMemoryExtractionPrompt(job, attachments.evidence);
30566
30685
  const result = await client.sessionPrompt(opened.sessionId, promptWithImages(prompt, attachments.images));
30567
30686
  const proposal = parseExtractionText(result.agentText, job.triggerKind);
@@ -30704,9 +30823,9 @@ function helperVersionHeader(value) {
30704
30823
 
30705
30824
  // apps/body/dist/host-read-budget.js
30706
30825
  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";
30826
+ 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
30827
  import { tmpdir as tmpdir4 } from "node:os";
30709
- import { join as join9 } from "node:path";
30828
+ import { join as join10 } from "node:path";
30710
30829
  var READ_SLOTS = 6;
30711
30830
  var BACKGROUND_SLOTS = 4;
30712
30831
  var MAX_WAITERS = 256;
@@ -30737,7 +30856,7 @@ var HostReadBudget = class {
30737
30856
  const uid = process.getuid?.() ?? "unknown";
30738
30857
  const origin = new URL(baseUrl).origin;
30739
30858
  const key = createHash5("sha256").update(origin).digest("hex").slice(0, 16);
30740
- this.directory = join9(root, `beeline-read-budget-${uid}-${key}`);
30859
+ this.directory = join10(root, `beeline-read-budget-${uid}-${key}`);
30741
30860
  }
30742
30861
  metrics() {
30743
30862
  return { waiting: this.waiting, active: this.active, totalWaitMs: this.totalWaitMs };
@@ -30745,7 +30864,7 @@ var HostReadBudget = class {
30745
30864
  async acquire(priority, deadlineAt) {
30746
30865
  await mkdir8(this.directory, { recursive: true, mode: 448 });
30747
30866
  const ticket = `wait-${priority === "urgent" ? "0" : "1"}-${String(Date.now()).padStart(13, "0")}-${randomUUID5()}`;
30748
- const ticketPath = join9(this.directory, ticket);
30867
+ const ticketPath = join10(this.directory, ticket);
30749
30868
  const tickets = (await readdir3(this.directory)).filter((name) => name.startsWith("wait-"));
30750
30869
  if (tickets.length >= MAX_WAITERS)
30751
30870
  throw new ReadBudgetFullError();
@@ -30757,14 +30876,14 @@ var HostReadBudget = class {
30757
30876
  const queue = (await readdir3(this.directory)).filter((name) => name.startsWith("wait-")).sort();
30758
30877
  for (const name of queue.slice(0, READ_SLOTS)) {
30759
30878
  if (name !== ticket)
30760
- await this.reapStaleTicket(join9(this.directory, name));
30879
+ await this.reapStaleTicket(join10(this.directory, name));
30761
30880
  }
30762
30881
  const liveQueue = (await readdir3(this.directory)).filter((name) => name.startsWith("wait-")).sort();
30763
30882
  const rank = liveQueue.indexOf(ticket);
30764
30883
  const available = priority === "urgent" ? READ_SLOTS : BACKGROUND_SLOTS;
30765
30884
  if (rank >= 0 && rank < available) {
30766
30885
  for (let slot = 0; slot < available; slot += 1) {
30767
- const slotPath = join9(this.directory, `slot-${slot}`);
30886
+ const slotPath = join10(this.directory, `slot-${slot}`);
30768
30887
  try {
30769
30888
  const handle2 = await open3(slotPath, "wx", 384);
30770
30889
  try {
@@ -30814,7 +30933,7 @@ var HostReadBudget = class {
30814
30933
  const details = await stat5(path).catch(() => void 0);
30815
30934
  if (!details || Date.now() - details.mtimeMs < 2e3)
30816
30935
  return;
30817
- const owner = await readFile11(path, "utf8").then((value) => {
30936
+ const owner = await readFile12(path, "utf8").then((value) => {
30818
30937
  try {
30819
30938
  return JSON.parse(value);
30820
30939
  } catch {
@@ -30829,13 +30948,13 @@ var HostReadBudget = class {
30829
30948
  const details = await stat5(path).catch(() => void 0);
30830
30949
  if (!details || Date.now() - details.mtimeMs < 2e3)
30831
30950
  return;
30832
- const pid = Number(await readFile11(path, "utf8").catch(() => "0"));
30951
+ const pid = Number(await readFile12(path, "utf8").catch(() => "0"));
30833
30952
  if (pid > 0 && ownerAlive(pid) && Date.now() - details.mtimeMs < STALE_SLOT_MS)
30834
30953
  return;
30835
30954
  await unlink4(path).catch(() => void 0);
30836
30955
  }
30837
30956
  async takeRateToken(deadlineAt) {
30838
- const lock = join9(this.directory, "rate.lock");
30957
+ const lock = join10(this.directory, "rate.lock");
30839
30958
  let handle2;
30840
30959
  try {
30841
30960
  handle2 = await open3(lock, "wx", 384);
@@ -30846,8 +30965,8 @@ var HostReadBudget = class {
30846
30965
  return false;
30847
30966
  }
30848
30967
  try {
30849
- const path = join9(this.directory, "next-admission");
30850
- const next = Number(await readFile11(path, "utf8").catch(() => "0")) || 0;
30968
+ const path = join10(this.directory, "next-admission");
30969
+ const next = Number(await readFile12(path, "utf8").catch(() => "0")) || 0;
30851
30970
  const now2 = Date.now();
30852
30971
  if (next > now2 || now2 >= deadlineAt)
30853
30972
  return false;
@@ -30860,6 +30979,251 @@ var HostReadBudget = class {
30860
30979
  }
30861
30980
  };
30862
30981
 
30982
+ // apps/body/dist/live-link.js
30983
+ var LIVE_LINK_TIMING = Object.freeze({
30984
+ handshakeTimeoutMs: 3e4,
30985
+ pongTimeoutMs: 15e3,
30986
+ backoffBaseMs: 1e3,
30987
+ backoffMaxMs: 3e4,
30988
+ stableSocketMs: 3e4,
30989
+ stableCloseJitterMs: 1e4,
30990
+ keepAliveDelayMs: 5 * 6e4
30991
+ });
30992
+ var NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
30993
+ "ECONNRESET",
30994
+ "ECONNREFUSED",
30995
+ "ENOTFOUND",
30996
+ "EAI_AGAIN",
30997
+ "ETIMEDOUT",
30998
+ "EHOSTUNREACH",
30999
+ "ENETUNREACH",
31000
+ "EPIPE",
31001
+ "ECONNABORTED"
31002
+ ]);
31003
+ function isNetworkFailure(error) {
31004
+ const seen = /* @__PURE__ */ new Set();
31005
+ const visit = (value) => {
31006
+ if (!value || typeof value !== "object" || seen.has(value))
31007
+ return false;
31008
+ seen.add(value);
31009
+ const code = value.code;
31010
+ if (typeof code === "string" && (NETWORK_ERROR_CODES.has(code) || code.startsWith("UND_ERR_")))
31011
+ return true;
31012
+ if (value instanceof AggregateError && value.errors.length > 0 && value.errors.every(visit))
31013
+ return true;
31014
+ return visit(value.cause);
31015
+ };
31016
+ return visit(error);
31017
+ }
31018
+ function retryAfterMs(value) {
31019
+ if (!value)
31020
+ return void 0;
31021
+ const seconds = Number(value);
31022
+ const delay = Number.isFinite(seconds) && seconds >= 0 ? seconds * 1e3 : Date.parse(value) - Date.now();
31023
+ return Number.isFinite(delay) && delay >= 0 ? Math.min(delay, 6e4) : void 0;
31024
+ }
31025
+ var LiveLink = class {
31026
+ options;
31027
+ #state = "disconnected";
31028
+ #socket;
31029
+ #openedAt;
31030
+ #failedConnects = 0;
31031
+ #retryAfterMs = 0;
31032
+ #reconnect;
31033
+ #pongDeadline;
31034
+ #wanted = false;
31035
+ #updateAnnounced = false;
31036
+ #reconnects = 0;
31037
+ #openListeners = /* @__PURE__ */ new Set();
31038
+ #timing;
31039
+ #random;
31040
+ #now;
31041
+ constructor(options) {
31042
+ this.options = options;
31043
+ this.#timing = { ...LIVE_LINK_TIMING, ...options.timing };
31044
+ this.#random = options.random ?? Math.random;
31045
+ this.#now = options.now ?? Date.now;
31046
+ }
31047
+ get state() {
31048
+ return this.#state;
31049
+ }
31050
+ get reconnects() {
31051
+ return this.#reconnects;
31052
+ }
31053
+ /** A suspect socket is still open for writes until its pong deadline. */
31054
+ isOpen() {
31055
+ return this.#state === "open" || this.#state === "suspect";
31056
+ }
31057
+ /** Called on every open, after the link's own open handling. */
31058
+ onOpen(listener) {
31059
+ this.#openListeners.add(listener);
31060
+ return () => this.#openListeners.delete(listener);
31061
+ }
31062
+ start() {
31063
+ if (this.#state === "update-required")
31064
+ return;
31065
+ this.#wanted = true;
31066
+ if (!this.#socket && !this.#reconnect)
31067
+ this.#connect();
31068
+ }
31069
+ stop() {
31070
+ this.#wanted = false;
31071
+ this.#clearTimers();
31072
+ const socket = this.#socket;
31073
+ this.#socket = void 0;
31074
+ if (this.#state !== "update-required")
31075
+ this.#state = "disconnected";
31076
+ socket?.close();
31077
+ }
31078
+ send(data) {
31079
+ if (!this.isOpen() || this.#socket?.readyState !== wrapper_default.OPEN)
31080
+ return false;
31081
+ this.#socket.send(data);
31082
+ return true;
31083
+ }
31084
+ /**
31085
+ * Something suggests the socket may be dead (a network-class HTTP failure,
31086
+ * an admitted fetch past its deadline, a failed reconcile). Ask the socket
31087
+ * itself: one ping, and no pong within the deadline drops it. A socket that
31088
+ * is not open is already on its way to a new one.
31089
+ */
31090
+ suspect() {
31091
+ const socket = this.#socket;
31092
+ if (this.#state !== "open" || !socket)
31093
+ return;
31094
+ this.#state = "suspect";
31095
+ try {
31096
+ socket.ping?.();
31097
+ } catch {
31098
+ }
31099
+ this.#pongDeadline = setTimeout(() => {
31100
+ this.#pongDeadline = void 0;
31101
+ if (this.#socket === socket && this.#state === "suspect")
31102
+ socket.terminate();
31103
+ }, this.#timing.pongTimeoutMs);
31104
+ this.#pongDeadline.unref?.();
31105
+ }
31106
+ /** The server requires a newer helper: stop for good and say so once. */
31107
+ requireUpdate(minVersion) {
31108
+ this.#markUpdateRequired();
31109
+ if (this.#updateAnnounced)
31110
+ return;
31111
+ this.#updateAnnounced = true;
31112
+ this.options.onUpdateRequired(minVersion);
31113
+ }
31114
+ #markUpdateRequired() {
31115
+ this.#state = "update-required";
31116
+ this.#clearTimers();
31117
+ const socket = this.#socket;
31118
+ this.#socket = void 0;
31119
+ socket?.close();
31120
+ }
31121
+ #clearTimers() {
31122
+ clearTimeout(this.#reconnect);
31123
+ this.#reconnect = void 0;
31124
+ clearTimeout(this.#pongDeadline);
31125
+ this.#pongDeadline = void 0;
31126
+ }
31127
+ #connect() {
31128
+ const socket = this.options.factory(this.options.url(), this.options.protocols(), {
31129
+ handshakeTimeout: this.#timing.handshakeTimeoutMs
31130
+ });
31131
+ this.#socket = socket;
31132
+ this.#state = "connecting";
31133
+ this.#openedAt = void 0;
31134
+ socket.on?.("upgrade", (response) => {
31135
+ response.socket?.setKeepAlive?.(true, this.#timing.keepAliveDelayMs);
31136
+ });
31137
+ socket.on?.("unexpected-response", (_request, response) => {
31138
+ if (response.statusCode !== 426) {
31139
+ this.#retryAfterMs = retryAfterMs(headerValue(response.headers?.["retry-after"])) ?? 0;
31140
+ socket.terminate();
31141
+ return;
31142
+ }
31143
+ this.#state = "update-required";
31144
+ this.#clearTimers();
31145
+ let body = "";
31146
+ response.on("data", (chunk) => {
31147
+ if (body.length < 1024)
31148
+ body += chunk.toString("utf8").slice(0, 1024 - body.length);
31149
+ });
31150
+ response.on("end", () => {
31151
+ try {
31152
+ const refusal = JSON.parse(body);
31153
+ if (refusal.error === "update_required" && typeof refusal.minVersion === "string")
31154
+ this.requireUpdate(refusal.minVersion);
31155
+ } catch {
31156
+ }
31157
+ });
31158
+ response.on("close", () => socket.terminate());
31159
+ });
31160
+ socket.on?.("pong", () => {
31161
+ if (this.#socket !== socket || this.#state !== "suspect")
31162
+ return;
31163
+ clearTimeout(this.#pongDeadline);
31164
+ this.#pongDeadline = void 0;
31165
+ this.#state = "open";
31166
+ });
31167
+ socket.onopen = () => {
31168
+ if (this.#socket !== socket)
31169
+ return;
31170
+ this.#state = "open";
31171
+ this.#openedAt = this.#now();
31172
+ this.options.onOpen();
31173
+ for (const listener of [...this.#openListeners]) {
31174
+ try {
31175
+ listener();
31176
+ } catch (error) {
31177
+ console.error("[live-link] open listener failed", error);
31178
+ }
31179
+ }
31180
+ };
31181
+ socket.onmessage = (message) => {
31182
+ if (this.#socket === socket)
31183
+ this.options.onMessage(String(message.data));
31184
+ };
31185
+ socket.onerror = () => void 0;
31186
+ socket.onclose = () => {
31187
+ if (this.#socket !== socket)
31188
+ return;
31189
+ this.#socket = void 0;
31190
+ clearTimeout(this.#pongDeadline);
31191
+ this.#pongDeadline = void 0;
31192
+ const openedAt = this.#openedAt;
31193
+ this.#openedAt = void 0;
31194
+ if (this.#state !== "update-required")
31195
+ this.#state = "disconnected";
31196
+ this.options.onClose();
31197
+ if (this.#state === "update-required" || !this.#wanted)
31198
+ return;
31199
+ this.#scheduleReconnect(openedAt);
31200
+ };
31201
+ }
31202
+ #scheduleReconnect(openedAt) {
31203
+ let delay;
31204
+ if (openedAt !== void 0 && this.#now() - openedAt >= this.#timing.stableSocketMs) {
31205
+ this.#failedConnects = 0;
31206
+ delay = this.#random() * this.#timing.stableCloseJitterMs;
31207
+ } else {
31208
+ const ceiling = Math.min(this.#timing.backoffMaxMs, this.#timing.backoffBaseMs * 2 ** this.#failedConnects);
31209
+ this.#failedConnects += 1;
31210
+ delay = this.#random() * ceiling;
31211
+ }
31212
+ delay = Math.floor(Math.max(delay, this.#retryAfterMs));
31213
+ this.#retryAfterMs = 0;
31214
+ this.#reconnects += 1;
31215
+ this.#reconnect = setTimeout(() => {
31216
+ this.#reconnect = void 0;
31217
+ if (this.#wanted && !this.#socket && this.#state !== "update-required")
31218
+ this.#connect();
31219
+ }, delay);
31220
+ this.#reconnect.unref?.();
31221
+ }
31222
+ };
31223
+ function headerValue(value) {
31224
+ return Array.isArray(value) ? value[0] : value;
31225
+ }
31226
+
30863
31227
  // apps/body/dist/daemon-api-client.js
30864
31228
  init_runtime();
30865
31229
  var DaemonApiError = class extends Error {
@@ -30908,32 +31272,19 @@ async function responseError(response) {
30908
31272
  }
30909
31273
  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
31274
  }
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
31275
  var REQUEST_DEADLINE_MS = 3e4;
30919
- var STABLE_SOCKET_MS = 3e4;
30920
31276
  var DaemonApiClient = class {
30921
31277
  baseUrl;
30922
31278
  daemonToken;
30923
31279
  agentId;
30924
31280
  fetchImpl;
30925
- webSocketFactory;
30926
31281
  presenceLifecycleId = randomUUID6();
30927
- liveSocket;
30928
- liveReconnect;
30929
- liveReconnectDelayMs = 1e3;
30930
- liveOpenedAt = 0;
31282
+ /** The one live socket; it reconnects by itself for as long as it is wanted. */
31283
+ link;
30931
31284
  inFlight = 0;
30932
31285
  requestCount = 0;
30933
31286
  timeoutCount = 0;
30934
- reconnectCount = 0;
30935
31287
  subscriptionCount = 0;
30936
- discoveryWakeSupported = false;
30937
31288
  readBudget;
30938
31289
  liveRooms = /* @__PURE__ */ new Map();
30939
31290
  roomsChangedListener;
@@ -30944,17 +31295,28 @@ var DaemonApiClient = class {
30944
31295
  cornerRestartListener;
30945
31296
  helperReleaseListener;
30946
31297
  forceUpdateListener;
30947
- forceUpdatePending = false;
30948
31298
  helperIdentity = {
30949
31299
  releaseVersion: "v0.0.0"
30950
31300
  };
30951
- constructor(baseUrl, daemonToken, agentId, fetchImpl = fetch, webSocketFactory = (url, protocols) => new wrapper_default(url, protocols), readBudget) {
31301
+ constructor(baseUrl, daemonToken, agentId, fetchImpl = fetch, webSocketFactory = (url, protocols, options) => new wrapper_default(url, protocols, options), readBudget, linkTiming) {
30952
31302
  this.baseUrl = baseUrl;
30953
31303
  this.daemonToken = daemonToken;
30954
31304
  this.agentId = agentId;
30955
31305
  this.fetchImpl = fetchImpl;
30956
- this.webSocketFactory = webSocketFactory;
30957
31306
  this.readBudget = readBudget ?? (fetchImpl === fetch ? new HostReadBudget(baseUrl) : void 0);
31307
+ this.link = new LiveLink({
31308
+ url: () => this.liveUrl(),
31309
+ protocols: () => [`bearer.${this.daemonToken}`],
31310
+ factory: webSocketFactory,
31311
+ onOpen: () => this.liveOpened(),
31312
+ onMessage: (data) => this.liveMessage(data),
31313
+ onClose: () => {
31314
+ for (const room of this.liveRooms.values())
31315
+ room.onState?.(false);
31316
+ },
31317
+ onUpdateRequired: (minVersion) => this.forceUpdateListener?.(minVersion),
31318
+ ...linkTiming ? { timing: linkTiming } : {}
31319
+ });
30958
31320
  }
30959
31321
  /** Connection material for the daemon-owned MCP proxy and corner credentials. */
30960
31322
  connection() {
@@ -30980,24 +31342,15 @@ var DaemonApiClient = class {
30980
31342
  inFlight: this.inFlight,
30981
31343
  requests: this.requestCount,
30982
31344
  timeouts: this.timeoutCount,
30983
- reconnects: this.reconnectCount,
31345
+ reconnects: this.link.reconnects,
30984
31346
  subscriptionsSent: this.subscriptionCount,
30985
31347
  readQueueDepth: budget?.waiting ?? 0,
30986
31348
  readQueueWaitMs: budget?.totalWaitMs ?? 0
30987
31349
  };
30988
31350
  }
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();
31351
+ /** Called on every live socket open, after resubscription and the wakes. */
31352
+ onLiveOpen(listener) {
31353
+ return this.link.onOpen(listener);
31001
31354
  }
31002
31355
  /** Add one Room to this agent's shared live socket. */
31003
31356
  liveSubscribe(roomId, cursor3, onItems, onState, presence, onCommands) {
@@ -31020,18 +31373,12 @@ var DaemonApiClient = class {
31020
31373
  });
31021
31374
  }
31022
31375
  this.ensureLiveSocket();
31023
- if (this.liveSocket?.readyState === wrapper_default.OPEN)
31024
- this.sendLiveSubscription(roomId);
31376
+ this.sendLiveSubscription(roomId);
31025
31377
  return () => {
31026
31378
  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
- }
31379
+ this.link.send(JSON.stringify({ type: "unsubscribe", roomId }));
31380
+ if (!this.liveRooms.size && !this.roomsChangedListener)
31381
+ this.link.stop();
31035
31382
  };
31036
31383
  }
31037
31384
  /** Register the one listener invoked when the server reports this agent's
@@ -31072,17 +31419,9 @@ var DaemonApiClient = class {
31072
31419
  if (room && cursor3)
31073
31420
  room.cursor = cursor3;
31074
31421
  }
31075
- /** Retry an uncertain discovery read through socket reconnect backoff. */
31076
- reconnectLive() {
31077
- this.liveSocket?.close();
31078
- }
31079
31422
  /** Release the long-lived socket when the helper itself is shutting down. */
31080
31423
  closeLive() {
31081
- clearTimeout(this.liveReconnect);
31082
- this.liveReconnect = void 0;
31083
- const socket = this.liveSocket;
31084
- this.liveSocket = void 0;
31085
- socket?.close();
31424
+ this.link.stop();
31086
31425
  }
31087
31426
  async execute(name, input) {
31088
31427
  const candidate = input;
@@ -31116,7 +31455,7 @@ var DaemonApiClient = class {
31116
31455
  if (!response.ok) {
31117
31456
  const error = await responseError(response);
31118
31457
  if (error.status === 426 && error.code === "update_required" && error.minVersion)
31119
- this.requestForceUpdate(error.minVersion);
31458
+ this.link.requireUpdate(error.minVersion);
31120
31459
  throw error;
31121
31460
  }
31122
31461
  output = await response.json();
@@ -31126,8 +31465,12 @@ var DaemonApiClient = class {
31126
31465
  }
31127
31466
  if (controller.signal.aborted) {
31128
31467
  this.timeoutCount += 1;
31468
+ if (admitted)
31469
+ this.link.suspect();
31129
31470
  throw new DaemonApiError(`monolith daemon ${String(name)} timed out`, 408, true, "deadline_exceeded");
31130
31471
  }
31472
+ if (isNetworkFailure(error))
31473
+ this.link.suspect();
31131
31474
  throw error;
31132
31475
  } finally {
31133
31476
  clearTimeout(deadline);
@@ -31138,171 +31481,121 @@ var DaemonApiClient = class {
31138
31481
  return output;
31139
31482
  }
31140
31483
  ensureLiveSocket() {
31141
- if (this.forceUpdatePending || this.liveSocket || !this.liveRooms.size && !this.roomsChangedListener)
31142
- return;
31484
+ if (this.liveRooms.size || this.roomsChangedListener)
31485
+ this.link.start();
31486
+ }
31487
+ liveUrl() {
31143
31488
  const liveUrl = new URL("/v1/phone/live", this.baseUrl);
31144
31489
  liveUrl.protocol = liveUrl.protocol === "https:" ? "wss:" : "ws:";
31145
31490
  liveUrl.searchParams.set("helperVersion", this.helperIdentity.releaseVersion);
31146
31491
  if (this.helperIdentity.sourceSha)
31147
31492
  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);
31493
+ return liveUrl.toString();
31494
+ }
31495
+ liveOpened() {
31496
+ for (const roomId of this.liveRooms.keys())
31497
+ this.sendLiveSubscription(roomId);
31498
+ this.roomsChangedListener?.();
31499
+ this.connectorAssignmentListener?.();
31500
+ this.memoryJobListener?.();
31501
+ }
31502
+ liveMessage(data) {
31503
+ let value;
31504
+ try {
31505
+ value = JSON.parse(data);
31506
+ } catch {
31507
+ return;
31508
+ }
31509
+ if (!value || typeof value !== "object")
31510
+ return;
31511
+ const event = value;
31512
+ if (event.type === "force-update" && typeof event.minVersion === "string") {
31513
+ this.link.requireUpdate(event.minVersion);
31514
+ return;
31515
+ }
31516
+ if (event.type === "helper-release" && typeof event.version === "string" && typeof event.sha === "string") {
31517
+ this.helperReleaseListener?.({ version: event.version, sha: event.sha });
31518
+ return;
31519
+ }
31520
+ if (event.type === "rooms-changed") {
31521
+ this.roomsChangedListener?.(membershipChange(event));
31522
+ return;
31523
+ }
31524
+ if (event.type === "discovery-wake" && (event.reason === "listener-resync" || event.reason === "database-recovered")) {
31177
31525
  this.roomsChangedListener?.();
31526
+ return;
31527
+ }
31528
+ if (event.type === "corner-complete" && typeof event.roomId === "string") {
31529
+ this.cornerCompleteListener?.(event.roomId);
31530
+ return;
31531
+ }
31532
+ if (event.type === "corner-restart" && typeof event.roomId === "string") {
31533
+ this.cornerRestartListener?.(event.roomId);
31534
+ return;
31535
+ }
31536
+ if (event.type === "connector-assignment") {
31178
31537
  this.connectorAssignmentListener?.();
31538
+ return;
31539
+ }
31540
+ if (event.type === "memory-job") {
31179
31541
  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)
31542
+ return;
31543
+ }
31544
+ if (event.type === "config-changed") {
31545
+ this.configChangedListener?.();
31546
+ return;
31547
+ }
31548
+ if (event.type === "subscribed" && typeof event.roomId === "string") {
31549
+ const capabilities = event.capabilities;
31550
+ this.liveRooms.get(event.roomId)?.onState?.(true, {
31551
+ pushIntake: capabilities?.pushIntake === true,
31552
+ connectionPresence: capabilities?.connectionPresence === true
31553
+ });
31554
+ return;
31555
+ }
31556
+ if (event.type === "commands" && typeof event.roomId === "string" && event.commandProtocol === 1 && Array.isArray(event.commands)) {
31557
+ const room2 = this.liveRooms.get(event.roomId);
31558
+ if (!room2)
31263
31559
  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
- }
31560
+ const commands = event.commands.filter((command) => {
31561
+ if (!isAgentCommand(command) || command.roomId !== event.roomId || command.agentId !== this.agentId || room2.pushedCommandIds.has(command.id))
31562
+ return false;
31563
+ room2.pushedCommandIds.add(command.id);
31564
+ return true;
31565
+ });
31566
+ while (room2.pushedCommandIds.size > 1e4)
31567
+ room2.pushedCommandIds.delete(room2.pushedCommandIds.values().next().value);
31568
+ if (commands.length)
31569
+ room2.onCommands?.(commands);
31570
+ return;
31571
+ }
31572
+ if (event.type !== "inbox" || typeof event.roomId !== "string" || !Array.isArray(event.items))
31573
+ return;
31574
+ const room = this.liveRooms.get(event.roomId);
31575
+ if (!room)
31576
+ return;
31577
+ const items = [];
31578
+ for (const candidate of event.items) {
31579
+ if (!candidate || typeof candidate !== "object")
31580
+ continue;
31581
+ const id = candidate.id;
31582
+ if (typeof id === "string") {
31583
+ if (!room.pushedIds.has(id))
31584
+ items.push(candidate);
31585
+ room.pushedIds.add(id);
31274
31586
  }
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;
31587
+ }
31588
+ while (room.pushedIds.size > 1e4)
31589
+ room.pushedIds.delete(room.pushedIds.values().next().value);
31590
+ if (items.length)
31591
+ room.onItems?.(items, typeof event.cursor === "string" ? event.cursor : void 0);
31299
31592
  }
31300
31593
  sendLiveSubscription(roomId) {
31301
31594
  const room = this.liveRooms.get(roomId);
31302
- if (!room || this.liveSocket?.readyState !== wrapper_default.OPEN)
31595
+ if (!room || !this.link.isOpen())
31303
31596
  return;
31304
31597
  this.subscriptionCount += 1;
31305
- this.liveSocket.send(JSON.stringify({
31598
+ this.link.send(JSON.stringify({
31306
31599
  type: "subscribe",
31307
31600
  roomId,
31308
31601
  lifecycleId: this.presenceLifecycleId,
@@ -31363,7 +31656,7 @@ import { promisify as promisify8 } from "node:util";
31363
31656
  // apps/body/dist/grant-runner.js
31364
31657
  import { execFile as execFile8 } from "node:child_process";
31365
31658
  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";
31659
+ 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
31660
  import { createServer as createServer3 } from "node:http";
31368
31661
  import { dirname as dirname12, resolve as resolve16 } from "node:path";
31369
31662
 
@@ -32058,7 +32351,7 @@ async function ensureBwrapSandbox(options = {}) {
32058
32351
  }
32059
32352
 
32060
32353
  // 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";
32354
+ import { chmod as chmod5, mkdir as mkdir9, readFile as readFile13, writeFile as writeFile9 } from "node:fs/promises";
32062
32355
  import { homedir as homedir12 } from "node:os";
32063
32356
  import { dirname as dirname11, resolve as resolve15 } from "node:path";
32064
32357
  var PROVIDER_KEY_ENV_VARS = {
@@ -32075,7 +32368,7 @@ function providerKeyStorePath(env = process.env) {
32075
32368
  }
32076
32369
  async function readProviderKeyStore(env = process.env) {
32077
32370
  const path = providerKeyStorePath(env);
32078
- const raw = await readFile12(path, "utf8").catch(() => void 0);
32371
+ const raw = await readFile13(path, "utf8").catch(() => void 0);
32079
32372
  if (!raw)
32080
32373
  return {};
32081
32374
  try {
@@ -32127,7 +32420,7 @@ function operatorSecretsPath(env = process.env) {
32127
32420
  return resolve16(dirname12(providerKeyStorePath(env)), "secrets.json");
32128
32421
  }
32129
32422
  async function readOperatorSecrets(env = process.env) {
32130
- const raw = await readFile13(operatorSecretsPath(env), "utf8").catch(() => void 0);
32423
+ const raw = await readFile14(operatorSecretsPath(env), "utf8").catch(() => void 0);
32131
32424
  if (!raw)
32132
32425
  return {};
32133
32426
  try {
@@ -32339,7 +32632,7 @@ ${ROOM_WRITE_REFUSED_NOTE}` : outcome.output, secrets), cap);
32339
32632
  return;
32340
32633
  let bytes;
32341
32634
  for (const candidate of candidates) {
32342
- bytes = await readFile13(candidate).catch(() => void 0);
32635
+ bytes = await readFile14(candidate).catch(() => void 0);
32343
32636
  if (bytes)
32344
32637
  break;
32345
32638
  }
@@ -32541,10 +32834,10 @@ function captureConnectionUsage(recorder, turn, calls) {
32541
32834
  }
32542
32835
 
32543
32836
  // apps/body/dist/turn-usage.js
32544
- import { readFile as readFile15 } from "node:fs/promises";
32837
+ import { readFile as readFile16 } from "node:fs/promises";
32545
32838
 
32546
32839
  // apps/body/dist/pi-turn-record.js
32547
- import { readdir as readdir5, readFile as readFile14 } from "node:fs/promises";
32840
+ import { readdir as readdir5, readFile as readFile15 } from "node:fs/promises";
32548
32841
  import { resolve as resolve17 } from "node:path";
32549
32842
  function summarizeProviderError(errorMessage2) {
32550
32843
  const trimmed = errorMessage2.trim();
@@ -32572,7 +32865,7 @@ async function piSessionFilePath(agentEnv, sessionId) {
32572
32865
  }
32573
32866
  async function sessionFileFromMap(home, sessionId) {
32574
32867
  try {
32575
- const raw = await readFile14(resolve17(home, ".pi", "pi-acp", "session-map.json"), "utf8");
32868
+ const raw = await readFile15(resolve17(home, ".pi", "pi-acp", "session-map.json"), "utf8");
32576
32869
  const map = JSON.parse(raw);
32577
32870
  const file = map.sessions?.[sessionId]?.sessionFile;
32578
32871
  return typeof file === "string" && file ? file : void 0;
@@ -32614,7 +32907,7 @@ async function readPiTurnRecord(input) {
32614
32907
  return void 0;
32615
32908
  let raw;
32616
32909
  try {
32617
- raw = await readFile14(file, "utf8");
32910
+ raw = await readFile15(file, "utf8");
32618
32911
  } catch {
32619
32912
  return void 0;
32620
32913
  }
@@ -32675,7 +32968,7 @@ async function readHarnessTurnUsage(input) {
32675
32968
  return void 0;
32676
32969
  let raw;
32677
32970
  try {
32678
- raw = await readFile15(file, "utf8");
32971
+ raw = await readFile16(file, "utf8");
32679
32972
  } catch {
32680
32973
  return void 0;
32681
32974
  }
@@ -32717,13 +33010,13 @@ import { randomUUID as randomUUID8 } from "node:crypto";
32717
33010
  import { mkdir as mkdir11, writeFile as writeFile11 } from "node:fs/promises";
32718
33011
  import { writeFileSync as writeFileSync3 } from "node:fs";
32719
33012
  import { tmpdir as tmpdir5 } from "node:os";
32720
- import { dirname as dirname13, join as join10 } from "node:path";
33013
+ import { dirname as dirname13, join as join11 } from "node:path";
32721
33014
  var CommandExecutionContext = class {
32722
33015
  generationId = randomUUID8();
32723
33016
  path;
32724
33017
  current;
32725
33018
  constructor(root) {
32726
- this.path = join10(root ?? tmpdir5(), `beeline-command-${this.generationId}.json`);
33019
+ this.path = join11(root ?? tmpdir5(), `beeline-command-${this.generationId}.json`);
32727
33020
  }
32728
33021
  async enter(command) {
32729
33022
  this.current = command;
@@ -32831,6 +33124,7 @@ async function runServerCommandIntake(options) {
32831
33124
  requestReconciliation();
32832
33125
  hasSubscribed = true;
32833
33126
  }, options.presence, notify2);
33127
+ let failed = false;
32834
33128
  try {
32835
33129
  while (!signal?.aborted) {
32836
33130
  if (reconciliationError)
@@ -32908,22 +33202,36 @@ async function runServerCommandIntake(options) {
32908
33202
  if (reconcile)
32909
33203
  requestReconciliation();
32910
33204
  }
33205
+ } catch (error) {
33206
+ failed = true;
33207
+ throw error;
32911
33208
  } finally {
32912
33209
  stopped = true;
32913
33210
  off?.();
32914
33211
  options.onWake?.(void 0);
32915
- if (context.current)
32916
- options.stop(context.current.turnRequestId);
32917
- await busy;
33212
+ const current = context.current;
33213
+ const stopCurrent = () => {
33214
+ if (current)
33215
+ options.stop(current.turnRequestId);
33216
+ };
33217
+ if (!failed || signal?.aborted)
33218
+ stopCurrent();
33219
+ else
33220
+ signal?.addEventListener("abort", stopCurrent, { once: true });
33221
+ try {
33222
+ await busy;
33223
+ } finally {
33224
+ signal?.removeEventListener("abort", stopCurrent);
33225
+ }
32918
33226
  }
32919
33227
  }
32920
33228
 
32921
33229
  // apps/body/dist/monolith-corner-turn.js
32922
33230
  import { execFile as execFile11 } from "node:child_process";
32923
33231
  import { createHash as createHash10 } from "node:crypto";
32924
- import { mkdir as mkdir20, readFile as readFile21, writeFile as writeFile16 } from "node:fs/promises";
33232
+ import { mkdir as mkdir20, readFile as readFile22, writeFile as writeFile16 } from "node:fs/promises";
32925
33233
  import { homedir as homedir15, hostname as hostname2 } from "node:os";
32926
- import { dirname as dirname18, join as join16, resolve as resolve28 } from "node:path";
33234
+ import { dirname as dirname18, join as join17, resolve as resolve28 } from "node:path";
32927
33235
  import { promisify as promisify7 } from "node:util";
32928
33236
 
32929
33237
  // apps/body/dist/agent-home.js
@@ -33726,9 +34034,9 @@ function stringify(obj, { maxDepth = 1e3, numbersAsFloat = false } = {}) {
33726
34034
 
33727
34035
  // apps/body/dist/agent-home.js
33728
34036
  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";
34037
+ 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
34038
  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";
34039
+ import { basename as basename6, dirname as dirname14, join as join13, relative as relative2, resolve as resolve21, sep } from "node:path";
33732
34040
 
33733
34041
  // apps/body/dist/draw-avatar-skill.js
33734
34042
  var DRAW_AVATAR_SKILL_NAME = "draw-avatar";
@@ -33852,14 +34160,235 @@ function isAgentPairingCode(value) {
33852
34160
  // packages/api-contract/dist/phone.js
33853
34161
  init_agent_access();
33854
34162
 
34163
+ // packages/api-contract/dist/message-search.js
34164
+ var MESSAGE_SEARCH_FILLER_WORDS = [
34165
+ // Articles and determiners
34166
+ "a",
34167
+ "an",
34168
+ "the",
34169
+ "this",
34170
+ "that",
34171
+ "these",
34172
+ "those",
34173
+ "some",
34174
+ "any",
34175
+ "each",
34176
+ "every",
34177
+ "all",
34178
+ "both",
34179
+ "either",
34180
+ "neither",
34181
+ "much",
34182
+ "many",
34183
+ "more",
34184
+ "most",
34185
+ "such",
34186
+ "other",
34187
+ "another",
34188
+ "own",
34189
+ "same",
34190
+ "no",
34191
+ "not",
34192
+ "only",
34193
+ "very",
34194
+ // Pronouns
34195
+ "i",
34196
+ "me",
34197
+ "my",
34198
+ "mine",
34199
+ "myself",
34200
+ "you",
34201
+ "your",
34202
+ "yours",
34203
+ "yourself",
34204
+ "he",
34205
+ "him",
34206
+ "his",
34207
+ "himself",
34208
+ "she",
34209
+ "her",
34210
+ "hers",
34211
+ "herself",
34212
+ "it",
34213
+ "its",
34214
+ "itself",
34215
+ "we",
34216
+ "us",
34217
+ "our",
34218
+ "ours",
34219
+ "ourselves",
34220
+ "they",
34221
+ "them",
34222
+ "their",
34223
+ "theirs",
34224
+ "themselves",
34225
+ "who",
34226
+ "whom",
34227
+ "whose",
34228
+ "which",
34229
+ "what",
34230
+ "someone",
34231
+ "something",
34232
+ "anyone",
34233
+ "anything",
34234
+ "everyone",
34235
+ "everything",
34236
+ // Auxiliaries
34237
+ "am",
34238
+ "is",
34239
+ "are",
34240
+ "was",
34241
+ "were",
34242
+ "be",
34243
+ "been",
34244
+ "being",
34245
+ "have",
34246
+ "has",
34247
+ "had",
34248
+ "having",
34249
+ "do",
34250
+ "does",
34251
+ "did",
34252
+ "doing",
34253
+ "done",
34254
+ "will",
34255
+ "would",
34256
+ "shall",
34257
+ "should",
34258
+ "can",
34259
+ "could",
34260
+ "might",
34261
+ "must",
34262
+ "get",
34263
+ "gets",
34264
+ "got",
34265
+ // Contraction pieces
34266
+ "s",
34267
+ "t",
34268
+ "d",
34269
+ "ll",
34270
+ "m",
34271
+ "re",
34272
+ "ve",
34273
+ "don",
34274
+ "doesn",
34275
+ "didn",
34276
+ "isn",
34277
+ "aren",
34278
+ "wasn",
34279
+ "weren",
34280
+ "haven",
34281
+ "hasn",
34282
+ "hadn",
34283
+ "won",
34284
+ "wouldn",
34285
+ "couldn",
34286
+ "shouldn",
34287
+ "cannot",
34288
+ // Conjunctions
34289
+ "and",
34290
+ "or",
34291
+ "but",
34292
+ "nor",
34293
+ "so",
34294
+ "yet",
34295
+ "if",
34296
+ "then",
34297
+ "than",
34298
+ "because",
34299
+ "as",
34300
+ "while",
34301
+ "though",
34302
+ "although",
34303
+ "unless",
34304
+ "until",
34305
+ "whether",
34306
+ "also",
34307
+ // Prepositions
34308
+ "of",
34309
+ "to",
34310
+ "in",
34311
+ "on",
34312
+ "at",
34313
+ "by",
34314
+ "for",
34315
+ "with",
34316
+ "from",
34317
+ "into",
34318
+ "onto",
34319
+ "about",
34320
+ "above",
34321
+ "below",
34322
+ "over",
34323
+ "under",
34324
+ "after",
34325
+ "before",
34326
+ "between",
34327
+ "through",
34328
+ "during",
34329
+ "without",
34330
+ "within",
34331
+ "again",
34332
+ "against",
34333
+ "upon",
34334
+ "via",
34335
+ "per",
34336
+ "around",
34337
+ // Adverbs
34338
+ "here",
34339
+ "there",
34340
+ "where",
34341
+ "when",
34342
+ "why",
34343
+ "how",
34344
+ "now",
34345
+ "just",
34346
+ "too",
34347
+ "still",
34348
+ "even",
34349
+ "ever",
34350
+ "never",
34351
+ "always",
34352
+ "really",
34353
+ "maybe",
34354
+ "already",
34355
+ "soon",
34356
+ // Chat filler
34357
+ "ok",
34358
+ "okay",
34359
+ "yes",
34360
+ "yeah",
34361
+ "yep",
34362
+ "yup",
34363
+ "nope",
34364
+ "hi",
34365
+ "hey",
34366
+ "hello",
34367
+ "thanks",
34368
+ "thank",
34369
+ "thx",
34370
+ "please",
34371
+ "pls",
34372
+ "like",
34373
+ "gonna",
34374
+ "wanna",
34375
+ "kinda",
34376
+ "lol",
34377
+ "oh",
34378
+ "ah",
34379
+ "um",
34380
+ "uh",
34381
+ "hmm",
34382
+ "oops",
34383
+ "anyway"
34384
+ ];
34385
+ var FILLER_WORDS = new Set(MESSAGE_SEARCH_FILLER_WORDS);
34386
+
33855
34387
  // apps/body/dist/beeline-skill.js
33856
34388
  var USING_BEELINE_SKILL_NAME = "using-beeline";
33857
34389
  var BEELINE_TRIAGE_SKILL_NAME = "beeline-triage";
33858
34390
  var BEELINE_REVIEW_SKILL_NAME = "beeline-review";
33859
34391
  var BEELINE_SPEC_SKILL_NAME = "beeline-spec";
33860
- function isConfiguredReviewer(agentHandle, reviewerHandle) {
33861
- return Boolean(agentHandle && reviewerHandle && agentHandle.replace(/^@/, "") === reviewerHandle.replace(/^@/, ""));
33862
- }
33863
34392
  var BEELINE_ROOM_MECHANICS = [
33864
34393
  "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
34394
  "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 +34553,7 @@ Only the author may report final_authorization as passed, after pr_checks_status
34024
34553
 
34025
34554
  Then take exactly one action:
34026
34555
 
34027
- - FAIL: reply \`@author\` with the confirmed findings to fix.
34556
+ - 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
34557
  - 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
34558
  - 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
34559
  `;
@@ -34194,7 +34723,7 @@ var SQUIRE_GOVERNED_TOOLS = [
34194
34723
  var SQUIRE_GOVERNED_TOOL_SET = new Set(SQUIRE_GOVERNED_TOOLS);
34195
34724
 
34196
34725
  // apps/body/dist/host-mcp-route.js
34197
- import { join as join11, resolve as resolve19 } from "node:path";
34726
+ import { join as join12, resolve as resolve19 } from "node:path";
34198
34727
  var import_yaml = __toESM(require_dist(), 1);
34199
34728
  init_squire_host();
34200
34729
  function grantedMcpServerNames(grants) {
@@ -34249,7 +34778,7 @@ function grantedSquireHostRouteNames(granted, declarations = {}) {
34249
34778
  });
34250
34779
  }
34251
34780
  function hostRouteEnv(hostHome) {
34252
- return { XDG_CONFIG_HOME: join11(resolve19(hostHome), ".config") };
34781
+ return { XDG_CONFIG_HOME: join12(resolve19(hostHome), ".config") };
34253
34782
  }
34254
34783
  function rewriteHostMcpDeclaration(name, declaration, hostHome, resourceAuthFile, squireScope) {
34255
34784
  const next = { ...declaration };
@@ -34368,7 +34897,7 @@ function mergeGooseHostRoutes(existing, routes) {
34368
34897
  init_squire_host();
34369
34898
 
34370
34899
  // apps/body/dist/openrouter-routing.js
34371
- import { mkdir as mkdir12, readFile as readFile16, writeFile as writeFile12 } from "node:fs/promises";
34900
+ import { mkdir as mkdir12, readFile as readFile17, writeFile as writeFile12 } from "node:fs/promises";
34372
34901
  import { resolve as resolve20 } from "node:path";
34373
34902
  var OPENROUTER_ENDPOINTS_BASE_URL = "https://openrouter.ai/api/v1/models";
34374
34903
  var OPENROUTER_COMPLETIONS_URL = "https://openrouter.ai/api/v1/chat/completions";
@@ -34496,7 +35025,7 @@ function cachePath(cacheDir, model) {
34496
35025
  }
34497
35026
  async function readCache(cacheDir, model) {
34498
35027
  try {
34499
- const parsed = JSON.parse(await readFile16(cachePath(cacheDir, model), "utf8"));
35028
+ const parsed = JSON.parse(await readFile17(cachePath(cacheDir, model), "utf8"));
34500
35029
  if (!parsed || typeof parsed !== "object")
34501
35030
  return void 0;
34502
35031
  const cached = parsed;
@@ -34539,7 +35068,7 @@ function probeCachePath(cacheDir, model) {
34539
35068
  }
34540
35069
  async function readProbeCache(cacheDir, model) {
34541
35070
  try {
34542
- const parsed = JSON.parse(await readFile16(probeCachePath(cacheDir, model), "utf8"));
35071
+ const parsed = JSON.parse(await readFile17(probeCachePath(cacheDir, model), "utf8"));
34543
35072
  if (!parsed || typeof parsed !== "object")
34544
35073
  return void 0;
34545
35074
  const cached = parsed;
@@ -35090,10 +35619,10 @@ function claudeLoginExpiresAt(text2) {
35090
35619
  }
35091
35620
  }
35092
35621
  async function hasNewerDetachedClaudeLogin(detached, source) {
35093
- const detachedExpiresAt = claudeLoginExpiresAt(await readFile17(detached, "utf8"));
35622
+ const detachedExpiresAt = claudeLoginExpiresAt(await readFile18(detached, "utf8"));
35094
35623
  if (detachedExpiresAt === void 0)
35095
35624
  return false;
35096
- const sourceExpiresAt = claudeLoginExpiresAt(await readFile17(source, "utf8"));
35625
+ const sourceExpiresAt = claudeLoginExpiresAt(await readFile18(source, "utf8"));
35097
35626
  return sourceExpiresAt !== void 0 && detachedExpiresAt > sourceExpiresAt;
35098
35627
  }
35099
35628
  async function repairRoomAgentCredentialLinks(input) {
@@ -35120,7 +35649,7 @@ async function repairRoomAgentCredentialLinks(input) {
35120
35649
  continue;
35121
35650
  }
35122
35651
  const quarantine = `${target}.beeline-quarantine-${Date.now()}-${randomUUID9()}`;
35123
- await rename6(target, quarantine);
35652
+ await rename7(target, quarantine);
35124
35653
  }
35125
35654
  await symlink2(source, target);
35126
35655
  }
@@ -35600,7 +36129,7 @@ async function provisionManagedSkillsDir(target, managedSkills, sharedSkills, op
35600
36129
  const existing = await lstat3(target).catch(() => void 0);
35601
36130
  if (existing)
35602
36131
  await rm7(target, { recursive: existing.isDirectory(), force: true });
35603
- await rename6(staged, target);
36132
+ await rename7(staged, target);
35604
36133
  } finally {
35605
36134
  await rm7(staged, { recursive: true, force: true });
35606
36135
  }
@@ -35621,8 +36150,8 @@ async function planManagedSkills(managedSkills, sharedSkills, optionalShares) {
35621
36150
  try {
35622
36151
  const tree = [];
35623
36152
  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))}`)
36153
+ directory: async (rel) => void tree.push(`d ${join13(shared.name, rel)}`),
36154
+ file: async (rel, realPath) => void tree.push(`f ${join13(shared.name, rel)} ${sha2563(await readFile18(realPath))}`)
35626
36155
  });
35627
36156
  entries.push({ kind: "shared", name: shared.name, source: shared.source });
35628
36157
  lines.push(...tree);
@@ -35642,7 +36171,7 @@ async function materializedSkillManifest(target) {
35642
36171
  const visit = async (directory, prefix) => {
35643
36172
  for (const entry of await readdir6(directory)) {
35644
36173
  const path = resolve21(directory, entry);
35645
- const rel = prefix ? join12(prefix, entry) : entry;
36174
+ const rel = prefix ? join13(prefix, entry) : entry;
35646
36175
  const entryStats = await lstat3(path);
35647
36176
  if (entryStats.isSymbolicLink())
35648
36177
  return false;
@@ -35654,7 +36183,7 @@ async function materializedSkillManifest(target) {
35654
36183
  }
35655
36184
  if (!entryStats.isFile() || entryStats.nlink !== 1)
35656
36185
  return false;
35657
- lines.push(`f ${rel} ${sha2563(await readFile17(path))}`);
36186
+ lines.push(`f ${rel} ${sha2563(await readFile18(path))}`);
35658
36187
  }
35659
36188
  return true;
35660
36189
  };
@@ -35810,7 +36339,7 @@ async function walkSafeSkillTree(source, sourceRoot, visitor, rel = "") {
35810
36339
  for (const entry of await readdir6(resolvedSource)) {
35811
36340
  if (entry === "." || entry === "..")
35812
36341
  throw new Error("invalid shared skill entry");
35813
- await walkSafeSkillTree(resolve21(source, entry), sourceRoot, visitor, rel ? join12(rel, entry) : entry);
36342
+ await walkSafeSkillTree(resolve21(source, entry), sourceRoot, visitor, rel ? join13(rel, entry) : entry);
35814
36343
  }
35815
36344
  return;
35816
36345
  }
@@ -35841,7 +36370,7 @@ async function writeIsolatedHarnessFile(path, content) {
35841
36370
  try {
35842
36371
  await writeFile13(temporary, content, { mode: 384, flag: "wx" });
35843
36372
  await chmod7(temporary, 384);
35844
- await rename6(temporary, path);
36373
+ await rename7(temporary, path);
35845
36374
  } finally {
35846
36375
  await unlink5(temporary).catch(() => void 0);
35847
36376
  }
@@ -36832,7 +37361,7 @@ function readOnlyMcpServer(config, cwd, agentMemoryDir) {
36832
37361
 
36833
37362
  // apps/body/dist/codegraph.js
36834
37363
  import { execFile as execFile10 } from "node:child_process";
36835
- import { access as access2, appendFile, mkdir as mkdir15, readFile as readFile18 } from "node:fs/promises";
37364
+ import { access as access2, appendFile, mkdir as mkdir15, readFile as readFile19 } from "node:fs/promises";
36836
37365
  import { dirname as dirname15, isAbsolute as isAbsolute3, resolve as resolve24 } from "node:path";
36837
37366
  import { promisify as promisify6 } from "node:util";
36838
37367
  var execFileAsync6 = promisify6(execFile10);
@@ -36882,7 +37411,7 @@ async function excludeIndexFromGitStatus(cwd) {
36882
37411
  return;
36883
37412
  const path = isAbsolute3(gitPath) ? gitPath : resolve24(cwd, gitPath);
36884
37413
  await mkdir15(dirname15(path), { recursive: true });
36885
- const existing = await readFile18(path, "utf8").catch(() => "");
37414
+ const existing = await readFile19(path, "utf8").catch(() => "");
36886
37415
  if (existing.split("\n").includes(".codegraph/"))
36887
37416
  return;
36888
37417
  await appendFile(path, `${existing && !existing.endsWith("\n") ? "\n" : ""}.codegraph/
@@ -36918,20 +37447,20 @@ init_cursor_acp_bridge();
36918
37447
  // apps/body/dist/monolith-room-turn.js
36919
37448
  import { mkdir as mkdir17, writeFile as writeFile14 } from "node:fs/promises";
36920
37449
  import { homedir as homedir14, hostname } from "node:os";
36921
- import { dirname as dirname16, join as join14 } from "node:path";
37450
+ import { dirname as dirname16, join as join15 } from "node:path";
36922
37451
 
36923
37452
  // packages/api-contract/dist/scheduled-prompts.js
36924
37453
  var SCHEDULE_SCHEDULER_NAME = "Beeline Scheduler";
36925
37454
  var SCHEDULE_RAN_VERB = "ran a schedule for";
36926
37455
 
36927
37456
  // apps/body/dist/model-context-window.js
36928
- import { readFile as readFile19 } from "node:fs/promises";
36929
- import { join as join13 } from "node:path";
37457
+ import { readFile as readFile20 } from "node:fs/promises";
37458
+ import { join as join14 } from "node:path";
36930
37459
  async function modelContextWindowTokens(model, piHome) {
36931
37460
  if (!model || !piHome)
36932
37461
  return void 0;
36933
37462
  try {
36934
- const catalog = JSON.parse(await readFile19(join13(piHome, "models.json"), "utf8"));
37463
+ const catalog = JSON.parse(await readFile20(join14(piHome, "models.json"), "utf8"));
36935
37464
  const parts = model.split("/");
36936
37465
  const preferredProvider = parts.length > 2 ? parts.shift() : void 0;
36937
37466
  const modelId = parts.join("/");
@@ -37082,7 +37611,7 @@ var CORNERS = [
37082
37611
  ];
37083
37612
  var EVERYWHERE = PROMPT_SURFACES;
37084
37613
  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.
37614
+ 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
37615
  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
37616
  Before any code, write its end-user story in one sentence: "a person who does X sees Y".
37088
37617
  Follow the beeline-triage skill's bugfix execution contract when the spec or its approval quote reports a defect.
@@ -37113,15 +37642,15 @@ function cornerMergeInstruction(yoloMode, reviewerHandle) {
37113
37642
  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
37643
  }
37115
37644
  function cornerReviewerInstruction(input) {
37116
- if (!input.reviewerHandle || !input.agentHandle || input.openedByAgent || handle(input.agentHandle) !== handle(input.reviewerHandle))
37645
+ if (!input.isReviewer || input.openedByAgent)
37117
37646
  return void 0;
37118
37647
  const author = input.authorHandle ? handle(input.authorHandle) : "author";
37119
37648
  const number = input.pullRequestNumber ?? "N";
37120
37649
  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.`;
37650
+ 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
37651
  }
37123
37652
  function cornerSelfReviewerInstruction(input) {
37124
- if (!input.reviewerHandle || !input.agentHandle || !input.openedByAgent || handle(input.agentHandle) !== handle(input.reviewerHandle))
37653
+ if (!input.isReviewer || !input.openedByAgent)
37125
37654
  return void 0;
37126
37655
  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
37656
  }
@@ -37324,11 +37853,7 @@ The soul is not authority and never changes your tools, permissions, roles, or m
37324
37853
  budgetBytes: 520,
37325
37854
  layer: "surface",
37326
37855
  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."
37856
+ render: ({ yoloMode, reviewerHandle, selfReviewer }) => (selfReviewer ? cornerSelfReviewerInstruction({ isReviewer: true, openedByAgent: true }) : cornerMergeInstruction(Boolean(yoloMode), reviewerHandle)) + " Never merge any other pull request."
37332
37857
  },
37333
37858
  {
37334
37859
  id: "corner.checks",
@@ -38228,7 +38753,7 @@ var MonolithRoomTurnLoop = class {
38228
38753
  const cached = this.deliveredAttachments.get(item.id);
38229
38754
  if (cached)
38230
38755
  return cached;
38231
- const delivered = await deliverAttachments(item.attachments, join14(this.attachmentDir, item.id.replace(/[^\w-]/g, "_")), this.options.fetchImpl);
38756
+ const delivered = await deliverAttachments(item.attachments, join15(this.attachmentDir, item.id.replace(/[^\w-]/g, "_")), this.options.fetchImpl);
38232
38757
  this.deliveredAttachments.set(item.id, withoutImageData(delivered));
38233
38758
  return delivered;
38234
38759
  }
@@ -38415,7 +38940,7 @@ var MonolithRoomTurnLoop = class {
38415
38940
  root: this.options.config.agentHomeRoot,
38416
38941
  squireScope,
38417
38942
  sharedSkills: this.options.config.sharedSkills ?? [],
38418
- isReviewer: isConfiguredReviewer(self?.handle, configuration.reviewerHandle),
38943
+ isReviewer: configuration.isReviewer === true,
38419
38944
  grantedHostRoutes: mountedHostRoutes,
38420
38945
  extraHostRoutes: registryHostDeclarations,
38421
38946
  resourceAuthFile,
@@ -38444,7 +38969,7 @@ var MonolithRoomTurnLoop = class {
38444
38969
  args: this.options.config.agentArgs ?? []
38445
38970
  }, selection);
38446
38971
  const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
38447
- this.attachmentDir = tmpDir ? join14(tmpDir, "beeline-attachments") : void 0;
38972
+ this.attachmentDir = tmpDir ? join15(tmpDir, "beeline-attachments") : void 0;
38448
38973
  this.sessionScratchDir = tmpDir;
38449
38974
  this.sessionStateDirs = stateDirs;
38450
38975
  const homeStateDirs = harnessHomeStateDirs(harnessLabel, agentEnv.HOME ?? operatorHome);
@@ -38693,7 +39218,6 @@ var MonolithRoomTurnLoop = class {
38693
39218
  };
38694
39219
  this.activeTurn = active;
38695
39220
  active.promise = this.prompt(active).catch((error) => {
38696
- this.options.health.failure(1e3);
38697
39221
  console.error(`[thin-core] monolith Room ${this.options.roomId} turn failed:`, error);
38698
39222
  }).finally(() => {
38699
39223
  if (this.activeTurn === active) {
@@ -39076,56 +39600,60 @@ var MonolithRoomTurnLoop = class {
39076
39600
  }
39077
39601
  }
39078
39602
  async run() {
39079
- const { api, roomId, signal } = this.options;
39603
+ const { api, roomId, signal, supervisor } = this.options;
39604
+ const intake = (progress) => runServerCommandIntake({
39605
+ api,
39606
+ roomId,
39607
+ agentId: this.agent.publicKey,
39608
+ context: this.commandContext,
39609
+ signal,
39610
+ pollMs: this.options.pollMs,
39611
+ presence: {
39612
+ ...this.options.config.daemonReleaseVersion ? { releaseVersion: this.options.config.daemonReleaseVersion } : {},
39613
+ ...this.options.config.daemonSourceSha ? { sourceSha: this.options.config.daemonSourceSha } : {},
39614
+ available: !this.options.config.modelUnavailable
39615
+ },
39616
+ onWake: (wake) => {
39617
+ this.wakeIntake = wake;
39618
+ },
39619
+ onPoll: () => {
39620
+ progress();
39621
+ this.options.health.poll();
39622
+ },
39623
+ onSubscriptionState: this.options.onSubscriptionState,
39624
+ onError: (error) => {
39625
+ this.options.onIntakeError?.(error);
39626
+ console.error("[thin-core] Room command failed", error);
39627
+ },
39628
+ // Marks this loop busy the instant it attempts to claim a real
39629
+ // turn, before the claim's own network round trip and before
39630
+ // context.enter()'s own file I/O - closing the window where
39631
+ // RoomRuntimeCoordinator.reconcile's repository-revision restart
39632
+ // could see a claiming-but-not-yet-`busy` Room as idle and stop it,
39633
+ // cancelling a turn that had already begun (or was about to) (C112).
39634
+ onClaiming: () => {
39635
+ this.busy = true;
39636
+ },
39637
+ onClaimFailed: () => {
39638
+ this.busy = false;
39639
+ },
39640
+ onEnter: (command) => this.squireRelay.activate(command, this.commandContext.generationId),
39641
+ onLeave: (command) => this.squireRelay.deactivate(command.turnRequestId, this.pausedOnGrantRequestId),
39642
+ stop: (requestId) => this.stopTurn(requestId),
39643
+ restart: () => this.options.onRestartRequested?.(),
39644
+ canStartTurn: this.options.canStartTurn,
39645
+ run: async (command) => {
39646
+ const item = {
39647
+ ...command.source,
39648
+ id: command.turnRequestId,
39649
+ body: command.action === "resume" ? `Resume the paused turn. The server supplied this answer: ${command.source.body}` : command.source.body
39650
+ };
39651
+ this.startPrompt(item);
39652
+ await this.activeTurn?.promise;
39653
+ }
39654
+ });
39080
39655
  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
- });
39656
+ await (supervisor ? supervisor.supervise(`Room ${roomId}`, signal, intake) : intake(() => void 0));
39129
39657
  } finally {
39130
39658
  this.squireRelay.close();
39131
39659
  this.options.grantRunner?.unregister(roomId);
@@ -39311,8 +39839,8 @@ process.exit(result.status ?? 1);
39311
39839
  // apps/body/dist/warm-node-modules.js
39312
39840
  import { createHash as createHash9, randomUUID as randomUUID10 } from "node:crypto";
39313
39841
  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";
39842
+ 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";
39843
+ import { dirname as dirname17, join as join16, resolve as resolve27 } from "node:path";
39316
39844
  var STORE_FORMAT = "v1";
39317
39845
  var STAGING_PREFIX = ".beeline-warm-";
39318
39846
  var STAGING_SWEEP_MS = 24 * 60 * 60 * 1e3;
@@ -39333,7 +39861,7 @@ function isPlanRefusal(value) {
39333
39861
  return "failure" in value;
39334
39862
  }
39335
39863
  async function readWarmPlan(worktreePath) {
39336
- const lockfile = await readFile20(resolve27(worktreePath, "package-lock.json")).catch(() => void 0);
39864
+ const lockfile = await readFile21(resolve27(worktreePath, "package-lock.json")).catch(() => void 0);
39337
39865
  if (!lockfile)
39338
39866
  return { failure: "no-lockfile" };
39339
39867
  const packages = parseLockfilePackages(lockfile);
@@ -39398,7 +39926,7 @@ async function seedWarmNodeModules(input) {
39398
39926
  for (const tree of plan.trees) {
39399
39927
  const target = resolve27(input.worktreePath, tree);
39400
39928
  await mkdir19(dirname17(target), { recursive: true });
39401
- await rename7(resolve27(staging, tree), target);
39929
+ await rename8(resolve27(staging, tree), target);
39402
39930
  placed.push(target);
39403
39931
  }
39404
39932
  return { reason: "seeded", key: plan.key };
@@ -39443,7 +39971,7 @@ async function harvestWarmNodeModules(input) {
39443
39971
  if (!await stagedTreeIsPublishable(input.worktreePath, staging, plan.key)) {
39444
39972
  return { reason: "changed", key: plan.key };
39445
39973
  }
39446
- await rename7(staging, entry);
39974
+ await rename8(staging, entry);
39447
39975
  await pruneWarmStore(input.storeRoot, WARM_STORE_MAX_ENTRIES);
39448
39976
  return { reason: "stored", key: plan.key };
39449
39977
  } catch (error) {
@@ -39458,18 +39986,18 @@ async function stagedTreeIsPublishable(worktreePath, staging, key) {
39458
39986
  const settled = await readWarmPlan(worktreePath);
39459
39987
  if (isPlanRefusal(settled) || settled.key !== key)
39460
39988
  return false;
39461
- const hidden = join15("node_modules", ".package-lock.json");
39989
+ const hidden = join16("node_modules", ".package-lock.json");
39462
39990
  const [copied, current] = await Promise.all([
39463
- readFile20(resolve27(staging, hidden)).catch(() => void 0),
39464
- readFile20(resolve27(worktreePath, hidden)).catch(() => void 0)
39991
+ readFile21(resolve27(staging, hidden)).catch(() => void 0),
39992
+ readFile21(resolve27(worktreePath, hidden)).catch(() => void 0)
39465
39993
  ]);
39466
39994
  if (!copied || !current || !copied.equals(current))
39467
39995
  return false;
39468
39996
  return (await missingInstalledPackages(worktreePath, staging)).length === 0;
39469
39997
  }
39470
39998
  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));
39999
+ const wanted = parseLockfilePackages(await readFile21(resolve27(worktreePath, "package-lock.json")).catch(() => void 0));
40000
+ const installed = parseLockfilePackages(await readFile21(resolve27(treeRoot, "node_modules", ".package-lock.json")).catch(() => void 0));
39473
40001
  if (!wanted)
39474
40002
  return ["package-lock.json is unreadable"];
39475
40003
  if (!installed)
@@ -39496,7 +40024,7 @@ async function missingInstalledPackages(worktreePath, treeRoot = worktreePath) {
39496
40024
  return missing.sort();
39497
40025
  }
39498
40026
  async function isInstalledPackage(path) {
39499
- return stat7(join15(path, "package.json")).then((info) => info.isFile(), () => false);
40027
+ return stat7(join16(path, "package.json")).then((info) => info.isFile(), () => false);
39500
40028
  }
39501
40029
  var INTEGRITY_READ_CONCURRENCY = 64;
39502
40030
  async function mapWithLimit(values, limit, visit) {
@@ -39530,8 +40058,8 @@ function isContainedTreePath(value) {
39530
40058
  async function cloneTree(source, target, file, topLevel = true) {
39531
40059
  await mkdir19(target, { recursive: true, mode: 493 });
39532
40060
  for (const entry of await readdir8(source, { withFileTypes: true })) {
39533
- const from = join15(source, entry.name);
39534
- const to = join15(target, entry.name);
40061
+ const from = join16(source, entry.name);
40062
+ const to = join16(target, entry.name);
39535
40063
  if (entry.isSymbolicLink()) {
39536
40064
  await symlink3(await readlink2(from), to);
39537
40065
  continue;
@@ -39561,13 +40089,13 @@ async function pruneWarmStore(storeRoot, keep) {
39561
40089
  const names = (await readdir8(storeRoot).catch(() => [])).filter((name) => !name.startsWith(STAGING_PREFIX));
39562
40090
  const entries = [];
39563
40091
  for (const name of names) {
39564
- const info = await lstat4(join15(storeRoot, name)).catch(() => void 0);
40092
+ const info = await lstat4(join16(storeRoot, name)).catch(() => void 0);
39565
40093
  if (info?.isDirectory())
39566
40094
  entries.push({ name, usedAt: info.mtimeMs });
39567
40095
  }
39568
40096
  const dropped = entries.sort((a2, b) => b.usedAt - a2.usedAt || a2.name.localeCompare(b.name)).slice(keep);
39569
40097
  for (const entry of dropped) {
39570
- await rm9(join15(storeRoot, entry.name), { recursive: true, force: true }).catch(() => void 0);
40098
+ await rm9(join16(storeRoot, entry.name), { recursive: true, force: true }).catch(() => void 0);
39571
40099
  }
39572
40100
  return dropped.map((entry) => entry.name);
39573
40101
  }
@@ -39576,7 +40104,7 @@ async function sweepStaleStaging(storeRoot, now2) {
39576
40104
  for (const name of entries) {
39577
40105
  if (!name.startsWith(STAGING_PREFIX))
39578
40106
  continue;
39579
- const path = join15(storeRoot, name);
40107
+ const path = join16(storeRoot, name);
39580
40108
  const info = await lstat4(path).catch(() => void 0);
39581
40109
  if (!info || now2 - info.mtimeMs < STAGING_SWEEP_MS)
39582
40110
  continue;
@@ -40092,8 +40620,7 @@ var MonolithCornerTurnLoop = class {
40092
40620
  this.yoloMode = configuration.yoloMode;
40093
40621
  const opener = this.options.openedBy ? roster.members.find((member) => member.identityId === this.options.openedBy) : void 0;
40094
40622
  const reviewerInput = {
40095
- reviewerHandle: configuration.reviewerHandle,
40096
- agentHandle: self?.handle,
40623
+ isReviewer: configuration.isReviewer === true,
40097
40624
  authorHandle: opener?.handle,
40098
40625
  openedByAgent: !this.options.openedBy || this.options.openedBy === this.agent.publicKey
40099
40626
  };
@@ -40135,7 +40662,7 @@ var MonolithCornerTurnLoop = class {
40135
40662
  root: this.options.config.agentHomeRoot,
40136
40663
  squireScope,
40137
40664
  sharedSkills: this.options.config.sharedSkills ?? [],
40138
- isReviewer: isConfiguredReviewer(self?.handle, configuration.reviewerHandle),
40665
+ isReviewer: Boolean(reviewerInstruction),
40139
40666
  grantedHostRoutes: mountedHostRoutes,
40140
40667
  extraHostRoutes: registryHostDeclarations,
40141
40668
  resourceAuthFile,
@@ -40193,7 +40720,7 @@ var MonolithCornerTurnLoop = class {
40193
40720
  args: this.options.config.agentArgs ?? []
40194
40721
  }, selection);
40195
40722
  const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
40196
- this.attachmentDir = tmpDir ? join16(tmpDir, "beeline-attachments") : void 0;
40723
+ this.attachmentDir = tmpDir ? join17(tmpDir, "beeline-attachments") : void 0;
40197
40724
  this.sessionScratchDir = tmpDir;
40198
40725
  const homeStateDirs = harnessHomeStateDirs(harnessLabel, agentEnv.HOME ?? operatorHome);
40199
40726
  await Promise.all(homeStateDirs.map((dir) => mkdir20(dir, { recursive: true })));
@@ -40594,10 +41121,10 @@ var MonolithCornerTurnLoop = class {
40594
41121
  size: file.size
40595
41122
  }));
40596
41123
  const allAttachments = [...attachments, ...briefAttachments];
40597
- const delivered = this.attachmentDir && allAttachments.length ? await deliverAttachments(allAttachments, join16(this.attachmentDir, requestId.replace(/[^\w-]/g, "_")), this.options.fetchImpl) : [];
41124
+ const delivered = this.attachmentDir && allAttachments.length ? await deliverAttachments(allAttachments, join17(this.attachmentDir, requestId.replace(/[^\w-]/g, "_")), this.options.fetchImpl) : [];
40598
41125
  const briefFileLines = await Promise.all((restored.brief?.attachments ?? []).map(async (file, index) => {
40599
41126
  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;
41127
+ const verified = entry?.path && createHash10("sha256").update(await readFile22(entry.path)).digest("hex") === file.sha256;
40601
41128
  return `- ${file.title} (${file.purpose}; ${file.required ? "required" : "optional"}; sha256 ${file.sha256}): ${verified ? entry.path : "UNAVAILABLE OR CONTENT MISMATCH"}`;
40602
41129
  }));
40603
41130
  const missingRequiredBriefFile = (restored.brief?.attachments ?? []).some((file, index) => file.required && briefFileLines[index]?.includes("UNAVAILABLE OR CONTENT MISMATCH"));
@@ -41037,68 +41564,72 @@ ${deviceGrantResumePrompt(grantedDevice, result.agentText)}`);
41037
41564
  });
41038
41565
  }
41039
41566
  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;
41567
+ const { api, cornerId, signal, supervisor } = this.options;
41568
+ const intake = (progress) => runServerCommandIntake({
41569
+ api,
41570
+ roomId: cornerId,
41571
+ agentId: this.agent.publicKey,
41572
+ context: this.commandContext,
41573
+ signal,
41574
+ pollMs: this.options.pollMs,
41575
+ presence: {
41576
+ ...this.options.config.daemonReleaseVersion ? { releaseVersion: this.options.config.daemonReleaseVersion } : {},
41577
+ ...this.options.config.daemonSourceSha ? { sourceSha: this.options.config.daemonSourceSha } : {},
41578
+ available: !this.options.config.modelUnavailable
41579
+ },
41580
+ onWake: (wake) => {
41581
+ this.wakeIntake = wake;
41582
+ },
41583
+ onPoll: () => {
41584
+ progress();
41585
+ this.options.onPoll();
41586
+ },
41587
+ onSubscriptionState: this.options.onSubscriptionState,
41588
+ onError: (error) => {
41589
+ this.options.onIntakeError?.(error);
41590
+ console.error("[thin-core] corner command failed", error);
41591
+ },
41592
+ // See monolith-room-turn.ts (C112): mark busy the instant a claim
41593
+ // attempt starts, before its own network round trip and before
41594
+ // context.enter()'s own file I/O, so a reconcile busy-check landing
41595
+ // anywhere in that window can never see this corner as idle when a
41596
+ // turn was already claimed (or about to be).
41597
+ onClaiming: () => {
41598
+ this.busy = true;
41599
+ },
41600
+ onClaimFailed: () => {
41601
+ this.busy = false;
41602
+ },
41603
+ onEnter: (command) => this.squireRelay.activate(command, this.commandContext.generationId),
41604
+ onLeave: (command) => this.squireRelay.deactivate(command.turnRequestId),
41605
+ stop: (requestId) => this.stopTurn(requestId),
41606
+ restart: () => this.options.onRestartRequested?.(),
41607
+ canStartTurn: this.options.canStartTurn,
41608
+ closed: async () => {
41609
+ if (this.closePushed) {
41610
+ this.closePushed = false;
41096
41611
  await this.harvest;
41097
41612
  await this.options.onCloseRequested();
41098
41613
  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
- });
41614
+ }
41615
+ if (!this.reconcileCloseRequested)
41616
+ return false;
41617
+ this.reconcileCloseRequested = false;
41618
+ const state = await api.execute("getCornerRestoreState", { cornerId });
41619
+ if (this.options.lane === "no_code" && state.lane !== "no_code") {
41620
+ this.options.onLaneChanged?.();
41621
+ return true;
41622
+ }
41623
+ if (!state.closeRequested)
41624
+ return false;
41625
+ await this.harvest;
41626
+ await this.options.onCloseRequested();
41627
+ return true;
41628
+ },
41629
+ 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)
41630
+ });
41631
+ try {
41632
+ await (supervisor ? supervisor.supervise(`corner ${cornerId}`, signal, intake) : intake(() => void 0));
41102
41633
  } finally {
41103
41634
  this.squireRelay.close();
41104
41635
  this.options.grantRunner?.unregister(cornerId);
@@ -41170,6 +41701,89 @@ var SurfaceHealth = class {
41170
41701
  }
41171
41702
  };
41172
41703
 
41704
+ // apps/body/dist/room-supervisor.js
41705
+ var ROOM_RETRY_DELAYS_MS = [1e3, 5e3, 3e4];
41706
+ var RoomSupervisor = class {
41707
+ delaysMs;
41708
+ #parked = /* @__PURE__ */ new Set();
41709
+ #wakes = 0;
41710
+ constructor(delaysMs = ROOM_RETRY_DELAYS_MS) {
41711
+ this.delaysMs = delaysMs;
41712
+ }
41713
+ /** The live socket opened or discovery was woken: every waiting loop retries now. */
41714
+ wake() {
41715
+ this.#wakes += 1;
41716
+ for (const resume of [...this.#parked])
41717
+ resume();
41718
+ }
41719
+ /** Wakes so far; a wait that started from an older count does not wait. */
41720
+ get wakes() {
41721
+ return this.#wakes;
41722
+ }
41723
+ /** How many timed retries follow one failure before a loop parks. */
41724
+ get retries() {
41725
+ return this.delaysMs.length;
41726
+ }
41727
+ /**
41728
+ * Wait before retrying failure number `failures` (1-based). Within the
41729
+ * bound the fixed delay applies, and a wake may cut it short; past the bound
41730
+ * only a wake (or the abort) ends the wait. A wake that already arrived
41731
+ * after `since` (a `wakes` count) ends it at once, so none is lost.
41732
+ */
41733
+ retryDelay(failures, signal, since = this.#wakes) {
41734
+ const delay = this.delaysMs[failures - 1];
41735
+ return new Promise((resolve40) => {
41736
+ let timer;
41737
+ const done = () => {
41738
+ clearTimeout(timer);
41739
+ this.#parked.delete(done);
41740
+ signal?.removeEventListener("abort", done);
41741
+ resolve40();
41742
+ };
41743
+ this.#parked.add(done);
41744
+ signal?.addEventListener("abort", done, { once: true });
41745
+ if (signal?.aborted || this.#wakes !== since) {
41746
+ done();
41747
+ return;
41748
+ }
41749
+ if (delay !== void 0) {
41750
+ timer = setTimeout(done, delay);
41751
+ timer.unref?.();
41752
+ }
41753
+ });
41754
+ }
41755
+ /**
41756
+ * Run one Room or corner intake under supervision: a failed read never ends
41757
+ * it. `run` resolves only when the intake itself is finished (aborted or
41758
+ * closed); it calls `progress` once its reads succeed again, which restores
41759
+ * the full retry budget. The intake rejects only after its active turn has
41760
+ * settled, so re-entering cannot claim the same command twice.
41761
+ */
41762
+ async supervise(label, signal, run2) {
41763
+ let failures = 0;
41764
+ for (; ; ) {
41765
+ const since = this.#wakes;
41766
+ try {
41767
+ await run2(() => {
41768
+ failures = 0;
41769
+ });
41770
+ return;
41771
+ } catch (error) {
41772
+ if (signal?.aborted)
41773
+ return;
41774
+ failures += 1;
41775
+ const parked = failures > this.delaysMs.length;
41776
+ 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);
41777
+ await this.retryDelay(failures, signal, since);
41778
+ if (signal?.aborted)
41779
+ return;
41780
+ if (parked)
41781
+ failures = 0;
41782
+ }
41783
+ }
41784
+ }
41785
+ };
41786
+
41173
41787
  // apps/body/dist/room-runtime.js
41174
41788
  init_runtime();
41175
41789
 
@@ -41597,11 +42211,6 @@ var SessionScheduler = class {
41597
42211
  // apps/body/dist/room-runtime.js
41598
42212
  var REMOVAL_CONFIRMATION_READS = 2;
41599
42213
  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
42214
  var DEFAULT_DRAIN_DEADLINE_MS = 30 * 6e4;
41606
42215
  var CORNER_BRANCH_DELETE_ATTEMPTS = 3;
41607
42216
  var CornerCredentialLookupError = class extends Error {
@@ -41609,25 +42218,6 @@ var CornerCredentialLookupError = class extends Error {
41609
42218
  super("corner repository credential lookup failed", { cause });
41610
42219
  }
41611
42220
  };
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
42221
  function shouldPostInitialCornerWorkingState(restore) {
41632
42222
  return !restore.lifecycle?.branch && !restore.lifecycle?.pr;
41633
42223
  }
@@ -41908,8 +42498,6 @@ var RoomRuntimeCoordinator = class {
41908
42498
  /** Parent ownership retained so a failed corner listing never authorizes removal. */
41909
42499
  monolithCornerParents = /* @__PURE__ */ new Map();
41910
42500
  now;
41911
- watchdogStaleMs;
41912
- reconcileHeartbeatMs;
41913
42501
  drainDeadlineMs;
41914
42502
  drainDeadlineAt;
41915
42503
  workspaceRemovalConfirmations = 0;
@@ -41921,14 +42509,9 @@ var RoomRuntimeCoordinator = class {
41921
42509
  repositoryStateFlights = /* @__PURE__ */ new Map();
41922
42510
  tokenCache = /* @__PURE__ */ new Map();
41923
42511
  tokenFlights = /* @__PURE__ */ new Map();
41924
- confirmationPending = false;
41925
42512
  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();
42513
+ /** Keeps Room and corner intakes alive across failed reads, without polling. */
42514
+ supervisor = new RoomSupervisor();
41932
42515
  discoveryWakeListener;
41933
42516
  interactiveIdleListener;
41934
42517
  /** One command-grant runner per daemon; Rooms and corners register their checkouts on it. */
@@ -41975,8 +42558,6 @@ var RoomRuntimeCoordinator = class {
41975
42558
  void this.scheduler.suspendIdle().catch((error) => console.error("[body] config-change session restart failed", error));
41976
42559
  void Promise.resolve(this.options.onConfigChanged?.()).catch((error) => console.error("[body] config-change catalog refresh failed", error));
41977
42560
  });
41978
- this.watchdogStaleMs = options.watchdogStaleMs ?? DEFAULT_ROOM_WATCHDOG_STALE_MS;
41979
- this.reconcileHeartbeatMs = options.reconcileHeartbeatMs ?? defaultReconcileHeartbeatMs();
41980
42561
  this.drainDeadlineMs = options.drainDeadlineMs ?? DEFAULT_DRAIN_DEADLINE_MS;
41981
42562
  const fixedWorkspaceCeiling = process.env.BUZZY_BODY_MAX_SESSIONS;
41982
42563
  this.scheduler = new SessionScheduler({
@@ -41998,12 +42579,18 @@ var RoomRuntimeCoordinator = class {
41998
42579
  setInteractiveIdleListener(listener) {
41999
42580
  this.interactiveIdleListener = listener;
42000
42581
  }
42001
- reconnectAfterFailure() {
42002
- if (!this.options.daemonApi.supportsDiscoveryWake?.())
42003
- this.options.daemonApi.reconnectLive?.();
42582
+ /**
42583
+ * A failed reconcile makes the live socket suspect: it is pinged, and a
42584
+ * silent one is replaced, which works with any server version. A response
42585
+ * the server actually sent (or a full local read budget) says nothing about
42586
+ * the socket.
42587
+ */
42588
+ suspectLink(error) {
42589
+ if (!(error instanceof DaemonApiError))
42590
+ this.options.daemonApi.link?.suspect();
42004
42591
  }
42005
42592
  wakeDiscovery() {
42006
- this.discoveryWakes.wake();
42593
+ this.supervisor.wake();
42007
42594
  this.discoveryWakeListener?.();
42008
42595
  }
42009
42596
  activeRoomCount() {
@@ -42027,12 +42614,6 @@ var RoomRuntimeCoordinator = class {
42027
42614
  schedulerSnapshot() {
42028
42615
  return this.scheduler.snapshot();
42029
42616
  }
42030
- needsFastReconcile() {
42031
- return this.confirmationPending || this.discoveryWakes.needsFastReconcile();
42032
- }
42033
- reconcileHeartbeatIntervalMs() {
42034
- return this.reconcileHeartbeatMs;
42035
- }
42036
42617
  isWorkspaceIdle() {
42037
42618
  return this.activeTurnCount() === 0;
42038
42619
  }
@@ -42058,6 +42639,16 @@ var RoomRuntimeCoordinator = class {
42058
42639
  room.controller.abort();
42059
42640
  return true;
42060
42641
  }
42642
+ /**
42643
+ * Undo `quiesceForUpdateIfIdle` after a managed restart failed: clear the
42644
+ * latch and let reconciliation start the Room loops the quiesce stopped.
42645
+ */
42646
+ resumeServing() {
42647
+ if (this.stopped)
42648
+ return;
42649
+ this.restartRequested = false;
42650
+ this.wakeDiscovery();
42651
+ }
42061
42652
  setDrainDeadlineAt(deadlineAt) {
42062
42653
  if (Number.isFinite(deadlineAt)) {
42063
42654
  this.drainDeadlineAt = Math.min(this.drainDeadlineAt ?? Number.POSITIVE_INFINITY, deadlineAt);
@@ -42080,9 +42671,7 @@ var RoomRuntimeCoordinator = class {
42080
42671
  async reconcile() {
42081
42672
  if (this.stopped)
42082
42673
  return "member";
42083
- this.confirmationPending = false;
42084
42674
  const existingRooms = new Set(this.running.keys());
42085
- const coveredWakes = this.discoveryWakes.beginReconcile();
42086
42675
  const bootstrap = await this.options.daemonApi.execute("getDaemonBootstrap", {
42087
42676
  agentId: this.agent.publicKey
42088
42677
  });
@@ -42091,7 +42680,6 @@ var RoomRuntimeCoordinator = class {
42091
42680
  if (!bootstrap.workspaceIds.includes(this.runtime.communityId)) {
42092
42681
  this.workspaceRemovalConfirmations += 1;
42093
42682
  if (this.workspaceRemovalConfirmations < REMOVAL_CONFIRMATION_READS) {
42094
- this.confirmationPending = true;
42095
42683
  this.wakeDiscovery();
42096
42684
  return "unknown";
42097
42685
  }
@@ -42179,10 +42767,8 @@ var RoomRuntimeCoordinator = class {
42179
42767
  continue;
42180
42768
  const confirmations = (this.roomRemovalConfirmations.get(channelId) ?? 0) + 1;
42181
42769
  this.roomRemovalConfirmations.set(channelId, confirmations);
42182
- if (confirmations < REMOVAL_CONFIRMATION_READS) {
42183
- this.confirmationPending = true;
42770
+ if (confirmations < REMOVAL_CONFIRMATION_READS)
42184
42771
  continue;
42185
- }
42186
42772
  await this.stopRunning(channelId, running);
42187
42773
  this.roomRemovalConfirmations.delete(channelId);
42188
42774
  }
@@ -42207,7 +42793,6 @@ var RoomRuntimeCoordinator = class {
42207
42793
  for (const [roomId, running] of this.running)
42208
42794
  if (existingRooms.has(roomId))
42209
42795
  running.body.requestReconciliation();
42210
- this.discoveryWakes.completeReconcile(coveredWakes);
42211
42796
  if (this.roomRemovalConfirmations.size)
42212
42797
  this.wakeDiscovery();
42213
42798
  return "member";
@@ -42310,7 +42895,6 @@ var RoomRuntimeCoordinator = class {
42310
42895
  await this.reapCornerScratch(running.scratch);
42311
42896
  } catch (error) {
42312
42897
  console.error(`[thin-core] corner ${channelId} cleanup failed; will retry:`, error);
42313
- this.confirmationPending = true;
42314
42898
  }
42315
42899
  }
42316
42900
  roomRecord(roomId) {
@@ -42373,7 +42957,6 @@ var RoomRuntimeCoordinator = class {
42373
42957
  const controller = new AbortController();
42374
42958
  const cwd = this.roomRoot(roomId);
42375
42959
  const grantRunnerEndpoint = await this.grantRunnerEndpoint();
42376
- const startedAt = this.now();
42377
42960
  const loop = new MonolithRoomTurnLoop({
42378
42961
  roomId,
42379
42962
  workspaceId: this.runtime.communityId,
@@ -42386,18 +42969,12 @@ var RoomRuntimeCoordinator = class {
42386
42969
  api: this.options.daemonApi,
42387
42970
  scheduler: this.scheduler,
42388
42971
  signal: controller.signal,
42389
- health: {
42390
- poll: () => this.notePoll(roomId),
42391
- failure: (retryInMs) => this.noteFailure(roomId, retryInMs),
42392
- presence: () => void 0
42393
- },
42972
+ health: { poll: () => this.notePoll(roomId) },
42394
42973
  onSubscriptionState: (connected) => this.surfaceHealth.subscribed(roomId, connected),
42395
42974
  onIntakeError: () => this.surfaceHealth.degraded(roomId, "Room command intake failed"),
42396
- onCornerOpened: () => {
42397
- this.confirmationPending = true;
42398
- },
42399
42975
  onRestartRequested: () => this.requestLifecycleRestart(),
42400
- canStartTurn: () => !this.restartRequested
42976
+ canStartTurn: () => !this.restartRequested,
42977
+ supervisor: this.supervisor
42401
42978
  });
42402
42979
  const promise = loop.run().catch((error) => {
42403
42980
  if (!controller.signal.aborted) {
@@ -42413,14 +42990,7 @@ var RoomRuntimeCoordinator = class {
42413
42990
  await promise;
42414
42991
  return;
42415
42992
  }
42416
- this.running.set(roomId, {
42417
- body: loop,
42418
- controller,
42419
- promise,
42420
- lastPollAt: startedAt,
42421
- backoffUntil: 0,
42422
- recovering: false
42423
- });
42993
+ this.running.set(roomId, { body: loop, controller, promise });
42424
42994
  console.log(`[thin-core] serving monolith Room ${roomId}`);
42425
42995
  }
42426
42996
  /**
@@ -42546,7 +43116,6 @@ var RoomRuntimeCoordinator = class {
42546
43116
  }
42547
43117
  const controller = new AbortController();
42548
43118
  const grantRunnerEndpoint = await this.grantRunnerEndpoint();
42549
- const startedAt = this.now();
42550
43119
  const loop = new MonolithCornerTurnLoop({
42551
43120
  cornerId: corner.cornerId,
42552
43121
  grantRunner: this.grantRunner,
@@ -42574,7 +43143,6 @@ var RoomRuntimeCoordinator = class {
42574
43143
  scheduler: this.scheduler,
42575
43144
  signal: controller.signal,
42576
43145
  onPoll: () => this.notePoll(corner.cornerId),
42577
- onFailure: (retryInMs) => this.noteFailure(corner.cornerId, retryInMs),
42578
43146
  onSubscriptionState: (connected) => this.surfaceHealth.subscribed(corner.cornerId, connected),
42579
43147
  onIntakeError: () => this.surfaceHealth.degraded(corner.cornerId, "corner command intake failed"),
42580
43148
  onCloseRequested: () => worktree ? this.reapCornerWorktree({
@@ -42586,7 +43154,8 @@ var RoomRuntimeCoordinator = class {
42586
43154
  }) : this.reapCornerScratch({ path: workspacePath, cornerId: corner.cornerId }),
42587
43155
  onLaneChanged: () => void this.applyCornerRestart(corner.cornerId).catch((error) => console.error("[thin-core] corner lane-change restart failed", error)),
42588
43156
  onRestartRequested: () => this.requestLifecycleRestart(),
42589
- canStartTurn: () => !this.restartRequested
43157
+ canStartTurn: () => !this.restartRequested,
43158
+ supervisor: this.supervisor
42590
43159
  });
42591
43160
  const promise = loop.run().catch((error) => {
42592
43161
  if (!controller.signal.aborted) {
@@ -42607,9 +43176,6 @@ var RoomRuntimeCoordinator = class {
42607
43176
  body: loop,
42608
43177
  controller,
42609
43178
  promise,
42610
- lastPollAt: startedAt,
42611
- backoffUntil: 0,
42612
- recovering: false,
42613
43179
  ...worktree ? {
42614
43180
  worktree: {
42615
43181
  ...worktree,
@@ -42937,8 +43503,6 @@ var RoomRuntimeCoordinator = class {
42937
43503
  const room = this.running.get(roomId);
42938
43504
  if (!room)
42939
43505
  return;
42940
- room.lastPollAt = this.now();
42941
- room.backoffUntil = 0;
42942
43506
  if (this.deferredRepositoryRestarts.has(roomId) && !room.body.isBusy()) {
42943
43507
  this.deferredRepositoryRestarts.delete(roomId);
42944
43508
  void this.stopRunning(roomId, room).then(() => this.stopped ? void 0 : this.startRoom(roomId)).catch((error) => {
@@ -42949,29 +43513,6 @@ var RoomRuntimeCoordinator = class {
42949
43513
  if (this.isWorkspaceIdle())
42950
43514
  this.interactiveIdleListener?.();
42951
43515
  }
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
43516
  beginShutdown() {
42976
43517
  if (this.stopped)
42977
43518
  return;
@@ -43160,6 +43701,10 @@ var ThinDaemonCore = class {
43160
43701
  quiesceForUpdateIfIdle() {
43161
43702
  return this.roomRuntime.quiesceForUpdateIfIdle();
43162
43703
  }
43704
+ /** A managed restart failed after intake closed: take turns again. */
43705
+ resumeServing() {
43706
+ this.roomRuntime.resumeServing();
43707
+ }
43163
43708
  async prepareForForcedUpdateRestart() {
43164
43709
  await this.roomRuntime.prepareForForcedUpdateRestart();
43165
43710
  }
@@ -43192,25 +43737,35 @@ var ThinDaemonCore = class {
43192
43737
  try {
43193
43738
  if (!opts.signal?.aborted)
43194
43739
  await opts.onEstablished?.();
43740
+ const supervisor = this.roomRuntime.supervisor;
43741
+ let failures = 0;
43195
43742
  while (!opts.signal?.aborted) {
43196
43743
  this.pendingDiscovery = false;
43744
+ const wakesBefore = supervisor.wakes;
43197
43745
  try {
43198
43746
  const membership = await this.roomRuntime.reconcile();
43199
43747
  if (membership === "not-member")
43200
43748
  return "agent-removed";
43201
43749
  this.discoveryStatus = membership === "unknown" ? "monolith membership degraded" : "";
43750
+ failures = 0;
43202
43751
  } catch (error) {
43203
43752
  if (isAgentRemovedError(error))
43204
43753
  return "agent-removed";
43205
- console.error("[thin-core] discovery failed; waiting for live recovery:", error);
43754
+ failures += 1;
43755
+ 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
43756
  this.discoveryStatus = `monolith discovery degraded: ${error instanceof Error ? error.message : String(error)}`;
43207
- this.roomRuntime.reconnectAfterFailure();
43757
+ this.roomRuntime.suspectLink(error);
43208
43758
  }
43209
43759
  if (opts.signal?.aborted)
43210
43760
  break;
43211
43761
  await opts.onProgress?.(this.healthStatus());
43212
- if (!this.pendingDiscovery)
43762
+ if (failures > 0) {
43763
+ await supervisor.retryDelay(failures, opts.signal, wakesBefore);
43764
+ if (failures > supervisor.retries)
43765
+ failures = 0;
43766
+ } else if (!this.pendingDiscovery) {
43213
43767
  await this.waitForDiscovery(opts.signal);
43768
+ }
43214
43769
  }
43215
43770
  return "aborted";
43216
43771
  } finally {
@@ -43220,28 +43775,169 @@ var ThinDaemonCore = class {
43220
43775
  }
43221
43776
  };
43222
43777
 
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)
43778
+ // apps/body/dist/helper-lifecycle.js
43779
+ init_systemd();
43780
+ var HELPER_HARD_STOP_MS = 8e4;
43781
+ var HELPER_EXIT_CODES = {
43782
+ stopped: 0,
43783
+ "restart-requested": 0,
43784
+ update: 0,
43785
+ failed: 75,
43786
+ "rolled-back": 75,
43787
+ "force-update-failed": 75,
43788
+ distress: DAEMON_DISTRESS_EXIT_STATUS,
43789
+ "agent-removed": DELIBERATE_REMOVAL_EXIT_STATUS,
43790
+ "unknown-agent": UNKNOWN_AGENT_EXIT_STATUS
43791
+ };
43792
+ var HelperLifecycle = class {
43793
+ options;
43794
+ #state = { kind: "serving" };
43795
+ #hardStop;
43796
+ #exitProcess;
43797
+ constructor(options) {
43798
+ this.options = options;
43799
+ this.#exitProcess = options.exitProcess ?? ((code) => process.exit(code));
43800
+ }
43801
+ get state() {
43802
+ return this.#state;
43803
+ }
43804
+ get serving() {
43805
+ return this.#state.kind === "serving";
43806
+ }
43807
+ /** The reason the final stop was asked for, once one was. */
43808
+ get stopReason() {
43809
+ return this.#state.kind === "stopping" ? this.#state.reason : void 0;
43810
+ }
43811
+ /**
43812
+ * Close intake. An update may only start from serving; `/restart` and a
43813
+ * forced update also take over a waiting update. Returns whether this call
43814
+ * moved the helper to quiescing.
43815
+ */
43816
+ quiesce(reason) {
43817
+ const state = this.#state;
43818
+ if (state.kind === "serving" || state.kind === "quiescing" && state.reason === "update" && reason !== "update") {
43819
+ this.#state = { kind: "quiescing", reason };
43820
+ return true;
43821
+ }
43822
+ return false;
43823
+ }
43824
+ /** A managed restart failed after intake closed: serve again. */
43825
+ resume() {
43826
+ if (this.#state.kind !== "quiescing")
43827
+ return false;
43828
+ this.#state = { kind: "serving" };
43829
+ return true;
43830
+ }
43831
+ /**
43832
+ * The managed update's idle gate: quiesce only while serving and only if
43833
+ * `closeIntakeIfIdle` proves no turn is running (and closes intake in the
43834
+ * same step). A busy helper stays serving, so the update waits.
43835
+ */
43836
+ quiesceUpdateIfIdle(closeIntakeIfIdle) {
43837
+ return this.serving && closeIntakeIfIdle() && this.quiesce("update");
43838
+ }
43839
+ /** A managed restart failed after its quiesce: serve again and reopen intake. */
43840
+ resumeAfterFailedUpdate(reopenIntake) {
43841
+ const state = this.#state;
43842
+ if (state.kind !== "quiescing" || state.reason !== "update" || !this.resume())
43843
+ return false;
43844
+ reopenIntake();
43845
+ return true;
43846
+ }
43847
+ /** The final stop: abort the core, and exit no later than the hard limit. */
43848
+ stop(reason) {
43849
+ if (this.#state.kind === "stopping" || this.#state.kind === "exited")
43230
43850
  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
- };
43851
+ this.#state = { kind: "stopping", reason };
43852
+ this.#hardStop = setTimeout(() => {
43853
+ console.error("[thin-core] shutdown deadline reached; exiting helper");
43854
+ this.exit(reason);
43855
+ }, this.options.hardStopMs ?? HELPER_HARD_STOP_MS);
43856
+ this.#hardStop.unref?.();
43857
+ this.options.controller.abort();
43858
+ }
43859
+ /** The one exit funnel: each reason maps to exactly one code. */
43860
+ exit(reason) {
43861
+ if (this.#state.kind === "exited")
43862
+ return;
43863
+ clearTimeout(this.#hardStop);
43864
+ const code = HELPER_EXIT_CODES[reason];
43865
+ this.#state = { kind: "exited", code };
43866
+ this.#exitProcess(code);
43867
+ }
43868
+ /** SIGTERM and SIGINT are a final stop. */
43869
+ installSignals(emitter = process) {
43870
+ const stop = () => this.stop("stopped");
43871
+ emitter.once("SIGINT", stop);
43872
+ emitter.once("SIGTERM", stop);
43873
+ return () => {
43874
+ emitter.removeListener("SIGINT", stop);
43875
+ emitter.removeListener("SIGTERM", stop);
43876
+ };
43877
+ }
43878
+ };
43879
+ function installUnhandledRejectionGuard(emitter = process) {
43880
+ const log2 = (reason) => console.error("[thin-core] unhandled rejection (kept running):", reason);
43881
+ emitter.on("unhandledRejection", log2);
43882
+ return () => emitter.removeListener("unhandledRejection", log2);
43883
+ }
43884
+ function isTransientServerFailure(error) {
43885
+ return isNetworkFailure(error) || error instanceof DaemonApiError && error.retryable;
43886
+ }
43887
+ function successorRollbackAllowed(error, deadlineAt, now2 = Date.now()) {
43888
+ return !isTransientServerFailure(error) || deadlineAt !== void 0 && now2 >= deadlineAt;
43889
+ }
43890
+ async function retryBeforeReady(work, options) {
43891
+ const now2 = options.now ?? Date.now;
43892
+ const slice = options.waitSliceMs ?? 6e4;
43893
+ let failures = 0;
43894
+ for (; ; ) {
43895
+ try {
43896
+ return await work();
43897
+ } catch (error) {
43898
+ if (!isTransientServerFailure(error) || options.signal?.aborted)
43899
+ throw error;
43900
+ if (options.deadlineAt !== void 0 && now2() >= options.deadlineAt)
43901
+ throw error;
43902
+ failures += 1;
43903
+ const timedRetry = ROOM_RETRY_DELAYS_MS[failures - 1];
43904
+ 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);
43905
+ let opened = false;
43906
+ let wake = () => void 0;
43907
+ const off = options.onLinkOpen(() => {
43908
+ opened = true;
43909
+ wake();
43910
+ });
43911
+ try {
43912
+ const waitStarted = now2();
43913
+ while (!opened && !options.signal?.aborted) {
43914
+ const remaining = [
43915
+ slice,
43916
+ options.deadlineAt === void 0 ? Infinity : options.deadlineAt - now2(),
43917
+ timedRetry === void 0 ? Infinity : timedRetry - (now2() - waitStarted)
43918
+ ];
43919
+ const wait2 = Math.min(...remaining);
43920
+ if (wait2 <= 0)
43921
+ break;
43922
+ await options.extendStartTimeout(wait2 + 15e3);
43923
+ await new Promise((resolve40) => {
43924
+ const timer = setTimeout(done, wait2);
43925
+ function done() {
43926
+ clearTimeout(timer);
43927
+ options.signal?.removeEventListener("abort", done);
43928
+ resolve40();
43929
+ }
43930
+ wake = done;
43931
+ options.signal?.addEventListener("abort", done, { once: true });
43932
+ if (opened || options.signal?.aborted)
43933
+ done();
43934
+ });
43935
+ }
43936
+ } finally {
43937
+ off();
43938
+ }
43939
+ }
43940
+ }
43245
43941
  }
43246
43942
 
43247
43943
  // apps/body/dist/force-update-journal.js
@@ -43667,7 +44363,7 @@ async function runUpdateFunctionalProbe(input) {
43667
44363
  }
43668
44364
 
43669
44365
  // 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";
44366
+ import { mkdir as mkdir23, readFile as readFile23, rename as rename9, unlink as unlink6, writeFile as writeFile17 } from "node:fs/promises";
43671
44367
  import { dirname as dirname20, resolve as resolve32 } from "node:path";
43672
44368
  var REPORT_INTERVAL_MS = 60 * 60 * 1e3;
43673
44369
  var lastLogged = /* @__PURE__ */ new Map();
@@ -43680,7 +44376,7 @@ async function writeAlert(runtimeDir, alert) {
43680
44376
  await mkdir23(dirname20(path), { recursive: true });
43681
44377
  await writeFile17(staged, `${JSON.stringify(alert, null, 2)}
43682
44378
  `, { mode: 384 });
43683
- await rename8(staged, path);
44379
+ await rename9(staged, path);
43684
44380
  }
43685
44381
  async function queueUpdateRollbackAlert(runtimeDir, releaseId, now2 = Date.now()) {
43686
44382
  const existing = await readUpdateRollbackAlert(runtimeDir);
@@ -43690,7 +44386,7 @@ async function queueUpdateRollbackAlert(runtimeDir, releaseId, now2 = Date.now()
43690
44386
  }
43691
44387
  async function readUpdateRollbackAlert(runtimeDir) {
43692
44388
  try {
43693
- const value = JSON.parse(await readFile22(updateRollbackAlertPath(runtimeDir), "utf8"));
44389
+ const value = JSON.parse(await readFile23(updateRollbackAlertPath(runtimeDir), "utf8"));
43694
44390
  if (value.version !== 1 || typeof value.releaseId !== "string")
43695
44391
  return void 0;
43696
44392
  return value;
@@ -44024,6 +44720,11 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
44024
44720
  env: this.#env,
44025
44721
  log: (line) => console.log(line)
44026
44722
  });
44723
+ await convergeAgentServiceUnit({
44724
+ libDir: this.#layout.libDir,
44725
+ env: this.#env,
44726
+ log: (line) => console.log(line)
44727
+ });
44027
44728
  } else if (process.platform === "darwin" && this.#env.BEELINE_LAUNCHD_USER !== "0") {
44028
44729
  await convergeLaunchdTrustySquireBrokerService({
44029
44730
  libDir: this.#layout.libDir,
@@ -44375,7 +45076,7 @@ init_runtime();
44375
45076
  init_launchd();
44376
45077
  init_systemd();
44377
45078
  init_runtime();
44378
- import { mkdir as mkdir25, rename as rename9 } from "node:fs/promises";
45079
+ import { mkdir as mkdir25, rename as rename10 } from "node:fs/promises";
44379
45080
  import { dirname as dirname22, resolve as resolve34 } from "node:path";
44380
45081
  async function retireRemovedAgent(runtime, options = {}) {
44381
45082
  const deletedRoot = resolve34(runtime.supervisorRoot, "beeline", "deleted-runtimes");
@@ -44400,7 +45101,7 @@ async function relocateAgentRuntime(runtime, target, options = {}) {
44400
45101
  });
44401
45102
  }
44402
45103
  await mkdir25(dirname22(destination), { recursive: true, mode: 448 });
44403
- await rename9(source, destination);
45104
+ await rename10(source, destination);
44404
45105
  return destination;
44405
45106
  }
44406
45107
 
@@ -44735,7 +45436,7 @@ async function runStartCommand(args, interactiveUi, dependencyOverrides = {}) {
44735
45436
  // apps/body/dist/connect-command.js
44736
45437
  import { spawn as spawn12 } from "node:child_process";
44737
45438
  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";
45439
+ import { chmod as chmod10, mkdir as mkdir26, readFile as readFile24, unlink as unlink7, writeFile as writeFile19 } from "node:fs/promises";
44739
45440
  import { homedir as homedir17, hostname as hostname3 } from "node:os";
44740
45441
  import { dirname as dirname24, resolve as resolve35 } from "node:path";
44741
45442
  import { stdin as stdin3, stdout as stdout4 } from "node:process";
@@ -45413,7 +46114,7 @@ async function readMachineId(env = process.env) {
45413
46114
  let machineId;
45414
46115
  let machineName = hostname3();
45415
46116
  try {
45416
- const existing = await readFile23(machineIdPath, "utf8");
46117
+ const existing = await readFile24(machineIdPath, "utf8");
45417
46118
  machineId = existing.trim();
45418
46119
  if (!/^[0-9a-f-]{32,}$/.test(machineId))
45419
46120
  throw new Error("invalid persisted machine id");
@@ -45669,12 +46370,12 @@ async function runConnectFinishCommand(path) {
45669
46370
  throw new Error("connect-finish may run only from the canonical installed Beeline launcher");
45670
46371
  }
45671
46372
  try {
45672
- const grant = JSON.parse(await readFile23(resolve35(path), "utf8"));
46373
+ const grant = JSON.parse(await readFile24(resolve35(path), "utf8"));
45673
46374
  if (!isDevicePairingGrant(grant))
45674
46375
  throw new Error("device connection grant is invalid");
45675
46376
  const connected = await completeDevicePairing(grant);
45676
46377
  await unlink7(resolve35(path));
45677
- const providerEnv = grant.llmEnvFile ? await readFile23(grant.llmEnvFile, "utf8").catch(() => "") : "";
46378
+ const providerEnv = grant.llmEnvFile ? await readFile24(grant.llmEnvFile, "utf8").catch(() => "") : "";
45678
46379
  const apiKey = (/^OPENROUTER_API_KEY=(\S+)/m.exec(providerEnv)?.[1] ?? "").replace(/^["']|["']$/g, "");
45679
46380
  const model = openRouterModelId(grant.model, { OPENROUTER_API_KEY: apiKey });
45680
46381
  if (model) {
@@ -45698,7 +46399,7 @@ async function runConnectFinishCommand(path) {
45698
46399
  init_self_update();
45699
46400
 
45700
46401
  // 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";
46402
+ import { mkdir as mkdir27, readFile as readFile25, rename as rename11, rm as rm13, writeFile as writeFile20 } from "node:fs/promises";
45702
46403
  import { dirname as dirname25, resolve as resolve36 } from "node:path";
45703
46404
  var DAEMON_FAILURE_LIMIT = 3;
45704
46405
  var DAEMON_FAILURE_WINDOW_MS = 5 * 6e4;
@@ -45707,7 +46408,7 @@ function daemonFailurePath(runtimeDir) {
45707
46408
  }
45708
46409
  async function readFailureRecord(runtimeDir) {
45709
46410
  try {
45710
- const value = JSON.parse(await readFile24(daemonFailurePath(runtimeDir), "utf8"));
46411
+ const value = JSON.parse(await readFile25(daemonFailurePath(runtimeDir), "utf8"));
45711
46412
  if (value.version !== 1 || !Array.isArray(value.failures) || value.failures.some((failure) => typeof failure !== "number") || typeof value.lastError !== "string") {
45712
46413
  return void 0;
45713
46414
  }
@@ -45722,7 +46423,14 @@ async function writeFailureRecord(runtimeDir, record6) {
45722
46423
  await mkdir27(dirname25(path), { recursive: true, mode: 448 });
45723
46424
  await writeFile20(staged, `${JSON.stringify(record6, null, 2)}
45724
46425
  `, { mode: 384 });
45725
- await rename10(staged, path);
46426
+ await rename11(staged, path);
46427
+ }
46428
+ function countsTowardDistress(error) {
46429
+ if (isNetworkFailure(error))
46430
+ return false;
46431
+ if (error instanceof DaemonApiError && (error.retryable || error.status === 401))
46432
+ return false;
46433
+ return true;
45726
46434
  }
45727
46435
  async function recordDaemonStartFailure(runtimeDir, error, now2 = Date.now()) {
45728
46436
  const prior = await readFailureRecord(runtimeDir);
@@ -45737,6 +46445,11 @@ async function recordDaemonStartFailure(runtimeDir, error, now2 = Date.now()) {
45737
46445
  await writeFailureRecord(runtimeDir, record6);
45738
46446
  return { distressed, count: failures.length, path: daemonFailurePath(runtimeDir) };
45739
46447
  }
46448
+ async function settleDaemonStartFailure(runtimeDir, error, now2 = Date.now()) {
46449
+ if (!countsTowardDistress(error))
46450
+ return { distressed: false };
46451
+ return recordDaemonStartFailure(runtimeDir, error, now2);
46452
+ }
45740
46453
  async function clearDaemonStartFailures(runtimeDir) {
45741
46454
  await rm13(daemonFailurePath(runtimeDir), { force: true });
45742
46455
  }
@@ -45746,7 +46459,7 @@ init_systemd();
45746
46459
 
45747
46460
  // apps/body/dist/current-release-probe.js
45748
46461
  import { spawn as spawn13 } from "node:child_process";
45749
- import { dirname as dirname26, join as join17 } from "node:path";
46462
+ import { dirname as dirname26, join as join18 } from "node:path";
45750
46463
  init_runtime();
45751
46464
  init_self_update();
45752
46465
  var CURRENT_RELEASE_PROBE_TIMEOUT_MS = 12e4;
@@ -45790,7 +46503,7 @@ function outcomeFromReport(report) {
45790
46503
  }
45791
46504
  }
45792
46505
  async function probeReleaseInSubprocess(input) {
45793
- const bundleDir = join17(input.layout.releasesRoot, input.releaseId);
46506
+ const bundleDir = join18(input.layout.releasesRoot, input.releaseId);
45794
46507
  const entrypoint = await resolveBundleEntrypoint(bundleDir);
45795
46508
  if (!entrypoint) {
45796
46509
  return { kind: "unavailable", reason: `release ${input.releaseId} has no runnable CLI entrypoint` };
@@ -45853,7 +46566,7 @@ async function runUpdateProbeCommand(args, options = {}) {
45853
46566
  const runtime = await readRuntimeRecord(configPath);
45854
46567
  const agent = runtimeAgentCommand(runtime);
45855
46568
  const config = loadBodyConfig({
45856
- workspaceRoot: join17(dirname26(configPath), "workspace"),
46569
+ workspaceRoot: join18(dirname26(configPath), "workspace"),
45857
46570
  llmEnvFile: runtime.llmEnvFile,
45858
46571
  env: { ...env, BUZZ_AGENT_BIN: agent.command, BUZZ_DEV_MCP_BIN: runtime.mcpBinary },
45859
46572
  agent
@@ -45879,14 +46592,14 @@ async function runUpdateProbeCommand(args, options = {}) {
45879
46592
  sandboxRequired: runtime.sandbox !== "off",
45880
46593
  sandboxUnavailableDetail: sandbox.advisory,
45881
46594
  // The successor's probe still holds `<runtimeDir>/update-functional-probe`.
45882
- probeRoot: join17(runtimeDir, "current-release-probe")
46595
+ probeRoot: join18(runtimeDir, "current-release-probe")
45883
46596
  }));
45884
46597
  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
46598
  write2(JSON.stringify(report));
45886
46599
  }
45887
46600
 
45888
46601
  // apps/body/dist/release-status.js
45889
- import { readFile as readFile25, readdir as readdir10, rename as rename11, writeFile as writeFile21 } from "node:fs/promises";
46602
+ import { readFile as readFile26, readdir as readdir10, rename as rename12, writeFile as writeFile21 } from "node:fs/promises";
45890
46603
  import { resolve as resolve37 } from "node:path";
45891
46604
  var DAEMON_RELEASE_STATUS_FILE = "release-status.json";
45892
46605
  var RELEASE_VERSION = /^v\d+\.\d+\.\d+$/;
@@ -45908,7 +46621,7 @@ async function writeDaemonReleaseStatus(runtimeDir, agentPubkey, identity, optio
45908
46621
  const temporary = `${target}.${status.pid}.tmp`;
45909
46622
  await writeFile21(temporary, `${JSON.stringify(status, null, 2)}
45910
46623
  `, { mode: 384 });
45911
- await rename11(temporary, target);
46624
+ await rename12(temporary, target);
45912
46625
  return status;
45913
46626
  }
45914
46627
 
@@ -46028,15 +46741,16 @@ All other config via env vars (see config.ts).
46028
46741
  process.exit(exitCode);
46029
46742
  }
46030
46743
  var daemonFailureRuntimeDir;
46744
+ var daemonLifecycle;
46031
46745
  var SCRATCH_SWEEP_INTERVAL_MS = 6 * 60 * 6e4;
46032
46746
  function runScratchSweepLogged(runtimeDir) {
46033
46747
  void runScratchSweep(runtimeDir).catch((error) => console.error("[body] scratch sweep failed:", error));
46034
46748
  }
46035
46749
  var DaemonExitError = class extends Error {
46036
- exitStatus;
46037
- constructor(message, exitStatus) {
46750
+ reason;
46751
+ constructor(message, reason) {
46038
46752
  super(message);
46039
- this.exitStatus = exitStatus;
46753
+ this.reason = reason;
46040
46754
  this.name = "DaemonExitError";
46041
46755
  }
46042
46756
  };
@@ -46117,7 +46831,9 @@ async function runStoredDaemon(pathOrPointer) {
46117
46831
  config.sandboxMaskPaths = [...config.sandboxMaskPaths ?? [], ...runtime.sandboxMaskPaths];
46118
46832
  }
46119
46833
  const controller = new AbortController();
46120
- const disposeStopSignals = installDaemonStopSignals(controller);
46834
+ const lifecycle = new HelperLifecycle({ controller });
46835
+ daemonLifecycle = lifecycle;
46836
+ const disposeStopSignals = lifecycle.installSignals();
46121
46837
  const runtimeDir = dirname27(configPath);
46122
46838
  const layout = beelineInstallLayout(process.env);
46123
46839
  const notifier = new SystemdNotifier();
@@ -46139,15 +46855,17 @@ async function runStoredDaemon(pathOrPointer) {
46139
46855
  let update;
46140
46856
  let pendingSuccessor = false;
46141
46857
  let successorRolledBack = false;
46858
+ let attemptDeadlineAt;
46142
46859
  if (layout) {
46143
46860
  const settle = await settleUpdateAttemptOnStart(layout);
46144
46861
  if (settle.kind === "rolled-back") {
46145
46862
  await queueUpdateRollbackAlert(runtimeDir, settle.record.releaseId);
46146
46863
  await drainRollbackAlert(runtime.rooms[0]?.channelId);
46147
46864
  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);
46865
+ throw new DaemonExitError("stale unconfirmed release rolled back; supervisor must restart", "rolled-back");
46149
46866
  } else if (settle.kind === "pending") {
46150
46867
  pendingSuccessor = true;
46868
+ attemptDeadlineAt = (await readUpdateAttempt(layout))?.confirmBy;
46151
46869
  }
46152
46870
  loadedRelease = await activeReleaseId(layout);
46153
46871
  await clearUpdateRollbackAlertIfConfirmed(runtimeDir, loadedRelease);
@@ -46162,6 +46880,9 @@ async function runStoredDaemon(pathOrPointer) {
46162
46880
  requiredProbeIds: [...await runningRuntimeProbeIds(process.env), runtime.agent.publicKey]
46163
46881
  });
46164
46882
  if (pendingSuccessor && process.platform === "linux" && process.env.BEELINE_SYSTEMD_USER !== "0") {
46883
+ await rewriteAgentServiceUnit({ lock: (work) => withInstallLock(layout, work) }).catch((error) => {
46884
+ console.error(`[beeline] agent unit not converged: ${error instanceof Error ? error.message : String(error)}`);
46885
+ });
46165
46886
  await installTrustySquireBrokerService().catch((error) => {
46166
46887
  console.error(`[beeline] host Squire broker unit not converged: ${error instanceof Error ? error.message : String(error)}`);
46167
46888
  });
@@ -46201,6 +46922,7 @@ async function runStoredDaemon(pathOrPointer) {
46201
46922
  return catalogRefresh;
46202
46923
  };
46203
46924
  let stoppingStatus = "daemon stopped";
46925
+ let exitReason;
46204
46926
  try {
46205
46927
  registryMcpBroker = new RegistryMcpHostBroker(
46206
46928
  config.operatorHome,
@@ -46224,16 +46946,16 @@ async function runStoredDaemon(pathOrPointer) {
46224
46946
  daemonApi,
46225
46947
  onConfigChanged: refreshCatalog,
46226
46948
  onRestartRequested: () => {
46227
- if (lifecycleRestart)
46949
+ if (lifecycleRestart || !lifecycle.quiesce("restart"))
46228
46950
  return;
46229
46951
  lifecycleRestart = (async () => {
46230
46952
  const cancelled = await core.cancelActiveWorkForRestart();
46231
46953
  stoppingStatus = cancelled ? "restart requested; active work cancelled" : "restart requested; no active work";
46232
- await notifier.stopping(stoppingStatus);
46233
- controller.abort();
46954
+ await notifier.stopping(stoppingStatus).catch((error) => console.error("[thin-core] stopping notification failed:", error));
46955
+ lifecycle.stop("restart-requested");
46234
46956
  })().catch((error) => {
46235
46957
  console.error("[thin-core] requested restart failed:", error);
46236
- controller.abort();
46958
+ lifecycle.stop("restart-requested");
46237
46959
  });
46238
46960
  }
46239
46961
  });
@@ -46242,6 +46964,7 @@ async function runStoredDaemon(pathOrPointer) {
46242
46964
  loadedVersion: loadedReleaseIdentity?.version,
46243
46965
  runtimeDir,
46244
46966
  interrupt: () => {
46967
+ lifecycle.quiesce("force-update");
46245
46968
  core.setDrainDeadlineAt(Date.now() + 6e4);
46246
46969
  return core.interruptForServerMinimum();
46247
46970
  },
@@ -46260,25 +46983,25 @@ async function runStoredDaemon(pathOrPointer) {
46260
46983
  if (staggerMs > 0)
46261
46984
  await new Promise((resolve40) => setTimeout(resolve40, staggerMs));
46262
46985
  stoppingStatus = `server minimum installed; restarting onto ${desiredRelease}`;
46263
- await notifier.stopping(stoppingStatus);
46264
- controller.abort();
46986
+ await notifier.stopping(stoppingStatus).catch((error) => console.error("[thin-core] stopping notification failed:", error));
46987
+ lifecycle.stop("update");
46265
46988
  },
46266
46989
  failed: (error) => {
46267
46990
  console.error("[thin-core] forced helper update failed:", error);
46268
- controller.abort();
46991
+ lifecycle.stop("force-update-failed");
46269
46992
  }
46270
46993
  }) : void 0;
46271
46994
  daemonApi.setForceUpdateListener((minVersion) => {
46272
46995
  if (!forceUpdate) {
46273
46996
  console.error("[thin-core] server requires a published helper, but this process has no install layout");
46274
- controller.abort();
46997
+ lifecycle.stop("stopped");
46275
46998
  return;
46276
46999
  }
46277
47000
  forceUpdate.request(minVersion);
46278
47001
  });
46279
47002
  const updateDrain = update ? new ManagedUpdateDrain({
46280
47003
  update,
46281
- quiesceIfIdle: () => !forceUpdate?.active && core.quiesceForUpdateIfIdle(),
47004
+ quiesceIfIdle: () => !forceUpdate?.active && lifecycle.quiesceUpdateIfIdle(() => core.quiesceForUpdateIfIdle()),
46282
47005
  activeTurnCount: () => core.activeTurnCount(),
46283
47006
  restart: async ({ desiredRelease, drainDeadlineAt }) => {
46284
47007
  core.setDrainDeadlineAt(drainDeadlineAt);
@@ -46286,20 +47009,29 @@ async function runStoredDaemon(pathOrPointer) {
46286
47009
  if (staggerMs > 0)
46287
47010
  await new Promise((resolve40) => setTimeout(resolve40, staggerMs));
46288
47011
  stoppingStatus = `update pending, converging; loaded_release=${loadedRelease ?? "unknown"}; desired_release=${desiredRelease}; active work drained; intake quiesced`;
46289
- await notifier.stopping(stoppingStatus);
46290
- controller.abort();
47012
+ await notifier.stopping(stoppingStatus).catch((error) => console.error("[thin-core] stopping notification failed:", error));
47013
+ lifecycle.stop("update");
46291
47014
  },
46292
47015
  waiting: async ({ desiredRelease }) => {
46293
47016
  await notifier.progress(`loaded_release=${loadedRelease ?? "unknown"}; update ready; active agent work is still running; handoff deferred; desired_release=${desiredRelease}`);
46294
47017
  }
46295
47018
  }) : void 0;
47019
+ const tickUpdate = async () => {
47020
+ try {
47021
+ await updateDrain?.tick();
47022
+ } catch (error) {
47023
+ console.error("[thin-core] managed restart failed; serving again:", error);
47024
+ lifecycle.resumeAfterFailedUpdate(() => core.resumeServing());
47025
+ }
47026
+ };
46296
47027
  daemonApi.setHelperReleaseListener(({ version, sha }) => {
46297
47028
  update?.notifyReleaseAvailable(`${version}:${sha}`);
46298
- void updateDrain?.tick();
47029
+ void tickUpdate();
46299
47030
  });
47031
+ daemonApi.onLiveOpen(() => void tickUpdate());
46300
47032
  core.setInteractiveIdleListener(() => {
46301
47033
  institutionalMemoryWorker?.wake();
46302
- void updateDrain?.tick();
47034
+ void tickUpdate();
46303
47035
  });
46304
47036
  const result = await core.run({
46305
47037
  signal: controller.signal,
@@ -46359,7 +47091,12 @@ async function runStoredDaemon(pathOrPointer) {
46359
47091
  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
47092
  }
46361
47093
  try {
46362
- await reportInterruptedTurns(runtimeDir, daemonApi, runtime.agent.publicKey);
47094
+ await retryBeforeReady(() => reportInterruptedTurns(runtimeDir, daemonApi, runtime.agent.publicKey), {
47095
+ onLinkOpen: (listener) => daemonApi.onLiveOpen(listener),
47096
+ extendStartTimeout: extendSystemdStartTimeout,
47097
+ ...attemptDeadlineAt !== void 0 ? { deadlineAt: attemptDeadlineAt } : {},
47098
+ signal: controller.signal
47099
+ });
46363
47100
  } catch (error) {
46364
47101
  if (error instanceof DaemonApiError && error.status === 426 && error.code === "update_required" && forceUpdate?.pending) {
46365
47102
  await forceUpdate.pending;
@@ -46407,18 +47144,18 @@ async function runStoredDaemon(pathOrPointer) {
46407
47144
  onProgress: async (status) => {
46408
47145
  lastCoreStatus = status;
46409
47146
  void drainRollbackAlert(core.activeRoomIds()[0] ?? runtime.rooms[0]?.channelId);
46410
- await notifier.progress(`loaded_release=${loadedRelease ?? "development"}; ${status}`);
46411
- await updateDrain?.tick();
47147
+ await notifier.progress(`loaded_release=${loadedRelease ?? "development"}; ${status}`).catch((error) => console.error("[thin-core] progress notification failed:", error));
47148
+ await tickUpdate();
46412
47149
  }
46413
47150
  });
46414
47151
  if (result === "agent-removed") {
46415
47152
  controller.abort();
46416
47153
  const archivedRuntime = await retireRemovedAgent(runtime);
46417
- process.exitCode = DELIBERATE_REMOVAL_EXIT_STATUS;
47154
+ exitReason = "agent-removed";
46418
47155
  console.log(`[beeline] agent ${runtime.agent.publicKey} removed; runtime archived at ${archivedRuntime}`);
46419
47156
  }
46420
47157
  } catch (error) {
46421
- const rolledBack = successorRolledBack || layout && pendingSuccessor && !ready && // Named, so a sibling daemon's journal can say whose failure reverted
47158
+ const rolledBack = successorRolledBack || layout && pendingSuccessor && !ready && successorRollbackAllowed(error, attemptDeadlineAt) && // Named, so a sibling daemon's journal can say whose failure reverted
46422
47159
  // the attempt it shares with this one.
46423
47160
  await rollbackFailedSuccessor(layout, runtimeDir, {
46424
47161
  probeId: runtime.agent.publicKey,
@@ -46441,6 +47178,7 @@ async function runStoredDaemon(pathOrPointer) {
46441
47178
  await clearDaemonPidRecordIfPid(configPath, process.pid);
46442
47179
  disposeStopSignals();
46443
47180
  }
47181
+ return exitReason ?? lifecycle.stopReason ?? "stopped";
46444
47182
  }
46445
47183
  async function main() {
46446
47184
  const args = process.argv.slice(2);
@@ -46529,6 +47267,7 @@ async function main() {
46529
47267
  return;
46530
47268
  }
46531
47269
  if (command === "daemon") {
47270
+ installUnhandledRejectionGuard();
46532
47271
  const configFlag = args.indexOf("--config");
46533
47272
  const agentFlag = args.indexOf("--agent");
46534
47273
  let configPath = configFlag >= 0 ? args[configFlag + 1] : void 0;
@@ -46547,11 +47286,12 @@ async function main() {
46547
47286
  configPath = configs.find((candidate) => dirname27(candidate).endsWith(agentPubkey));
46548
47287
  }
46549
47288
  if (!configPath && agentPubkey) {
46550
- throw new DaemonExitError(`unknown agent ${agentPubkey}: no durable runtime exists; refusing service restart loop`, UNKNOWN_AGENT_EXIT_STATUS);
47289
+ throw new DaemonExitError(`unknown agent ${agentPubkey}: no durable runtime exists; refusing service restart loop`, "unknown-agent");
46551
47290
  }
46552
47291
  if (!configPath)
46553
47292
  throw new Error("daemon requires --config <runtime.json> or --agent <pubkey>");
46554
- await runStoredDaemon(resolve39(configPath));
47293
+ const reason = await runStoredDaemon(resolve39(configPath));
47294
+ daemonLifecycle?.exit(reason);
46555
47295
  return;
46556
47296
  }
46557
47297
  if (command === "update") {
@@ -46591,7 +47331,8 @@ async function main() {
46591
47331
  main().catch(async (err) => {
46592
47332
  if (process.argv[2] === "daemon") {
46593
47333
  const layout = beelineInstallLayout(process.env);
46594
- if (layout && await rollbackFailedSuccessor(layout).catch(() => false)) {
47334
+ const attempt = layout ? await readUpdateAttempt(layout).catch(() => void 0) : void 0;
47335
+ if (layout && successorRollbackAllowed(err, attempt?.confirmBy) && await rollbackFailedSuccessor(layout).catch(() => false)) {
46595
47336
  console.error("[thin-core] successor failed during startup; previous release restored once");
46596
47337
  }
46597
47338
  }
@@ -46603,19 +47344,24 @@ main().catch(async (err) => {
46603
47344
  } else {
46604
47345
  console.error(import_picocolors3.default.red("[body] fatal:"), err);
46605
47346
  }
46606
- let exitStatus = err instanceof DaemonExitError ? err.exitStatus : 1;
46607
- if (process.argv[2] === "daemon" && daemonFailureRuntimeDir && exitStatus === 1) {
47347
+ if (process.argv[2] !== "daemon")
47348
+ process.exit(1);
47349
+ let reason = err instanceof DaemonExitError ? err.reason : "failed";
47350
+ if (daemonFailureRuntimeDir && reason === "failed") {
46608
47351
  try {
46609
- const failure = await recordDaemonStartFailure(daemonFailureRuntimeDir, err);
47352
+ const failure = await settleDaemonStartFailure(daemonFailureRuntimeDir, err);
46610
47353
  if (failure.distressed) {
46611
- exitStatus = DAEMON_DISTRESS_EXIT_STATUS;
47354
+ reason = "distress";
46612
47355
  console.error(`[thin-core] daemon start failed ${failure.count} times; service restart stopped. operator record: ${failure.path}`);
46613
47356
  }
46614
47357
  } catch (recordError) {
46615
47358
  console.error("[thin-core] could not persist daemon distress record:", recordError);
46616
47359
  }
46617
47360
  }
46618
- process.exit(exitStatus);
47361
+ if (daemonLifecycle)
47362
+ daemonLifecycle.exit(reason);
47363
+ else
47364
+ process.exit(HELPER_EXIT_CODES[reason]);
46619
47365
  });
46620
47366
  /*! Bundled license information:
46621
47367