usebeeline 0.0.128 → 0.0.130

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.
@@ -1080,6 +1080,7 @@ var init_agent_command = __esm({
1080
1080
 
1081
1081
  // apps/body/dist/squire-host.js
1082
1082
  import { spawn as spawn5 } from "node:child_process";
1083
+ import { createHash } from "node:crypto";
1083
1084
  import { existsSync as existsSync5, lstatSync, mkdirSync } from "node:fs";
1084
1085
  import { createRequire as createRequire3 } from "node:module";
1085
1086
  import { homedir as homedir2 } from "node:os";
@@ -1120,8 +1121,16 @@ function squireBrokerSocketReady(socketPath) {
1120
1121
  return false;
1121
1122
  }
1122
1123
  }
1123
- function squireFacadeLaunch(home) {
1124
- const env = squireHostRewriteEnv(home);
1124
+ function squireAgentIdentity({ agentId, roomId }) {
1125
+ if (!agentId || !roomId)
1126
+ throw new Error("Squire agent and Room IDs are required");
1127
+ return `beeline:${createHash("sha256").update(JSON.stringify([agentId, roomId])).digest("hex")}`;
1128
+ }
1129
+ function squireFacadeLaunch(home, scope) {
1130
+ const env = {
1131
+ ...squireHostRewriteEnv(home),
1132
+ TRUSTY_SQUIRE_AGENT_IDENTITY: squireAgentIdentity(scope)
1133
+ };
1125
1134
  const meta = "usebeeline:npm";
1126
1135
  if (meta.startsWith("beeline:")) {
1127
1136
  const entry = process.argv[1];
@@ -3478,7 +3487,7 @@ var require_websocket = __commonJS({
3478
3487
  var http = __require("http");
3479
3488
  var net2 = __require("net");
3480
3489
  var tls = __require("tls");
3481
- var { randomBytes: randomBytes8, createHash: createHash10 } = __require("crypto");
3490
+ var { randomBytes: randomBytes8, createHash: createHash11 } = __require("crypto");
3482
3491
  var { Duplex, Readable } = __require("stream");
3483
3492
  var { URL: URL2 } = __require("url");
3484
3493
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -4146,7 +4155,7 @@ var require_websocket = __commonJS({
4146
4155
  abortHandshake(websocket, socket, "Invalid Upgrade header");
4147
4156
  return;
4148
4157
  }
4149
- const digest = createHash10("sha1").update(key + GUID).digest("base64");
4158
+ const digest = createHash11("sha1").update(key + GUID).digest("base64");
4150
4159
  if (res.headers["sec-websocket-accept"] !== digest) {
4151
4160
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
4152
4161
  return;
@@ -4515,7 +4524,7 @@ var require_websocket_server = __commonJS({
4515
4524
  var EventEmitter2 = __require("events");
4516
4525
  var http = __require("http");
4517
4526
  var { Duplex } = __require("stream");
4518
- var { createHash: createHash10 } = __require("crypto");
4527
+ var { createHash: createHash11 } = __require("crypto");
4519
4528
  var extension2 = require_extension();
4520
4529
  var PerMessageDeflate2 = require_permessage_deflate();
4521
4530
  var subprotocol2 = require_subprotocol();
@@ -4822,7 +4831,7 @@ var require_websocket_server = __commonJS({
4822
4831
  );
4823
4832
  }
4824
4833
  if (this._state > RUNNING) return abortHandshake(socket, 503);
4825
- const digest = createHash10("sha1").update(key + GUID).digest("base64");
4834
+ const digest = createHash11("sha1").update(key + GUID).digest("base64");
4826
4835
  const headers = [
4827
4836
  "HTTP/1.1 101 Switching Protocols",
4828
4837
  "Upgrade: websocket",
@@ -22072,7 +22081,7 @@ __export(self_update_exports, {
22072
22081
  writeUpdateAttemptFixture: () => writeUpdateAttemptFixture,
22073
22082
  writeUpdateState: () => writeUpdateState
22074
22083
  });
22075
- import { createHash as createHash8 } from "node:crypto";
22084
+ import { createHash as createHash9 } from "node:crypto";
22076
22085
  import { constants as fsConstants2 } from "node:fs";
22077
22086
  import { access as access2, chmod as chmod7, lstat as lstat4, mkdir as mkdir19, open, readFile as readFile15, rename as rename7, rm as rm9, symlink as symlink3, writeFile as writeFile14 } from "node:fs/promises";
22078
22087
  import { spawn as spawn10 } from "node:child_process";
@@ -22305,7 +22314,7 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
22305
22314
  if (!response.ok || !response.body) {
22306
22315
  throw new Error(`downloading ${published.file} failed: HTTP ${response.status}`);
22307
22316
  }
22308
- const hash = createHash8("sha256");
22317
+ const hash = createHash9("sha256");
22309
22318
  const chunks = [];
22310
22319
  for await (const chunk of response.body) {
22311
22320
  const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
@@ -22667,7 +22676,7 @@ var init_self_update = __esm({
22667
22676
  if (previousAttempt?.status === "reverted" && !opts.force) {
22668
22677
  const sameCommit = Boolean(previousAttempt.to.commit && previousAttempt.to.commit === bundle.commit);
22669
22678
  const sameVersion = Boolean(previousAttempt.to.version && previousAttempt.to.version === bundle.version);
22670
- if (sameCommit || sameVersion) {
22679
+ if (sameCommit && sameVersion) {
22671
22680
  const line = `release ${describeIdentity(previousAttempt.to)} reverted after a failed served-turn proof; waiting for a newer publish or \`beeline update --force\``;
22672
22681
  if (line !== this.lastVerdictLog) {
22673
22682
  this.log(`[body] self-update: ${line}`);
@@ -26737,13 +26746,13 @@ async function applyRuntimeModelPreflight(config, agent, selection, validate = v
26737
26746
  }
26738
26747
 
26739
26748
  // apps/body/dist/model-catalog-sync.js
26740
- import { createHash } from "node:crypto";
26749
+ import { createHash as createHash2 } from "node:crypto";
26741
26750
  import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
26742
26751
  import { resolve as resolve6 } from "node:path";
26743
26752
  var MODEL_CATALOG_PROBE_TIMEOUT_MS = 3e4;
26744
26753
  var MODEL_CATALOG_HASH_FILE = "model-catalog.sha256";
26745
26754
  function modelCatalogHash(options, selection, startupUnavailable) {
26746
- return createHash("sha256").update(JSON.stringify({
26755
+ return createHash2("sha256").update(JSON.stringify({
26747
26756
  options,
26748
26757
  selection: selection ?? null,
26749
26758
  startupUnavailable: startupUnavailable ?? null
@@ -27963,7 +27972,7 @@ var StdioSquireMcpClient = class {
27963
27972
  initialized;
27964
27973
  closed = false;
27965
27974
  log;
27966
- constructor(options = {}) {
27975
+ constructor(options) {
27967
27976
  this.options = options;
27968
27977
  this.log = options.log ?? (() => {
27969
27978
  });
@@ -28007,8 +28016,13 @@ var StdioSquireMcpClient = class {
28007
28016
  return this.initialized;
28008
28017
  }
28009
28018
  initialize() {
28010
- const launch = squireFacadeLaunch(homedir4());
28011
- const child = (this.options.spawn ?? spawn8)(this.options.command ?? launch.command, [...this.options.args ?? launch.args], { env: this.options.env ?? { ...squireConnectProcessEnv(), ...launch.env } });
28019
+ const launch = squireFacadeLaunch(homedir4(), this.options.scope);
28020
+ const child = (this.options.spawn ?? spawn8)(this.options.command ?? launch.command, [...this.options.args ?? launch.args], {
28021
+ env: {
28022
+ ...this.options.env ?? { ...squireConnectProcessEnv(), ...launch.env },
28023
+ TRUSTY_SQUIRE_AGENT_IDENTITY: launch.env.TRUSTY_SQUIRE_AGENT_IDENTITY
28024
+ }
28025
+ });
28012
28026
  this.child = child;
28013
28027
  this.buffer = "";
28014
28028
  child.stdout.setEncoding("utf8");
@@ -28085,12 +28099,12 @@ var StdioSquireMcpClient = class {
28085
28099
  }
28086
28100
  }
28087
28101
  };
28088
- function defaultSquireMcpClient() {
28089
- return new StdioSquireMcpClient();
28102
+ function defaultSquireMcpClient(scope) {
28103
+ return new StdioSquireMcpClient({ scope });
28090
28104
  }
28091
28105
 
28092
28106
  // apps/body/dist/registry-mcp.js
28093
- import { createHash as createHash2, randomBytes } from "node:crypto";
28107
+ import { createHash as createHash3, randomBytes } from "node:crypto";
28094
28108
  import { lookup } from "node:dns/promises";
28095
28109
  import { createRequire as createRequire4 } from "node:module";
28096
28110
  import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
@@ -28101,13 +28115,28 @@ import { createInterface as createInterface4 } from "node:readline";
28101
28115
  import { fileURLToPath as fileURLToPath4 } from "node:url";
28102
28116
 
28103
28117
  // apps/body/dist/mcp-route-class.js
28118
+ import { isIP } from "node:net";
28104
28119
  var CODE_OWNED_HOST_MCP_NAMES = ["squire"];
28105
28120
  var MCP_ROUTE_CLASS_KEY = "beeline_route";
28106
28121
  var MCP_ROUTE_HOST = "host";
28107
28122
  function isCodeOwnedHostMcpName(name) {
28108
28123
  return CODE_OWNED_HOST_MCP_NAMES.includes(name.trim().toLowerCase());
28109
28124
  }
28110
- function classifyImportedMcpServer(_input) {
28125
+ function classifyImportedMcpServer(input) {
28126
+ const declaration = input.declaration;
28127
+ if (!declaration || isCodeOwnedHostMcpName(input.name))
28128
+ return "host";
28129
+ const publicKeys = /* @__PURE__ */ new Set(["url", "enabled", "startup_timeout_sec", "tool_timeout_sec"]);
28130
+ if (Object.keys(declaration).some((key) => !publicKeys.has(key)))
28131
+ return "host";
28132
+ if (typeof declaration.url !== "string")
28133
+ return "host";
28134
+ try {
28135
+ const url = new URL(declaration.url);
28136
+ if (url.protocol === "https:" && url.hostname.includes(".") && !url.hostname.endsWith(".local") && !isIP(url.hostname) && !url.username && !url.password && !url.search && !url.hash)
28137
+ return "local";
28138
+ } catch {
28139
+ }
28111
28140
  return "host";
28112
28141
  }
28113
28142
  function hasSquireBrokerEnvironment(declaration) {
@@ -28458,7 +28487,7 @@ async function installRegistryMcp(input) {
28458
28487
  if (typeof client.client_id !== "string")
28459
28488
  throw new Error("dynamic client registration returned no client id");
28460
28489
  const verifier = base64url(randomBytes(48));
28461
- const challenge3 = base64url(createHash2("sha256").update(verifier).digest());
28490
+ const challenge3 = base64url(createHash3("sha256").update(verifier).digest());
28462
28491
  const resourceIndicator = safeUrl(resource.resource) ?? remoteUrl;
28463
28492
  const scope = Array.isArray(resource.scopes_supported) ? resource.scopes_supported.filter((item) => typeof item === "string").join(" ") : challengeParameter(initialize.headers.get("www-authenticate"), "scope");
28464
28493
  const authorizationUrl = new URL(authorizationEndpoint);
@@ -29004,7 +29033,7 @@ var ConnectorAssignmentLoop = class {
29004
29033
  })();
29005
29034
  }
29006
29035
  squire() {
29007
- this.mcp ??= defaultSquireMcpClient();
29036
+ this.mcp ??= defaultSquireMcpClient({ agentId: this.agentId, roomId: "connector" });
29008
29037
  return this.mcp;
29009
29038
  }
29010
29039
  async handle(assignment) {
@@ -30053,6 +30082,7 @@ var DaemonApiClient = class {
30053
30082
  connectorAssignmentListener;
30054
30083
  cornerCompleteListener;
30055
30084
  cornerRestartListener;
30085
+ helperReleaseListener;
30056
30086
  constructor(baseUrl, daemonToken, agentId, fetchImpl = fetch, webSocketFactory = (url, protocols) => new wrapper_default(url, protocols)) {
30057
30087
  this.baseUrl = baseUrl;
30058
30088
  this.daemonToken = daemonToken;
@@ -30127,6 +30157,9 @@ var DaemonApiClient = class {
30127
30157
  setCornerRestartListener(listener) {
30128
30158
  this.cornerRestartListener = listener;
30129
30159
  }
30160
+ setHelperReleaseListener(listener) {
30161
+ this.helperReleaseListener = listener;
30162
+ }
30130
30163
  updateLiveCursor(roomId, cursor3) {
30131
30164
  const room = this.liveRooms.get(roomId);
30132
30165
  if (room && cursor3)
@@ -30180,6 +30213,10 @@ var DaemonApiClient = class {
30180
30213
  if (!value || typeof value !== "object")
30181
30214
  return;
30182
30215
  const event = value;
30216
+ if (event.type === "helper-release" && typeof event.version === "string" && typeof event.sha === "string") {
30217
+ this.helperReleaseListener?.({ version: event.version, sha: event.sha });
30218
+ return;
30219
+ }
30183
30220
  if (event.type === "rooms-changed") {
30184
30221
  this.roomsChangedListener?.(membershipChange(event));
30185
30222
  return;
@@ -30321,7 +30358,7 @@ async function activateDaemonTransport(path, fetchImpl = fetch) {
30321
30358
 
30322
30359
  // apps/body/dist/room-runtime.js
30323
30360
  import { execFile as execFile9 } from "node:child_process";
30324
- import { createHash as createHash7 } from "node:crypto";
30361
+ import { createHash as createHash8 } from "node:crypto";
30325
30362
  import { existsSync as existsSync8, mkdirSync as mkdirSync4 } from "node:fs";
30326
30363
  import { mkdir as mkdir15, realpath as realpath2, rm as rm7 } from "node:fs/promises";
30327
30364
  import { dirname as dirname15, relative as relative3, resolve as resolve25, sep as sep2 } from "node:path";
@@ -30329,7 +30366,7 @@ import { promisify as promisify5 } from "node:util";
30329
30366
 
30330
30367
  // apps/body/dist/grant-runner.js
30331
30368
  import { execFile as execFile5 } from "node:child_process";
30332
- import { createHash as createHash3, randomBytes as randomBytes7 } from "node:crypto";
30369
+ import { createHash as createHash4, randomBytes as randomBytes7 } from "node:crypto";
30333
30370
  import { readFile as readFile5 } from "node:fs/promises";
30334
30371
  import { createServer as createServer2 } from "node:http";
30335
30372
  import { dirname as dirname8, resolve as resolve12 } from "node:path";
@@ -31260,7 +31297,7 @@ ${ROOM_WRITE_REFUSED_NOTE}` : outcome.output, secrets), cap);
31260
31297
  if (!script || script.path !== argument.path) {
31261
31298
  throw new Error(`${argument.path} is a script no human has read: this grant was approved without it. Ask with request_grant kind=command so the card carries what the command runs.`);
31262
31299
  }
31263
- if (createHash3("sha256").update(bytes).digest("hex") !== script.sha256) {
31300
+ if (createHash4("sha256").update(bytes).digest("hex") !== script.sha256) {
31264
31301
  throw new Error(`${argument.path} changed after it was approved, so the approval no longer covers it. Ask with request_grant kind=command again so a human reads what runs now.`);
31265
31302
  }
31266
31303
  }
@@ -31816,7 +31853,7 @@ async function runServerCommandIntake(options) {
31816
31853
 
31817
31854
  // apps/body/dist/monolith-corner-turn.js
31818
31855
  import { execFile as execFile8 } from "node:child_process";
31819
- import { createHash as createHash6 } from "node:crypto";
31856
+ import { createHash as createHash7 } from "node:crypto";
31820
31857
  import { mkdir as mkdir14, readFile as readFile12, writeFile as writeFile11 } from "node:fs/promises";
31821
31858
  import { homedir as homedir11, hostname as hostname2 } from "node:os";
31822
31859
  import { dirname as dirname14, join as join13, resolve as resolve24 } from "node:path";
@@ -32621,7 +32658,7 @@ function stringify(obj, { maxDepth = 1e3, numbersAsFloat = false } = {}) {
32621
32658
  }
32622
32659
 
32623
32660
  // apps/body/dist/agent-home.js
32624
- import { createHash as createHash4, randomUUID as randomUUID5 } from "node:crypto";
32661
+ import { createHash as createHash5, randomUUID as randomUUID5 } from "node:crypto";
32625
32662
  import { chmod as chmod3, copyFile, lstat as lstat2, mkdir as mkdir6, readFile as readFile9, readlink, readdir as readdir3, realpath, rename as rename3, rm as rm4, symlink, unlink as unlink3, writeFile as writeFile7 } from "node:fs/promises";
32626
32663
  import { homedir as homedir9 } from "node:os";
32627
32664
  import { basename as basename4, dirname as dirname10, join as join9, relative as relative2, resolve as resolve17, sep } from "node:path";
@@ -32763,7 +32800,7 @@ var BEELINE_ROOM_CAPABILITIES = [
32763
32800
  "Tag another agent only when you need something from them: a question, a handoff, a task. Never tag to acknowledge, agree, or say you are ready. If nothing is actionable, do not reply.",
32764
32801
  "Tag the user only when you need a decision or input, or when the task they asked for is finished. Never tag for progress, acknowledgement, or questions the transcript already answers.",
32765
32802
  "If a shell command is refused, say so plainly rather than retrying it, and continue with read-only inspection: call beeline-readonly-mcp.search_text to find code and beeline-readonly-mcp.read_file to read it. Use CodeGraph first when it is available for indexed code relationships.",
32766
- "A mounted tool is not standing resource consent: each resource call checks the original requester. Repository permission under yolo bypasses prompts for anyone; personal resources bypass only for their owner requester. Otherwise repository asks go to Workspace managers in the Room and resource asks go privately to the resource owner. Approved resource access includes paid calls within its scope, without a generic budget card. Delegation and follow-up work retain the original requester. Network web search is enabled.",
32803
+ "An agent may use its own owner\u2019s tools and resources regardless of who started the turn or whether yolo is on. Access to another person\u2019s files or tools outside the sandbox asks that resource owner. Wallet access keeps its separate approval rule. The two command hard stops remain: credential files named by argv and scripts nobody has read. Delegation and follow-up work retain the original requester. Network web search is enabled.",
32767
32804
  BEELINE_AMBIENT_CONNECTOR_CAPABILITY,
32768
32805
  "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.",
32769
32806
  "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.",
@@ -32782,7 +32819,7 @@ var BEELINE_DM_CAPABILITIES = [
32782
32819
  "This is a private direct-message conversation with one person. Every message they send is addressed to you; reply without tagging.",
32783
32820
  "This Room is strictly conversational: there is no repository binding and no corner can be opened from here.",
32784
32821
  "The repository filesystem is read-only in this session.",
32785
- "A mounted tool is not standing resource consent: each resource call checks the original requester. Repository permission under yolo bypasses prompts for anyone; personal resources bypass only for their owner requester. Otherwise repository asks go to Workspace managers in the Room and resource asks go privately to the resource owner. Approved resource access includes paid calls within its scope, without a generic budget card. Delegation and follow-up work retain the original requester. Network web search is enabled.",
32822
+ "An agent may use its own owner\u2019s tools and resources regardless of who started the turn or whether yolo is on. Access to another person\u2019s files or tools outside the sandbox asks that resource owner. Wallet access keeps its separate approval rule. The two command hard stops remain: credential files named by argv and scripts nobody has read. Delegation and follow-up work retain the original requester. Network web search is enabled.",
32786
32823
  BEELINE_AMBIENT_CONNECTOR_CAPABILITY,
32787
32824
  "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.",
32788
32825
  "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.",
@@ -33201,14 +33238,14 @@ function grantedSquireHostRouteNames(granted, declarations = {}) {
33201
33238
  function hostRouteEnv(hostHome) {
33202
33239
  return { XDG_CONFIG_HOME: join8(resolve15(hostHome), ".config") };
33203
33240
  }
33204
- function rewriteHostMcpDeclaration(name, declaration, hostHome, resourceAuthFile) {
33241
+ function rewriteHostMcpDeclaration(name, declaration, hostHome, resourceAuthFile, squireScope) {
33205
33242
  const next = { ...declaration };
33206
33243
  delete next[MCP_ROUTE_CLASS_KEY];
33207
33244
  const explicitResourceTarget = typeof next[MCP_RESOURCE_TARGET_KEY] === "string" ? next[MCP_RESOURCE_TARGET_KEY] : void 0;
33208
33245
  const transportGate = next[MCP_RESOURCE_GATE_KEY] === MCP_RESOURCE_GATE_TRANSPORT;
33209
33246
  delete next[MCP_RESOURCE_TARGET_KEY];
33210
33247
  delete next[MCP_RESOURCE_GATE_KEY];
33211
- const launch = isSquireDeclaration(name, declaration) ? squireFacadeLaunch(hostHome) : void 0;
33248
+ const launch = isSquireDeclaration(name, declaration) ? squireFacadeLaunch(hostHome, requiredSquireScope(squireScope)) : void 0;
33212
33249
  const routeEnv = launch?.env ?? hostRouteEnv(hostHome);
33213
33250
  const gooseShape = "cmd" in declaration && !("command" in declaration);
33214
33251
  if (launch) {
@@ -33240,7 +33277,12 @@ function rewriteHostMcpDeclaration(name, declaration, hostHome, resourceAuthFile
33240
33277
  }
33241
33278
  return next;
33242
33279
  }
33243
- function rewriteGrantedHostRoutes(declarations, granted, hostHome, resourceAuthFile) {
33280
+ function requiredSquireScope(scope) {
33281
+ if (!scope)
33282
+ throw new Error("Squire agent and Room scope is required");
33283
+ return scope;
33284
+ }
33285
+ function rewriteGrantedHostRoutes(declarations, granted, hostHome, resourceAuthFile, squireScope) {
33244
33286
  const allowed = new Set(granted);
33245
33287
  const rewritten = {};
33246
33288
  for (const [name, declaration] of Object.entries(declarations)) {
@@ -33248,17 +33290,17 @@ function rewriteGrantedHostRoutes(declarations, granted, hostHome, resourceAuthF
33248
33290
  continue;
33249
33291
  if (classifyImportedMcpServer({ name, declaration }) !== "host")
33250
33292
  continue;
33251
- rewritten[name] = rewriteHostMcpDeclaration(name, declaration, hostHome, resourceAuthFile);
33293
+ rewritten[name] = rewriteHostMcpDeclaration(name, declaration, hostHome, resourceAuthFile, squireScope);
33252
33294
  }
33253
33295
  for (const name of granted) {
33254
33296
  if (rewritten[name] || !isCodeOwnedHostMcpName(name))
33255
33297
  continue;
33256
- rewritten[name] = rewriteHostMcpDeclaration(name, {}, hostHome, resourceAuthFile);
33298
+ rewritten[name] = rewriteHostMcpDeclaration(name, {}, hostHome, resourceAuthFile, squireScope);
33257
33299
  }
33258
33300
  return rewritten;
33259
33301
  }
33260
- function grantedHostRouteWires(granted, hostHome, declarations = {}, resourceAuthFile) {
33261
- const routes = rewriteGrantedHostRoutes(declarations, granted, hostHome, resourceAuthFile);
33302
+ function grantedHostRouteWires(granted, hostHome, declarations = {}, resourceAuthFile, squireScope) {
33303
+ const routes = rewriteGrantedHostRoutes(declarations, granted, hostHome, resourceAuthFile, squireScope);
33262
33304
  return Object.entries(routes).flatMap(([name, declaration]) => {
33263
33305
  const command = typeof declaration.command === "string" && declaration.command || typeof declaration.cmd === "string" && declaration.cmd || "";
33264
33306
  if (!command)
@@ -34043,7 +34085,7 @@ async function prepareRoomAgentHome(input) {
34043
34085
  console.error(`[body] could not repair shared credentials under ${root}:`, error);
34044
34086
  }
34045
34087
  const prior = agentHomeProvisionQueues.get(root) ?? Promise.resolve();
34046
- const provision = prior.catch(() => void 0).then(() => provisionAgentSkillsAndMcp(root, operatorHome, input.skillReleaseId ?? runningBeelineReleaseId(), input.failClosed === true || Boolean(input.resourceAuthFile), input.sharedSkills ?? [], agentSkillDir(input.agentKind), input.openRouterRouting, input.isReviewer ?? false, input.grantedHostRoutes ?? [], input.extraHostRoutes ?? {}, input.agentKind, input.resourceAuthFile));
34088
+ const provision = prior.catch(() => void 0).then(() => provisionAgentSkillsAndMcp(root, operatorHome, input.skillReleaseId ?? runningBeelineReleaseId(), input.failClosed === true || Boolean(input.resourceAuthFile), input.sharedSkills ?? [], agentSkillDir(input.agentKind), input.openRouterRouting, input.isReviewer ?? false, input.grantedHostRoutes ?? [], input.extraHostRoutes ?? {}, input.agentKind, input.resourceAuthFile, input.squireScope));
34047
34089
  agentHomeProvisionQueues.set(root, provision);
34048
34090
  try {
34049
34091
  await provision;
@@ -34053,7 +34095,7 @@ async function prepareRoomAgentHome(input) {
34053
34095
  }
34054
34096
  return roomAgentHomeEnv(root);
34055
34097
  }
34056
- async function provisionAgentSkillsAndMcp(root, operatorHome, skillReleaseId, failClosed, sharedSkills, skillDir, openRouterRouting, isReviewer, grantedHostRoutes, extraHostRoutes, agentKind, resourceAuthFile) {
34098
+ async function provisionAgentSkillsAndMcp(root, operatorHome, skillReleaseId, failClosed, sharedSkills, skillDir, openRouterRouting, isReviewer, grantedHostRoutes, extraHostRoutes, agentKind, resourceAuthFile, squireScope) {
34057
34099
  const managedSkills = [
34058
34100
  { name: DRAW_AVATAR_SKILL_NAME, content: drawAvatarSkillMarkdown(skillReleaseId) },
34059
34101
  { name: USING_BEELINE_SKILL_NAME, content: usingBeelineSkillMarkdown(skillReleaseId) },
@@ -34137,19 +34179,19 @@ async function provisionAgentSkillsAndMcp(root, operatorHome, skillReleaseId, fa
34137
34179
  throw error;
34138
34180
  console.warn("[body] claude web-search settings provisioning failed:", error);
34139
34181
  }
34140
- await applyGrantedHostRoutes(root, operatorHome, grantedHostRoutes, agentKind, failClosed, resourceAuthFile, extraHostRoutes);
34182
+ await applyGrantedHostRoutes(root, operatorHome, grantedHostRoutes, agentKind, failClosed, resourceAuthFile, extraHostRoutes, squireScope);
34141
34183
  await provisionPiCustomModelConfig(root, operatorHome, failClosed, openRouterRouting);
34142
34184
  }
34143
34185
  function appliesToHarness(selected, harness) {
34144
34186
  return !selected || selected === harness;
34145
34187
  }
34146
- async function applyGrantedHostRoutes(root, operatorHome, granted, agentKind, failClosed, resourceAuthFile, extraHostRoutes = {}) {
34188
+ async function applyGrantedHostRoutes(root, operatorHome, granted, agentKind, failClosed, resourceAuthFile, extraHostRoutes = {}, squireScope) {
34147
34189
  if (granted.length === 0)
34148
34190
  return;
34149
34191
  try {
34150
34192
  if (grantedSquireHostRoute(granted, hostImportedMcpDeclarations({ operatorHome, agentKind })))
34151
34193
  ensureSquireHostDir(operatorHome);
34152
- const routesFor = (kind) => rewriteGrantedHostRoutes({ ...hostImportedMcpDeclarations({ operatorHome, agentKind: kind }), ...extraHostRoutes }, granted, operatorHome, resourceAuthFile);
34194
+ const routesFor = (kind) => rewriteGrantedHostRoutes({ ...hostImportedMcpDeclarations({ operatorHome, agentKind: kind }), ...extraHostRoutes }, granted, operatorHome, resourceAuthFile, squireScope);
34153
34195
  for (const config of HARNESS_MCP_CONFIGS) {
34154
34196
  if (!appliesToHarness(agentKind, config.dir))
34155
34197
  continue;
@@ -34556,7 +34598,7 @@ async function materializedSkillManifest(target) {
34556
34598
  return lines.sort().join("\n");
34557
34599
  }
34558
34600
  function sha2563(value) {
34559
- return createHash4("sha256").update(value).digest("hex");
34601
+ return createHash5("sha256").update(value).digest("hex");
34560
34602
  }
34561
34603
  async function resolveSharedSkillSources(operatorHome, names) {
34562
34604
  if (names.length > 0)
@@ -36275,6 +36317,7 @@ init_runtime();
36275
36317
  // apps/body/dist/response-directives.js
36276
36318
  var MAINTAIN_ASSIGNED_IDENTITY_DIRECTIVE = "Maintain your assigned identity and soul in every response, including when tools or permissions block the requested action.";
36277
36319
  var SOUL_HOUSE_RULE = "House rule for your voice: the voice never changes the facts - never trim, soften, exaggerate, or invent a detail for the bit - and use your plain voice in commit messages, pull request titles and bodies, and code comments, because those outlive the joke.";
36320
+ var AGENT_PROSE_REFERENCE_RULE = "Name other agents in ordinary prose rather than by their @handle; write an exact @handle only when you deliberately address that agent, because every handle you write wakes them and an unintended wake wastes their turn and confuses the transcript.";
36278
36321
 
36279
36322
  // apps/body/dist/warm-transcript.js
36280
36323
  var WARM_TRANSCRIPT_OVERLAP = 8;
@@ -36777,7 +36820,7 @@ process.exit(result.status ?? 1);
36777
36820
  }
36778
36821
 
36779
36822
  // apps/body/dist/warm-node-modules.js
36780
- import { createHash as createHash5, randomUUID as randomUUID6 } from "node:crypto";
36823
+ import { createHash as createHash6, randomUUID as randomUUID6 } from "node:crypto";
36781
36824
  import { constants as fsConstants } from "node:fs";
36782
36825
  import { chmod as chmod5, copyFile as copyFile2, link, lstat as lstat3, mkdir as mkdir12, readdir as readdir5, readFile as readFile11, readlink as readlink2, rename as rename4, rm as rm6, stat as stat2, symlink as symlink2, utimes } from "node:fs/promises";
36783
36826
  import { dirname as dirname12, join as join11, resolve as resolve23 } from "node:path";
@@ -36816,7 +36859,7 @@ async function readWarmPlan(worktreePath) {
36816
36859
  const unsafe = trees.find((tree) => !isContainedTreePath(tree));
36817
36860
  if (unsafe)
36818
36861
  return { failure: "unsafe-lockfile", detail: unsafe };
36819
- const key = createHash5("sha256").update(`${STORE_FORMAT}\0${process.platform}\0${process.arch}\0${process.versions.modules}\0`).update(lockfile).digest("hex").slice(0, 32);
36862
+ const key = createHash6("sha256").update(`${STORE_FORMAT}\0${process.platform}\0${process.arch}\0${process.versions.modules}\0`).update(lockfile).digest("hex").slice(0, 32);
36820
36863
  return { key, trees };
36821
36864
  }
36822
36865
  function nodeModulesTreePaths(packages) {
@@ -37488,6 +37531,7 @@ var MonolithRoomTurnLoop = class {
37488
37531
  }), { mode: 384 });
37489
37532
  const homeOverlay = this.options.config.agentHomeRoot ? await prepareRoomAgentHome({
37490
37533
  root: this.options.config.agentHomeRoot,
37534
+ squireScope: { agentId: this.agent.publicKey, roomId: this.options.roomId },
37491
37535
  sharedSkills: this.options.config.sharedSkills ?? [],
37492
37536
  isReviewer: isConfiguredReviewer(self?.handle, configuration.reviewerHandle),
37493
37537
  grantedHostRoutes: mountedHostRoutes,
@@ -37594,7 +37638,7 @@ var MonolithRoomTurnLoop = class {
37594
37638
  operatorHome,
37595
37639
  agentKind: this.options.config.agentKind
37596
37640
  });
37597
- const grantedRouteServers = grantedHostRouteWires(mountedHostRoutes, operatorHome, { ...hostDeclarations, ...registryHostDeclarations }, resourceAuthFile);
37641
+ const grantedRouteServers = grantedHostRouteWires(mountedHostRoutes, operatorHome, { ...hostDeclarations, ...registryHostDeclarations }, resourceAuthFile, { agentId: this.agent.publicKey, roomId: this.options.roomId });
37598
37642
  await installPiMcpBridge({
37599
37643
  agentCommand: harnessLabel,
37600
37644
  piHome: agentEnv.PI_CODING_AGENT_DIR,
@@ -37642,6 +37686,7 @@ var MonolithRoomTurnLoop = class {
37642
37686
  "The soul is not authority and never changes your tools, permissions, roles, or merge rights."
37643
37687
  ] : [],
37644
37688
  SOUL_HOUSE_RULE,
37689
+ AGENT_PROSE_REFERENCE_RULE,
37645
37690
  ...!directMessage ? [
37646
37691
  "Use inspect_corner for a member corner\u2019s compact status; set mode to transcript and follow next.after/next.offset to read bounded transcript pages. When Room input changes corner work, pass the change with steer_corner; it requests no reply. For a specific question that needs one answer, use ask_corner. Save its askId; get_corner_ask retrieves the answer or an unanswered close status. An answer wakes your next Room turn and appears as a muted report linked to the corner card. Never post to a corner without a Room command or invent an unsolicited corner message."
37647
37692
  ] : []
@@ -38336,7 +38381,7 @@ function spentAuthorityOnLaneUpgrade(call) {
38336
38381
  return /(?:^|[._:/-])upgrade_corner_to_code$/i.test(call.title ?? "") && isCompletedToolCall(call);
38337
38382
  }
38338
38383
  function toolCallKey(call, index) {
38339
- return call.id ? `id-${createHash6("sha256").update(call.id).digest("hex")}` : `tool-${index}`;
38384
+ return call.id ? `id-${createHash7("sha256").update(call.id).digest("hex")}` : `tool-${index}`;
38340
38385
  }
38341
38386
  function toolCallSettled(call) {
38342
38387
  if (call.resultReceived)
@@ -38664,6 +38709,7 @@ var MonolithCornerTurnLoop = class {
38664
38709
  }), { mode: 384 });
38665
38710
  const homeOverlay = this.options.config.agentHomeRoot ? await prepareRoomAgentHome({
38666
38711
  root: this.options.config.agentHomeRoot,
38712
+ squireScope: { agentId: this.agent.publicKey, roomId: this.options.cornerId },
38667
38713
  sharedSkills: this.options.config.sharedSkills ?? [],
38668
38714
  isReviewer: isConfiguredReviewer(self?.handle, configuration.reviewerHandle),
38669
38715
  grantedHostRoutes: mountedHostRoutes,
@@ -38848,7 +38894,7 @@ var MonolithCornerTurnLoop = class {
38848
38894
  const youtube = youtubeMcpServer(this.options.config, this.options.youtubeAccessToken, resourceAuthFile);
38849
38895
  if (youtube)
38850
38896
  servers.push(youtube);
38851
- const grantedRouteServers = grantedHostRouteWires(mountedHostRoutes, operatorHome, { ...hostDeclarations, ...registryHostDeclarations }, resourceAuthFile);
38897
+ const grantedRouteServers = grantedHostRouteWires(mountedHostRoutes, operatorHome, { ...hostDeclarations, ...registryHostDeclarations }, resourceAuthFile, { agentId: this.agent.publicKey, roomId: this.options.cornerId });
38852
38898
  await installPiMcpBridge({
38853
38899
  agentCommand: harnessLabel,
38854
38900
  piHome: agentEnv.PI_CODING_AGENT_DIR,
@@ -38858,7 +38904,8 @@ var MonolithCornerTurnLoop = class {
38858
38904
  const identityInstructions = `Your Beeline identity is ${self?.name ?? this.agent.name}.`;
38859
38905
  const personaInstructions = [
38860
38906
  ...persona?.instructions ? [`Human-authored Workspace persona: ${persona.name}. ${persona.instructions}`] : [],
38861
- SOUL_HOUSE_RULE
38907
+ SOUL_HOUSE_RULE,
38908
+ AGENT_PROSE_REFERENCE_RULE
38862
38909
  ].join("\n");
38863
38910
  this.turnIdentityInstructions = harnessHonorsSessionSystemPrompt(command) ? "" : [identityInstructions, personaInstructions].filter(Boolean).join("\n\n");
38864
38911
  const opened = await this.client.sessionNew({
@@ -39091,7 +39138,7 @@ var MonolithCornerTurnLoop = class {
39091
39138
  const delivered = this.attachmentDir && allAttachments.length ? await deliverAttachments(allAttachments, join13(this.attachmentDir, requestId.replace(/[^\w-]/g, "_")), this.options.fetchImpl) : [];
39092
39139
  const briefFileLines = await Promise.all((restored.brief?.attachments ?? []).map(async (file, index) => {
39093
39140
  const entry = delivered.find((item) => item.attachment === briefAttachments[index]);
39094
- const verified = entry?.path && createHash6("sha256").update(await readFile12(entry.path)).digest("hex") === file.sha256;
39141
+ const verified = entry?.path && createHash7("sha256").update(await readFile12(entry.path)).digest("hex") === file.sha256;
39095
39142
  return `- ${file.title} (${file.purpose}; ${file.required ? "required" : "optional"}; sha256 ${file.sha256}): ${verified ? entry.path : "UNAVAILABLE OR CONTENT MISMATCH"}`;
39096
39143
  }));
39097
39144
  const missingRequiredBriefFile = (restored.brief?.attachments ?? []).some((file, index) => file.required && briefFileLines[index]?.includes("UNAVAILABLE OR CONTENT MISMATCH"));
@@ -40012,7 +40059,7 @@ function cornerStartConfigKey(repository, objective) {
40012
40059
  }
40013
40060
  function cornerRepositoryCacheDir(supervisorRoot, remote) {
40014
40061
  const normalizedRemote = roomCheckoutRemote(remote);
40015
- const repositoryHash = createHash7("sha256").update(normalizedRemote).digest("hex").slice(0, 24);
40062
+ const repositoryHash = createHash8("sha256").update(normalizedRemote).digest("hex").slice(0, 24);
40016
40063
  return resolve25(supervisorRoot, "beeline", "repositories", `${repositoryHash}.git`);
40017
40064
  }
40018
40065
  function isStandingCornerStartFault(error) {
@@ -40689,7 +40736,7 @@ var RoomRuntimeCoordinator = class {
40689
40736
  return this.roomRoot(roomId);
40690
40737
  const remote = roomCheckoutRemote(repository.remote);
40691
40738
  const targetBranch = repository.targetBranch || "main";
40692
- const checkoutId = createHash7("sha256").update(`${roomId}\0${remote}\0${targetBranch}`).digest("hex").slice(0, 24);
40739
+ const checkoutId = createHash8("sha256").update(`${roomId}\0${remote}\0${targetBranch}`).digest("hex").slice(0, 24);
40693
40740
  const path = resolve25(this.runtime.supervisorRoot, "beeline", "room-checkouts", checkoutId);
40694
40741
  await mkdir15(dirname15(path), { recursive: true, mode: 448 });
40695
40742
  const token = remote.startsWith("https://github.com/") ? await this.options.daemonApi.execute("getRoomGitHubToken", { roomId }) : void 0;
@@ -41330,21 +41377,332 @@ init_runtime();
41330
41377
  init_launchd();
41331
41378
  init_systemd();
41332
41379
  import { spawn as spawn11 } from "node:child_process";
41333
- import { mkdir as mkdir21, rm as rm10, stat as stat5, writeFile as writeFile16 } from "node:fs/promises";
41334
- import { dirname as dirname21, resolve as resolve31 } from "node:path";
41380
+ import { mkdir as mkdir22, rm as rm11, stat as stat5, writeFile as writeFile16 } from "node:fs/promises";
41381
+ import { dirname as dirname21, resolve as resolve32 } from "node:path";
41382
+
41383
+ // apps/body/dist/update-functional-probe.js
41384
+ import { mkdir as mkdir20, rm as rm10 } from "node:fs/promises";
41385
+ import { homedir as homedir15 } from "node:os";
41386
+ import { resolve as resolve30 } from "node:path";
41387
+ init_cursor_acp_bridge();
41388
+ var UPDATE_PROBE_SESSION_TIMEOUT_MS = 1e4;
41389
+ var UPDATE_PROBE_SESSION_OPEN_TIMEOUT_MS = 2e4;
41390
+ var UPDATE_PROBE_TURN_TIMEOUT_MS = 45e3;
41391
+ var UPDATE_PROBE_RETRY_DELAY_MS = 2e3;
41392
+ var ACP_ERROR_CODE = /\bACP error (-\d+):/;
41393
+ var ACP_PROMPT_INACTIVITY = /\bACP session\/prompt timed out after \d+ms of inactivity\b/;
41394
+ function isServerInternalCode(code) {
41395
+ return code === -32603 || code <= -32e3 && code >= -32099;
41396
+ }
41397
+ function classifyAcpTurnFailure(error) {
41398
+ const message = error instanceof Error ? error.message : typeof error === "string" ? error : void 0;
41399
+ if (!message)
41400
+ return void 0;
41401
+ if (ACP_PROMPT_INACTIVITY.test(message))
41402
+ return { kind: "prompt-inactivity" };
41403
+ const code = Number(ACP_ERROR_CODE.exec(message)?.[1]);
41404
+ if (Number.isFinite(code) && isServerInternalCode(code))
41405
+ return { kind: "server-internal", code };
41406
+ return void 0;
41407
+ }
41408
+ function sameAcpTurnFailure(one, other) {
41409
+ if (one.kind === "server-internal") {
41410
+ return other.kind === "server-internal" && one.code === other.code;
41411
+ }
41412
+ return other.kind === one.kind;
41413
+ }
41414
+ var UpdateFunctionalProbeError = class extends Error {
41415
+ reason;
41416
+ code = "BEELINE_UPDATE_FUNCTIONAL_PROBE_FAILED";
41417
+ /** Present when the turn failed on a provider refusal with an HTTP status. */
41418
+ providerRefusal;
41419
+ constructor(reason, detail, options) {
41420
+ super(`functional update probe failed (${reason}): ${detail}`, options);
41421
+ this.reason = reason;
41422
+ this.name = "UpdateFunctionalProbeError";
41423
+ this.providerRefusal = options?.providerRefusal;
41424
+ }
41425
+ };
41426
+ async function probeOutcome(run2) {
41427
+ try {
41428
+ await run2();
41429
+ return { kind: "served" };
41430
+ } catch (error) {
41431
+ if (error instanceof UpdateFunctionalProbeError && error.reason === "sandbox-unavailable") {
41432
+ return { kind: "sandbox-unavailable", reason: error.message };
41433
+ }
41434
+ if (error instanceof UpdateFunctionalProbeError && error.providerRefusal) {
41435
+ return { kind: "refused", ...error.providerRefusal };
41436
+ }
41437
+ return { kind: "unavailable", reason: error instanceof Error ? error.message : String(error) };
41438
+ }
41439
+ }
41440
+ function describeCurrentReleaseOutcome(outcome) {
41441
+ switch (outcome.kind) {
41442
+ case "served":
41443
+ return "the current release answered";
41444
+ case "refused":
41445
+ return `the current release got a different refusal (${outcome.reason})`;
41446
+ case "sandbox-unavailable":
41447
+ return `the current release has the same unavailable sandbox (${outcome.reason})`;
41448
+ case "model-unavailable":
41449
+ return `the current release cannot serve that model either (${outcome.reason})`;
41450
+ case "unavailable":
41451
+ return `the current release could not be compared (${outcome.reason})`;
41452
+ }
41453
+ }
41454
+ async function runUpdateFunctionalProbe(input) {
41455
+ const command = input.config.agentCommand ?? input.config.agentBinary;
41456
+ const harnessLabel = harnessIdentityLabel({
41457
+ kind: input.config.agentKind,
41458
+ command
41459
+ });
41460
+ const harness = input.config.agentKind ?? command;
41461
+ if (input.config.modelUnavailable) {
41462
+ const detail = input.config.modelUnavailable.detail;
41463
+ if (!input.compareWithCurrentRelease) {
41464
+ throw new UpdateFunctionalProbeError("model-unavailable", detail);
41465
+ }
41466
+ const current = await input.compareWithCurrentRelease({
41467
+ kind: "model-unavailable",
41468
+ reason: detail
41469
+ });
41470
+ if (current.kind !== "model-unavailable" && current.kind !== "unavailable") {
41471
+ throw new UpdateFunctionalProbeError("model-unavailable", `${detail}; ${describeCurrentReleaseOutcome(current)}`);
41472
+ }
41473
+ console.warn("[body] update probe: the configured model is unavailable to this release and the current release alike; that is the account catalog, not the bundle, so the probe passes without a model answer");
41474
+ return {
41475
+ harness,
41476
+ sandboxed: false,
41477
+ sessionStarted: false,
41478
+ turnCompleted: false,
41479
+ nativeTools: [],
41480
+ modelAnswer: "unavailable",
41481
+ modelAnswerReason: `${detail} (the current release cannot serve that model either)`
41482
+ };
41483
+ }
41484
+ if (input.sandboxRequired && !input.config.bwrapPath) {
41485
+ const detail = input.sandboxUnavailableDetail ?? "the configured bubblewrap boundary did not pass its startup self-test";
41486
+ if (!input.compareWithCurrentRelease) {
41487
+ throw new UpdateFunctionalProbeError("sandbox-unavailable", detail);
41488
+ }
41489
+ const current = await input.compareWithCurrentRelease({
41490
+ kind: "sandbox-unavailable",
41491
+ reason: detail
41492
+ });
41493
+ if (current.kind !== "sandbox-unavailable") {
41494
+ throw new UpdateFunctionalProbeError("sandbox-unavailable", `${detail}; ${describeCurrentReleaseOutcome(current)}`);
41495
+ }
41496
+ console.warn("[body] update probe: the sandbox is unavailable to this release and the current release alike; preserving fail-closed sandboxing and accepting the bundle as inconclusive");
41497
+ return {
41498
+ harness,
41499
+ sandboxed: false,
41500
+ sessionStarted: false,
41501
+ turnCompleted: false,
41502
+ nativeTools: [],
41503
+ modelAnswer: "unavailable",
41504
+ modelAnswerReason: `${detail} (the current release has the same host sandbox failure)`
41505
+ };
41506
+ }
41507
+ const root = input.probeRoot ?? resolve30(input.runtimeDir, "update-functional-probe");
41508
+ const cwd = resolve30(root, "checkout");
41509
+ const homeRoot = resolve30(root, "agent-home");
41510
+ await rm10(root, { recursive: true, force: true });
41511
+ await mkdir20(cwd, { recursive: true, mode: 448 });
41512
+ let client;
41513
+ try {
41514
+ const agentEnv = {
41515
+ ...input.config.agentEnv,
41516
+ ...await prepareRoomAgentHome({
41517
+ root: homeRoot,
41518
+ operatorHome: input.config.operatorHome ?? homedir15(),
41519
+ sharedSkills: input.config.sharedSkills ?? [],
41520
+ ...input.config.agentKind ? { agentKind: input.config.agentKind } : {},
41521
+ skillReleaseId: input.releaseId,
41522
+ failClosed: true,
41523
+ ...openRouterRoutingInput({ ...input.config, openRouterRoutingCacheDir: openRouterRoutingCacheDir(input.runtimeDir) }, input.config.modelSelection)
41524
+ })
41525
+ };
41526
+ const selectedAgent = {
41527
+ kind: input.config.agentKind,
41528
+ command,
41529
+ args: input.config.agentArgs ?? []
41530
+ };
41531
+ let spawnCommand = {
41532
+ command,
41533
+ args: agentArgsWithModelSelection(selectedAgent, input.config.modelSelection)
41534
+ };
41535
+ let modelAnswer = {};
41536
+ let turnCompleted = true;
41537
+ if (input.config.bwrapPath) {
41538
+ const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
41539
+ const operatorHome = input.config.operatorHome ?? homedir15();
41540
+ const homeStateDirs = harnessHomeStateDirs(harnessLabel, agentEnv.HOME ?? operatorHome);
41541
+ await Promise.all(homeStateDirs.map((dir) => mkdir20(dir, { recursive: true })));
41542
+ spawnCommand = wrapAgentCommand({
41543
+ bwrapPath: input.config.bwrapPath,
41544
+ spec: {
41545
+ mode: "readonly",
41546
+ cwd,
41547
+ harnessStateDirs: stateDirs,
41548
+ harnessHomeStateDirs: homeStateDirs,
41549
+ maskPaths: credentialMaskPaths(input.config.sandboxMaskPaths, operatorHome),
41550
+ ...tmpDir ? { tmpDir } : {}
41551
+ },
41552
+ command,
41553
+ args: spawnCommand.args
41554
+ });
41555
+ }
41556
+ client = new AcpClient({
41557
+ agentCommand: spawnCommand.command,
41558
+ agentArgs: spawnCommand.args,
41559
+ agentLabel: harnessLabel,
41560
+ agentEnv,
41561
+ agentCwd: cwd,
41562
+ autoApprovePermissions: true
41563
+ });
41564
+ const sessionTimeoutMs = input.sessionTimeoutMs ?? UPDATE_PROBE_SESSION_TIMEOUT_MS;
41565
+ const sessionOpenTimeoutMs = input.sessionOpenTimeoutMs ?? input.sessionTimeoutMs ?? UPDATE_PROBE_SESSION_OPEN_TIMEOUT_MS;
41566
+ const openSessionAndPrompt = async () => {
41567
+ const opened = await client.sessionNew({
41568
+ cwd,
41569
+ mcpServers: [],
41570
+ systemPrompt: "This is Beeline update validation. Answer only READY.",
41571
+ mode: "readonly",
41572
+ timeoutMs: sessionOpenTimeoutMs
41573
+ });
41574
+ if (input.config.modelSelection) {
41575
+ await applyAgentModelSelection(client, opened.sessionId, parseAdvertisedConfigOptions(opened.raw, input.config.modelSelection.model, isGrokAgentCommand(selectedAgent)), input.config.modelSelection);
41576
+ }
41577
+ return {
41578
+ sessionId: opened.sessionId,
41579
+ served: await client.sessionPrompt(opened.sessionId, "Reply READY.", input.turnTimeoutMs ?? UPDATE_PROBE_TURN_TIMEOUT_MS)
41580
+ };
41581
+ };
41582
+ try {
41583
+ await client.start(sessionTimeoutMs);
41584
+ try {
41585
+ const { sessionId, served } = await openSessionAndPrompt();
41586
+ if (served.agentText.trim()) {
41587
+ modelAnswer = { modelAnswer: "served" };
41588
+ } else {
41589
+ const explained = await explainEmptyAgentTurn({
41590
+ agentLabel: harnessLabel,
41591
+ agentEnv,
41592
+ sessionId,
41593
+ result: served
41594
+ });
41595
+ const modelSide = isAccountOrProviderRefusal(explained.record) || explained.record?.kind === "empty";
41596
+ if (!modelSide) {
41597
+ const refusal = explained.record?.kind === "error" && explained.record.status !== void 0 ? { status: explained.record.status, reason: explained.record.reason } : void 0;
41598
+ const detail = `the harness completed a session/prompt without an agent answer: ${explained.reason}`;
41599
+ if (!refusal || !input.compareWithCurrentRelease) {
41600
+ throw new UpdateFunctionalProbeError("turn-failed", detail, {
41601
+ ...refusal ? { providerRefusal: refusal } : {}
41602
+ });
41603
+ }
41604
+ const current = await input.compareWithCurrentRelease({
41605
+ kind: "provider-refusal",
41606
+ reason: refusal.reason,
41607
+ refusal
41608
+ });
41609
+ if (current.kind !== "refused" || current.status !== refusal.status) {
41610
+ throw new UpdateFunctionalProbeError("turn-failed", `${detail}; ${describeCurrentReleaseOutcome(current)}`, { providerRefusal: refusal });
41611
+ }
41612
+ const reason = `${explained.reason} (the current release gets the same ${refusal.status})`;
41613
+ console.warn(`[body] update probe: the provider refused this release and the current release alike (${refusal.reason}); that refusal is not this bundle's doing, so the probe passes as inconclusive`);
41614
+ modelAnswer = { modelAnswer: "unavailable", modelAnswerReason: reason };
41615
+ } else {
41616
+ console.warn(`[body] update probe: the bundle reached the model boundary but the model answered nothing (${explained.reason}); that is the model's own answer on any release, so the probe passes without one`);
41617
+ modelAnswer = { modelAnswer: "unavailable", modelAnswerReason: explained.reason };
41618
+ }
41619
+ }
41620
+ } catch (initialError) {
41621
+ if (initialError instanceof UpdateFunctionalProbeError)
41622
+ throw initialError;
41623
+ if (initialError instanceof ModelSelectionUnavailableError) {
41624
+ throw new UpdateFunctionalProbeError("model-unavailable", initialError.message, {
41625
+ cause: initialError
41626
+ });
41627
+ }
41628
+ let error = initialError;
41629
+ let detail = error instanceof Error ? error.message : String(error);
41630
+ let failure2 = classifyAcpTurnFailure(error);
41631
+ if (failure2) {
41632
+ await new Promise((resolveWait) => setTimeout(resolveWait, input.retryDelayMs ?? UPDATE_PROBE_RETRY_DELAY_MS));
41633
+ try {
41634
+ const retried = await openSessionAndPrompt();
41635
+ if (retried.served.agentText.trim()) {
41636
+ turnCompleted = true;
41637
+ modelAnswer = { modelAnswer: "served" };
41638
+ console.warn(`[body] update probe: the probe turn failed once (${detail}) but a fresh retry answered; treating the first failure as the provider's, not this bundle's`);
41639
+ error = void 0;
41640
+ }
41641
+ } catch (retryError) {
41642
+ const retryDetail = retryError instanceof Error ? retryError.message : String(retryError);
41643
+ const retryFailure = classifyAcpTurnFailure(retryError);
41644
+ if (!retryFailure || !sameAcpTurnFailure(failure2, retryFailure)) {
41645
+ throw new UpdateFunctionalProbeError("turn-failed", retryDetail, {
41646
+ cause: retryError
41647
+ });
41648
+ }
41649
+ error = retryError;
41650
+ detail = retryDetail;
41651
+ failure2 = retryFailure;
41652
+ }
41653
+ }
41654
+ if (error === void 0) {
41655
+ } else if (!failure2 || !input.compareWithCurrentRelease) {
41656
+ throw new UpdateFunctionalProbeError("turn-failed", detail, { cause: error });
41657
+ } else {
41658
+ const current = await input.compareWithCurrentRelease({
41659
+ kind: "acp-turn-failure",
41660
+ reason: detail,
41661
+ failure: failure2
41662
+ });
41663
+ const currentFailure = current.kind === "unavailable" ? classifyAcpTurnFailure(current.reason) : void 0;
41664
+ if (!currentFailure || !sameAcpTurnFailure(failure2, currentFailure)) {
41665
+ throw new UpdateFunctionalProbeError("turn-failed", `${detail}; ${describeCurrentReleaseOutcome(current)}`, { cause: error });
41666
+ }
41667
+ console.warn(`[body] update probe: the probe turn failed the same way on this release and the current release (${detail}); that failure is not this bundle's doing, so the probe passes as inconclusive`);
41668
+ turnCompleted = false;
41669
+ modelAnswer = {
41670
+ modelAnswer: "unavailable",
41671
+ modelAnswerReason: `${detail} (the current release fails the same way)`
41672
+ };
41673
+ }
41674
+ }
41675
+ } catch (error) {
41676
+ if (error instanceof UpdateFunctionalProbeError)
41677
+ throw error;
41678
+ throw new UpdateFunctionalProbeError("session-start-failed", error instanceof Error ? error.message : String(error), { cause: error });
41679
+ }
41680
+ return {
41681
+ harness,
41682
+ sandboxed: Boolean(input.config.bwrapPath),
41683
+ sessionStarted: true,
41684
+ turnCompleted,
41685
+ nativeTools: [],
41686
+ ...modelAnswer
41687
+ };
41688
+ } finally {
41689
+ await client?.stop().catch(() => void 0);
41690
+ await rm10(root, { recursive: true, force: true }).catch(() => void 0);
41691
+ }
41692
+ }
41335
41693
 
41336
41694
  // apps/body/dist/update-rollback-alert.js
41337
- import { mkdir as mkdir20, readFile as readFile16, rename as rename8, unlink as unlink4, writeFile as writeFile15 } from "node:fs/promises";
41338
- import { dirname as dirname20, resolve as resolve30 } from "node:path";
41695
+ import { mkdir as mkdir21, readFile as readFile16, rename as rename8, unlink as unlink4, writeFile as writeFile15 } from "node:fs/promises";
41696
+ import { dirname as dirname20, resolve as resolve31 } from "node:path";
41339
41697
  var REPORT_INTERVAL_MS = 60 * 60 * 1e3;
41340
41698
  var lastLogged = /* @__PURE__ */ new Map();
41341
41699
  function updateRollbackAlertPath(runtimeDir) {
41342
- return resolve30(runtimeDir, "update-rollback-alert.json");
41700
+ return resolve31(runtimeDir, "update-rollback-alert.json");
41343
41701
  }
41344
41702
  async function writeAlert(runtimeDir, alert) {
41345
41703
  const path = updateRollbackAlertPath(runtimeDir);
41346
41704
  const staged = `${path}.${process.pid}.tmp`;
41347
- await mkdir20(dirname20(path), { recursive: true });
41705
+ await mkdir21(dirname20(path), { recursive: true });
41348
41706
  await writeFile15(staged, `${JSON.stringify(alert, null, 2)}
41349
41707
  `, { mode: 384 });
41350
41708
  await rename8(staged, path);
@@ -41408,13 +41766,13 @@ var LOCK_STALE_MS = UPDATE_WORKER_DEADLINE_MS + 5 * 6e4;
41408
41766
  var DEFAULT_UPDATE_INITIAL_DELAY_MS = 0;
41409
41767
  async function withInstallLock(layout, work, options = {}) {
41410
41768
  const now2 = options.now ?? Date.now;
41411
- const lock = resolve31(layout.releasesRoot, ".state", "install.lock");
41769
+ const lock = resolve32(layout.releasesRoot, ".state", "install.lock");
41412
41770
  const deadline = now2() + (options.waitMs ?? 1e4);
41413
- await mkdir21(dirname21(lock), { recursive: true });
41771
+ await mkdir22(dirname21(lock), { recursive: true });
41414
41772
  for (; ; ) {
41415
41773
  try {
41416
- await mkdir21(lock);
41417
- await writeFile16(resolve31(lock, "owner"), `${process.pid}
41774
+ await mkdir22(lock);
41775
+ await writeFile16(resolve32(lock, "owner"), `${process.pid}
41418
41776
  ${now2()}
41419
41777
  `, "utf8");
41420
41778
  break;
@@ -41423,7 +41781,7 @@ ${now2()}
41423
41781
  throw error;
41424
41782
  const age = now2() - await stat5(lock).then((value) => value.mtimeMs).catch(() => now2());
41425
41783
  if (age > LOCK_STALE_MS) {
41426
- await rm10(lock, { recursive: true, force: true });
41784
+ await rm11(lock, { recursive: true, force: true });
41427
41785
  continue;
41428
41786
  }
41429
41787
  if (now2() >= deadline)
@@ -41434,7 +41792,7 @@ ${now2()}
41434
41792
  try {
41435
41793
  return await work();
41436
41794
  } finally {
41437
- await rm10(lock, { recursive: true, force: true });
41795
+ await rm11(lock, { recursive: true, force: true });
41438
41796
  }
41439
41797
  }
41440
41798
  var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
@@ -41451,6 +41809,7 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
41451
41809
  #requested = false;
41452
41810
  #restartRequest;
41453
41811
  #stagedReleaseId;
41812
+ #notifiedReleaseKey;
41454
41813
  constructor(options) {
41455
41814
  this.#layout = options.layout;
41456
41815
  this.#loadedRelease = options.loadedRelease;
@@ -41471,6 +41830,13 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
41471
41830
  ...options
41472
41831
  });
41473
41832
  }
41833
+ /** The live server socket wakes this install immediately; the manifest stays authoritative. */
41834
+ notifyReleaseAvailable(releaseKey) {
41835
+ if (!releaseKey || this.#notifiedReleaseKey === releaseKey)
41836
+ return;
41837
+ this.#notifiedReleaseKey = releaseKey;
41838
+ this.#nextUpdateCheckAt = 0;
41839
+ }
41474
41840
  /**
41475
41841
  * Return true once either a verified release is staged behind the serving
41476
41842
  * daemon's idle gate or desired-release drift is represented by the single
@@ -41519,9 +41885,15 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
41519
41885
  return;
41520
41886
  const state2 = await readUpdateState(this.#layout);
41521
41887
  const interval = numberEnv(this.#env, "BEELINE_UPDATE_INTERVAL_MS", DEFAULT_UPDATE_INTERVAL_MS);
41522
- if (state2.lastCheckAt !== void 0 && now2 - state2.lastCheckAt < interval)
41888
+ if (state2.notifiedReleaseKey === this.#notifiedReleaseKey && state2.lastCheckAt !== void 0 && now2 - state2.lastCheckAt < interval)
41523
41889
  return;
41524
41890
  await this.#runUpdateWorker();
41891
+ if (this.#notifiedReleaseKey) {
41892
+ await writeUpdateState(this.#layout, {
41893
+ ...await readUpdateState(this.#layout),
41894
+ notifiedReleaseKey: this.#notifiedReleaseKey
41895
+ });
41896
+ }
41525
41897
  }).catch((error) => {
41526
41898
  console.error(`[thin-core] update worker failed: ${error instanceof Error ? error.message : String(error)}`);
41527
41899
  }).finally(() => {
@@ -41538,7 +41910,7 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
41538
41910
  if (!attempt || attempt.releaseId !== desiredRelease || attempt.status !== "pending") {
41539
41911
  const from = await readInstalledBundleIdentity({
41540
41912
  ...this.#layout,
41541
- libDir: resolve31(this.#layout.releasesRoot, this.#loadedRelease)
41913
+ libDir: resolve32(this.#layout.releasesRoot, this.#loadedRelease)
41542
41914
  }).catch(() => void 0) ?? {};
41543
41915
  const to = await readInstalledBundleIdentity(this.#layout).catch(() => void 0) ?? {};
41544
41916
  const record6 = {
@@ -41570,7 +41942,7 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
41570
41942
  drainDeadlineAt
41571
41943
  };
41572
41944
  this.#requested = true;
41573
- console.log(`[thin-core] update restart armed: loaded release ${this.#loadedRelease} -> ${desiredRelease}; absolute drain deadline ${new Date(drainDeadlineAt).toISOString()}`);
41945
+ console.log(`[thin-core] update restart armed: loaded release ${this.#loadedRelease} -> ${desiredRelease}; drain target ${new Date(drainDeadlineAt).toISOString()}`);
41574
41946
  return true;
41575
41947
  }
41576
41948
  /**
@@ -41578,10 +41950,7 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
41578
41950
  * Busy means a turn is executing right now; serving Rooms and corners with
41579
41951
  * no turn in flight is idle. `quiesceIfIdle` closes intake in the same
41580
41952
  * synchronous transition that proves idle, so a new turn cannot race
41581
- * activation. A staged release stays inert while a turn runs — until the
41582
- * absolute drain deadline, when the restart is forced (`forced: true`) and
41583
- * the caller cancels the running turn; the convergence contract outranks a
41584
- * stuck turn. `ManagedUpdateDrain` enforces that deadline with a timer.
41953
+ * activation. A staged release stays inert for as long as a turn runs.
41585
41954
  */
41586
41955
  async restartRequest(quiesceIfIdle) {
41587
41956
  const activeDrift = await this.check();
@@ -41590,11 +41959,8 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
41590
41959
  const request = this.#restartRequest;
41591
41960
  if (!request)
41592
41961
  throw new Error("update drift was detected without an in-memory restart request");
41593
- let forced = false;
41594
41962
  if (!quiesceIfIdle()) {
41595
- if (this.#now() < request.drainDeadlineAt)
41596
- return { kind: "waiting", request };
41597
- forced = true;
41963
+ return { kind: "waiting", request };
41598
41964
  }
41599
41965
  if (this.#stagedReleaseId) {
41600
41966
  const stagedReleaseId = this.#stagedReleaseId;
@@ -41627,7 +41993,7 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
41627
41993
  this.#restartRequest = void 0;
41628
41994
  await this.#journalDrift(stagedReleaseId);
41629
41995
  }
41630
- return { kind: "restart", request: this.#restartRequest ?? request, forced };
41996
+ return { kind: "restart", request: this.#restartRequest ?? request };
41631
41997
  }
41632
41998
  };
41633
41999
  async function coordinateManagedUpdateHandoff(update, quiesceIfIdle, restart, waiting = async () => void 0) {
@@ -41638,23 +42004,20 @@ async function coordinateManagedUpdateHandoff(update, quiesceIfIdle, restart, wa
41638
42004
  await waiting(next.request);
41639
42005
  return "waiting-for-idle";
41640
42006
  }
41641
- await restart(next.request, next.forced ? "forced" : "drained");
42007
+ await restart(next.request, "drained");
41642
42008
  return "restarting";
41643
42009
  }
41644
42010
  var UPDATE_DRAIN_WAIT_LOG_INTERVAL_MS = 6e4;
41645
42011
  var ManagedUpdateDrain = class {
41646
42012
  #options;
41647
- #now;
41648
42013
  #log;
41649
42014
  #setTimer;
41650
42015
  #clearTimer;
41651
- #deadlineTimer;
41652
42016
  #waitLogTimer;
41653
42017
  #resolving;
41654
42018
  #restarting = false;
41655
42019
  constructor(options) {
41656
42020
  this.#options = options;
41657
- this.#now = options.now ?? Date.now;
41658
42021
  this.#log = options.log ?? ((line) => console.log(line));
41659
42022
  this.#setTimer = options.setTimer ?? ((callback, ms) => setTimeout(callback, ms));
41660
42023
  this.#clearTimer = options.clearTimer ?? ((handle) => clearTimeout(handle));
@@ -41674,49 +42037,39 @@ var ManagedUpdateDrain = class {
41674
42037
  return this.#resolving;
41675
42038
  }
41676
42039
  async #step() {
41677
- const { update, quiesceIfIdle, activeTurnCount } = this.#options;
42040
+ const { update, quiesceIfIdle } = this.#options;
41678
42041
  let armed;
41679
42042
  const progress = await coordinateManagedUpdateHandoff(update, quiesceIfIdle, async (request, mode) => {
41680
42043
  this.#disarm();
41681
- if (mode === "forced") {
41682
- this.#log(`[thin-core] update restart forced: drain deadline reached with ${activeTurnCount()} active turn(s); cancelling them and restarting onto ${request.desiredRelease}`);
41683
- }
41684
42044
  await this.#options.restart(request, mode);
41685
42045
  this.#restarting = true;
41686
42046
  }, async (request) => {
41687
42047
  armed = request;
41688
42048
  await this.#options.waiting?.(request);
41689
42049
  });
41690
- if (armed && !this.#deadlineTimer)
41691
- this.#arm(armed);
42050
+ if (armed && !this.#waitLogTimer)
42051
+ this.#arm();
41692
42052
  return progress;
41693
42053
  }
41694
- #arm(request) {
41695
- this.#logWaiting(request);
41696
- this.#scheduleWaitLog(request);
41697
- this.#deadlineTimer = this.#setTimer(() => void this.#resolve().catch((error) => {
41698
- this.#log(`[thin-core] update restart at the drain deadline failed; retrying on the next tick: ${error instanceof Error ? error.message : String(error)}`);
41699
- }), Math.max(0, request.drainDeadlineAt - this.#now()));
42054
+ #arm() {
42055
+ this.#logWaiting();
42056
+ this.#scheduleWaitLog();
41700
42057
  }
41701
- #scheduleWaitLog(request) {
42058
+ #scheduleWaitLog() {
41702
42059
  this.#waitLogTimer = this.#setTimer(() => {
41703
42060
  this.#waitLogTimer = void 0;
41704
42061
  if (this.#restarting)
41705
42062
  return;
41706
- this.#logWaiting(request);
41707
- this.#scheduleWaitLog(request);
42063
+ this.#logWaiting();
42064
+ this.#scheduleWaitLog();
41708
42065
  }, UPDATE_DRAIN_WAIT_LOG_INTERVAL_MS);
41709
42066
  }
41710
- #logWaiting(request) {
41711
- const minutesLeft = Math.max(0, Math.ceil((request.drainDeadlineAt - this.#now()) / 6e4));
41712
- this.#log(`[thin-core] update restart waiting: ${this.#options.activeTurnCount()} active turn(s); deadline in ${minutesLeft}m`);
42067
+ #logWaiting() {
42068
+ this.#log(`[thin-core] update restart waiting: ${this.#options.activeTurnCount()} active turn(s); will resume after work finishes`);
41713
42069
  }
41714
42070
  #disarm() {
41715
- if (this.#deadlineTimer !== void 0)
41716
- this.#clearTimer(this.#deadlineTimer);
41717
42071
  if (this.#waitLogTimer !== void 0)
41718
42072
  this.#clearTimer(this.#waitLogTimer);
41719
- this.#deadlineTimer = void 0;
41720
42073
  this.#waitLogTimer = void 0;
41721
42074
  }
41722
42075
  };
@@ -41829,16 +42182,17 @@ async function proveLoadedReleaseReady(layout, runtimeDir, loadedRelease, option
41829
42182
  return false;
41830
42183
  const confirmed = [.../* @__PURE__ */ new Set([...current.confirmedProbeIds ?? [], probeId])].sort();
41831
42184
  const required = current.requiredProbeIds ?? [probeId];
42185
+ const satisfied = [...confirmed, ...current.unavailableProbeIds ?? []];
41832
42186
  await replaceUpdateAttempt(layout, {
41833
42187
  ...current,
41834
42188
  confirmedProbeIds: confirmed,
41835
- ...required.every((id) => confirmed.includes(id)) ? { status: "confirmed" } : {}
42189
+ ...required.every((id) => satisfied.includes(id)) ? { status: "confirmed" } : {}
41836
42190
  });
41837
42191
  return true;
41838
42192
  });
41839
42193
  if (!accepted)
41840
42194
  return false;
41841
- await writeFile16(resolve31(runtimeDir, "daemon-ready.json"), `${JSON.stringify({
42195
+ await writeFile16(resolve32(runtimeDir, "daemon-ready.json"), `${JSON.stringify({
41842
42196
  readyAt: (options.now ?? Date.now)(),
41843
42197
  loadedRelease,
41844
42198
  functionalProof: options.functionalProof
@@ -41846,6 +42200,22 @@ async function proveLoadedReleaseReady(layout, runtimeDir, loadedRelease, option
41846
42200
  `, { mode: 384 });
41847
42201
  return true;
41848
42202
  }
42203
+ async function recordUnavailableProbe(input) {
42204
+ return withInstallLock(input.layout, async () => {
42205
+ const current = await readUpdateAttempt(input.layout);
42206
+ if (!current || current.status !== "pending" || current.releaseId !== input.loadedRelease || await activeReleaseId(input.layout) !== input.loadedRelease)
42207
+ return false;
42208
+ const unavailableProbeIds = [.../* @__PURE__ */ new Set([...current.unavailableProbeIds ?? [], input.probeId])].sort();
42209
+ const required = current.requiredProbeIds ?? [input.probeId];
42210
+ const satisfied = [...current.confirmedProbeIds ?? [], ...unavailableProbeIds];
42211
+ await replaceUpdateAttempt(input.layout, {
42212
+ ...current,
42213
+ unavailableProbeIds,
42214
+ ...required.every((id) => satisfied.includes(id)) ? { status: "confirmed" } : {}
42215
+ });
42216
+ return true;
42217
+ });
42218
+ }
41849
42219
  function siblingRevertNote(attempt, loadedRelease) {
41850
42220
  if (!loadedRelease || attempt?.releaseId !== loadedRelease || attempt.status !== "reverted") {
41851
42221
  return void 0;
@@ -41877,6 +42247,9 @@ async function gateManagedSuccessor(input) {
41877
42247
  }
41878
42248
  return { kind: "passed", proof };
41879
42249
  } catch (error) {
42250
+ if (error instanceof UpdateFunctionalProbeError && error.reason === "model-unavailable" && await recordUnavailableProbe(input)) {
42251
+ return { kind: "agent-unavailable", error };
42252
+ }
41880
42253
  return {
41881
42254
  kind: "failed",
41882
42255
  error,
@@ -42228,10 +42601,10 @@ async function runStartCommand(args, interactiveUi, dependencyOverrides = {}) {
42228
42601
 
42229
42602
  // apps/body/dist/connect-command.js
42230
42603
  import { spawn as spawn12 } from "node:child_process";
42231
- import { createHash as createHash9, randomUUID as randomUUID7 } from "node:crypto";
42232
- import { chmod as chmod8, mkdir as mkdir22, readFile as readFile17, unlink as unlink5, writeFile as writeFile17 } from "node:fs/promises";
42233
- import { homedir as homedir15, hostname as hostname3 } from "node:os";
42234
- import { dirname as dirname23, resolve as resolve32 } from "node:path";
42604
+ import { createHash as createHash10, randomUUID as randomUUID7 } from "node:crypto";
42605
+ import { chmod as chmod8, mkdir as mkdir23, readFile as readFile17, unlink as unlink5, writeFile as writeFile17 } from "node:fs/promises";
42606
+ import { homedir as homedir16, hostname as hostname3 } from "node:os";
42607
+ import { dirname as dirname23, resolve as resolve33 } from "node:path";
42235
42608
  import { stdin as stdin3, stdout as stdout4 } from "node:process";
42236
42609
  init_access_policy();
42237
42610
  init_agent_command();
@@ -42897,8 +43270,8 @@ function parseConnectSubscriptions(value) {
42897
43270
  ];
42898
43271
  }
42899
43272
  async function readMachineId(env = process.env) {
42900
- const configDir = resolve32(env.XDG_CONFIG_HOME ?? resolve32(homedir15(), ".config"), "beeline");
42901
- const machineIdPath = resolve32(configDir, "machine-id");
43273
+ const configDir = resolve33(env.XDG_CONFIG_HOME ?? resolve33(homedir16(), ".config"), "beeline");
43274
+ const machineIdPath = resolve33(configDir, "machine-id");
42902
43275
  let machineId;
42903
43276
  let machineName = hostname3();
42904
43277
  try {
@@ -42909,11 +43282,11 @@ async function readMachineId(env = process.env) {
42909
43282
  } catch {
42910
43283
  machineId = randomUUID7();
42911
43284
  try {
42912
- await mkdir22(configDir, { recursive: true, mode: 448 });
43285
+ await mkdir23(configDir, { recursive: true, mode: 448 });
42913
43286
  await writeFile17(machineIdPath, `${machineId}
42914
43287
  `, { mode: 384 });
42915
43288
  } catch {
42916
- machineId = createHash9("sha256").update(machineName).digest("hex").slice(0, 36);
43289
+ machineId = createHash10("sha256").update(machineName).digest("hex").slice(0, 36);
42917
43290
  }
42918
43291
  }
42919
43292
  return { machineId, machineName };
@@ -42922,7 +43295,7 @@ function requestConnectGrant(baseUrl, pairingCode, selection, fetchImpl, machine
42922
43295
  const normalizedPairingCode = normalizeAgentPairingCode(pairingCode);
42923
43296
  if (!normalizedPairingCode)
42924
43297
  throw new Error("invalid pairing code");
42925
- const avatarSeed = createHash9("sha256").update(normalizedPairingCode.toUpperCase()).digest("hex").slice(0, 32);
43298
+ const avatarSeed = createHash10("sha256").update(normalizedPairingCode.toUpperCase()).digest("hex").slice(0, 32);
42926
43299
  return jsonRequest(`${baseUrl}/auth/agent/connect`, {
42927
43300
  pairing_code: normalizedPairingCode,
42928
43301
  harness: selection.harness,
@@ -42971,7 +43344,7 @@ async function installCurrentRelease(fetchImpl) {
42971
43344
  });
42972
43345
  await activateRelease(layout, releaseId);
42973
43346
  return {
42974
- binary: resolve32(layout.binDir, "beeline"),
43347
+ binary: resolve33(layout.binDir, "beeline"),
42975
43348
  version: published.version ?? releaseId
42976
43349
  };
42977
43350
  }
@@ -42994,7 +43367,7 @@ function providerEnvironment(selection) {
42994
43367
  };
42995
43368
  }
42996
43369
  async function writePrivateJson(path, value) {
42997
- await mkdir22(dirname23(path), { recursive: true, mode: 448 });
43370
+ await mkdir23(dirname23(path), { recursive: true, mode: 448 });
42998
43371
  await writeFile17(path, `${JSON.stringify(value, null, 2)}
42999
43372
  `, { mode: 384 });
43000
43373
  await chmod8(path, 384);
@@ -43003,8 +43376,8 @@ async function writeProviderEnv(selection, agentPubkey) {
43003
43376
  const values = providerEnvironment(selection);
43004
43377
  if (Object.keys(values).length === 0)
43005
43378
  return void 0;
43006
- const path = resolve32(defaultSupervisorRoot(process.env), "beeline", "connect", `${agentPubkey}.env`);
43007
- await mkdir22(dirname23(path), { recursive: true, mode: 448 });
43379
+ const path = resolve33(defaultSupervisorRoot(process.env), "beeline", "connect", `${agentPubkey}.env`);
43380
+ await mkdir23(dirname23(path), { recursive: true, mode: 448 });
43008
43381
  const contents = Object.entries(values).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join("\n");
43009
43382
  await writeFile17(path, `${contents}
43010
43383
  `, { mode: 384 });
@@ -43073,7 +43446,7 @@ async function runConnectWizard(code, fetchImpl, eventSubscriptions, accessPolic
43073
43446
  await finishConnectedAgentPairing(baseUrl, pairingCode, grant.workspace_joined, eventSubscriptions, fetchImpl);
43074
43447
  const installedRelease = await brassSpinner("Installing the Beeline daemon\u2026", () => installCurrentRelease(fetchImpl), (release) => `Installed Beeline helper ${release.version}`);
43075
43448
  const llmEnvFile = await writeProviderEnv(selection, grant.agent_pubkey);
43076
- const grantPath = resolve32(defaultSupervisorRoot(process.env), "beeline", "connect", `grant-${process.pid}-${Date.now()}.json`);
43449
+ const grantPath = resolve33(defaultSupervisorRoot(process.env), "beeline", "connect", `grant-${process.pid}-${Date.now()}.json`);
43077
43450
  await writePrivateJson(grantPath, {
43078
43451
  agentSecretKey: grant.agent_secret_key,
43079
43452
  bodySecretKey: grant.body_secret_key,
@@ -43158,11 +43531,11 @@ async function runConnectFinishCommand(path) {
43158
43531
  throw new Error("connect-finish may run only from the canonical installed Beeline launcher");
43159
43532
  }
43160
43533
  try {
43161
- const grant = JSON.parse(await readFile17(resolve32(path), "utf8"));
43534
+ const grant = JSON.parse(await readFile17(resolve33(path), "utf8"));
43162
43535
  if (!isDevicePairingGrant(grant))
43163
43536
  throw new Error("device connection grant is invalid");
43164
43537
  const connected = await completeDevicePairing(grant);
43165
- await unlink5(resolve32(path));
43538
+ await unlink5(resolve33(path));
43166
43539
  const providerEnv = grant.llmEnvFile ? await readFile17(grant.llmEnvFile, "utf8").catch(() => "") : "";
43167
43540
  const apiKey = (/^OPENROUTER_API_KEY=(\S+)/m.exec(providerEnv)?.[1] ?? "").replace(/^["']|["']$/g, "");
43168
43541
  const model = openRouterModelId(grant.model, { OPENROUTER_API_KEY: apiKey });
@@ -43187,12 +43560,12 @@ async function runConnectFinishCommand(path) {
43187
43560
  init_self_update();
43188
43561
 
43189
43562
  // apps/body/dist/daemon-failure.js
43190
- import { mkdir as mkdir23, readFile as readFile18, rename as rename9, rm as rm11, writeFile as writeFile18 } from "node:fs/promises";
43191
- import { dirname as dirname24, resolve as resolve33 } from "node:path";
43563
+ import { mkdir as mkdir24, readFile as readFile18, rename as rename9, rm as rm12, writeFile as writeFile18 } from "node:fs/promises";
43564
+ import { dirname as dirname24, resolve as resolve34 } from "node:path";
43192
43565
  var DAEMON_FAILURE_LIMIT = 3;
43193
43566
  var DAEMON_FAILURE_WINDOW_MS = 5 * 6e4;
43194
43567
  function daemonFailurePath(runtimeDir) {
43195
- return resolve33(runtimeDir, "daemon-distress.json");
43568
+ return resolve34(runtimeDir, "daemon-distress.json");
43196
43569
  }
43197
43570
  async function readFailureRecord(runtimeDir) {
43198
43571
  try {
@@ -43208,7 +43581,7 @@ async function readFailureRecord(runtimeDir) {
43208
43581
  async function writeFailureRecord(runtimeDir, record6) {
43209
43582
  const path = daemonFailurePath(runtimeDir);
43210
43583
  const staged = `${path}.${process.pid}.tmp`;
43211
- await mkdir23(dirname24(path), { recursive: true, mode: 448 });
43584
+ await mkdir24(dirname24(path), { recursive: true, mode: 448 });
43212
43585
  await writeFile18(staged, `${JSON.stringify(record6, null, 2)}
43213
43586
  `, { mode: 384 });
43214
43587
  await rename9(staged, path);
@@ -43227,318 +43600,12 @@ async function recordDaemonStartFailure(runtimeDir, error, now2 = Date.now()) {
43227
43600
  return { distressed, count: failures.length, path: daemonFailurePath(runtimeDir) };
43228
43601
  }
43229
43602
  async function clearDaemonStartFailures(runtimeDir) {
43230
- await rm11(daemonFailurePath(runtimeDir), { force: true });
43603
+ await rm12(daemonFailurePath(runtimeDir), { force: true });
43231
43604
  }
43232
43605
 
43233
43606
  // apps/body/dist/cli.js
43234
43607
  init_systemd();
43235
43608
 
43236
- // apps/body/dist/update-functional-probe.js
43237
- import { mkdir as mkdir24, rm as rm12 } from "node:fs/promises";
43238
- import { homedir as homedir16 } from "node:os";
43239
- import { resolve as resolve34 } from "node:path";
43240
- init_cursor_acp_bridge();
43241
- var UPDATE_PROBE_SESSION_TIMEOUT_MS = 1e4;
43242
- var UPDATE_PROBE_SESSION_OPEN_TIMEOUT_MS = 2e4;
43243
- var UPDATE_PROBE_TURN_TIMEOUT_MS = 45e3;
43244
- var UPDATE_PROBE_RETRY_DELAY_MS = 2e3;
43245
- var ACP_ERROR_CODE = /\bACP error (-\d+):/;
43246
- var ACP_PROMPT_INACTIVITY = /\bACP session\/prompt timed out after \d+ms of inactivity\b/;
43247
- function isServerInternalCode(code) {
43248
- return code === -32603 || code <= -32e3 && code >= -32099;
43249
- }
43250
- function classifyAcpTurnFailure(error) {
43251
- const message = error instanceof Error ? error.message : typeof error === "string" ? error : void 0;
43252
- if (!message)
43253
- return void 0;
43254
- if (ACP_PROMPT_INACTIVITY.test(message))
43255
- return { kind: "prompt-inactivity" };
43256
- const code = Number(ACP_ERROR_CODE.exec(message)?.[1]);
43257
- if (Number.isFinite(code) && isServerInternalCode(code))
43258
- return { kind: "server-internal", code };
43259
- return void 0;
43260
- }
43261
- function sameAcpTurnFailure(one, other) {
43262
- if (one.kind === "server-internal") {
43263
- return other.kind === "server-internal" && one.code === other.code;
43264
- }
43265
- return other.kind === one.kind;
43266
- }
43267
- var UpdateFunctionalProbeError = class extends Error {
43268
- reason;
43269
- code = "BEELINE_UPDATE_FUNCTIONAL_PROBE_FAILED";
43270
- /** Present when the turn failed on a provider refusal with an HTTP status. */
43271
- providerRefusal;
43272
- constructor(reason, detail, options) {
43273
- super(`functional update probe failed (${reason}): ${detail}`, options);
43274
- this.reason = reason;
43275
- this.name = "UpdateFunctionalProbeError";
43276
- this.providerRefusal = options?.providerRefusal;
43277
- }
43278
- };
43279
- async function probeOutcome(run2) {
43280
- try {
43281
- await run2();
43282
- return { kind: "served" };
43283
- } catch (error) {
43284
- if (error instanceof UpdateFunctionalProbeError && error.reason === "sandbox-unavailable") {
43285
- return { kind: "sandbox-unavailable", reason: error.message };
43286
- }
43287
- if (error instanceof UpdateFunctionalProbeError && error.providerRefusal) {
43288
- return { kind: "refused", ...error.providerRefusal };
43289
- }
43290
- return { kind: "unavailable", reason: error instanceof Error ? error.message : String(error) };
43291
- }
43292
- }
43293
- function describeCurrentReleaseOutcome(outcome) {
43294
- switch (outcome.kind) {
43295
- case "served":
43296
- return "the current release answered";
43297
- case "refused":
43298
- return `the current release got a different refusal (${outcome.reason})`;
43299
- case "sandbox-unavailable":
43300
- return `the current release has the same unavailable sandbox (${outcome.reason})`;
43301
- case "model-unavailable":
43302
- return `the current release cannot serve that model either (${outcome.reason})`;
43303
- case "unavailable":
43304
- return `the current release could not be compared (${outcome.reason})`;
43305
- }
43306
- }
43307
- async function runUpdateFunctionalProbe(input) {
43308
- const command = input.config.agentCommand ?? input.config.agentBinary;
43309
- const harnessLabel = harnessIdentityLabel({
43310
- kind: input.config.agentKind,
43311
- command
43312
- });
43313
- const harness = input.config.agentKind ?? command;
43314
- if (input.config.modelUnavailable) {
43315
- const detail = input.config.modelUnavailable.detail;
43316
- if (!input.compareWithCurrentRelease) {
43317
- throw new UpdateFunctionalProbeError("model-unavailable", detail);
43318
- }
43319
- const current = await input.compareWithCurrentRelease({
43320
- kind: "model-unavailable",
43321
- reason: detail
43322
- });
43323
- if (current.kind !== "model-unavailable" && current.kind !== "unavailable") {
43324
- throw new UpdateFunctionalProbeError("model-unavailable", `${detail}; ${describeCurrentReleaseOutcome(current)}`);
43325
- }
43326
- console.warn("[body] update probe: the configured model is unavailable to this release and the current release alike; that is the account catalog, not the bundle, so the probe passes without a model answer");
43327
- return {
43328
- harness,
43329
- sandboxed: false,
43330
- sessionStarted: false,
43331
- turnCompleted: false,
43332
- nativeTools: [],
43333
- modelAnswer: "unavailable",
43334
- modelAnswerReason: `${detail} (the current release cannot serve that model either)`
43335
- };
43336
- }
43337
- if (input.sandboxRequired && !input.config.bwrapPath) {
43338
- const detail = input.sandboxUnavailableDetail ?? "the configured bubblewrap boundary did not pass its startup self-test";
43339
- if (!input.compareWithCurrentRelease) {
43340
- throw new UpdateFunctionalProbeError("sandbox-unavailable", detail);
43341
- }
43342
- const current = await input.compareWithCurrentRelease({
43343
- kind: "sandbox-unavailable",
43344
- reason: detail
43345
- });
43346
- if (current.kind !== "sandbox-unavailable") {
43347
- throw new UpdateFunctionalProbeError("sandbox-unavailable", `${detail}; ${describeCurrentReleaseOutcome(current)}`);
43348
- }
43349
- console.warn("[body] update probe: the sandbox is unavailable to this release and the current release alike; preserving fail-closed sandboxing and accepting the bundle as inconclusive");
43350
- return {
43351
- harness,
43352
- sandboxed: false,
43353
- sessionStarted: false,
43354
- turnCompleted: false,
43355
- nativeTools: [],
43356
- modelAnswer: "unavailable",
43357
- modelAnswerReason: `${detail} (the current release has the same host sandbox failure)`
43358
- };
43359
- }
43360
- const root = input.probeRoot ?? resolve34(input.runtimeDir, "update-functional-probe");
43361
- const cwd = resolve34(root, "checkout");
43362
- const homeRoot = resolve34(root, "agent-home");
43363
- await rm12(root, { recursive: true, force: true });
43364
- await mkdir24(cwd, { recursive: true, mode: 448 });
43365
- let client;
43366
- try {
43367
- const agentEnv = {
43368
- ...input.config.agentEnv,
43369
- ...await prepareRoomAgentHome({
43370
- root: homeRoot,
43371
- operatorHome: input.config.operatorHome ?? homedir16(),
43372
- sharedSkills: input.config.sharedSkills ?? [],
43373
- ...input.config.agentKind ? { agentKind: input.config.agentKind } : {},
43374
- skillReleaseId: input.releaseId,
43375
- failClosed: true,
43376
- ...openRouterRoutingInput({ ...input.config, openRouterRoutingCacheDir: openRouterRoutingCacheDir(input.runtimeDir) }, input.config.modelSelection)
43377
- })
43378
- };
43379
- const selectedAgent = {
43380
- kind: input.config.agentKind,
43381
- command,
43382
- args: input.config.agentArgs ?? []
43383
- };
43384
- let spawnCommand = {
43385
- command,
43386
- args: agentArgsWithModelSelection(selectedAgent, input.config.modelSelection)
43387
- };
43388
- let modelAnswer = {};
43389
- let turnCompleted = true;
43390
- if (input.config.bwrapPath) {
43391
- const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
43392
- const operatorHome = input.config.operatorHome ?? homedir16();
43393
- const homeStateDirs = harnessHomeStateDirs(harnessLabel, agentEnv.HOME ?? operatorHome);
43394
- await Promise.all(homeStateDirs.map((dir) => mkdir24(dir, { recursive: true })));
43395
- spawnCommand = wrapAgentCommand({
43396
- bwrapPath: input.config.bwrapPath,
43397
- spec: {
43398
- mode: "readonly",
43399
- cwd,
43400
- harnessStateDirs: stateDirs,
43401
- harnessHomeStateDirs: homeStateDirs,
43402
- maskPaths: credentialMaskPaths(input.config.sandboxMaskPaths, operatorHome),
43403
- ...tmpDir ? { tmpDir } : {}
43404
- },
43405
- command,
43406
- args: spawnCommand.args
43407
- });
43408
- }
43409
- client = new AcpClient({
43410
- agentCommand: spawnCommand.command,
43411
- agentArgs: spawnCommand.args,
43412
- agentLabel: harnessLabel,
43413
- agentEnv,
43414
- agentCwd: cwd,
43415
- autoApprovePermissions: true
43416
- });
43417
- const sessionTimeoutMs = input.sessionTimeoutMs ?? UPDATE_PROBE_SESSION_TIMEOUT_MS;
43418
- const sessionOpenTimeoutMs = input.sessionOpenTimeoutMs ?? input.sessionTimeoutMs ?? UPDATE_PROBE_SESSION_OPEN_TIMEOUT_MS;
43419
- const openSessionAndPrompt = async () => {
43420
- const opened = await client.sessionNew({
43421
- cwd,
43422
- mcpServers: [],
43423
- systemPrompt: "This is Beeline update validation. Answer only READY.",
43424
- mode: "readonly",
43425
- timeoutMs: sessionOpenTimeoutMs
43426
- });
43427
- if (input.config.modelSelection) {
43428
- await applyAgentModelSelection(client, opened.sessionId, parseAdvertisedConfigOptions(opened.raw, input.config.modelSelection.model, isGrokAgentCommand(selectedAgent)), input.config.modelSelection);
43429
- }
43430
- return {
43431
- sessionId: opened.sessionId,
43432
- served: await client.sessionPrompt(opened.sessionId, "Reply READY.", input.turnTimeoutMs ?? UPDATE_PROBE_TURN_TIMEOUT_MS)
43433
- };
43434
- };
43435
- try {
43436
- await client.start(sessionTimeoutMs);
43437
- try {
43438
- const { sessionId, served } = await openSessionAndPrompt();
43439
- if (served.agentText.trim()) {
43440
- modelAnswer = { modelAnswer: "served" };
43441
- } else {
43442
- const explained = await explainEmptyAgentTurn({
43443
- agentLabel: harnessLabel,
43444
- agentEnv,
43445
- sessionId,
43446
- result: served
43447
- });
43448
- const modelSide = isAccountOrProviderRefusal(explained.record) || explained.record?.kind === "empty";
43449
- if (!modelSide) {
43450
- const refusal = explained.record?.kind === "error" && explained.record.status !== void 0 ? { status: explained.record.status, reason: explained.record.reason } : void 0;
43451
- const detail = `the harness completed a session/prompt without an agent answer: ${explained.reason}`;
43452
- if (!refusal || !input.compareWithCurrentRelease) {
43453
- throw new UpdateFunctionalProbeError("turn-failed", detail, {
43454
- ...refusal ? { providerRefusal: refusal } : {}
43455
- });
43456
- }
43457
- const current = await input.compareWithCurrentRelease({
43458
- kind: "provider-refusal",
43459
- reason: refusal.reason,
43460
- refusal
43461
- });
43462
- if (current.kind !== "refused" || current.status !== refusal.status) {
43463
- throw new UpdateFunctionalProbeError("turn-failed", `${detail}; ${describeCurrentReleaseOutcome(current)}`, { providerRefusal: refusal });
43464
- }
43465
- const reason = `${explained.reason} (the current release gets the same ${refusal.status})`;
43466
- console.warn(`[body] update probe: the provider refused this release and the current release alike (${refusal.reason}); that refusal is not this bundle's doing, so the probe passes as inconclusive`);
43467
- modelAnswer = { modelAnswer: "unavailable", modelAnswerReason: reason };
43468
- } else {
43469
- console.warn(`[body] update probe: the bundle reached the model boundary but the model answered nothing (${explained.reason}); that is the model's own answer on any release, so the probe passes without one`);
43470
- modelAnswer = { modelAnswer: "unavailable", modelAnswerReason: explained.reason };
43471
- }
43472
- }
43473
- } catch (initialError) {
43474
- if (initialError instanceof UpdateFunctionalProbeError)
43475
- throw initialError;
43476
- let error = initialError;
43477
- let detail = error instanceof Error ? error.message : String(error);
43478
- let failure2 = classifyAcpTurnFailure(error);
43479
- if (failure2) {
43480
- await new Promise((resolveWait) => setTimeout(resolveWait, input.retryDelayMs ?? UPDATE_PROBE_RETRY_DELAY_MS));
43481
- try {
43482
- const retried = await openSessionAndPrompt();
43483
- if (retried.served.agentText.trim()) {
43484
- turnCompleted = true;
43485
- modelAnswer = { modelAnswer: "served" };
43486
- console.warn(`[body] update probe: the probe turn failed once (${detail}) but a fresh retry answered; treating the first failure as the provider's, not this bundle's`);
43487
- error = void 0;
43488
- }
43489
- } catch (retryError) {
43490
- const retryDetail = retryError instanceof Error ? retryError.message : String(retryError);
43491
- const retryFailure = classifyAcpTurnFailure(retryError);
43492
- if (!retryFailure || !sameAcpTurnFailure(failure2, retryFailure)) {
43493
- throw new UpdateFunctionalProbeError("turn-failed", retryDetail, {
43494
- cause: retryError
43495
- });
43496
- }
43497
- error = retryError;
43498
- detail = retryDetail;
43499
- failure2 = retryFailure;
43500
- }
43501
- }
43502
- if (error === void 0) {
43503
- } else if (!failure2 || !input.compareWithCurrentRelease) {
43504
- throw new UpdateFunctionalProbeError("turn-failed", detail, { cause: error });
43505
- } else {
43506
- const current = await input.compareWithCurrentRelease({
43507
- kind: "acp-turn-failure",
43508
- reason: detail,
43509
- failure: failure2
43510
- });
43511
- const currentFailure = current.kind === "unavailable" ? classifyAcpTurnFailure(current.reason) : void 0;
43512
- if (!currentFailure || !sameAcpTurnFailure(failure2, currentFailure)) {
43513
- throw new UpdateFunctionalProbeError("turn-failed", `${detail}; ${describeCurrentReleaseOutcome(current)}`, { cause: error });
43514
- }
43515
- console.warn(`[body] update probe: the probe turn failed the same way on this release and the current release (${detail}); that failure is not this bundle's doing, so the probe passes as inconclusive`);
43516
- turnCompleted = false;
43517
- modelAnswer = {
43518
- modelAnswer: "unavailable",
43519
- modelAnswerReason: `${detail} (the current release fails the same way)`
43520
- };
43521
- }
43522
- }
43523
- } catch (error) {
43524
- if (error instanceof UpdateFunctionalProbeError)
43525
- throw error;
43526
- throw new UpdateFunctionalProbeError("session-start-failed", error instanceof Error ? error.message : String(error), { cause: error });
43527
- }
43528
- return {
43529
- harness,
43530
- sandboxed: Boolean(input.config.bwrapPath),
43531
- sessionStarted: true,
43532
- turnCompleted,
43533
- nativeTools: [],
43534
- ...modelAnswer
43535
- };
43536
- } finally {
43537
- await client?.stop().catch(() => void 0);
43538
- await rm12(root, { recursive: true, force: true }).catch(() => void 0);
43539
- }
43540
- }
43541
-
43542
43609
  // apps/body/dist/current-release-probe.js
43543
43610
  import { spawn as spawn13 } from "node:child_process";
43544
43611
  import { dirname as dirname25, join as join15 } from "node:path";
@@ -44049,18 +44116,20 @@ async function runStoredDaemon(pathOrPointer) {
44049
44116
  update,
44050
44117
  quiesceIfIdle: () => core.quiesceForUpdateIfIdle(),
44051
44118
  activeTurnCount: () => core.activeTurnCount(),
44052
- restart: async ({ desiredRelease, drainDeadlineAt }, mode) => {
44053
- if (mode === "forced")
44054
- await core.prepareForForcedUpdateRestart();
44119
+ restart: async ({ desiredRelease, drainDeadlineAt }) => {
44055
44120
  core.setDrainDeadlineAt(drainDeadlineAt);
44056
- stoppingStatus = `update pending, converging; loaded_release=${loadedRelease ?? "unknown"}; desired_release=${desiredRelease}; ${mode === "forced" ? "active work cancelled at the drain deadline" : "active work drained"}; intake quiesced; exit_deadline=${new Date(drainDeadlineAt).toISOString()}`;
44121
+ stoppingStatus = `update pending, converging; loaded_release=${loadedRelease ?? "unknown"}; desired_release=${desiredRelease}; active work drained; intake quiesced`;
44057
44122
  await notifier.stopping(stoppingStatus);
44058
44123
  controller.abort();
44059
44124
  },
44060
- waiting: async ({ desiredRelease, drainDeadlineAt }) => {
44061
- await notifier.progress(`loaded_release=${loadedRelease ?? "unknown"}; update ready; active agent work is still running; handoff deferred; desired_release=${desiredRelease}; exit_deadline=${new Date(drainDeadlineAt).toISOString()}`);
44125
+ waiting: async ({ desiredRelease }) => {
44126
+ await notifier.progress(`loaded_release=${loadedRelease ?? "unknown"}; update ready; active agent work is still running; handoff deferred; desired_release=${desiredRelease}`);
44062
44127
  }
44063
44128
  }) : void 0;
44129
+ daemonApi.setHelperReleaseListener(({ version, sha }) => {
44130
+ update?.notifyReleaseAvailable(`${version}:${sha}`);
44131
+ void updateDrain?.tick();
44132
+ });
44064
44133
  const result = await core.run({
44065
44134
  signal: controller.signal,
44066
44135
  onEstablished: async () => {
@@ -44095,10 +44164,18 @@ async function runStoredDaemon(pathOrPointer) {
44095
44164
  successorRolledBack = gate.rolledBack;
44096
44165
  throw gate.error;
44097
44166
  }
44098
- functionalProof = gate.proof;
44167
+ if (gate.kind === "agent-unavailable") {
44168
+ if (!config.modelUnavailable) {
44169
+ config.modelUnavailable = modelUnavailableState(config.modelSelection ?? runtime.modelSelection ?? {}, gate.error.cause ?? gate.error);
44170
+ }
44171
+ console.warn(`[thin-core] new release retained; this agent's selected model is unavailable: ${gate.error.message}`);
44172
+ } else {
44173
+ functionalProof = gate.proof;
44174
+ }
44099
44175
  pendingSuccessor = false;
44100
44176
  await clearUpdateRollbackAlert(runtimeDir);
44101
- 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})` : ""));
44177
+ if (functionalProof)
44178
+ 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})` : ""));
44102
44179
  }
44103
44180
  await clearDaemonStartFailures(runtimeDir);
44104
44181
  await writeDaemonReleaseStatus(runtimeDir, runtime.agent.publicKey, loadedReleaseIdentity);