usebeeline 0.0.121 → 0.0.123

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +11 -1
  2. package/dist/usebeeline.mjs +1191 -705
  3. package/package.json +1 -1
@@ -11315,12 +11315,12 @@ __export(self_update_exports, {
11315
11315
  });
11316
11316
  import { createHash as createHash8 } from "node:crypto";
11317
11317
  import { constants as fsConstants2 } from "node:fs";
11318
- import { access, chmod as chmod6, lstat as lstat4, mkdir as mkdir17, open, readFile as readFile10, rename as rename6, rm as rm6, symlink as symlink3, writeFile as writeFile11 } from "node:fs/promises";
11318
+ import { access as access2, chmod as chmod6, lstat as lstat4, mkdir as mkdir18, open, readFile as readFile11, rename as rename6, rm as rm6, symlink as symlink3, writeFile as writeFile11 } from "node:fs/promises";
11319
11319
  import { spawn as spawn8 } from "node:child_process";
11320
11320
  import { homedir as homedir12 } from "node:os";
11321
- import { dirname as dirname15, join as join13, resolve as resolve26 } from "node:path";
11321
+ import { dirname as dirname16, join as join13, resolve as resolve27 } from "node:path";
11322
11322
  function anchorLayout(rawLibDir) {
11323
- const libDir = resolve26(rawLibDir);
11323
+ const libDir = resolve27(rawLibDir);
11324
11324
  const segments = libDir.split(/[/\\]/);
11325
11325
  const idx = segments.lastIndexOf(RELEASES_SEGMENT);
11326
11326
  if (idx >= 2 && segments[idx - 1] === "lib") {
@@ -11336,9 +11336,9 @@ function anchorLayout(rawLibDir) {
11336
11336
  // prefix's bin, NOT <prefix>/lib/bin — deriving it one level up was the
11337
11337
  // defect that made activateRelease write its stable forwarders where
11338
11338
  // nothing executed them, leaving stale raw wrappers in <prefix>/bin.
11339
- binDir: resolve26(libDir, "../../bin"),
11339
+ binDir: resolve27(libDir, "../../bin"),
11340
11340
  libDir,
11341
- releasesRoot: resolve26(libDir, `../${RELEASES_SEGMENT}`)
11341
+ releasesRoot: resolve27(libDir, `../${RELEASES_SEGMENT}`)
11342
11342
  };
11343
11343
  }
11344
11344
  function beelineInstallLayout(env = process.env) {
@@ -11350,18 +11350,18 @@ function beelineInstallLayout(env = process.env) {
11350
11350
  function releaseIdFromPath(path) {
11351
11351
  if (!path?.trim())
11352
11352
  return void 0;
11353
- const segments = resolve26(path.trim()).split(/[/\\]/);
11353
+ const segments = resolve27(path.trim()).split(/[/\\]/);
11354
11354
  const idx = segments.lastIndexOf(RELEASES_SEGMENT);
11355
11355
  const id = idx >= 0 ? segments[idx + 1] : void 0;
11356
11356
  return id ? sanitizeReleaseId(id) : void 0;
11357
11357
  }
11358
11358
  function runningReleaseId(layout, env = process.env, invocationPath = process.argv[1]) {
11359
- const releasesRoot = resolve26(layout.releasesRoot);
11360
- const anchor = resolve26(layout.libDir);
11359
+ const releasesRoot = resolve27(layout.releasesRoot);
11360
+ const anchor = resolve27(layout.libDir);
11361
11361
  for (const raw of [env.BEELINE_LIB_DIR, invocationPath]) {
11362
11362
  if (!raw?.trim())
11363
11363
  continue;
11364
- const resolved = resolve26(raw.trim());
11364
+ const resolved = resolve27(raw.trim());
11365
11365
  if (resolved !== anchor && !resolved.startsWith(`${releasesRoot}/`))
11366
11366
  continue;
11367
11367
  const id = releaseIdFromPath(resolved);
@@ -11372,7 +11372,7 @@ function runningReleaseId(layout, env = process.env, invocationPath = process.ar
11372
11372
  }
11373
11373
  function defaultBeelineInstallLayout(env = process.env) {
11374
11374
  const home = env.HOME?.trim() || homedir12();
11375
- return anchorLayout(resolve26(home, ".local", "lib", "beeline"));
11375
+ return anchorLayout(resolve27(home, ".local", "lib", "beeline"));
11376
11376
  }
11377
11377
  function discoveredBeelineInstallLayout(env = process.env) {
11378
11378
  const explicitAnchor = env.BEELINE_INSTALL_LIB_DIR?.trim();
@@ -11380,7 +11380,7 @@ function discoveredBeelineInstallLayout(env = process.env) {
11380
11380
  return anchorLayout(explicitAnchor);
11381
11381
  const explicitBinDir = env.BEELINE_INSTALL_DIR?.trim();
11382
11382
  if (explicitBinDir)
11383
- return anchorLayout(resolve26(dirname15(resolve26(explicitBinDir)), "lib", "beeline"));
11383
+ return anchorLayout(resolve27(dirname16(resolve27(explicitBinDir)), "lib", "beeline"));
11384
11384
  return defaultBeelineInstallLayout(env);
11385
11385
  }
11386
11386
  function hostPlatformKey() {
@@ -11397,7 +11397,7 @@ async function readBundleJson(bundleDir) {
11397
11397
  let raw;
11398
11398
  for (const candidate of bundleJsonCandidates(bundleDir)) {
11399
11399
  try {
11400
- raw = await readFile10(candidate, "utf8");
11400
+ raw = await readFile11(candidate, "utf8");
11401
11401
  break;
11402
11402
  } catch {
11403
11403
  }
@@ -11419,13 +11419,13 @@ function updateStatePath(layout) {
11419
11419
  }
11420
11420
  async function readUpdateState(layout) {
11421
11421
  try {
11422
- return JSON.parse(await readFile10(updateStatePath(layout), "utf8"));
11422
+ return JSON.parse(await readFile11(updateStatePath(layout), "utf8"));
11423
11423
  } catch {
11424
11424
  return {};
11425
11425
  }
11426
11426
  }
11427
11427
  async function writeUpdateState(layout, state) {
11428
- await mkdir17(join13(layout.releasesRoot, ".state"), { recursive: true });
11428
+ await mkdir18(join13(layout.releasesRoot, ".state"), { recursive: true });
11429
11429
  await writeFile11(updateStatePath(layout), `${JSON.stringify(state, null, 2)}
11430
11430
  `, "utf8");
11431
11431
  }
@@ -11512,7 +11512,7 @@ function entrypointCandidates(bundleDir) {
11512
11512
  async function resolveBundleEntrypoint(bundleDir) {
11513
11513
  for (const candidate of entrypointCandidates(bundleDir)) {
11514
11514
  try {
11515
- await access(candidate, fsConstants2.F_OK);
11515
+ await access2(candidate, fsConstants2.F_OK);
11516
11516
  return candidate;
11517
11517
  } catch {
11518
11518
  }
@@ -11520,10 +11520,7 @@ async function resolveBundleEntrypoint(bundleDir) {
11520
11520
  return void 0;
11521
11521
  }
11522
11522
  function requiredBundlePaths() {
11523
- return [
11524
- BUNDLE_ENTRYPOINT,
11525
- PI_MCP_ADAPTER_ENTRYPOINT
11526
- ];
11523
+ return [BUNDLE_ENTRYPOINT, PI_MCP_ADAPTER_ENTRYPOINT, CODEGRAPH_ENTRYPOINT];
11527
11524
  }
11528
11525
  async function stageRelease(layout, manifestUrl, published, opts = {}) {
11529
11526
  const fetchImpl = opts.fetchImpl ?? fetch;
@@ -11533,13 +11530,13 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
11533
11530
  const okMarker = join13(releaseDir, ".stage-ok");
11534
11531
  let previouslyVerified = false;
11535
11532
  try {
11536
- const recorded = await readFile10(okMarker, "utf8");
11533
+ const recorded = await readFile11(okMarker, "utf8");
11537
11534
  if (recorded.trim() === published.sha256)
11538
11535
  return releaseId;
11539
11536
  previouslyVerified = true;
11540
11537
  } catch {
11541
11538
  }
11542
- await mkdir17(releaseDir, { recursive: true });
11539
+ await mkdir18(releaseDir, { recursive: true });
11543
11540
  const tempArchive = join13(layout.releasesRoot, `.download-${releaseId}-${process.pid}.tar.gz`);
11544
11541
  try {
11545
11542
  log2(`downloading ${published.file}`);
@@ -11580,7 +11577,7 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
11580
11577
  throw new Error(`extracting bundle failed: ${extract2.stderr}`);
11581
11578
  for (const relative3 of requiredBundlePaths()) {
11582
11579
  try {
11583
- await access(join13(releaseDir, relative3), fsConstants2.F_OK);
11580
+ await access2(join13(releaseDir, relative3), fsConstants2.F_OK);
11584
11581
  } catch {
11585
11582
  throw new Error(`staged bundle is missing ${relative3}`);
11586
11583
  }
@@ -11625,9 +11622,9 @@ async function replaceFile(path, contents, mode) {
11625
11622
  }
11626
11623
  async function activateRelease(layout, releaseId) {
11627
11624
  const releaseDir = join13(layout.releasesRoot, releaseId);
11628
- await access(join13(releaseDir, BUNDLE_ENTRYPOINT), fsConstants2.F_OK);
11629
- await mkdir17(layout.releasesRoot, { recursive: true });
11630
- await mkdir17(layout.binDir, { recursive: true });
11625
+ await access2(join13(releaseDir, BUNDLE_ENTRYPOINT), fsConstants2.F_OK);
11626
+ await mkdir18(layout.releasesRoot, { recursive: true });
11627
+ await mkdir18(layout.binDir, { recursive: true });
11631
11628
  let previousReleaseId = await activeReleaseId(layout);
11632
11629
  const kind = await pathKind(layout.libDir);
11633
11630
  if (kind === "directory") {
@@ -11635,7 +11632,7 @@ async function activateRelease(layout, releaseId) {
11635
11632
  const legacyId = sanitizeReleaseId(legacyIdentity?.commit ?? legacyIdentity?.version ?? `legacy-${Date.now()}`);
11636
11633
  const legacyDir = join13(layout.releasesRoot, legacyId);
11637
11634
  try {
11638
- await access(legacyDir, fsConstants2.F_OK);
11635
+ await access2(legacyDir, fsConstants2.F_OK);
11639
11636
  previousReleaseId = `${legacyId}-${Date.now()}`;
11640
11637
  await rename6(layout.libDir, join13(layout.releasesRoot, previousReleaseId));
11641
11638
  await normalizeLegacyBundleShape(join13(layout.releasesRoot, previousReleaseId));
@@ -11649,7 +11646,7 @@ async function activateRelease(layout, releaseId) {
11649
11646
  await rm6(tempLink, { force: true });
11650
11647
  await symlink3(join13("beeline-releases", releaseId), tempLink);
11651
11648
  await rename6(tempLink, layout.libDir);
11652
- await fsyncDir(dirname15(layout.libDir));
11649
+ await fsyncDir(dirname16(layout.libDir));
11653
11650
  await writeBinForwarders(layout, releaseDir);
11654
11651
  return { previousReleaseId };
11655
11652
  }
@@ -11658,7 +11655,7 @@ async function normalizeLegacyBundleShape(bundleDir) {
11658
11655
  let anyFlat = false;
11659
11656
  for (const name of LEGACY_FLAT_BUNDLE_FILES) {
11660
11657
  try {
11661
- await access(join13(bundleDir, name), fsConstants2.F_OK);
11658
+ await access2(join13(bundleDir, name), fsConstants2.F_OK);
11662
11659
  anyFlat = true;
11663
11660
  break;
11664
11661
  } catch {
@@ -11666,10 +11663,10 @@ async function normalizeLegacyBundleShape(bundleDir) {
11666
11663
  }
11667
11664
  if (!anyFlat)
11668
11665
  return;
11669
- await mkdir17(innerLib, { recursive: true });
11666
+ await mkdir18(innerLib, { recursive: true });
11670
11667
  for (const name of LEGACY_FLAT_BUNDLE_FILES) {
11671
11668
  try {
11672
- await access(join13(innerLib, name), fsConstants2.F_OK);
11669
+ await access2(join13(innerLib, name), fsConstants2.F_OK);
11673
11670
  continue;
11674
11671
  } catch {
11675
11672
  }
@@ -11684,7 +11681,7 @@ async function writeBinForwarders(layout, activeBundleRoot) {
11684
11681
  for (const [name, tool] of entries) {
11685
11682
  const target = join13(activeBundleRoot, "bin", tool);
11686
11683
  try {
11687
- await access(target, fsConstants2.X_OK);
11684
+ await access2(target, fsConstants2.X_OK);
11688
11685
  } catch {
11689
11686
  continue;
11690
11687
  }
@@ -11697,7 +11694,7 @@ async function repairInstallForwarders(layout, opts = {}) {
11697
11694
  const forwarderHealthy = async (name, tool) => {
11698
11695
  let current;
11699
11696
  try {
11700
- current = await readFile10(join13(layout.binDir, name), "utf8");
11697
+ current = await readFile11(join13(layout.binDir, name), "utf8");
11701
11698
  } catch {
11702
11699
  current = void 0;
11703
11700
  }
@@ -11710,7 +11707,7 @@ async function repairInstallForwarders(layout, opts = {}) {
11710
11707
  }
11711
11708
  if (healthy)
11712
11709
  return false;
11713
- await mkdir17(layout.binDir, { recursive: true });
11710
+ await mkdir18(layout.binDir, { recursive: true });
11714
11711
  await writeBinForwarders(layout, layout.libDir);
11715
11712
  opts.logger?.(`[body] self-update: repaired <prefix>/bin forwarders to follow the active-bundle anchor (${layout.libDir})`);
11716
11713
  return true;
@@ -11725,14 +11722,14 @@ async function rollbackToPreviousRelease(layout, previousReleaseId) {
11725
11722
  await rm6(tempLink, { force: true });
11726
11723
  await symlink3(join13("beeline-releases", previousReleaseId), tempLink);
11727
11724
  await rename6(tempLink, layout.libDir);
11728
- await fsyncDir(dirname15(layout.libDir));
11725
+ await fsyncDir(dirname16(layout.libDir));
11729
11726
  }
11730
11727
  function updateAttemptPath(layout) {
11731
11728
  return join13(layout.releasesRoot, ".state", "update-attempt.json");
11732
11729
  }
11733
11730
  async function readUpdateAttempt(layout) {
11734
11731
  try {
11735
- const raw = JSON.parse(await readFile10(updateAttemptPath(layout), "utf8"));
11732
+ const raw = JSON.parse(await readFile11(updateAttemptPath(layout), "utf8"));
11736
11733
  if (raw.version !== 1 || typeof raw.appliedAt !== "number" || typeof raw.confirmBy !== "number" || typeof raw.releaseId !== "string" || !["pending", "confirmed", "reverted"].includes(raw.status)) {
11737
11734
  return void 0;
11738
11735
  }
@@ -11742,7 +11739,7 @@ async function readUpdateAttempt(layout) {
11742
11739
  }
11743
11740
  }
11744
11741
  async function writeUpdateAttempt(layout, record3) {
11745
- await mkdir17(join13(layout.releasesRoot, ".state"), { recursive: true });
11742
+ await mkdir18(join13(layout.releasesRoot, ".state"), { recursive: true });
11746
11743
  const path = updateAttemptPath(layout);
11747
11744
  const staged = `${path}.${process.pid}.tmp`;
11748
11745
  await writeFile11(staged, `${JSON.stringify(record3, null, 2)}
@@ -11790,7 +11787,7 @@ function describeIdentity(identity) {
11790
11787
  parts.push(identity.commit.slice(0, 12));
11791
11788
  return parts.join(" ") || "unknown";
11792
11789
  }
11793
- var RELEASES_SEGMENT, BUNDLE_ENTRYPOINT, PI_MCP_ADAPTER_ENTRYPOINT, FORWARDER_TOOLS, FORWARDER_ALIASES, LEGACY_FLAT_BUNDLE_FILES, DEFAULT_UPDATE_CONFIRM_WINDOW_MS, SelfUpdateManager;
11790
+ var RELEASES_SEGMENT, BUNDLE_ENTRYPOINT, PI_MCP_ADAPTER_ENTRYPOINT, CODEGRAPH_ENTRYPOINT, FORWARDER_TOOLS, FORWARDER_ALIASES, LEGACY_FLAT_BUNDLE_FILES, DEFAULT_UPDATE_CONFIRM_WINDOW_MS, SelfUpdateManager;
11794
11791
  var init_self_update = __esm({
11795
11792
  "apps/body/dist/self-update.js"() {
11796
11793
  "use strict";
@@ -11798,7 +11795,14 @@ var init_self_update = __esm({
11798
11795
  RELEASES_SEGMENT = "beeline-releases";
11799
11796
  BUNDLE_ENTRYPOINT = "lib/beeline/beeline-cli.mjs";
11800
11797
  PI_MCP_ADAPTER_ENTRYPOINT = "lib/beeline/pi-mcp-adapter.mjs";
11801
- FORWARDER_TOOLS = ["beeline", "buzz-agent", "buzz-dev-mcp", "beeline-readonly-mcp"];
11798
+ CODEGRAPH_ENTRYPOINT = "lib/beeline/codegraph/bin/codegraph";
11799
+ FORWARDER_TOOLS = [
11800
+ "beeline",
11801
+ "buzz-agent",
11802
+ "buzz-dev-mcp",
11803
+ "beeline-readonly-mcp",
11804
+ "codegraph"
11805
+ ];
11802
11806
  FORWARDER_ALIASES = {
11803
11807
  usebeeline: "beeline"
11804
11808
  };
@@ -12020,7 +12024,7 @@ function configureNetworkFamilyDefaults(network = net) {
12020
12024
  configureNetworkFamilyDefaults();
12021
12025
 
12022
12026
  // apps/body/dist/cli.js
12023
- import { dirname as dirname22, resolve as resolve34 } from "node:path";
12027
+ import { dirname as dirname23, resolve as resolve35 } from "node:path";
12024
12028
  import { stdin as stdin4, stdout as stdout5 } from "node:process";
12025
12029
 
12026
12030
  // node_modules/@clack/core/dist/index.mjs
@@ -13469,7 +13473,7 @@ function parseCursorModelsOutput(output) {
13469
13473
  };
13470
13474
  }
13471
13475
  async function enumerateCursorModels(env = process.env) {
13472
- return new Promise((resolve35) => {
13476
+ return new Promise((resolve36) => {
13473
13477
  const child = spawn("cursor-agent", ["models"], {
13474
13478
  env,
13475
13479
  stdio: ["ignore", "pipe", "ignore"]
@@ -13483,11 +13487,11 @@ async function enumerateCursorModels(env = process.env) {
13483
13487
  });
13484
13488
  child.on("error", () => {
13485
13489
  clearTimeout(timer);
13486
- resolve35(void 0);
13490
+ resolve36(void 0);
13487
13491
  });
13488
13492
  child.on("close", (code) => {
13489
13493
  clearTimeout(timer);
13490
- resolve35(code === 0 ? parseCursorModelsOutput(output) : void 0);
13494
+ resolve36(code === 0 ? parseCursorModelsOutput(output) : void 0);
13491
13495
  });
13492
13496
  });
13493
13497
  }
@@ -13914,13 +13918,13 @@ var CursorAcpServer = class {
13914
13918
  consumeLine(line);
13915
13919
  });
13916
13920
  try {
13917
- const { code, signal } = await new Promise((resolve35) => {
13921
+ const { code, signal } = await new Promise((resolve36) => {
13918
13922
  let settled = false;
13919
13923
  const finish = (exitCode, exitSignal) => {
13920
13924
  if (settled)
13921
13925
  return;
13922
13926
  settled = true;
13923
- resolve35({ code: exitCode, signal: exitSignal });
13927
+ resolve36({ code: exitCode, signal: exitSignal });
13924
13928
  };
13925
13929
  child.once("error", (error) => {
13926
13930
  spawnError = error instanceof Error ? error.message : String(error);
@@ -14364,7 +14368,10 @@ function resolveReadonlyMcpCommand(env = process.env) {
14364
14368
  throw new Error("read-only tools unavailable: beeline-readonly-mcp was not found. Reinstall Beeline or set BEELINE_READONLY_MCP_BIN / BEELINE_READONLY_MCP_SCRIPT");
14365
14369
  }
14366
14370
  function resolveCodegraphCommand(env = process.env) {
14367
- const configured = env.BUZZ_CODEGRAPH_BIN;
14371
+ const bundledBinary = commandInRunningBundle("codegraph");
14372
+ if (bundledBinary)
14373
+ return bundledBinary;
14374
+ const configured = env.BEELINE_CODEGRAPH_BIN ?? env.BUZZ_CODEGRAPH_BIN;
14368
14375
  if (configured) {
14369
14376
  try {
14370
14377
  accessSync2(configured, constants2.X_OK);
@@ -15172,6 +15179,7 @@ var AcpClient = class extends EventEmitter {
15172
15179
  osSandbox;
15173
15180
  permissionHandler;
15174
15181
  permissionAllowlist;
15182
+ commandsHandler;
15175
15183
  constructor(opts) {
15176
15184
  super();
15177
15185
  const command = opts.agentCommand ?? opts.agentBinary;
@@ -15188,6 +15196,7 @@ var AcpClient = class extends EventEmitter {
15188
15196
  this.osSandbox = opts.osSandbox ?? false;
15189
15197
  this.permissionHandler = opts.permissionHandler;
15190
15198
  this.permissionAllowlist = opts.permissionAllowlist;
15199
+ this.commandsHandler = opts.onCommands;
15191
15200
  }
15192
15201
  async start(timeoutMs = 6e4) {
15193
15202
  if (this.alive)
@@ -15562,6 +15571,7 @@ var AcpClient = class extends EventEmitter {
15562
15571
  if (params.update.sessionUpdate === "available_commands_update") {
15563
15572
  const commands = parseAvailableCommands(params.update.availableCommands);
15564
15573
  this.sessionCommands.set(params.sessionId, commands);
15574
+ this.commandsHandler?.(commands);
15565
15575
  this.emit("commands", { sessionId: params.sessionId, commands });
15566
15576
  }
15567
15577
  const toolCallId = params.update.toolCallId;
@@ -15609,7 +15619,7 @@ var AcpClient = class extends EventEmitter {
15609
15619
  const current = this.activeRunIds.get(sessionId);
15610
15620
  if (current)
15611
15621
  return Promise.resolve(current);
15612
- return new Promise((resolve35, reject) => {
15622
+ return new Promise((resolve36, reject) => {
15613
15623
  const onUpdate = (update) => {
15614
15624
  if (update.sessionId !== sessionId)
15615
15625
  return;
@@ -15617,7 +15627,7 @@ var AcpClient = class extends EventEmitter {
15617
15627
  if (!runId)
15618
15628
  return;
15619
15629
  cleanup();
15620
- resolve35(runId);
15630
+ resolve36(runId);
15621
15631
  };
15622
15632
  const timer = setTimeout(() => {
15623
15633
  cleanup();
@@ -15701,13 +15711,13 @@ var AcpClient = class extends EventEmitter {
15701
15711
  }
15702
15712
  const id = this.nextId++;
15703
15713
  const payload = { jsonrpc: "2.0", id, method, params };
15704
- return new Promise((resolve35, reject) => {
15714
+ return new Promise((resolve36, reject) => {
15705
15715
  const timer = setTimeout(() => {
15706
15716
  this.pending.delete(id);
15707
15717
  reject(new AcpRequestTimeoutError(method, timeoutMs, this.stderrTail, Boolean(onStart), detail));
15708
15718
  }, timeoutMs);
15709
15719
  this.pending.set(id, {
15710
- resolve: resolve35,
15720
+ resolve: resolve36,
15711
15721
  reject,
15712
15722
  timer,
15713
15723
  method,
@@ -15995,39 +16005,28 @@ function modelSelectionTargets(selection) {
15995
16005
  }
15996
16006
  ];
15997
16007
  }
15998
- function assertModelSelectionAdvertised(advertisedOptions, selection) {
15999
- for (const target of modelSelectionTargets(selection)) {
16000
- if (!target.value)
16001
- continue;
16002
- const axis = advertisedOptions.find((option) => target.categories.includes(option.category));
16003
- if (!axis) {
16004
- continue;
16005
- }
16006
- assertModelConfigAxisAllowed(axis.id, advertisedOptions);
16007
- if (!axis.options.some((choice) => choice.id === target.value)) {
16008
- throw new ModelSelectionUnavailableError({
16009
- label: target.label,
16010
- value: target.value,
16011
- reason: "not-advertised"
16012
- });
16013
- }
16014
- }
16015
- }
16016
16008
  function assertModelConfigAxisAllowed(configId, advertisedOptions) {
16017
16009
  const axis = advertisedOptions.find((option) => option.id === configId);
16018
16010
  if (!axis || !isAllowedAgentModelConfigCategory(axis.category)) {
16019
16011
  throw new DisallowedModelConfigOptionError(configId);
16020
16012
  }
16021
16013
  }
16022
- async function applyAgentModelSelection(client, sessionId, advertisedOptions, selection) {
16023
- assertModelSelectionAdvertised(advertisedOptions, selection);
16014
+ async function applyAgentModelSelectionWithUpdatedCatalog(client, sessionId, advertisedOptions, selection) {
16015
+ let currentOptions = advertisedOptions;
16024
16016
  for (const target of modelSelectionTargets(selection)) {
16025
16017
  if (!target.value)
16026
16018
  continue;
16027
- const axis = advertisedOptions.find((option) => target.categories.includes(option.category));
16019
+ const axis = currentOptions.find((option) => target.categories.includes(option.category));
16028
16020
  if (!axis)
16029
16021
  continue;
16030
- assertModelConfigAxisAllowed(axis.id, advertisedOptions);
16022
+ assertModelConfigAxisAllowed(axis.id, currentOptions);
16023
+ if (!axis.options.some((choice) => choice.id === target.value)) {
16024
+ throw new ModelSelectionUnavailableError({
16025
+ label: target.label,
16026
+ value: target.value,
16027
+ reason: "not-advertised"
16028
+ });
16029
+ }
16031
16030
  try {
16032
16031
  if (axis.id === GROK_SESSION_MODEL_AXIS_ID) {
16033
16032
  if (!client.setModel)
@@ -16038,7 +16037,10 @@ async function applyAgentModelSelection(client, sessionId, advertisedOptions, se
16038
16037
  throw new Error(`Grok started with reasoning effort "${axis.currentValue ?? "unknown"}", not "${target.value}"`);
16039
16038
  }
16040
16039
  } else {
16041
- await client.setConfigOption(sessionId, axis.id, target.value);
16040
+ const updated = await client.setConfigOption(sessionId, axis.id, target.value);
16041
+ const refreshed = filterAllowedModelConfigOptions(parseAdvertisedConfigOptions(updated, selection.model));
16042
+ if (refreshed.length > 0)
16043
+ currentOptions = refreshed;
16042
16044
  }
16043
16045
  } catch (error) {
16044
16046
  throw new ModelSelectionUnavailableError({
@@ -16049,6 +16051,10 @@ async function applyAgentModelSelection(client, sessionId, advertisedOptions, se
16049
16051
  });
16050
16052
  }
16051
16053
  }
16054
+ return currentOptions;
16055
+ }
16056
+ async function applyAgentModelSelection(client, sessionId, advertisedOptions, selection) {
16057
+ await applyAgentModelSelectionWithUpdatedCatalog(client, sessionId, advertisedOptions, selection);
16052
16058
  }
16053
16059
 
16054
16060
  // apps/body/dist/model-catalog.js
@@ -16099,27 +16105,34 @@ async function withAgentModelCatalog(agent, agentEnv, selection, inspect, limits
16099
16105
  async function fetchAgentModelCatalog(agent, agentEnv, selection, limits = {}) {
16100
16106
  return withAgentModelCatalog(agent, agentEnv, selection, async ({ client, sessionId, raw, catalog }) => ({
16101
16107
  raw,
16102
- catalog: await filterModelChoicesByLiveValidation(client, sessionId, catalog)
16108
+ catalog: await filterModelChoicesByLiveValidation(client, sessionId, catalog, selection?.model)
16103
16109
  }), limits);
16104
16110
  }
16105
- async function filterModelChoicesByLiveValidation(client, sessionId, catalog) {
16111
+ async function filterModelChoicesByLiveValidation(client, sessionId, catalog, preferredModelId) {
16106
16112
  const modelAxis = catalog.find((axis) => axis.category === "model");
16107
16113
  if (!modelAxis)
16108
16114
  return catalog;
16109
16115
  const available = [];
16116
+ const preferred = preferredModelId ?? modelAxis.currentValue;
16117
+ let preferredCatalog;
16110
16118
  for (const choice of modelAxis.options) {
16111
16119
  try {
16112
- await applyAgentModelSelection(client, sessionId, catalog, { model: choice.id });
16120
+ const updated = await applyAgentModelSelectionWithUpdatedCatalog(client, sessionId, catalog, {
16121
+ model: choice.id
16122
+ });
16113
16123
  available.push(choice);
16124
+ if (choice.id === preferred)
16125
+ preferredCatalog = updated;
16114
16126
  } catch {
16115
16127
  }
16116
16128
  }
16117
- return catalog.map((axis) => axis === modelAxis ? { ...axis, options: available } : axis);
16129
+ const effective = preferredCatalog ?? catalog;
16130
+ return effective.map((axis) => axis.category === "model" ? { ...axis, options: available } : axis);
16118
16131
  }
16119
16132
  async function validateAgentModelSelection(agent, agentEnv, selection) {
16120
16133
  return withAgentModelCatalog(agent, agentEnv, selection, async ({ client, sessionId, raw, catalog }) => {
16121
- await applyAgentModelSelection(client, sessionId, catalog, selection);
16122
- return { raw, catalog };
16134
+ const applied = await applyAgentModelSelectionWithUpdatedCatalog(client, sessionId, catalog, selection);
16135
+ return { raw, catalog: applied };
16123
16136
  });
16124
16137
  }
16125
16138
 
@@ -16205,7 +16218,7 @@ async function syncAgentModelCatalog(input) {
16205
16218
  const effectiveCatalog = input.startupUnavailable ? catalog : withEffectiveCurrentValues(catalog, selection);
16206
16219
  const hash = modelCatalogHash(effectiveCatalog, selection, input.startupUnavailable);
16207
16220
  const previous = await readFile(hashPath, "utf8").catch(() => "");
16208
- if (previous.trim() === hash)
16221
+ if (!input.force && previous.trim() === hash)
16209
16222
  return "unchanged";
16210
16223
  await input.api.execute("postAgentModelCatalog", {
16211
16224
  agentId: input.agentId,
@@ -16226,8 +16239,8 @@ async function syncAgentModelCatalog(input) {
16226
16239
  }
16227
16240
 
16228
16241
  // apps/body/dist/connector-google.js
16229
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync } from "node:fs";
16230
- import { dirname as dirname5, join as join4 } from "node:path";
16242
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "node:fs";
16243
+ import { dirname as dirname4, join as join3 } from "node:path";
16231
16244
 
16232
16245
  // apps/body/dist/google-workspace-client.js
16233
16246
  function credentialsTokenSource(credentials) {
@@ -16601,14 +16614,170 @@ function googleWorkspaceClient(tokenSource, transport = defaultGoogleApiTranspor
16601
16614
  };
16602
16615
  }
16603
16616
 
16617
+ // apps/body/dist/connector-google.js
16618
+ var GOOGLE_TOOL_SCOPES = {
16619
+ "google-gmail": [
16620
+ "https://www.googleapis.com/auth/gmail.send",
16621
+ "https://www.googleapis.com/auth/gmail.readonly",
16622
+ "https://www.googleapis.com/auth/gmail.compose"
16623
+ ],
16624
+ "google-calendar": ["https://www.googleapis.com/auth/calendar.events", "https://www.googleapis.com/auth/calendar.readonly"],
16625
+ "google-drive": ["https://www.googleapis.com/auth/drive.readonly"],
16626
+ "google-youtube": [
16627
+ "https://www.googleapis.com/auth/youtube.readonly",
16628
+ "https://www.googleapis.com/auth/yt-analytics.readonly"
16629
+ ]
16630
+ };
16631
+ function isGoogleToolConnectorType(type) {
16632
+ return type in GOOGLE_TOOL_SCOPES;
16633
+ }
16634
+ async function readGoogleCredentialsFromVault(mcp) {
16635
+ if (!mcp)
16636
+ return { source: "unavailable", reason: "no Trusty Squire connector is paired" };
16637
+ let raw;
16638
+ try {
16639
+ raw = await mcp.call("google_oauth_credentials", {});
16640
+ } catch (error) {
16641
+ const detail = describe(error);
16642
+ return {
16643
+ source: "unavailable",
16644
+ reason: `Squire has no Google OAuth grant on record yet (connect your Google account in Squire first: ${detail})`
16645
+ };
16646
+ }
16647
+ const record3 = raw && typeof raw === "object" ? raw : {};
16648
+ const inner = record3.credentials && typeof record3.credentials === "object" ? record3.credentials : record3;
16649
+ const accessToken = inner.accessToken ?? inner.access_token;
16650
+ if (!accessToken || typeof accessToken !== "string") {
16651
+ return { source: "unavailable", reason: "Squire returned a Google grant without an access token" };
16652
+ }
16653
+ return {
16654
+ source: "squire",
16655
+ credentials: {
16656
+ accessToken,
16657
+ refreshToken: typeof record3.refreshToken === "string" ? record3.refreshToken : typeof record3.refresh_token === "string" ? record3.refresh_token : void 0,
16658
+ expiresAt: typeof record3.expiresAt === "number" ? record3.expiresAt : void 0,
16659
+ accountEmail: typeof record3.accountEmail === "string" ? record3.accountEmail : typeof record3.email === "string" ? record3.email : void 0
16660
+ }
16661
+ };
16662
+ }
16663
+ function manualGoogleCredentialsSearchPaths(home) {
16664
+ return [join3(home, "google-credentials.json")];
16665
+ }
16666
+ function loadManualGoogleCredentials(home, env = process.env) {
16667
+ if (env.BEELINE_GOOGLE_ACCESS_TOKEN) {
16668
+ return {
16669
+ source: "manual",
16670
+ credentials: { accessToken: env.BEELINE_GOOGLE_ACCESS_TOKEN }
16671
+ };
16672
+ }
16673
+ for (const path of manualGoogleCredentialsSearchPaths(home)) {
16674
+ try {
16675
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
16676
+ if (typeof parsed.accessToken === "string" || typeof parsed.access_token === "string") {
16677
+ return {
16678
+ source: "manual",
16679
+ credentials: {
16680
+ accessToken: parsed.accessToken ?? parsed.access_token,
16681
+ refreshToken: typeof parsed.refreshToken === "string" ? parsed.refreshToken : void 0,
16682
+ expiresAt: typeof parsed.expiresAt === "number" ? parsed.expiresAt : void 0,
16683
+ accountEmail: typeof parsed.accountEmail === "string" ? parsed.accountEmail : void 0
16684
+ }
16685
+ };
16686
+ }
16687
+ } catch {
16688
+ }
16689
+ }
16690
+ return {
16691
+ source: "manual-missing",
16692
+ reason: `no Google credentials found. Put a google-credentials.json (OAuth access token) at ${manualGoogleCredentialsSearchPaths(home)[0]} or connect your Google account in Trusty Squire first.`
16693
+ };
16694
+ }
16695
+ function persistManualGoogleCredentials(home, credentials) {
16696
+ const path = manualGoogleCredentialsSearchPaths(home)[0];
16697
+ mkdirSync2(dirname4(path), { recursive: true, mode: 448 });
16698
+ writeFileSync(path, `${JSON.stringify({
16699
+ accessToken: credentials.accessToken,
16700
+ ...credentials.refreshToken ? { refreshToken: credentials.refreshToken } : {},
16701
+ ...credentials.expiresAt ? { expiresAt: credentials.expiresAt } : {},
16702
+ ...credentials.accountEmail ? { accountEmail: credentials.accountEmail } : {}
16703
+ })}
16704
+ `, { encoding: "utf8", mode: 384 });
16705
+ return path;
16706
+ }
16707
+ var step = (label, status, extra) => ({
16708
+ label,
16709
+ status,
16710
+ ...extra
16711
+ });
16712
+ function outputTail(text2, maxChars = 800) {
16713
+ const trimmed = text2.trim();
16714
+ return trimmed.length <= maxChars ? trimmed : `\u2026${trimmed.slice(-maxChars)}`;
16715
+ }
16716
+ async function installGoogleTool(options) {
16717
+ const steps = [step("helper reached", "done")];
16718
+ const emit = () => options.onProgress?.([...steps]);
16719
+ const push = (next) => {
16720
+ steps.push(next);
16721
+ emit();
16722
+ };
16723
+ const fail = (label, reason, output) => {
16724
+ push(step(label, "failed", { reason, ...output ? { output } : {} }));
16725
+ return { status: "error", steps, errorMessage: reason };
16726
+ };
16727
+ emit();
16728
+ if (!isGoogleToolConnectorType(options.connectorType)) {
16729
+ return fail("connector type", `${options.connectorType} is not a Google tool connector`);
16730
+ }
16731
+ push(step("Google credentials resolved", "running", { output: "resolving\u2026" }));
16732
+ const resolved = options.resolvedCredentials ? await options.resolvedCredentials : options.resolveCredentials ? await options.resolveCredentials() : await (async () => {
16733
+ const oneClick = await readGoogleCredentialsFromVault(options.squire);
16734
+ if (oneClick.source === "squire")
16735
+ return oneClick;
16736
+ return loadManualGoogleCredentials(options.home, options.env);
16737
+ })();
16738
+ if (!("credentials" in resolved)) {
16739
+ const reason = resolved.reason;
16740
+ steps[1] = step("Google credentials resolved", "failed", { reason, output: outputTail(reason) });
16741
+ emit();
16742
+ return { status: "error", steps, errorMessage: reason };
16743
+ }
16744
+ steps[1] = step("Google credentials resolved", "done", {
16745
+ output: resolved.source === "squire" ? "one-click: read the Google grant from Trusty Squire" : "manual: local google-credentials.json"
16746
+ });
16747
+ emit();
16748
+ const client = options.client ?? googleWorkspaceClient(refreshableTokenSource(resolved.credentials, options.env?.BEELINE_GOOGLE_CLIENT_ID, options.env?.BEELINE_GOOGLE_CLIENT_SECRET));
16749
+ push(step("authorized with Google", "running", { output: "verifying the grant with Google\u2026" }));
16750
+ const verify = await client.verify();
16751
+ if (!verify.ok) {
16752
+ return fail("authorized with Google", verify.reason, outputTail(verify.reason));
16753
+ }
16754
+ steps[2] = step("authorized with Google", "done", {
16755
+ ...verify.account ? { output: `signed in as ${verify.account}` } : {}
16756
+ });
16757
+ emit();
16758
+ push(step("tools enabled", "done", {
16759
+ output: `${GOOGLE_TOOL_SCOPES[options.connectorType].length} Google scopes granted`
16760
+ }));
16761
+ persistManualGoogleCredentials(options.home, resolved.credentials);
16762
+ return {
16763
+ status: "connected",
16764
+ steps,
16765
+ ...verify.account ? { signedInAs: verify.account } : {}
16766
+ };
16767
+ }
16768
+ function describe(error) {
16769
+ return error instanceof Error ? error.message : String(error);
16770
+ }
16771
+
16604
16772
  // apps/body/dist/connector-squire.js
16605
16773
  import { execFile, spawn as spawn5 } from "node:child_process";
16606
16774
  import { createHash as createHash2 } from "node:crypto";
16607
- import { lstatSync as lstatSync3, readFileSync as readFileSync3, realpathSync, rmSync } from "node:fs";
16775
+ import { lstatSync as lstatSync3, readFileSync as readFileSync4, realpathSync, rmSync } from "node:fs";
16608
16776
  import { homedir as homedir3, hostname, tmpdir as tmpdir2 } from "node:os";
16609
- import { basename as basename4, dirname as dirname4, join as join3, resolve as resolve7 } from "node:path";
16777
+ import { basename as basename4, dirname as dirname5, join as join4, resolve as resolve7 } from "node:path";
16610
16778
  var SQUIRE_MCP_NAME = "@trusty-squire/mcp";
16611
- var SQUIRE_CONNECT_PACKAGE = `${SQUIRE_MCP_NAME}@latest`;
16779
+ var SQUIRE_CONNECT_VERSION = "1.1.16-rc.4";
16780
+ var SQUIRE_CONNECT_PACKAGE = `${SQUIRE_MCP_NAME}@${SQUIRE_CONNECT_VERSION}`;
16612
16781
  var activeConnectSession;
16613
16782
  function squireConnectSession() {
16614
16783
  return activeConnectSession;
@@ -16644,9 +16813,9 @@ function squireProfilePathIdentity(profileDir) {
16644
16813
  let candidate = absolute;
16645
16814
  for (; ; ) {
16646
16815
  try {
16647
- return join3(realpathSync.native(candidate), ...suffix.reverse());
16816
+ return join4(realpathSync.native(candidate), ...suffix.reverse());
16648
16817
  } catch {
16649
- const parent = dirname4(candidate);
16818
+ const parent = dirname5(candidate);
16650
16819
  if (parent === candidate)
16651
16820
  return absolute;
16652
16821
  suffix.push(basename4(candidate));
@@ -16656,20 +16825,14 @@ function squireProfilePathIdentity(profileDir) {
16656
16825
  }
16657
16826
  function squireProfileLockPath(profileDir = squireChromeProfileDir(), lockRoot = tmpdir2()) {
16658
16827
  const digest = createHash2("sha256").update(squireProfilePathIdentity(profileDir)).digest("hex").slice(0, 24);
16659
- return join3(lockRoot, `trusty-squire-profile-${digest}.lock`);
16660
- }
16661
- var SQUIRE_BROWSER_BUSY_GOOGLE_REASON = "Trusty Squire is still using the browser \u2014 connect Trusty Squire first";
16662
- function isSquireBrowserSessionFailure(text2) {
16663
- if (typeof text2 !== "string" || text2.length === 0)
16664
- return false;
16665
- return /Trusty Squire[\s\S]{0,80}browser/i.test(text2) || /browser[\s\S]{0,80}Trusty Squire/i.test(text2);
16828
+ return join4(lockRoot, `trusty-squire-profile-${digest}.lock`);
16666
16829
  }
16667
16830
  function squireForeignClaimAction(owner) {
16668
16831
  return `Trusty Squire's browser is in use by another process (pid ${owner.pid}). Finish or close that Trusty Squire session, then press Connect again.`;
16669
16832
  }
16670
16833
  function readLinuxStartTime(pid) {
16671
16834
  try {
16672
- const stat5 = readFileSync3(`/proc/${pid}/stat`, "utf8");
16835
+ const stat5 = readFileSync4(`/proc/${pid}/stat`, "utf8");
16673
16836
  const close = stat5.lastIndexOf(")");
16674
16837
  return close < 0 ? void 0 : stat5.slice(close + 2).split(" ")[19];
16675
16838
  } catch {
@@ -16688,8 +16851,8 @@ function lockOwnerIsAlive(owner) {
16688
16851
  }
16689
16852
  function readLockFileOwner(lockPath) {
16690
16853
  try {
16691
- const target = lstatSync3(lockPath).isDirectory() ? join3(lockPath, "owner.json") : lockPath;
16692
- const parsed = JSON.parse(readFileSync3(target, "utf8"));
16854
+ const target = lstatSync3(lockPath).isDirectory() ? join4(lockPath, "owner.json") : lockPath;
16855
+ const parsed = JSON.parse(readFileSync4(target, "utf8"));
16693
16856
  if (typeof parsed.host !== "string" || typeof parsed.pid !== "number")
16694
16857
  return void 0;
16695
16858
  return {
@@ -16727,10 +16890,10 @@ function reclaimSquireProfileClaim(options) {
16727
16890
  options?.log?.(`cleared dead trusty-squire on-disk browser claim (pid ${owner.pid} gone)`);
16728
16891
  return { kind: "reclaimed-dead", owner };
16729
16892
  }
16730
- var defaultShellRunner = (command, args) => new Promise((resolve35) => {
16893
+ var defaultShellRunner = (command, args) => new Promise((resolve36) => {
16731
16894
  execFile(command, [...args], { timeout: 12e4, maxBuffer: 4 * 1024 * 1024, encoding: "utf8" }, (error, stdout6, stderr) => {
16732
16895
  const code = error?.code;
16733
- resolve35({
16896
+ resolve36({
16734
16897
  code: typeof code === "number" ? code : error ? 1 : 0,
16735
16898
  stdout: String(stdout6 ?? ""),
16736
16899
  stderr: String(stderr ?? "")
@@ -16755,7 +16918,7 @@ function killConnectTree(child) {
16755
16918
  }
16756
16919
  child.kill();
16757
16920
  }
16758
- var defaultStreamedRunner = (command, args, env) => new Promise((resolve35) => {
16921
+ var defaultStreamedRunner = (command, args, env) => new Promise((resolve36) => {
16759
16922
  const child = spawn5(command, args, {
16760
16923
  stdio: ["ignore", "pipe", "pipe"],
16761
16924
  env: env ?? squireConnectProcessEnv(),
@@ -16764,10 +16927,12 @@ var defaultStreamedRunner = (command, args, env) => new Promise((resolve35) => {
16764
16927
  let stdout6 = "";
16765
16928
  let stderr = "";
16766
16929
  let resolved = false;
16930
+ let consumed = 0;
16931
+ let report;
16767
16932
  const safetyTimer = setTimeout(() => {
16768
16933
  if (!resolved) {
16769
16934
  resolved = true;
16770
- resolve35({ stdout: stdout6, stderr, pid: child.pid ?? void 0, signIn: void 0, abort: () => {
16935
+ resolve36({ stdout: stdout6, stderr, pid: child.pid ?? void 0, report, abort: () => {
16771
16936
  } });
16772
16937
  }
16773
16938
  killConnectTree(child);
@@ -16785,128 +16950,163 @@ var defaultStreamedRunner = (command, args, env) => new Promise((resolve35) => {
16785
16950
  const finish = (result) => {
16786
16951
  if (!resolved) {
16787
16952
  resolved = true;
16788
- resolve35(result);
16953
+ resolve36(result);
16789
16954
  }
16790
16955
  };
16791
16956
  const settle = (result) => {
16792
16957
  clearTimeout(safetyTimer);
16793
16958
  finish(result);
16794
16959
  };
16795
- const checkOutput = () => {
16796
- const signIn = parseConnectOutput(stdout6) ?? parseConnectOutput(stderr);
16797
- if (signIn) {
16798
- finish({ stdout: stdout6, stderr, signIn, abort });
16960
+ const consumeReports = (final) => {
16961
+ const pending = stdout6.slice(consumed);
16962
+ const lastNewline = pending.lastIndexOf("\n");
16963
+ if (lastNewline < 0 && !final)
16964
+ return;
16965
+ const complete = lastNewline < 0 ? pending : pending.slice(0, lastNewline);
16966
+ for (const line of complete.split("\n")) {
16967
+ if (line.trim() === "")
16968
+ continue;
16969
+ const parsed = parseConnectReport(line);
16970
+ if (parsed)
16971
+ report = parsed;
16972
+ }
16973
+ consumed += lastNewline < 0 ? pending.length : lastNewline + 1;
16974
+ };
16975
+ const publishSignIn = () => {
16976
+ consumeReports(false);
16977
+ if (report && isPublishableConnectReport(report)) {
16978
+ finish({ stdout: stdout6, stderr, pid: child.pid ?? void 0, report, abort });
16799
16979
  }
16800
16980
  };
16801
- const finalSignIn = () => parseConnectOutput(`${stdout6}
16802
- `) ?? parseConnectOutput(`${stderr}
16803
- `);
16804
16981
  child.stdout?.on("data", (chunk) => {
16805
16982
  stdout6 += String(chunk);
16806
- checkOutput();
16983
+ publishSignIn();
16807
16984
  });
16808
16985
  child.stderr?.on("data", (chunk) => {
16809
16986
  stderr += String(chunk);
16810
- checkOutput();
16811
16987
  });
16812
16988
  child.on("close", () => {
16989
+ consumeReports(true);
16813
16990
  settle({
16814
16991
  stdout: stdout6,
16815
16992
  stderr,
16816
16993
  pid: child.pid ?? void 0,
16817
- signIn: finalSignIn(),
16994
+ report,
16818
16995
  abort: () => {
16819
16996
  }
16820
16997
  });
16821
16998
  });
16822
16999
  child.on("error", () => {
16823
- settle({ stdout: stdout6, stderr, pid: child.pid ?? void 0, signIn: void 0, abort: () => {
17000
+ settle({ stdout: stdout6, stderr, pid: child.pid ?? void 0, report, abort: () => {
16824
17001
  } });
16825
17002
  });
16826
17003
  });
16827
- var step = (label, status, reason) => ({
17004
+ var step2 = (label, status, reason) => ({
16828
17005
  label,
16829
17006
  status,
16830
17007
  ...reason ? { reason } : {}
16831
17008
  });
16832
- function isKnownCeremonySurface(url) {
16833
- try {
16834
- const parsed = new URL(url);
16835
- if (parsed.protocol !== "https:")
16836
- return false;
16837
- return parsed.hash.startsWith("#p=") || /(^|\/)install(\/|$)/.test(parsed.pathname);
16838
- } catch {
16839
- return false;
16840
- }
17009
+ function isRecord(value) {
17010
+ return value !== null && typeof value === "object" && !Array.isArray(value);
17011
+ }
17012
+ function connectAccount(raw) {
17013
+ if (!isRecord(raw) || typeof raw.id !== "string")
17014
+ return null;
17015
+ return {
17016
+ id: raw.id,
17017
+ // An absent or unreadable `providers` is `null` (could not look), never
17018
+ // `[]` (looked and found nothing).
17019
+ providers: Array.isArray(raw.providers) ? raw.providers.filter((provider) => typeof provider === "string") : null
17020
+ };
16841
17021
  }
16842
- function isForeignUrl(url) {
17022
+ function parseConnectReport(line) {
17023
+ let parsed;
16843
17024
  try {
16844
- const parsed = new URL(url);
16845
- const host = parsed.hostname.toLowerCase();
16846
- if (host === "developers.cloudflare.com")
16847
- return true;
16848
- if (host === "github.com" && parsed.pathname.toLowerCase().startsWith("/npm/cli"))
16849
- return true;
16850
- return host === "npmjs.com" || host === "www.npmjs.com";
17025
+ parsed = JSON.parse(line);
16851
17026
  } catch {
16852
- return true;
17027
+ return void 0;
16853
17028
  }
17029
+ if (!isRecord(parsed) || typeof parsed.state !== "string")
17030
+ return void 0;
17031
+ return {
17032
+ state: parsed.state,
17033
+ terminal: parsed.terminal === true,
17034
+ reason: typeof parsed.reason === "string" ? parsed.reason : null,
17035
+ sign_in_url: typeof parsed.sign_in_url === "string" ? parsed.sign_in_url : null,
17036
+ account: connectAccount(parsed.account),
17037
+ holder: parsed.holder,
17038
+ browser_location: parsed.browser_location
17039
+ };
16854
17040
  }
16855
- var BOXED_ROW = /^\s*\u2502(.*)\u2502\s*$/;
16856
- var BOX_BORDER = /^\s*[\u2500-\u257F]+\s*$/;
16857
- function rejoinBoxRows(cells) {
16858
- const written = cells.filter((cell) => cell.trim() !== "");
16859
- const pad2 = (measure) => written.length === 0 ? 0 : Math.min(...written.map(measure));
16860
- const left = pad2((cell) => cell.length - cell.trimStart().length);
16861
- const right = pad2((cell) => cell.length - cell.trimEnd().length);
16862
- const lines = [];
16863
- let carry;
16864
- for (const cell of cells) {
16865
- const content = cell.slice(left, cell.length - right);
16866
- const text2 = content.replace(/\s+$/, "");
16867
- carry = (carry ?? "") + text2;
16868
- if (text2.length < content.length) {
16869
- lines.push(carry);
16870
- carry = void 0;
16871
- }
17041
+ function isOutstandingSignIn(report) {
17042
+ if (report.terminal || report.state !== "needs-sign-in" || !report.sign_in_url)
17043
+ return false;
17044
+ const location = report.browser_location;
17045
+ if (!isRecord(location) || typeof location.kind !== "string")
17046
+ return false;
17047
+ return location.kind !== "none" && location.kind !== "unreachable";
17048
+ }
17049
+ function isPublishableConnectReport(report) {
17050
+ return report.terminal || isOutstandingSignIn(report);
17051
+ }
17052
+ var CONNECT_REASON_LINES = {
17053
+ provider_session_missing: "the bot's Chrome profile has no live provider session",
17054
+ requested_provider_missing: "the provider sign-in you asked to refresh did not complete",
17055
+ account_mismatch: "this machine is bound to a different account",
17056
+ profile_unverifiable: "the bot's Chrome profile could not be verified",
17057
+ install_expired: "the sign-in page expired before it was used",
17058
+ cached_cookie_evidence: "the shared profile already carried the session",
17059
+ run_failed: "the connect run failed before it could finish"
17060
+ };
17061
+ function holderLine(raw) {
17062
+ if (!isRecord(raw))
17063
+ return void 0;
17064
+ if (raw.kind === "other") {
17065
+ const pid = typeof raw.pid === "number" ? raw.pid : void 0;
17066
+ return pid === void 0 ? "another Trusty Squire session is using the browser" : squireForeignClaimAction({ host: hostname(), pid, startTime: null });
16872
17067
  }
16873
- if (carry !== void 0)
16874
- lines.push(carry);
16875
- return lines;
17068
+ if (raw.kind === "unknown")
17069
+ return "another process may be using the browser";
17070
+ return void 0;
16876
17071
  }
16877
- function unframeBoxedOutput(output) {
16878
- const lines = [];
16879
- let box = [];
16880
- for (const row of output.split("\n")) {
16881
- const framed = BOXED_ROW.exec(row);
16882
- if (framed) {
16883
- box.push(framed[1] ?? "");
16884
- continue;
16885
- }
16886
- if (box.length > 0 && BOX_BORDER.test(row))
16887
- lines.push(...rejoinBoxRows(box));
16888
- box = [];
16889
- lines.push(row);
17072
+ function connectBlockedLine(report) {
17073
+ if (report.state === "connected" || isOutstandingSignIn(report))
17074
+ return void 0;
17075
+ if (report.reason && CONNECT_REASON_LINES[report.reason]) {
17076
+ return CONNECT_REASON_LINES[report.reason];
16890
17077
  }
16891
- return lines.join("\n");
16892
- }
16893
- function parseConnectOutput(output) {
16894
- const urls = unframeBoxedOutput(output).match(/https:\/\/[^\s"'<>]+(?=[\s"'<>])/g) ?? [];
16895
- const preferred = urls.find(isKnownCeremonySurface);
16896
- if (preferred)
16897
- return { method: "streamed-page", url: preferred };
16898
- const fallback = urls.find((url) => !isForeignUrl(url));
16899
- if (fallback) {
16900
- console.log(`[body] squire connect: no known ceremony surface matched; using fallback URL ${fallback}`);
16901
- return { method: "streamed-page", url: fallback };
17078
+ const location = isRecord(report.browser_location) ? report.browser_location : void 0;
17079
+ if (location?.kind === "unreachable") {
17080
+ return "the sign-in page could not be shown on this machine";
16902
17081
  }
16903
- return void 0;
16904
- }
16905
- function parseConnectAlreadyConnected(output) {
16906
- return /Already connected \(/i.test(output) && /config refreshed/i.test(output);
17082
+ if (report.state === "needs-sign-in") {
17083
+ return "the connect run ended before the sign-in was completed";
17084
+ }
17085
+ const holder = holderLine(report.holder);
17086
+ if (holder)
17087
+ return holder;
17088
+ return `Trusty Squire reported the connect state "${report.state}"`;
16907
17089
  }
16908
- function withoutForceReloginHint(output) {
16909
- return output.split("\n").filter((line) => !/--force-relogin/.test(line)).join("\n").trim();
17090
+ function connectBrowserLocation(raw) {
17091
+ if (!isRecord(raw))
17092
+ return void 0;
17093
+ switch (raw.kind) {
17094
+ case "host_screen":
17095
+ return { kind: "host_screen" };
17096
+ case "virtual":
17097
+ return typeof raw.url === "string" ? { kind: "virtual", url: raw.url } : void 0;
17098
+ case "unreachable":
17099
+ return {
17100
+ kind: "unreachable",
17101
+ reason: typeof raw.reason === "string" ? raw.reason : "the sign-in page could not be shown"
17102
+ };
17103
+ case "none":
17104
+ return { kind: "none" };
17105
+ case "unknown":
17106
+ return typeof raw.reason === "string" ? { kind: "unknown", reason: raw.reason } : void 0;
17107
+ default:
17108
+ return void 0;
17109
+ }
16910
17110
  }
16911
17111
  async function installedSquireVersion(run2, spec = SQUIRE_CONNECT_PACKAGE, preferOnline = false) {
16912
17112
  const probe = await run2("npx", [
@@ -16919,7 +17119,7 @@ async function installedSquireVersion(run2, spec = SQUIRE_CONNECT_PACKAGE, prefe
16919
17119
  return version;
16920
17120
  }
16921
17121
  async function currentSquireRelease(run2) {
16922
- const probe = await run2("npm", ["view", SQUIRE_MCP_NAME, "version"]);
17122
+ const probe = await run2("npm", ["view", SQUIRE_CONNECT_PACKAGE, "version"]);
16923
17123
  if (probe.code !== 0)
16924
17124
  return void 0;
16925
17125
  return probe.stdout.match(/\d+\.\d+\.\d+[^\s]*/)?.[0];
@@ -16951,9 +17151,6 @@ async function resolveSquireConnectSpec(run2) {
16951
17151
  reResolved: true
16952
17152
  };
16953
17153
  }
16954
- function parseSignedInAs(output) {
16955
- return output.match(/signed in as ([^\s,;]+)/i)?.[1];
16956
- }
16957
17154
  async function installSquire(options) {
16958
17155
  const profileDir = options.profileDir ?? squireChromeProfileDir();
16959
17156
  const lockRoot = options.lockRoot ?? tmpdir2();
@@ -16961,14 +17158,14 @@ async function installSquire(options) {
16961
17158
  const streamRun = options.streamRun ?? defaultStreamedRunner;
16962
17159
  const log2 = options.log ?? (() => {
16963
17160
  });
16964
- const steps = [step("helper reached", "done")];
17161
+ const steps = [step2("helper reached", "done")];
16965
17162
  const emit = () => options.onProgress?.([...steps]);
16966
17163
  const push = (next) => {
16967
17164
  steps.push(next);
16968
17165
  emit();
16969
17166
  };
16970
17167
  const fail = (reason) => {
16971
- steps.push(step("waiting for sign-in", "pending"));
17168
+ steps.push(step2("waiting for sign-in", "pending"));
16972
17169
  emit();
16973
17170
  return { status: "error", steps, errorMessage: reason };
16974
17171
  };
@@ -16982,62 +17179,62 @@ async function installSquire(options) {
16982
17179
  ...previousPid !== void 0 ? { ourPids: [previousPid] } : {}
16983
17180
  });
16984
17181
  if (claim.kind === "blocked-foreign") {
16985
- push(step("trusty-squire installed", "failed", claim.action));
17182
+ push(step2("trusty-squire installed", "failed", claim.action));
16986
17183
  return fail(claim.action);
16987
17184
  }
16988
17185
  const resolution = await resolveSquireConnectSpec(run2);
16989
17186
  if (resolution.reResolved) {
16990
17187
  log2(`trusty-squire stale copy ${resolution.resolvedVersion ?? "unknown"} re-resolved against current release ${resolution.currentRelease}`);
16991
17188
  }
16992
- const install = await streamRun("npx", [...resolution.npxArgs, "connect", "--target=codex"], squireConnectProcessEnv(profileDir));
16993
- const alreadyConnected = !install.signIn && parseConnectAlreadyConnected(`${install.stdout}
16994
- ${install.stderr}`);
16995
- if (!install.signIn && !alreadyConnected) {
16996
- const stderr = withoutForceReloginHint(install.stderr);
16997
- let stepReason = stderr || "connect printed no sign-in URL";
16998
- let failReason = stderr || "the trusty-squire connect command printed no sign-in surface";
16999
- if (isSquireBrowserSessionFailure(stderr)) {
17000
- const again = reclaimSquireProfileClaim({ log: log2, profileDir, lockRoot });
17001
- if (again.kind === "blocked-foreign") {
17002
- stepReason = again.action;
17003
- failReason = again.action;
17004
- }
17005
- }
17006
- push(step("trusty-squire installed", "failed", stepReason));
17007
- return fail(failReason);
17008
- }
17189
+ const install = await streamRun("npx", [...resolution.npxArgs, "connect", "--target=codex", "--json"], squireConnectProcessEnv(profileDir));
17190
+ const report = install.report;
17009
17191
  const version = resolution.resolvedVersion;
17010
- push(step(`trusty-squire${version ? ` ${version}` : ""} installed`, "done"));
17011
- const signIn = install.signIn;
17012
- const signedInAs = parseSignedInAs(`${install.stdout}
17013
- ${install.stderr}`);
17014
- push(step("waiting for sign-in", signIn ? "pending" : "done"));
17192
+ if (!report) {
17193
+ const reason = "Trusty Squire did not report a connect result";
17194
+ push(step2("trusty-squire installed", "failed", reason));
17195
+ return fail(reason);
17196
+ }
17197
+ if (report.state === "needs-sign-in" && !report.sign_in_url) {
17198
+ const reason = "Trusty Squire reported a sign-in without a page";
17199
+ push(step2("trusty-squire installed", "failed", reason));
17200
+ return fail(reason);
17201
+ }
17202
+ if (report.state !== "connected" && !isOutstandingSignIn(report)) {
17203
+ const reason = connectBlockedLine(report) ?? "Trusty Squire could not connect";
17204
+ push(step2("trusty-squire installed", "failed", reason));
17205
+ return fail(reason);
17206
+ }
17207
+ push(step2(`trusty-squire${version ? ` ${version}` : ""} installed`, "done"));
17208
+ const location = connectBrowserLocation(report.browser_location);
17209
+ const signIn = isOutstandingSignIn(report) && report.sign_in_url ? {
17210
+ method: "streamed-page",
17211
+ url: report.sign_in_url,
17212
+ ...location ? { browserLocation: location } : {}
17213
+ } : void 0;
17214
+ push(step2("waiting for sign-in", signIn ? "pending" : "done"));
17015
17215
  const pair = await pairSquire(options.mcp, options.workspaceId);
17016
17216
  if (!pair.ok) {
17017
- push(step("paired to workspace", "failed", pair.reason));
17217
+ push(step2("paired to workspace", "failed", pair.reason));
17018
17218
  return {
17019
17219
  status: "installing",
17020
17220
  steps,
17021
- signIn,
17022
- ...version ? { squireVersion: version } : {},
17023
- ...signedInAs ? { signedInAs } : {}
17221
+ ...signIn ? { signIn } : {},
17222
+ ...version ? { squireVersion: version } : {}
17024
17223
  };
17025
17224
  }
17026
- push(step("paired to workspace", "done"));
17225
+ push(step2("paired to workspace", "done"));
17027
17226
  if (signIn) {
17028
17227
  return {
17029
17228
  status: "installing",
17030
17229
  steps,
17031
17230
  signIn,
17032
- ...version ? { squireVersion: version } : {},
17033
- ...signedInAs ? { signedInAs } : {}
17231
+ ...version ? { squireVersion: version } : {}
17034
17232
  };
17035
17233
  }
17036
17234
  return {
17037
17235
  status: "connected",
17038
17236
  steps,
17039
- ...version ? { squireVersion: version } : {},
17040
- ...signedInAs ? { signedInAs } : {}
17237
+ ...version ? { squireVersion: version } : {}
17041
17238
  };
17042
17239
  }
17043
17240
  async function pairSquire(mcp, _workspaceId) {
@@ -17122,169 +17319,6 @@ async function revokeGrants(mcp, ref) {
17122
17319
  return { revoked, failed };
17123
17320
  }
17124
17321
 
17125
- // apps/body/dist/connector-google.js
17126
- var GOOGLE_TOOL_SCOPES = {
17127
- "google-gmail": [
17128
- "https://www.googleapis.com/auth/gmail.send",
17129
- "https://www.googleapis.com/auth/gmail.readonly",
17130
- "https://www.googleapis.com/auth/gmail.compose"
17131
- ],
17132
- "google-calendar": ["https://www.googleapis.com/auth/calendar.events", "https://www.googleapis.com/auth/calendar.readonly"],
17133
- "google-drive": ["https://www.googleapis.com/auth/drive.readonly"],
17134
- "google-youtube": [
17135
- "https://www.googleapis.com/auth/youtube.readonly",
17136
- "https://www.googleapis.com/auth/yt-analytics.readonly"
17137
- ]
17138
- };
17139
- function isGoogleToolConnectorType(type) {
17140
- return type in GOOGLE_TOOL_SCOPES;
17141
- }
17142
- async function readGoogleCredentialsFromVault(mcp) {
17143
- if (!mcp)
17144
- return { source: "unavailable", reason: "no Trusty Squire connector is paired" };
17145
- let raw;
17146
- try {
17147
- raw = await mcp.call("google_oauth_credentials", {});
17148
- } catch (error) {
17149
- const detail = describe(error);
17150
- if (isSquireBrowserSessionFailure(detail)) {
17151
- return {
17152
- source: "unavailable",
17153
- reason: SQUIRE_BROWSER_BUSY_GOOGLE_REASON
17154
- };
17155
- }
17156
- return {
17157
- source: "unavailable",
17158
- reason: `Squire has no Google OAuth grant on record yet (connect your Google account in Squire first: ${detail})`
17159
- };
17160
- }
17161
- const record3 = raw && typeof raw === "object" ? raw : {};
17162
- const inner = record3.credentials && typeof record3.credentials === "object" ? record3.credentials : record3;
17163
- const accessToken = inner.accessToken ?? inner.access_token;
17164
- if (!accessToken || typeof accessToken !== "string") {
17165
- return { source: "unavailable", reason: "Squire returned a Google grant without an access token" };
17166
- }
17167
- return {
17168
- source: "squire",
17169
- credentials: {
17170
- accessToken,
17171
- refreshToken: typeof record3.refreshToken === "string" ? record3.refreshToken : typeof record3.refresh_token === "string" ? record3.refresh_token : void 0,
17172
- expiresAt: typeof record3.expiresAt === "number" ? record3.expiresAt : void 0,
17173
- accountEmail: typeof record3.accountEmail === "string" ? record3.accountEmail : typeof record3.email === "string" ? record3.email : void 0
17174
- }
17175
- };
17176
- }
17177
- function manualGoogleCredentialsSearchPaths(home) {
17178
- return [join4(home, "google-credentials.json")];
17179
- }
17180
- function loadManualGoogleCredentials(home, env = process.env) {
17181
- if (env.BEELINE_GOOGLE_ACCESS_TOKEN) {
17182
- return {
17183
- source: "manual",
17184
- credentials: { accessToken: env.BEELINE_GOOGLE_ACCESS_TOKEN }
17185
- };
17186
- }
17187
- for (const path of manualGoogleCredentialsSearchPaths(home)) {
17188
- try {
17189
- const parsed = JSON.parse(readFileSync4(path, "utf8"));
17190
- if (typeof parsed.accessToken === "string" || typeof parsed.access_token === "string") {
17191
- return {
17192
- source: "manual",
17193
- credentials: {
17194
- accessToken: parsed.accessToken ?? parsed.access_token,
17195
- refreshToken: typeof parsed.refreshToken === "string" ? parsed.refreshToken : void 0,
17196
- expiresAt: typeof parsed.expiresAt === "number" ? parsed.expiresAt : void 0,
17197
- accountEmail: typeof parsed.accountEmail === "string" ? parsed.accountEmail : void 0
17198
- }
17199
- };
17200
- }
17201
- } catch {
17202
- }
17203
- }
17204
- return {
17205
- source: "manual-missing",
17206
- reason: `no Google credentials found. Put a google-credentials.json (OAuth access token) at ${manualGoogleCredentialsSearchPaths(home)[0]} or connect your Google account in Trusty Squire first.`
17207
- };
17208
- }
17209
- function persistManualGoogleCredentials(home, credentials) {
17210
- const path = manualGoogleCredentialsSearchPaths(home)[0];
17211
- mkdirSync2(dirname5(path), { recursive: true, mode: 448 });
17212
- writeFileSync(path, `${JSON.stringify({
17213
- accessToken: credentials.accessToken,
17214
- ...credentials.refreshToken ? { refreshToken: credentials.refreshToken } : {},
17215
- ...credentials.expiresAt ? { expiresAt: credentials.expiresAt } : {},
17216
- ...credentials.accountEmail ? { accountEmail: credentials.accountEmail } : {}
17217
- })}
17218
- `, { encoding: "utf8", mode: 384 });
17219
- return path;
17220
- }
17221
- var step2 = (label, status, extra) => ({
17222
- label,
17223
- status,
17224
- ...extra
17225
- });
17226
- function outputTail(text2, maxChars = 800) {
17227
- const trimmed = text2.trim();
17228
- return trimmed.length <= maxChars ? trimmed : `\u2026${trimmed.slice(-maxChars)}`;
17229
- }
17230
- async function installGoogleTool(options) {
17231
- const steps = [step2("helper reached", "done")];
17232
- const emit = () => options.onProgress?.([...steps]);
17233
- const push = (next) => {
17234
- steps.push(next);
17235
- emit();
17236
- };
17237
- const fail = (label, reason, output) => {
17238
- push(step2(label, "failed", { reason, ...output ? { output } : {} }));
17239
- return { status: "error", steps, errorMessage: reason };
17240
- };
17241
- emit();
17242
- if (!isGoogleToolConnectorType(options.connectorType)) {
17243
- return fail("connector type", `${options.connectorType} is not a Google tool connector`);
17244
- }
17245
- push(step2("Google credentials resolved", "running", { output: "resolving\u2026" }));
17246
- const resolved = options.resolvedCredentials ? await options.resolvedCredentials : options.resolveCredentials ? await options.resolveCredentials() : await (async () => {
17247
- const oneClick = await readGoogleCredentialsFromVault(options.squire);
17248
- if (oneClick.source === "squire")
17249
- return oneClick;
17250
- if (isSquireBrowserSessionFailure(oneClick.reason))
17251
- return oneClick;
17252
- return loadManualGoogleCredentials(options.home, options.env);
17253
- })();
17254
- if (!("credentials" in resolved)) {
17255
- const reason = resolved.reason;
17256
- steps[1] = step2("Google credentials resolved", "failed", { reason, output: outputTail(reason) });
17257
- emit();
17258
- return { status: "error", steps, errorMessage: reason };
17259
- }
17260
- steps[1] = step2("Google credentials resolved", "done", {
17261
- output: resolved.source === "squire" ? "one-click: read the Google grant from Trusty Squire" : "manual: local google-credentials.json"
17262
- });
17263
- emit();
17264
- const client = options.client ?? googleWorkspaceClient(refreshableTokenSource(resolved.credentials, options.env?.BEELINE_GOOGLE_CLIENT_ID, options.env?.BEELINE_GOOGLE_CLIENT_SECRET));
17265
- push(step2("authorized with Google", "running", { output: "verifying the grant with Google\u2026" }));
17266
- const verify = await client.verify();
17267
- if (!verify.ok) {
17268
- return fail("authorized with Google", verify.reason, outputTail(verify.reason));
17269
- }
17270
- steps[2] = step2("authorized with Google", "done", {
17271
- ...verify.account ? { output: `signed in as ${verify.account}` } : {}
17272
- });
17273
- emit();
17274
- push(step2("tools enabled", "done", {
17275
- output: `${GOOGLE_TOOL_SCOPES[options.connectorType].length} Google scopes granted`
17276
- }));
17277
- persistManualGoogleCredentials(options.home, resolved.credentials);
17278
- return {
17279
- status: "connected",
17280
- steps,
17281
- ...verify.account ? { signedInAs: verify.account } : {}
17282
- };
17283
- }
17284
- function describe(error) {
17285
- return error instanceof Error ? error.message : String(error);
17286
- }
17287
-
17288
17322
  // apps/body/dist/squire-mcp-client.js
17289
17323
  import { spawn as spawn6 } from "node:child_process";
17290
17324
  import { homedir as homedir4 } from "node:os";
@@ -17378,12 +17412,12 @@ var StdioSquireMcpClient = class {
17378
17412
  const child = this.child;
17379
17413
  if (!child)
17380
17414
  return Promise.reject(new Error("Squire MCP session is not running"));
17381
- return new Promise((resolve35, reject) => {
17415
+ return new Promise((resolve36, reject) => {
17382
17416
  const timer = setTimeout(() => {
17383
17417
  this.pending.delete(id);
17384
17418
  reject(new Error(`${method} timed out`));
17385
17419
  }, method === "initialize" ? INITIALIZE_TIMEOUT_MS : CALL_TIMEOUT_MS);
17386
- this.pending.set(id, { resolve: resolve35, reject, timer });
17420
+ this.pending.set(id, { resolve: resolve36, reject, timer });
17387
17421
  child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}
17388
17422
  `);
17389
17423
  });
@@ -17426,7 +17460,8 @@ function defaultSquireMcpClient() {
17426
17460
  }
17427
17461
 
17428
17462
  // apps/body/dist/connector-assignments.js
17429
- var CONNECTOR_POLL_INTERVAL_MS = 1e4;
17463
+ var CONNECTOR_POLL_INTERVAL_MS = 5 * 6e4;
17464
+ var CONNECT_WATCH_INTERVAL_MS = 2e3;
17430
17465
  var CEREMONY_EXPIRED = "the Trusty Squire sign-in page expired before it was used \xB7 tap Retry to open a new one";
17431
17466
  var ConnectorAssignmentLoop = class {
17432
17467
  agentId;
@@ -17441,6 +17476,8 @@ var ConnectorAssignmentLoop = class {
17441
17476
  schedule;
17442
17477
  cancel;
17443
17478
  timer;
17479
+ /** Armed only while this helper owns a live sign-in ceremony. */
17480
+ connectWatch;
17444
17481
  started = false;
17445
17482
  stopped = false;
17446
17483
  /** One install at a time per connector; other polls skip it. */
@@ -17476,12 +17513,56 @@ var ConnectorAssignmentLoop = class {
17476
17513
  void this.runOnce();
17477
17514
  this.timer = this.schedule(() => this.poll(), this.intervalMs);
17478
17515
  }
17479
- stop() {
17480
- this.stopped = true;
17481
- if (this.timer !== void 0) {
17482
- this.cancel(this.timer);
17483
- this.timer = void 0;
17516
+ /** Event-driven drain. The interval stays the recovery net. */
17517
+ wake() {
17518
+ if (this.stopped)
17519
+ return;
17520
+ void this.runOnce();
17521
+ }
17522
+ stop() {
17523
+ this.stopped = true;
17524
+ if (this.timer !== void 0) {
17525
+ this.cancel(this.timer);
17526
+ this.timer = void 0;
17527
+ }
17528
+ if (this.connectWatch !== void 0) {
17529
+ this.cancel(this.connectWatch);
17530
+ this.connectWatch = void 0;
17531
+ }
17532
+ }
17533
+ /**
17534
+ * Watch the sign-in this helper is holding open. The phone paints
17535
+ * `installing` for the whole ceremony and only a drain that reaches
17536
+ * Squire's already-connected short-circuit flips the row to `connected`;
17537
+ * nothing on the wire announces that the human finished, so the connect
17538
+ * process exiting is the signal. Drain on that, and the row settles on the
17539
+ * same cadence it always has instead of waiting out the recovery poll.
17540
+ */
17541
+ watchConnectSignIn() {
17542
+ if (this.stopped || this.connectWatch !== void 0)
17543
+ return;
17544
+ if (!this.connectCeremonyLive())
17545
+ return;
17546
+ this.connectWatch = this.schedule(() => this.checkConnectSignIn(), CONNECT_WATCH_INTERVAL_MS);
17547
+ }
17548
+ /** A ceremony of ours still worth waiting on: claimed, unspent, alive. */
17549
+ connectCeremonyLive() {
17550
+ const claim = squireConnectSession();
17551
+ if (!claim)
17552
+ return false;
17553
+ if (Date.now() - claim.claimedAt >= CONNECT_TIMEOUT_MS)
17554
+ return false;
17555
+ return isProcessAlive(claim.pid);
17556
+ }
17557
+ checkConnectSignIn() {
17558
+ this.connectWatch = void 0;
17559
+ if (this.stopped)
17560
+ return;
17561
+ if (this.connectCeremonyLive()) {
17562
+ this.connectWatch = this.schedule(() => this.checkConnectSignIn(), CONNECT_WATCH_INTERVAL_MS);
17563
+ return;
17484
17564
  }
17565
+ void this.runOnce();
17485
17566
  }
17486
17567
  /** One interval tick: poll, then re-arm. */
17487
17568
  poll() {
@@ -17548,8 +17629,6 @@ var ConnectorAssignmentLoop = class {
17548
17629
  const oneClick = await readGoogleCredentialsFromVault(this.squire());
17549
17630
  if (oneClick.source === "squire")
17550
17631
  return oneClick;
17551
- if (isSquireBrowserSessionFailure(oneClick.reason))
17552
- return oneClick;
17553
17632
  } catch (error) {
17554
17633
  this.log(`google one-click grant lookup failed: ${describe2(error)}`);
17555
17634
  }
@@ -17605,6 +17684,7 @@ var ConnectorAssignmentLoop = class {
17605
17684
  if (claim && !spent && isProcessAlive(claim.pid)) {
17606
17685
  if (!await this.rearmedByHuman(connectorId)) {
17607
17686
  this.log(`trusty-squire connect still waiting for sign-in (pid ${String(claim.pid)}); leaving it alone`);
17687
+ this.watchConnectSignIn();
17608
17688
  return;
17609
17689
  }
17610
17690
  this.log("trusty-squire re-pair requested; superseding the connect holding the browser");
@@ -17646,8 +17726,7 @@ var ConnectorAssignmentLoop = class {
17646
17726
  await this.api.execute("installConnector", {
17647
17727
  agentId: this.agentId,
17648
17728
  connectorId,
17649
- ...result.squireVersion ? { squireVersion: result.squireVersion } : {},
17650
- ...result.signedInAs ? { signedInAs: result.signedInAs } : {}
17729
+ ...result.squireVersion ? { squireVersion: result.squireVersion } : {}
17651
17730
  });
17652
17731
  await this.reportVault(connectorId);
17653
17732
  return;
@@ -17657,9 +17736,9 @@ var ConnectorAssignmentLoop = class {
17657
17736
  connectorId,
17658
17737
  steps: result.steps,
17659
17738
  signIn: result.signIn ?? null,
17660
- ...result.squireVersion ? { squireVersion: result.squireVersion } : {},
17661
- ...result.signedInAs ? { signedInAs: result.signedInAs } : {}
17739
+ ...result.squireVersion ? { squireVersion: result.squireVersion } : {}
17662
17740
  });
17741
+ this.watchConnectSignIn();
17663
17742
  }
17664
17743
  /** This connector's own row, or undefined when the server cannot answer. */
17665
17744
  async connectorRow(connectorId) {
@@ -23759,9 +23838,9 @@ function isHex32(input) {
23759
23838
  return true;
23760
23839
  }
23761
23840
  var verifiedSymbol = /* @__PURE__ */ Symbol("verified");
23762
- var isRecord = (obj) => obj instanceof Object;
23841
+ var isRecord2 = (obj) => obj instanceof Object;
23763
23842
  function validateEvent(event) {
23764
- if (!isRecord(event))
23843
+ if (!isRecord2(event))
23765
23844
  return false;
23766
23845
  if (typeof event.kind !== "number")
23767
23846
  return false;
@@ -26685,6 +26764,15 @@ var AGENT_REMOVED_CODE = "agent_removed";
26685
26764
  function isAgentRemovedError(error) {
26686
26765
  return error instanceof DaemonApiError && error.status === 403 && error.code === AGENT_REMOVED_CODE;
26687
26766
  }
26767
+ function membershipChange(event) {
26768
+ return {
26769
+ ...typeof event.roomId === "string" && event.roomId ? { roomId: event.roomId } : {},
26770
+ ...typeof event.parentRoomId === "string" ? { parentRoomId: event.parentRoomId } : {},
26771
+ ...typeof event.openedBy === "string" ? { openedBy: event.openedBy } : {},
26772
+ ...event.archived === true ? { archived: true } : {},
26773
+ ...event.removed === true ? { removed: true } : {}
26774
+ };
26775
+ }
26688
26776
  function endpoint(origin, path) {
26689
26777
  return new URL(path, `${origin}/`).toString();
26690
26778
  }
@@ -26712,6 +26800,8 @@ var DaemonApiClient = class {
26712
26800
  roomsChangedListener;
26713
26801
  configChangedListener;
26714
26802
  hiccupRestartListener;
26803
+ connectorAssignmentListener;
26804
+ cornerCompleteListener;
26715
26805
  constructor(baseUrl, daemonToken, agentId, fetchImpl = fetch, webSocketFactory = (url, protocols) => new wrapper_default(url, protocols)) {
26716
26806
  this.baseUrl = baseUrl;
26717
26807
  this.daemonToken = daemonToken;
@@ -26759,8 +26849,8 @@ var DaemonApiClient = class {
26759
26849
  };
26760
26850
  }
26761
26851
  /** Register the one listener invoked when the server reports this agent's
26762
- * Room/corner memberships changed — the wake that discovers a freshly
26763
- * created Room without waiting for the reconciliation heartbeat. */
26852
+ * Room/corner memberships changed — a scoped event applies incrementally;
26853
+ * an unscoped wake (reconnect) still runs the recovery reconcile. */
26764
26854
  setRoomsChangedListener(listener) {
26765
26855
  this.roomsChangedListener = listener;
26766
26856
  }
@@ -26774,6 +26864,14 @@ var DaemonApiClient = class {
26774
26864
  setHiccupRestartListener(listener) {
26775
26865
  this.hiccupRestartListener = listener;
26776
26866
  }
26867
+ /** Connect / pending_ops drain wake. Never a catalog. */
26868
+ setConnectorAssignmentListener(listener) {
26869
+ this.connectorAssignmentListener = listener;
26870
+ }
26871
+ /** corner-complete on the subscribed corner — close now, poll is recovery. */
26872
+ setCornerCompleteListener(listener) {
26873
+ this.cornerCompleteListener = listener;
26874
+ }
26777
26875
  updateLiveCursor(roomId, cursor3) {
26778
26876
  const room = this.liveRooms.get(roomId);
26779
26877
  if (room && cursor3)
@@ -26815,6 +26913,7 @@ var DaemonApiClient = class {
26815
26913
  for (const roomId of this.liveRooms.keys())
26816
26914
  this.sendLiveSubscription(roomId);
26817
26915
  this.roomsChangedListener?.();
26916
+ this.connectorAssignmentListener?.();
26818
26917
  };
26819
26918
  socket.onmessage = (message) => {
26820
26919
  let value;
@@ -26827,7 +26926,15 @@ var DaemonApiClient = class {
26827
26926
  return;
26828
26927
  const event = value;
26829
26928
  if (event.type === "rooms-changed") {
26830
- this.roomsChangedListener?.();
26929
+ this.roomsChangedListener?.(membershipChange(event));
26930
+ return;
26931
+ }
26932
+ if (event.type === "corner-complete" && typeof event.roomId === "string") {
26933
+ this.cornerCompleteListener?.(event.roomId);
26934
+ return;
26935
+ }
26936
+ if (event.type === "connector-assignment") {
26937
+ this.connectorAssignmentListener?.();
26831
26938
  return;
26832
26939
  }
26833
26940
  if (event.type === "config-changed") {
@@ -26954,12 +27061,12 @@ async function activateDaemonTransport(path, fetchImpl = fetch) {
26954
27061
  }
26955
27062
 
26956
27063
  // apps/body/dist/room-runtime.js
26957
- import { execFile as execFile6 } from "node:child_process";
27064
+ import { execFile as execFile7 } from "node:child_process";
26958
27065
  import { createHash as createHash7 } from "node:crypto";
26959
27066
  import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "node:fs";
26960
- import { mkdir as mkdir14, rm as rm5 } from "node:fs/promises";
26961
- import { dirname as dirname12, resolve as resolve23 } from "node:path";
26962
- import { promisify as promisify4 } from "node:util";
27067
+ import { mkdir as mkdir15, rm as rm5 } from "node:fs/promises";
27068
+ import { dirname as dirname13, resolve as resolve24 } from "node:path";
27069
+ import { promisify as promisify5 } from "node:util";
26963
27070
 
26964
27071
  // apps/body/dist/grant-runner.js
26965
27072
  import { execFile as execFile3 } from "node:child_process";
@@ -27297,9 +27404,10 @@ function sandboxMountPlan(spec) {
27297
27404
  ...spec.additionalWritablePaths ?? [],
27298
27405
  ...harnessState
27299
27406
  ] : (
27300
- // A Room writes no checkout and no host path — only its own harness
27301
- // state, explicitly granted agent-private paths (persistent memory and
27302
- // the ephemeral workbench — never the repo), and the private /tmp.
27407
+ // A Room keeps source files read-only. Its explicit capabilities are
27408
+ // limited to harness state, agent-private paths, and release-owned
27409
+ // generated state such as the repository's .codegraph index; callers
27410
+ // must name each path. /tmp remains private.
27303
27411
  [...spec.additionalWritablePaths ?? [], ...harnessState]
27304
27412
  ));
27305
27413
  const tmpRestores = normalize([
@@ -27426,8 +27534,8 @@ function detectBwrapSandbox(options = {}) {
27426
27534
  });
27427
27535
  const result = run2(probe.command, probe.args);
27428
27536
  if (result.status !== 0) {
27429
- const detail = (result.stderr ?? "").trim().split("\n").pop() ?? `exit ${result.status}`;
27430
- const appArmorRemediation = /No permissions to create new namespace|setting up uid map: Permission denied|userns_create/i.test(result.stderr ?? "") ? " Ubuntu AppArmor may be blocking unprivileged user namespaces. Keep the system-wide restriction enabled; install a narrow userns profile for /usr/bin/bwrap as documented in apps/body/README.md, then restart the agent." : "";
27537
+ const detail = (result.stderr ?? "").trim().split("\n").filter(Boolean).pop() ?? `exit ${result.status}`;
27538
+ const appArmorRemediation = /No permissions to create (?:a )?new namespace|setting up uid map: Permission denied|userns_create/i.test(result.stderr ?? "") ? " Ubuntu AppArmor may be blocking unprivileged user namespaces. If the agent's user service inherited an AppArmor profile, reinstall the Beeline user unit and restart the agent; the unit explicitly transitions to unconfined before /usr/bin/bwrap enters Ubuntu's bwrap profile." : "";
27431
27539
  return {
27432
27540
  advisory: `harness OS sandbox UNAVAILABLE: ${bwrapPath} self-test failed (${detail}); ACP children run unconfined and the Room read-only rule rests on the permission handler alone.${appArmorRemediation}`
27433
27541
  };
@@ -27501,6 +27609,7 @@ var GRANT_COMMAND_OUTPUT_CAP_BYTES = 64 * 1024;
27501
27609
  var ARGV_MAX_WORDS = 256;
27502
27610
  var ARGV_WORD_MAX_LENGTH = 4096;
27503
27611
  var WRITE_REFUSED = /Read-only file system|EROFS/;
27612
+ var SANDBOX_STARTED_SCRIPT = ['printf "%s\\n" "$1" >&2', "shift", 'exec "$@"'].join("\n");
27504
27613
  var ROOM_SANDBOX_UNAVAILABLE = "this Room cannot run granted commands: a Room promises a read-only filesystem and that promise is enforced by bubblewrap, which is not usable on this host. Open a corner with open_corner and run it there, where writes belong.";
27505
27614
  var ROOM_WRITE_REFUSED_NOTE = "[beeline] the Room filesystem is read-only outside your scratch directory, so this write was refused by the kernel. Open a corner with open_corner to change files.";
27506
27615
  function operatorSecretResolver(env = process.env) {
@@ -27624,7 +27733,9 @@ var GrantCommandRunner = class {
27624
27733
  ...Object.fromEntries(secrets)
27625
27734
  };
27626
27735
  const cap = this.options.outputCapBytes ?? GRANT_COMMAND_OUTPUT_CAP_BYTES;
27627
- const spawn13 = surfaceAllows(policy.surface, "run-host-command") ? { command: argv[0], args: argv.slice(1) } : roomSandboxCommand(policy, room.cwd, argv);
27736
+ const runsOnHost = surfaceAllows(policy.surface, "run-host-command");
27737
+ const sandboxMarker = runsOnHost ? void 0 : `[beeline-sandbox-started:${randomBytes6(16).toString("hex")}]`;
27738
+ const spawn13 = runsOnHost ? { command: argv[0], args: argv.slice(1) } : roomSandboxCommand(policy, room.cwd, argv, sandboxMarker);
27628
27739
  const outcome = await new Promise((resolveRun) => {
27629
27740
  const child = execFile3(spawn13.command, spawn13.args, {
27630
27741
  cwd: room.cwd,
@@ -27645,7 +27756,13 @@ var GrantCommandRunner = class {
27645
27756
  });
27646
27757
  });
27647
27758
  });
27648
- const writeRefused = !surfaceAllows(policy.surface, "run-host-command") && WRITE_REFUSED.test(outcome.output);
27759
+ const sandboxStarted = sandboxMarker ? outcome.output.includes(sandboxMarker) : true;
27760
+ if (sandboxMarker && sandboxStarted) {
27761
+ outcome.output = outcome.output.replace(`${sandboxMarker}
27762
+ `, "").replace(sandboxMarker, "");
27763
+ }
27764
+ const writeRefused = !runsOnHost && WRITE_REFUSED.test(outcome.output);
27765
+ const sandboxFailure = !runsOnHost && !sandboxStarted;
27649
27766
  const output = capOutput(scrubSecrets(writeRefused ? `${outcome.output.trimEnd()}
27650
27767
  ${ROOM_WRITE_REFUSED_NOTE}` : outcome.output, secrets), cap);
27651
27768
  const turn = room.turn();
@@ -27653,7 +27770,7 @@ ${ROOM_WRITE_REFUSED_NOTE}` : outcome.output, secrets), cap);
27653
27770
  pubkey: grant.requestedBy,
27654
27771
  ...grant.requestedByName ? { name: grant.requestedByName } : {}
27655
27772
  };
27656
- const status = outcome.timedOut ? "timed out" : outcome.exitCode === null ? "error" : `exit ${outcome.exitCode}`;
27773
+ const status = sandboxFailure ? "sandbox failed" : outcome.timedOut ? "timed out" : outcome.exitCode === null ? "error" : `exit ${outcome.exitCode}`;
27657
27774
  await this.options.api.execute("postAgentActivity", {
27658
27775
  agentId: this.options.agentId,
27659
27776
  roomId: input.roomId,
@@ -27677,7 +27794,8 @@ ${ROOM_WRITE_REFUSED_NOTE}` : outcome.output, secrets), cap);
27677
27794
  ...outcome.signal ? { signal: outcome.signal } : {},
27678
27795
  timedOut: outcome.timedOut,
27679
27796
  output,
27680
- ...writeRefused ? { writeRefused: true } : {}
27797
+ ...writeRefused ? { writeRefused: true } : {},
27798
+ ...sandboxFailure ? { sandboxFailure: true } : {}
27681
27799
  };
27682
27800
  }
27683
27801
  /**
@@ -27719,7 +27837,7 @@ function scriptCandidates(cwd, scratch, argv) {
27719
27837
  ];
27720
27838
  return [...new Set(paths)];
27721
27839
  }
27722
- function roomSandboxCommand(policy, cwd, argv) {
27840
+ function roomSandboxCommand(policy, cwd, argv, sandboxMarker) {
27723
27841
  if (!policy.bwrapPath)
27724
27842
  throw new Error(ROOM_SANDBOX_UNAVAILABLE);
27725
27843
  return wrapAgentCommand({
@@ -27734,8 +27852,8 @@ function roomSandboxCommand(policy, cwd, argv) {
27734
27852
  ...policy.scratch ? { tmpDir: policy.scratch } : {},
27735
27853
  ...policy.maskPaths ? { maskPaths: policy.maskPaths } : {}
27736
27854
  },
27737
- command: argv[0],
27738
- args: argv.slice(1)
27855
+ command: "/bin/sh",
27856
+ args: ["-c", SANDBOX_STARTED_SCRIPT, "beeline-grant-sandbox", sandboxMarker, ...argv]
27739
27857
  });
27740
27858
  }
27741
27859
  var GrantRunnerServer = class {
@@ -28050,13 +28168,13 @@ async function runServerCommandIntake(options) {
28050
28168
  }
28051
28169
  }
28052
28170
  options.onPoll?.();
28053
- const reconcile = await new Promise((resolve35) => {
28171
+ const reconcile = await new Promise((resolve36) => {
28054
28172
  const done = (needed) => {
28055
28173
  if (timer)
28056
28174
  clearTimeout(timer);
28057
28175
  signal?.removeEventListener("abort", aborted);
28058
28176
  wake = void 0;
28059
- resolve35(needed);
28177
+ resolve36(needed);
28060
28178
  };
28061
28179
  const aborted = () => done(false);
28062
28180
  wake = done;
@@ -28081,12 +28199,12 @@ async function runServerCommandIntake(options) {
28081
28199
  }
28082
28200
 
28083
28201
  // apps/body/dist/monolith-corner-turn.js
28084
- import { execFile as execFile5 } from "node:child_process";
28202
+ import { execFile as execFile6 } from "node:child_process";
28085
28203
  import { createHash as createHash6 } from "node:crypto";
28086
- import { mkdir as mkdir13 } from "node:fs/promises";
28204
+ import { mkdir as mkdir14 } from "node:fs/promises";
28087
28205
  import { homedir as homedir10 } from "node:os";
28088
28206
  import { join as join12 } from "node:path";
28089
- import { promisify as promisify3 } from "node:util";
28207
+ import { promisify as promisify4 } from "node:util";
28090
28208
 
28091
28209
  // apps/body/dist/agent-home.js
28092
28210
  var import_yaml2 = __toESM(require_dist(), 1);
@@ -28896,6 +29014,9 @@ import { basename as basename5, dirname as dirname10, join as join8, relative as
28896
29014
  import { readFileSync as readFileSync6 } from "node:fs";
28897
29015
  import { resolve as resolve13 } from "node:path";
28898
29016
 
29017
+ // packages/api-contract/dist/phone-types.js
29018
+ var MESSAGE_REACTION_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F389}", "\u{1F440}", "\u2705"];
29019
+
28899
29020
  // packages/api-contract/dist/workbench.js
28900
29021
  var CONNECTABLE_CONNECTOR_KINDS = [
28901
29022
  "trusty-squire",
@@ -28942,11 +29063,13 @@ var BEELINE_ROOM_CAPABILITIES = [
28942
29063
  "You may address any Room member, including another agent, by writing @name in your reply; the server routes that mention to them. Each turn prompt lists the Room members and the exact spelling that tags each one - use those spellings, and never guess or reuse one from an older message.",
28943
29064
  "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.",
28944
29065
  "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.",
29066
+ "If shell access is blocked, continue with read-only inspection instead of retrying it: 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.",
28945
29067
  "Every MCP server mounted into this session is approved tool by tool - use operator and host tools freely; the read-only filesystem sandbox is the boundary, not a tool list. Network web search is enabled.",
28946
29068
  BEELINE_AMBIENT_CONNECTOR_CAPABILITY,
28947
29069
  "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.",
28948
29070
  "To create a file (this Room has no other way to write one), 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.",
28949
29071
  "To run something later or repeatedly, call beeline-agent create_schedule (interval in minutes or a 5-field cron, optional maxRuns); list_schedules / delete_schedule manage them.",
29072
+ `To react to a message in this Room, call beeline-agent react_to_message with its message id and one supported emoji (${MESSAGE_REACTION_EMOJIS.join(" ")}).`,
28950
29073
  `To react to things that HAPPEN in this Room rather than only to what is said to you, call beeline-agent subscribe_events with the kinds you want (${SERVER_EVENT_KINDS.join(", ")}); each one then wakes you for a turn. Subscriptions are per Room and cover every way the event lands: joining a Room you subscribed to wakes you, and so does a person arriving in the Workspace when that arrival projects into this Room - subscribe to joined in an onboarding Room and every newcomer wakes you, exactly like a greeter. It replaces your list, so send every kind you want - list_event_subscriptions shows the current one. You do this yourself: nobody has to configure it for you. grant-decided carries the grant id and status and resumes the turn that asked for the grant. choice-answered, choice-skipped, and poll-closed start a new input turn for the asking agent; they do not resume a paused grant.`,
28951
29074
  "If you need reach outside the sandbox, call beeline-agent request_grant. If you already know discrete options, call beeline-agent ask_choice (one human, optional) or open_poll (every human in this Room, required deadline). A poll is refused below two electors and above fifty, and in a DM. A plurality is a fact, never permission to deploy, delete, merge, or spend. Open-ended asks stay tagged prose. Never put Always / Once / No on a preference.",
28952
29075
  "To state something that happened so the Room and other agents can act on it, call beeline-agent emit_event with your own agent:<slug> kind, one sentence, and optionally the agent members to wake. Chains of events are bounded and a refused emit posts nothing.",
@@ -28966,6 +29089,7 @@ var BEELINE_DM_CAPABILITIES = [
28966
29089
  "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.",
28967
29090
  "To create a file (this Room has no other way to write one), 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.",
28968
29091
  "Tag the person only when you need a decision or input, or when the task they asked for is finished.",
29092
+ `To react to a message in this Room, call beeline-agent react_to_message with its message id and one supported emoji (${MESSAGE_REACTION_EMOJIS.join(" ")}).`,
28969
29093
  "If you already know discrete options, call beeline-agent ask_choice. A pick is a preference, never sandbox, spend, or merge authority. open_poll is refused here: one human is a question.",
28970
29094
  "Never claim an action or reply happened unless the prompt or a tool result proves it."
28971
29095
  ].join(" ");
@@ -30807,12 +30931,22 @@ function harnessStateDirsFromEnv(env) {
30807
30931
  return { stateDirs, ...tmp ? { tmpDir: resolve16(tmp) } : {} };
30808
30932
  }
30809
30933
 
30934
+ // apps/body/dist/agent-command-catalog.js
30935
+ function agentCommandCatalogPublisher(input) {
30936
+ return (commands) => {
30937
+ void input.api.execute("postAgentCommands", {
30938
+ agentId: input.agentId,
30939
+ workspaceId: input.workspaceId,
30940
+ commands
30941
+ }).catch(input.report ?? ((error) => console.error("[body] agent command catalog publish failed:", error)));
30942
+ };
30943
+ }
30944
+
30810
30945
  // apps/body/dist/attachment-delivery.js
30811
30946
  import { mkdir as mkdir7, writeFile as writeFile8 } from "node:fs/promises";
30812
30947
  import { basename as basename6, extname, join as join9 } from "node:path";
30813
30948
  var MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
30814
- var MEDIA_TTL_HOURS = 24;
30815
- var EXPIRED_REASON = `expired: attachments are kept for ${MEDIA_TTL_HOURS} hours and these bytes are past that window`;
30949
+ var EXPIRED_REASON = "expired: these attachment bytes are past their retention window";
30816
30950
  var FETCH_TIMEOUT_MS = 3e4;
30817
30951
  var BoundedSizeError = class extends Error {
30818
30952
  bytes;
@@ -31449,14 +31583,18 @@ export default async function (pi) {
31449
31583
  label: labelOf(tool.name),
31450
31584
  description: typeof tool.description === 'string' ? tool.description : tool.name,
31451
31585
  parameters: schema,
31452
- async execute(_toolCallId, params, signal) {
31586
+ async execute(toolCallId, params, signal) {
31453
31587
  const result = await callServer(
31454
31588
  server,
31455
31589
  {
31456
31590
  jsonrpc: '2.0',
31457
31591
  id: 1,
31458
31592
  method: 'tools/call',
31459
- params: { name: tool.name, arguments: params ?? {} },
31593
+ params: {
31594
+ name: tool.name,
31595
+ arguments: params ?? {},
31596
+ _meta: { beelineToolCallId: toolCallId },
31597
+ },
31460
31598
  },
31461
31599
  signal,
31462
31600
  );
@@ -31951,6 +32089,7 @@ function beelineAgentMcpServer(config, api, context) {
31951
32089
  { name: "BEELINE_DAEMON_ROOM_ID", value: context.roomId },
31952
32090
  { name: "BEELINE_DAEMON_WORKSPACE_ID", value: context.workspaceId },
31953
32091
  ...context.cornerId ? [{ name: "BEELINE_DAEMON_CORNER_ID", value: context.cornerId }] : [],
32092
+ ...context.agentMayCloseCorner ? [{ name: "BEELINE_CORNER_AGENT_CLOSE", value: "1" }] : [],
31954
32093
  ...context.reviewer ? [{ name: "BEELINE_CORNER_REVIEWER", value: "1" }] : [],
31955
32094
  ...context.attachRoot ? [{ name: "BEELINE_ATTACH_ROOT", value: context.attachRoot }] : [],
31956
32095
  ...context.attachScratchRoot ? [{ name: "BEELINE_ATTACH_SCRATCH_ROOT", value: context.attachScratchRoot }] : [],
@@ -31996,9 +32135,91 @@ function youtubeMcpServer(config, accessToken) {
31996
32135
  };
31997
32136
  }
31998
32137
 
32138
+ // apps/body/dist/codegraph.js
32139
+ import { execFile as execFile5 } from "node:child_process";
32140
+ import { access, appendFile, mkdir as mkdir9, readFile as readFile7 } from "node:fs/promises";
32141
+ import { dirname as dirname11, isAbsolute as isAbsolute3, resolve as resolve19 } from "node:path";
32142
+ import { promisify as promisify3 } from "node:util";
32143
+ var execFileAsync3 = promisify3(execFile5);
32144
+ var CODEGRAPH_MCP_SERVER_NAME = "codegraph";
32145
+ var CODEGRAPH_INDEX_PATH = ".codegraph/codegraph.db";
32146
+ var CODEGRAPH_PREPARE_TIMEOUT_MS = 12e4;
32147
+ function codegraphIndexDirectory(cwd) {
32148
+ return resolve19(cwd, dirname11(CODEGRAPH_INDEX_PATH));
32149
+ }
32150
+ function codegraphMcpServer(config, cwd, options) {
32151
+ if (!config.codegraphCommand)
32152
+ return void 0;
32153
+ return {
32154
+ name: CODEGRAPH_MCP_SERVER_NAME,
32155
+ command: config.codegraphCommand,
32156
+ args: ["serve", "--mcp", "--path", resolve19(cwd), ...options.readonly ? ["--no-watch"] : []],
32157
+ env: [
32158
+ { name: "CODEGRAPH_TELEMETRY", value: "0" },
32159
+ // One MCP child per harness session is easier to contain than a daemon
32160
+ // that can outlive the Room/corner whose index and permissions it used.
32161
+ { name: "CODEGRAPH_NO_DAEMON", value: "1" }
32162
+ ]
32163
+ };
32164
+ }
32165
+ function codegraphFingerprintServers(config, servers, mounted = Boolean(config.codegraphCommand)) {
32166
+ return config.codegraphCommand && mounted ? [...servers, CODEGRAPH_MCP_SERVER_NAME] : [...servers];
32167
+ }
32168
+ async function isGitWorktree(cwd) {
32169
+ try {
32170
+ const { stdout: stdout6 } = await execFileAsync3("git", ["rev-parse", "--show-toplevel"], {
32171
+ cwd,
32172
+ timeout: 1e4
32173
+ });
32174
+ return resolve19(stdout6.trim()) === resolve19(cwd);
32175
+ } catch {
32176
+ return false;
32177
+ }
32178
+ }
32179
+ async function excludeIndexFromGitStatus(cwd) {
32180
+ try {
32181
+ const { stdout: stdout6 } = await execFileAsync3("git", ["rev-parse", "--git-path", "info/exclude"], {
32182
+ cwd,
32183
+ timeout: 1e4
32184
+ });
32185
+ const gitPath = stdout6.trim();
32186
+ if (!gitPath)
32187
+ return;
32188
+ const path = isAbsolute3(gitPath) ? gitPath : resolve19(cwd, gitPath);
32189
+ await mkdir9(dirname11(path), { recursive: true });
32190
+ const existing = await readFile7(path, "utf8").catch(() => "");
32191
+ if (existing.split("\n").includes(".codegraph/"))
32192
+ return;
32193
+ await appendFile(path, `${existing && !existing.endsWith("\n") ? "\n" : ""}.codegraph/
32194
+ `);
32195
+ } catch {
32196
+ }
32197
+ }
32198
+ async function prepareCodegraphIndex(config, cwd) {
32199
+ const command = config.codegraphCommand;
32200
+ if (!command || !await isGitWorktree(cwd))
32201
+ return false;
32202
+ await excludeIndexFromGitStatus(cwd);
32203
+ const indexPath = resolve19(cwd, CODEGRAPH_INDEX_PATH);
32204
+ const indexed = await access(indexPath).then(() => true, () => false);
32205
+ const args = indexed ? ["sync", "--quiet", cwd] : ["init", "--yes", cwd];
32206
+ try {
32207
+ await execFileAsync3(command, args, {
32208
+ cwd,
32209
+ env: { ...process.env, CODEGRAPH_TELEMETRY: "0", CODEGRAPH_NO_DAEMON: "1" },
32210
+ timeout: CODEGRAPH_PREPARE_TIMEOUT_MS,
32211
+ maxBuffer: 4 * 1024 * 1024
32212
+ });
32213
+ return true;
32214
+ } catch (error) {
32215
+ console.warn(`[body] CodeGraph ${args[0]} failed for ${cwd}; continuing with Beeline read tools:`, error);
32216
+ return false;
32217
+ }
32218
+ }
32219
+
31999
32220
  // apps/body/dist/pi-turn-record.js
32000
- import { readdir as readdir3, readFile as readFile7 } from "node:fs/promises";
32001
- import { resolve as resolve19 } from "node:path";
32221
+ import { readdir as readdir3, readFile as readFile8 } from "node:fs/promises";
32222
+ import { resolve as resolve20 } from "node:path";
32002
32223
  function summarizeProviderError(errorMessage2) {
32003
32224
  const trimmed = errorMessage2.trim();
32004
32225
  const statusMatch = /^(\d{3}):\s*([\s\S]*)$/.exec(trimmed);
@@ -32019,7 +32240,7 @@ function summarizeProviderError(errorMessage2) {
32019
32240
  }
32020
32241
  async function sessionFileFromMap(home, sessionId) {
32021
32242
  try {
32022
- const raw = await readFile7(resolve19(home, ".pi", "pi-acp", "session-map.json"), "utf8");
32243
+ const raw = await readFile8(resolve20(home, ".pi", "pi-acp", "session-map.json"), "utf8");
32023
32244
  const map = JSON.parse(raw);
32024
32245
  const file = map.sessions?.[sessionId]?.sessionFile;
32025
32246
  return typeof file === "string" && file ? file : void 0;
@@ -32028,7 +32249,7 @@ async function sessionFileFromMap(home, sessionId) {
32028
32249
  }
32029
32250
  }
32030
32251
  async function sessionFileFromLayout(piDir, sessionId) {
32031
- const sessionsRoot = resolve19(piDir, "sessions");
32252
+ const sessionsRoot = resolve20(piDir, "sessions");
32032
32253
  const suffix = `_${sessionId}.jsonl`;
32033
32254
  let projects;
32034
32255
  try {
@@ -32037,7 +32258,7 @@ async function sessionFileFromLayout(piDir, sessionId) {
32037
32258
  return void 0;
32038
32259
  }
32039
32260
  for (const project of projects) {
32040
- const dir = resolve19(sessionsRoot, project);
32261
+ const dir = resolve20(sessionsRoot, project);
32041
32262
  let files;
32042
32263
  try {
32043
32264
  files = await readdir3(dir);
@@ -32046,7 +32267,7 @@ async function sessionFileFromLayout(piDir, sessionId) {
32046
32267
  }
32047
32268
  const match = files.find((file) => file.endsWith(suffix));
32048
32269
  if (match)
32049
- return resolve19(dir, match);
32270
+ return resolve20(dir, match);
32050
32271
  }
32051
32272
  return void 0;
32052
32273
  }
@@ -32064,7 +32285,7 @@ async function readPiTurnRecord(input) {
32064
32285
  return void 0;
32065
32286
  let raw;
32066
32287
  try {
32067
- raw = await readFile7(file, "utf8");
32288
+ raw = await readFile8(file, "utf8");
32068
32289
  } catch {
32069
32290
  return void 0;
32070
32291
  }
@@ -32220,8 +32441,8 @@ async function withTurnReceiptHeartbeat(api, receipt, task, onHeartbeatError) {
32220
32441
  }
32221
32442
 
32222
32443
  // apps/body/dist/turn-trace.js
32223
- import { appendFile, mkdir as mkdir9, readdir as readdir4, rm as rm3 } from "node:fs/promises";
32224
- import { resolve as resolve20 } from "node:path";
32444
+ import { appendFile as appendFile2, mkdir as mkdir10, readdir as readdir4, rm as rm3 } from "node:fs/promises";
32445
+ import { resolve as resolve21 } from "node:path";
32225
32446
  import { performance as performance2 } from "node:perf_hooks";
32226
32447
  var TURN_PHASES = [
32227
32448
  /** Enqueued on `SessionScheduler` until this turn holds a slot. A capacity wait lives here. */
@@ -32451,7 +32672,7 @@ function formatTurnTraceLine(record3) {
32451
32672
  return [head, ...record3.attempts.map((attempt) => formatTurnAttempt(attempt))].join("\n ");
32452
32673
  }
32453
32674
  function turnTraceDirectory(runtimeDir) {
32454
- return resolve20(runtimeDir, "turn-traces");
32675
+ return resolve21(runtimeDir, "turn-traces");
32455
32676
  }
32456
32677
  var TURN_TRACE_RETENTION_DAYS = 7;
32457
32678
  function traceFileName(date) {
@@ -32468,14 +32689,14 @@ var TurnTraceFile = class {
32468
32689
  this.options = options;
32469
32690
  }
32470
32691
  path(now2 = (this.options.clock ?? (() => /* @__PURE__ */ new Date()))()) {
32471
- return resolve20(this.directory, traceFileName(now2));
32692
+ return resolve21(this.directory, traceFileName(now2));
32472
32693
  }
32473
32694
  write(record3) {
32474
32695
  this.tail = this.tail.catch(() => void 0).then(async () => {
32475
32696
  const now2 = (this.options.clock ?? (() => /* @__PURE__ */ new Date()))();
32476
32697
  const path = this.path(now2);
32477
- await mkdir9(this.directory, { recursive: true, mode: 448 });
32478
- await appendFile(path, `${JSON.stringify(record3)}
32698
+ await mkdir10(this.directory, { recursive: true, mode: 448 });
32699
+ await appendFile2(path, `${JSON.stringify(record3)}
32479
32700
  `, { mode: 384 });
32480
32701
  await this.prune(now2);
32481
32702
  const log2 = this.options.log ?? ((line) => console.log(line));
@@ -32492,16 +32713,16 @@ var TurnTraceFile = class {
32492
32713
  this.prunedDay = day;
32493
32714
  const cutoff = traceFileName(new Date(now2.getTime() - TURN_TRACE_RETENTION_DAYS * 864e5));
32494
32715
  const names = await readdir4(this.directory).catch(() => []);
32495
- await Promise.all(names.filter((name) => /^turns-\d{4}-\d{2}-\d{2}\.jsonl$/.test(name) && name < cutoff).map((name) => rm3(resolve20(this.directory, name), { force: true })));
32716
+ await Promise.all(names.filter((name) => /^turns-\d{4}-\d{2}-\d{2}\.jsonl$/.test(name) && name < cutoff).map((name) => rm3(resolve21(this.directory, name), { force: true })));
32496
32717
  }
32497
32718
  };
32498
32719
 
32499
32720
  // apps/body/dist/corner-github-auth.js
32500
- import { chmod as chmod4, mkdir as mkdir10, writeFile as writeFile9 } from "node:fs/promises";
32501
- import { delimiter as delimiter2, resolve as resolve21 } from "node:path";
32721
+ import { chmod as chmod4, mkdir as mkdir11, writeFile as writeFile9 } from "node:fs/promises";
32722
+ import { delimiter as delimiter2, resolve as resolve22 } from "node:path";
32502
32723
  async function installCornerGitHubWrappers(input) {
32503
- const bin = resolve21(input.root, "beeline-github-bin");
32504
- await mkdir10(bin, { recursive: true, mode: 448 });
32724
+ const bin = resolve22(input.root, "beeline-github-bin");
32725
+ await mkdir11(bin, { recursive: true, mode: 448 });
32505
32726
  const common = {
32506
32727
  node: process.execPath,
32507
32728
  cli: input.cliEntrypoint,
@@ -32510,13 +32731,13 @@ async function installCornerGitHubWrappers(input) {
32510
32731
  featureBranch: input.featureBranch,
32511
32732
  targetBranch: input.targetBranch
32512
32733
  };
32513
- await writeLauncher(resolve21(bin, "git"), {
32734
+ await writeLauncher(resolve22(bin, "git"), {
32514
32735
  ...common,
32515
32736
  command: input.gitBinary,
32516
32737
  launcher: "git"
32517
32738
  });
32518
32739
  if (input.ghBinary)
32519
- await writeLauncher(resolve21(bin, "gh"), {
32740
+ await writeLauncher(resolve22(bin, "gh"), {
32520
32741
  ...common,
32521
32742
  command: input.ghBinary,
32522
32743
  launcher: "gh"
@@ -32653,23 +32874,23 @@ process.exit(result.status ?? 1);
32653
32874
  // apps/body/dist/warm-node-modules.js
32654
32875
  import { createHash as createHash5, randomUUID as randomUUID5 } from "node:crypto";
32655
32876
  import { constants as fsConstants } from "node:fs";
32656
- import { chmod as chmod5, copyFile as copyFile2, link, lstat as lstat3, mkdir as mkdir11, readdir as readdir5, readFile as readFile8, readlink, rename as rename4, rm as rm4, stat as stat2, symlink as symlink2, utimes } from "node:fs/promises";
32657
- import { dirname as dirname11, join as join10, resolve as resolve22 } from "node:path";
32877
+ import { chmod as chmod5, copyFile as copyFile2, link, lstat as lstat3, mkdir as mkdir12, readdir as readdir5, readFile as readFile9, readlink, rename as rename4, rm as rm4, stat as stat2, symlink as symlink2, utimes } from "node:fs/promises";
32878
+ import { dirname as dirname12, join as join10, resolve as resolve23 } from "node:path";
32658
32879
  var STORE_FORMAT = "v1";
32659
32880
  var STAGING_PREFIX = ".beeline-warm-";
32660
32881
  var STAGING_SWEEP_MS = 24 * 60 * 60 * 1e3;
32661
32882
  var WARM_STORE_MAX_ENTRIES = 3;
32662
32883
  function sharedNpmCacheDir(supervisorRoot) {
32663
- return resolve22(supervisorRoot, "beeline", "npm-cache");
32884
+ return resolve23(supervisorRoot, "beeline", "npm-cache");
32664
32885
  }
32665
32886
  function warmNodeModulesStoreDir(supervisorRoot) {
32666
- return resolve22(supervisorRoot, "beeline", "node-modules");
32887
+ return resolve23(supervisorRoot, "beeline", "node-modules");
32667
32888
  }
32668
32889
  function isPlanRefusal(value) {
32669
32890
  return "failure" in value;
32670
32891
  }
32671
32892
  async function readWarmPlan(worktreePath) {
32672
- const lockfile = await readFile8(resolve22(worktreePath, "package-lock.json")).catch(() => void 0);
32893
+ const lockfile = await readFile9(resolve23(worktreePath, "package-lock.json")).catch(() => void 0);
32673
32894
  if (!lockfile)
32674
32895
  return { failure: "no-lockfile" };
32675
32896
  const packages = parseLockfilePackages(lockfile);
@@ -32708,11 +32929,11 @@ async function seedWarmNodeModules(input) {
32708
32929
  return { reason: plan.failure, ...plan.detail ? { detail: plan.detail } : {} };
32709
32930
  }
32710
32931
  for (const tree of plan.trees) {
32711
- if (await pathExists(resolve22(input.worktreePath, tree))) {
32932
+ if (await pathExists(resolve23(input.worktreePath, tree))) {
32712
32933
  return { reason: "present", key: plan.key };
32713
32934
  }
32714
32935
  }
32715
- const entry = resolve22(input.storeRoot, plan.key);
32936
+ const entry = resolve23(input.storeRoot, plan.key);
32716
32937
  if (!await isDirectory(entry))
32717
32938
  return { reason: "cold", key: plan.key };
32718
32939
  const [storeDevice, checkoutDevice] = await Promise.all([
@@ -32722,19 +32943,19 @@ async function seedWarmNodeModules(input) {
32722
32943
  if (storeDevice === void 0 || storeDevice !== checkoutDevice) {
32723
32944
  return { reason: "cross-device", key: plan.key };
32724
32945
  }
32725
- const staging = resolve22(input.storeRoot, `${STAGING_PREFIX}${process.pid}.${randomUUID5()}`);
32946
+ const staging = resolve23(input.storeRoot, `${STAGING_PREFIX}${process.pid}.${randomUUID5()}`);
32726
32947
  const placed = [];
32727
32948
  const now2 = (input.now ?? Date.now)();
32728
32949
  try {
32729
32950
  await sweepStaleStaging(input.storeRoot, now2);
32730
32951
  await utimes(entry, now2 / 1e3, now2 / 1e3).catch(() => void 0);
32731
32952
  for (const tree of plan.trees) {
32732
- await cloneTree(resolve22(entry, tree), resolve22(staging, tree), seedFile);
32953
+ await cloneTree(resolve23(entry, tree), resolve23(staging, tree), seedFile);
32733
32954
  }
32734
32955
  for (const tree of plan.trees) {
32735
- const target = resolve22(input.worktreePath, tree);
32736
- await mkdir11(dirname11(target), { recursive: true });
32737
- await rename4(resolve22(staging, tree), target);
32956
+ const target = resolve23(input.worktreePath, tree);
32957
+ await mkdir12(dirname12(target), { recursive: true });
32958
+ await rename4(resolve23(staging, tree), target);
32738
32959
  placed.push(target);
32739
32960
  }
32740
32961
  return { reason: "seeded", key: plan.key };
@@ -32752,11 +32973,11 @@ async function harvestWarmNodeModules(input) {
32752
32973
  if (isPlanRefusal(plan)) {
32753
32974
  return { reason: plan.failure, ...plan.detail ? { detail: plan.detail } : {} };
32754
32975
  }
32755
- const entry = resolve22(input.storeRoot, plan.key);
32976
+ const entry = resolve23(input.storeRoot, plan.key);
32756
32977
  if (await pathExists(entry))
32757
32978
  return { reason: "already-warm", key: plan.key };
32758
32979
  for (const tree of plan.trees) {
32759
- if (!await isDirectory(resolve22(input.worktreePath, tree))) {
32980
+ if (!await isDirectory(resolve23(input.worktreePath, tree))) {
32760
32981
  return { reason: "no-node-modules", key: plan.key, detail: tree };
32761
32982
  }
32762
32983
  }
@@ -32768,12 +32989,12 @@ async function harvestWarmNodeModules(input) {
32768
32989
  detail: `${missing.length} absent, e.g. ${missing.slice(0, 3).join(", ")}`
32769
32990
  };
32770
32991
  }
32771
- const staging = resolve22(input.storeRoot, `${STAGING_PREFIX}${process.pid}.${randomUUID5()}`);
32992
+ const staging = resolve23(input.storeRoot, `${STAGING_PREFIX}${process.pid}.${randomUUID5()}`);
32772
32993
  try {
32773
- await mkdir11(input.storeRoot, { recursive: true, mode: 493 });
32994
+ await mkdir12(input.storeRoot, { recursive: true, mode: 493 });
32774
32995
  await sweepStaleStaging(input.storeRoot, (input.now ?? Date.now)());
32775
32996
  for (const tree of plan.trees) {
32776
- await cloneTree(resolve22(input.worktreePath, tree), resolve22(staging, tree), harvestFile);
32997
+ await cloneTree(resolve23(input.worktreePath, tree), resolve23(staging, tree), harvestFile);
32777
32998
  }
32778
32999
  await input.onCopied?.();
32779
33000
  if (!await stagedTreeIsPublishable(input.worktreePath, staging, plan.key)) {
@@ -32796,16 +33017,16 @@ async function stagedTreeIsPublishable(worktreePath, staging, key) {
32796
33017
  return false;
32797
33018
  const hidden = join10("node_modules", ".package-lock.json");
32798
33019
  const [copied, current] = await Promise.all([
32799
- readFile8(resolve22(staging, hidden)).catch(() => void 0),
32800
- readFile8(resolve22(worktreePath, hidden)).catch(() => void 0)
33020
+ readFile9(resolve23(staging, hidden)).catch(() => void 0),
33021
+ readFile9(resolve23(worktreePath, hidden)).catch(() => void 0)
32801
33022
  ]);
32802
33023
  if (!copied || !current || !copied.equals(current))
32803
33024
  return false;
32804
33025
  return (await missingInstalledPackages(worktreePath, staging)).length === 0;
32805
33026
  }
32806
33027
  async function missingInstalledPackages(worktreePath, treeRoot = worktreePath) {
32807
- const wanted = parseLockfilePackages(await readFile8(resolve22(worktreePath, "package-lock.json")).catch(() => void 0));
32808
- const installed = parseLockfilePackages(await readFile8(resolve22(treeRoot, "node_modules", ".package-lock.json")).catch(() => void 0));
33028
+ const wanted = parseLockfilePackages(await readFile9(resolve23(worktreePath, "package-lock.json")).catch(() => void 0));
33029
+ const installed = parseLockfilePackages(await readFile9(resolve23(treeRoot, "node_modules", ".package-lock.json")).catch(() => void 0));
32809
33030
  if (!wanted)
32810
33031
  return ["package-lock.json is unreadable"];
32811
33032
  if (!installed)
@@ -32825,7 +33046,7 @@ async function missingInstalledPackages(worktreePath, treeRoot = worktreePath) {
32825
33046
  required.push(path);
32826
33047
  }
32827
33048
  await mapWithLimit(required, INTEGRITY_READ_CONCURRENCY, async (path) => {
32828
- if (!(path in installed) || !await isInstalledPackage(resolve22(treeRoot, path))) {
33049
+ if (!(path in installed) || !await isInstalledPackage(resolve23(treeRoot, path))) {
32829
33050
  missing.push(path);
32830
33051
  }
32831
33052
  });
@@ -32864,7 +33085,7 @@ function isContainedTreePath(value) {
32864
33085
  return isContainedPath(value) && value.split("/").pop() === "node_modules";
32865
33086
  }
32866
33087
  async function cloneTree(source, target, file, topLevel = true) {
32867
- await mkdir11(target, { recursive: true, mode: 493 });
33088
+ await mkdir12(target, { recursive: true, mode: 493 });
32868
33089
  for (const entry of await readdir5(source, { withFileTypes: true })) {
32869
33090
  const from = join10(source, entry.name);
32870
33091
  const to = join10(target, entry.name);
@@ -32933,7 +33154,7 @@ function describe3(error) {
32933
33154
  }
32934
33155
 
32935
33156
  // apps/body/dist/monolith-room-turn.js
32936
- import { mkdir as mkdir12 } from "node:fs/promises";
33157
+ import { mkdir as mkdir13 } from "node:fs/promises";
32937
33158
  import { homedir as homedir9 } from "node:os";
32938
33159
  import { join as join11 } from "node:path";
32939
33160
 
@@ -33020,6 +33241,8 @@ var MonolithRoomTurnLoop = class {
33020
33241
  sessionId;
33021
33242
  /** The configuration the live session baked in; a change invalidates it. */
33022
33243
  sessionFingerprint;
33244
+ /** Whether CodeGraph preparation succeeded for the live session. */
33245
+ sessionCodegraphReady = false;
33023
33246
  /** The live session's environment, read back for pi's own turn record. */
33024
33247
  agentEnv = {};
33025
33248
  /** OpenRouter providers this activation pinned, in order (C92). */
@@ -33168,6 +33391,7 @@ var MonolithRoomTurnLoop = class {
33168
33391
  this.client = void 0;
33169
33392
  this.sessionId = void 0;
33170
33393
  this.sessionFingerprint = void 0;
33394
+ this.sessionCodegraphReady = false;
33171
33395
  this.pinnedProviderOverride = void 0;
33172
33396
  if (client?.isAlive)
33173
33397
  await client.stop();
@@ -33199,11 +33423,11 @@ var MonolithRoomTurnLoop = class {
33199
33423
  effort: configuration.effort ?? this.options.config.modelSelection?.effort,
33200
33424
  soul: configuration.soul ?? self?.soul,
33201
33425
  agentName: self?.name ?? this.agent.name,
33202
- mcpServers: expectedMountedImportedMcpServerNames({
33426
+ mcpServers: codegraphFingerprintServers(this.options.config, expectedMountedImportedMcpServerNames({
33203
33427
  operatorHome: this.options.config.operatorHome,
33204
33428
  agentKind: this.options.config.agentKind,
33205
33429
  grantedHostRoutes
33206
- })
33430
+ }), this.sessionCodegraphReady)
33207
33431
  });
33208
33432
  }
33209
33433
  async grantedHostRoutes() {
@@ -33237,7 +33461,7 @@ var MonolithRoomTurnLoop = class {
33237
33461
  ]);
33238
33462
  const self = roster.members.find((member) => member.identityId === this.agent.publicKey);
33239
33463
  const directMessage = Array.isArray(repositoryState.directParticipants) && repositoryState.directParticipants.length === 2;
33240
- await mkdir12(this.options.cwd, { recursive: true });
33464
+ await mkdir13(this.options.cwd, { recursive: true });
33241
33465
  const selectionModel = configuration.model ?? this.options.config.modelSelection?.model;
33242
33466
  const selectionEffort = configuration.effort ?? this.options.config.modelSelection?.effort;
33243
33467
  const selection = selectionModel || selectionEffort ? { model: selectionModel, effort: selectionEffort } : void 0;
@@ -33264,17 +33488,6 @@ var MonolithRoomTurnLoop = class {
33264
33488
  command
33265
33489
  });
33266
33490
  const agentEnv = { ...this.options.config.agentEnv, ...homeOverlay };
33267
- const fingerprint = sessionConfigFingerprint({
33268
- model: configuration.model ?? this.options.config.modelSelection?.model,
33269
- effort: configuration.effort ?? this.options.config.modelSelection?.effort,
33270
- soul: configuration.soul ?? self?.soul,
33271
- agentName: self?.name ?? this.agent.name,
33272
- mcpServers: expectedMountedImportedMcpServerNames({
33273
- operatorHome: this.options.config.operatorHome,
33274
- agentKind: this.options.config.agentKind,
33275
- grantedHostRoutes
33276
- })
33277
- });
33278
33491
  this.agentEnv = agentEnv;
33279
33492
  const agentArgs = agentArgsWithModelSelection({
33280
33493
  kind: this.options.config.agentKind,
@@ -33286,10 +33499,22 @@ var MonolithRoomTurnLoop = class {
33286
33499
  this.sessionScratchDir = tmpDir;
33287
33500
  this.sessionStateDirs = stateDirs;
33288
33501
  const homeStateDirs = harnessHomeStateDirs(harnessLabel, agentEnv.HOME ?? operatorHome);
33289
- await Promise.all(homeStateDirs.map((dir) => mkdir12(dir, { recursive: true })));
33502
+ await Promise.all(homeStateDirs.map((dir) => mkdir13(dir, { recursive: true })));
33290
33503
  const attachScratchRoot = this.options.config.agentHomeRoot ?? tmpDir;
33291
33504
  if (attachScratchRoot)
33292
- await mkdir12(attachScratchRoot, { recursive: true });
33505
+ await mkdir13(attachScratchRoot, { recursive: true });
33506
+ const codegraphReady = await prepareCodegraphIndex(this.options.config, this.options.cwd);
33507
+ const fingerprint = sessionConfigFingerprint({
33508
+ model: configuration.model ?? this.options.config.modelSelection?.model,
33509
+ effort: configuration.effort ?? this.options.config.modelSelection?.effort,
33510
+ soul: configuration.soul ?? self?.soul,
33511
+ agentName: self?.name ?? this.agent.name,
33512
+ mcpServers: codegraphFingerprintServers(this.options.config, expectedMountedImportedMcpServerNames({
33513
+ operatorHome: this.options.config.operatorHome,
33514
+ agentKind: this.options.config.agentKind,
33515
+ grantedHostRoutes
33516
+ }), codegraphReady)
33517
+ });
33293
33518
  const spawnCommand = wrapAgentCommand({
33294
33519
  bwrapPath: this.options.config.bwrapPath,
33295
33520
  spec: {
@@ -33300,6 +33525,7 @@ var MonolithRoomTurnLoop = class {
33300
33525
  ...tmpDir ? { tmpDir } : {},
33301
33526
  additionalWritablePaths: [
33302
33527
  ...attachScratchRoot ? [attachScratchRoot] : [],
33528
+ ...codegraphReady ? [codegraphIndexDirectory(this.options.cwd)] : [],
33303
33529
  ...grantedSquireHostBindPaths({
33304
33530
  operatorHome,
33305
33531
  agentKind: this.options.config.agentKind,
@@ -33324,9 +33550,16 @@ var MonolithRoomTurnLoop = class {
33324
33550
  attachScratchRoot,
33325
33551
  turnContextPath: this.commandContext.path,
33326
33552
  directMessage,
33327
- ...this.options.grantRunnerEndpoint ? { grantRunner: this.options.grantRunnerEndpoint } : {}
33553
+ ...this.options.grantRunnerEndpoint && this.options.config.bwrapPath ? { grantRunner: this.options.grantRunnerEndpoint } : {}
33328
33554
  })
33329
33555
  ];
33556
+ if (codegraphReady) {
33557
+ const codegraph = codegraphMcpServer(this.options.config, this.options.cwd, {
33558
+ readonly: true
33559
+ });
33560
+ if (codegraph)
33561
+ servers.push(codegraph);
33562
+ }
33330
33563
  const youtube = youtubeMcpServer(this.options.config, this.options.youtubeAccessToken);
33331
33564
  if (youtube)
33332
33565
  servers.push(youtube);
@@ -33358,7 +33591,12 @@ var MonolithRoomTurnLoop = class {
33358
33591
  // (`config.ts`), which is exactly when `wrapAgentCommand` above wraps.
33359
33592
  osSandbox: Boolean(this.options.config.bwrapPath),
33360
33593
  autoApprovePermissions: false,
33361
- permissionAllowlist: (request) => isRoomMcpPermissionRequest(request, mountedServers, hostServers)
33594
+ permissionAllowlist: (request) => isRoomMcpPermissionRequest(request, mountedServers, hostServers),
33595
+ onCommands: agentCommandCatalogPublisher({
33596
+ api: this.options.api,
33597
+ agentId: this.agent.publicKey,
33598
+ workspaceId: this.options.workspaceId
33599
+ })
33362
33600
  };
33363
33601
  this.client = (this.options.createAcpClient ?? ((value) => new AcpClient(value)))(clientOptions);
33364
33602
  await this.client.start();
@@ -33390,6 +33628,7 @@ var MonolithRoomTurnLoop = class {
33390
33628
  });
33391
33629
  this.sessionId = opened.sessionId;
33392
33630
  this.sessionFingerprint = fingerprint;
33631
+ this.sessionCodegraphReady = codegraphReady;
33393
33632
  if (selection) {
33394
33633
  const options = filterAllowedModelConfigOptions(parseAdvertisedConfigOptions(opened.raw, selection.model));
33395
33634
  await applyAgentModelSelection(this.client, opened.sessionId, options, selection);
@@ -33453,6 +33692,7 @@ var MonolithRoomTurnLoop = class {
33453
33692
  this.client = void 0;
33454
33693
  this.sessionId = void 0;
33455
33694
  this.sessionFingerprint = void 0;
33695
+ this.sessionCodegraphReady = false;
33456
33696
  if (client?.isAlive)
33457
33697
  await client.stop();
33458
33698
  this.pinnedProviderOverride = next;
@@ -33545,7 +33785,7 @@ var MonolithRoomTurnLoop = class {
33545
33785
  const names = new Map(roster.members.map((member) => [member.identityId, member.name]));
33546
33786
  const transcriptRows = conversation.items.filter((message) => message.type === "message" && message.id !== item.id && !active.steers.some((steerItem) => steerItem.id === message.id)).slice(-80).map((message) => ({
33547
33787
  id: message.id,
33548
- line: roomMessagePrompt(names.get(message.authorId) ?? message.authorId.slice(0, 12), message.body, message.attachments, this.deliveredAttachments.get(message.id), this.acceptsImages())
33788
+ line: roomMessagePrompt(names.get(message.authorId) ?? message.authorId.slice(0, 12), message.body, message.attachments, this.deliveredAttachments.get(message.id), this.acceptsImages(), message.id)
33549
33789
  }));
33550
33790
  const grantDecision = this.commandContext.current?.action === "resume";
33551
33791
  const resumedRequestId = grantDecision ? this.pausedOnGrantRequestId : void 0;
@@ -33565,7 +33805,7 @@ ${JSON.stringify((corners.corners ?? []).filter((corner) => !corner.archived))}`
33565
33805
  MAINTAIN_ASSIGNED_IDENTITY_DIRECTIVE
33566
33806
  ].join(" "),
33567
33807
  `Current task selected by the server from ${inboxItemAuthorName(item, names)}:`,
33568
- roomMessagePrompt("", inboxItemPromptBody(item), item.attachments, delivered, this.acceptsImages())
33808
+ roomMessagePrompt("", inboxItemPromptBody(item), item.attachments, delivered, this.acceptsImages(), item.type === "message" ? item.id : void 0)
33569
33809
  ].filter(Boolean).join("\n\n");
33570
33810
  const stream = new AgentTurnStream({
33571
33811
  api,
@@ -33608,7 +33848,7 @@ ${JSON.stringify((corners.corners ?? []).filter((corner) => !corner.archived))}`
33608
33848
  "The previous run was cancelled because its harness could not accept every live steer.",
33609
33849
  "Resume the same turn. Keep the original request and everything that happened before it was cancelled.",
33610
33850
  "Human messages that arrived after the original request, in transcript order:",
33611
- ...active.steers.map((steerItem) => roomMessagePrompt(steerItem.authorId.slice(0, 12), steerItem.body, steerItem.attachments, this.deliveredAttachments.get(steerItem.id), this.acceptsImages())),
33851
+ ...active.steers.map((steerItem) => roomMessagePrompt(steerItem.authorId.slice(0, 12), steerItem.body, steerItem.attachments, this.deliveredAttachments.get(steerItem.id), this.acceptsImages(), steerItem.type === "message" ? steerItem.id : void 0)),
33612
33852
  "Continue now and answer the updated request without erasing the earlier context."
33613
33853
  ].join("\n\n");
33614
33854
  }
@@ -33766,14 +34006,18 @@ function openCornerToolCall(calls) {
33766
34006
  function openedACorner(call) {
33767
34007
  return !!call && isCompletedToolCall(call);
33768
34008
  }
33769
- function roomMessagePrompt(author, body, attachments, delivered, harnessAcceptsImages = true) {
34009
+ function roomMessagePrompt(author, body, attachments, delivered, harnessAcceptsImages = true, messageId) {
33770
34010
  const message = body.trim() || "(shared attachments)";
33771
34011
  const rendered = author ? `${author}: ${message}` : message;
33772
- return [rendered, ...attachmentPromptLines(attachments, delivered, harnessAcceptsImages)].join("\n");
34012
+ return [
34013
+ ...messageId ? [`[message id: ${messageId}]`] : [],
34014
+ rendered,
34015
+ ...attachmentPromptLines(attachments, delivered, harnessAcceptsImages)
34016
+ ].join("\n");
33773
34017
  }
33774
34018
 
33775
34019
  // apps/body/dist/monolith-corner-turn.js
33776
- var execFileAsync3 = promisify3(execFile5);
34020
+ var execFileAsync4 = promisify4(execFile6);
33777
34021
  var TOOL_ARGUMENT_MAX_BYTES = 1200;
33778
34022
  var TOOL_OUTPUT_MAX_BYTES = 3200;
33779
34023
  var TOOL_PATH_LIMIT = 12;
@@ -33790,6 +34034,8 @@ function cornerReviewerInstruction(input) {
33790
34034
  const headSha = input.headSha ?? "<head sha>";
33791
34035
  return `Checks are green on PR #${number} at ${headSha}. Review it now with the beeline-review skill against that exact head. FAIL: reply \`@${author}\` with the confirmed findings to fix. PASS: call the approve_merge tool for ${headSha}, then reply \`@${author} approved ${headSha}, merge\`. Never merge yourself. Never say you are holding or waiting for checks.`;
33792
34036
  }
34037
+ var CORNER_REVIEWER_SESSION_INSTRUCTION = "You are this Room's configured reviewer. The active turn prompt names the latest stable green PR head. Review and approve only that exact head; if no stable green head is named, end the turn without a verdict. Never merge yourself.";
34038
+ var CORNER_REVIEWER_UNSTABLE_HEAD_INSTRUCTION = "There is no stable green PR head for this active reviewer turn. Do not review or call approve_merge. End this turn without a verdict; the next green transition will wake you.";
33793
34039
  function cornerSelfReviewerInstruction(input) {
33794
34040
  if (!input.reviewerHandle || !input.agentHandle || !input.openedByAgent || input.agentHandle.replace(/^@/, "") !== input.reviewerHandle.replace(/^@/, ""))
33795
34041
  return void 0;
@@ -33816,8 +34062,8 @@ function isCornerChecksTurn(trigger, restates) {
33816
34062
  async function cornerUndeliveredRepositoryState(worktreePath, featureBranch, targetBranch) {
33817
34063
  try {
33818
34064
  const [{ stdout: status }, ahead] = await Promise.all([
33819
- execFileAsync3("git", ["-C", worktreePath, "status", "--porcelain=v1"]),
33820
- featureBranch ? firstResolvedRemoteRef(worktreePath, [featureBranch, targetBranch]).then((remoteRef) => remoteRef ? execFileAsync3("git", [
34065
+ execFileAsync4("git", ["-C", worktreePath, "status", "--porcelain=v1"]),
34066
+ featureBranch ? firstResolvedRemoteRef(worktreePath, [featureBranch, targetBranch]).then((remoteRef) => remoteRef ? execFileAsync4("git", [
33821
34067
  "-C",
33822
34068
  worktreePath,
33823
34069
  "rev-list",
@@ -33837,7 +34083,7 @@ async function firstResolvedRemoteRef(worktreePath, branches) {
33837
34083
  if (!branch)
33838
34084
  continue;
33839
34085
  const ref = `refs/remotes/origin/${branch}`;
33840
- const resolved = await execFileAsync3("git", ["-C", worktreePath, "rev-parse", "--verify", ref]).then(() => true).catch(() => false);
34086
+ const resolved = await execFileAsync4("git", ["-C", worktreePath, "rev-parse", "--verify", ref]).then(() => true).catch(() => false);
33841
34087
  if (resolved)
33842
34088
  return ref;
33843
34089
  }
@@ -33945,7 +34191,7 @@ async function cornerToolActivity(call, worktreePath, requestedBy) {
33945
34191
  let title = oneLine(redactToolDetail(call.title ?? "")) || `${operation} tool`;
33946
34192
  if (isSuccessfulCommit(call)) {
33947
34193
  try {
33948
- const shown = await execFileAsync3("git", ["-C", worktreePath, "show", "--format=%s", "--name-only", "--no-renames", "HEAD"], { maxBuffer: 1024 * 1024 });
34194
+ const shown = await execFileAsync4("git", ["-C", worktreePath, "show", "--format=%s", "--name-only", "--no-renames", "HEAD"], { maxBuffer: 1024 * 1024 });
33949
34195
  const lines = shown.stdout.split(/\r?\n/);
33950
34196
  const subject = oneLine(lines.shift() ?? "commit");
33951
34197
  const files = new Set(lines.map(oneLine).filter(Boolean));
@@ -33967,20 +34213,36 @@ async function cornerToolActivity(call, worktreePath, requestedBy) {
33967
34213
  ...paths.length ? { files: paths.map((path) => ({ path })) } : {}
33968
34214
  };
33969
34215
  }
34216
+ var CORNER_CLOSE_POLL_BASE_MS = 10 * 6e4;
34217
+ function cornerClosePollMs(random = Math.random) {
34218
+ return CORNER_CLOSE_POLL_BASE_MS + Math.floor(random() * 3e3);
34219
+ }
33970
34220
  var MonolithCornerTurnLoop = class {
33971
34221
  options;
33972
34222
  commandContext;
33973
34223
  agent;
33974
34224
  wakeIntake;
33975
- /** Called by the daemon's one slow workspace reconciliation sweep. */
34225
+ /**
34226
+ * Called by the daemon's one slow workspace reconciliation sweep, and by the
34227
+ * fast reconcile a socket reconnect arms. A `corner-complete` published while
34228
+ * that socket was down is never replayed, so the sweep clears the close
34229
+ * throttle too: the durable read is what recovers the frame nobody heard.
34230
+ *
34231
+ * The wake is the intake loop's own stable notify and is handed back exactly
34232
+ * once, so it is kept: clearing it here left `requestClose` waking nothing
34233
+ * after the first sweep, and a pushed `corner-complete` then waited out the
34234
+ * idle timer. Intake clears it itself when it exits.
34235
+ */
33976
34236
  requestReconciliation() {
34237
+ this.lastCloseCheck = 0;
33977
34238
  this.wakeIntake?.();
33978
- this.wakeIntake = void 0;
33979
34239
  }
33980
34240
  client;
33981
34241
  sessionId;
33982
34242
  /** The configuration the live session baked in; a change invalidates it. */
33983
34243
  sessionFingerprint;
34244
+ /** Whether CodeGraph preparation succeeded for the live session. */
34245
+ sessionCodegraphReady = false;
33984
34246
  /** What this exact ACP session has already been prompted with (`warm-transcript.ts`). */
33985
34247
  warmTranscript = new WarmTranscript();
33986
34248
  /** The live session's environment, read back for pi's own turn record. */
@@ -33995,6 +34257,8 @@ var MonolithCornerTurnLoop = class {
33995
34257
  yoloMode = false;
33996
34258
  /** The live parent-Room reviewer baked into the current session. */
33997
34259
  reviewerHandle;
34260
+ /** Identity-only reviewer context; the exact PR head is refreshed inside each active turn. */
34261
+ reviewerInstructionInput;
33998
34262
  /** The role-specific second-chance instruction for this session. */
33999
34263
  cornerTurnEndNudge = CORNER_DELIVERY_NUDGE;
34000
34264
  /** Repository state already given a delivery reminder, until that state changes. */
@@ -34023,10 +34287,17 @@ var MonolithCornerTurnLoop = class {
34023
34287
  * by the same replay window the inbox already de-duplicates over.
34024
34288
  */
34025
34289
  stoppedTurns = /* @__PURE__ */ new Set();
34290
+ /** Live corner-complete: close now, do not wait for the recovery GET. */
34291
+ closePushed = false;
34292
+ /** Last durable close read; 0 means the first check still runs. */
34293
+ lastCloseCheck = 0;
34294
+ /** This corner's own jittered recovery interval, drawn once. */
34295
+ closePollMs;
34026
34296
  /** In-flight warm-store harvest, awaited at shutdown and never by a turn. */
34027
34297
  harvest;
34028
34298
  constructor(options) {
34029
34299
  this.options = options;
34300
+ this.closePollMs = options.closePollMs ?? cornerClosePollMs();
34030
34301
  this.agent = runtimeIdentity(options.runtime.agent);
34031
34302
  this.commandContext = new CommandExecutionContext(options.config.agentHomeRoot);
34032
34303
  this.options = { ...options, api: this.commandContext.bind(options.api) };
@@ -34046,6 +34317,11 @@ var MonolithCornerTurnLoop = class {
34046
34317
  isBusy() {
34047
34318
  return this.busy;
34048
34319
  }
34320
+ /** corner-complete on the wire: wake intake so `closed()` reaps now. */
34321
+ requestClose() {
34322
+ this.closePushed = true;
34323
+ this.wakeIntake?.();
34324
+ }
34049
34325
  refreshPersonaForSoulUpdate() {
34050
34326
  return this.options.scheduler.suspend(this.options.cornerId);
34051
34327
  }
@@ -34091,6 +34367,7 @@ var MonolithCornerTurnLoop = class {
34091
34367
  this.client = void 0;
34092
34368
  this.sessionId = void 0;
34093
34369
  this.sessionFingerprint = void 0;
34370
+ this.sessionCodegraphReady = false;
34094
34371
  this.pinnedProviderOverride = void 0;
34095
34372
  if (client?.isAlive)
34096
34373
  await client.stop();
@@ -34117,11 +34394,11 @@ var MonolithCornerTurnLoop = class {
34117
34394
  soul: configuration.soul ?? self?.soul,
34118
34395
  agentName: self?.name ?? this.agent.name,
34119
34396
  yoloMode: configuration.yoloMode,
34120
- mcpServers: expectedMountedImportedMcpServerNames({
34397
+ mcpServers: codegraphFingerprintServers(this.options.config, expectedMountedImportedMcpServerNames({
34121
34398
  operatorHome: this.options.config.operatorHome,
34122
34399
  agentKind: this.options.config.agentKind,
34123
34400
  grantedHostRoutes
34124
- }),
34401
+ }), this.sessionCodegraphReady),
34125
34402
  reviewerHandle: configuration.reviewerHandle
34126
34403
  });
34127
34404
  }
@@ -34156,20 +34433,11 @@ var MonolithCornerTurnLoop = class {
34156
34433
  authorHandle: opener?.handle,
34157
34434
  openedByAgent: !this.options.openedBy || this.options.openedBy === this.agent.publicKey
34158
34435
  };
34159
- let reviewerInstruction = cornerReviewerInstruction(reviewerInput);
34160
- if (reviewerInstruction) {
34161
- const restore = await this.options.api.execute("getCornerRestoreState", {
34162
- cornerId: this.options.cornerId
34163
- });
34164
- reviewerInstruction = cornerReviewerInstruction({
34165
- ...reviewerInput,
34166
- pullRequestNumber: restore.lifecycle?.pr?.number,
34167
- headSha: restore.lifecycle?.pr?.headSha
34168
- });
34169
- }
34436
+ const reviewerInstruction = cornerReviewerInstruction(reviewerInput) ? CORNER_REVIEWER_SESSION_INSTRUCTION : void 0;
34437
+ this.reviewerInstructionInput = reviewerInstruction ? reviewerInput : void 0;
34170
34438
  const selfReviewerInstruction = cornerSelfReviewerInstruction(reviewerInput);
34171
34439
  this.cornerTurnEndNudge = reviewerInstruction ?? selfReviewerInstruction ?? cornerMergeInstruction(configuration.yoloMode, configuration.reviewerHandle);
34172
- await mkdir13(this.options.worktreePath, { recursive: true });
34440
+ await mkdir14(this.options.worktreePath, { recursive: true });
34173
34441
  const selection = configuration.model || configuration.effort ? { model: configuration.model, effort: configuration.effort } : this.options.config.modelSelection;
34174
34442
  const homeOverlay = this.options.config.agentHomeRoot ? await prepareRoomAgentHome({
34175
34443
  root: this.options.config.agentHomeRoot,
@@ -34195,8 +34463,8 @@ var MonolithCornerTurnLoop = class {
34195
34463
  const repository = this.options.repository;
34196
34464
  let githubEnv = repository ? { GH_TOKEN: repository.githubToken, GITHUB_TOKEN: repository.githubToken } : {};
34197
34465
  if (repository && this.options.config.runtimeConfigPath && this.options.config.agentHomeRoot) {
34198
- const gitBinary = (await execFileAsync3("which", ["git"])).stdout.trim();
34199
- const ghBinary = await execFileAsync3("which", ["gh"]).then((result) => result.stdout.trim()).catch(() => void 0);
34466
+ const gitBinary = (await execFileAsync4("which", ["git"])).stdout.trim();
34467
+ const ghBinary = await execFileAsync4("which", ["gh"]).then((result) => result.stdout.trim()).catch(() => void 0);
34200
34468
  githubEnv = await installCornerGitHubWrappers({
34201
34469
  root: this.options.config.agentHomeRoot,
34202
34470
  runtimeConfigPath: this.options.config.runtimeConfigPath,
@@ -34210,7 +34478,7 @@ var MonolithCornerTurnLoop = class {
34210
34478
  });
34211
34479
  }
34212
34480
  const npmCacheDir = sharedNpmCacheDir(this.options.runtime.supervisorRoot);
34213
- await mkdir13(npmCacheDir, { recursive: true, mode: 448 });
34481
+ await mkdir14(npmCacheDir, { recursive: true, mode: 448 });
34214
34482
  const agentEnv = {
34215
34483
  ...this.options.config.agentEnv,
34216
34484
  ...homeOverlay,
@@ -34218,19 +34486,6 @@ var MonolithCornerTurnLoop = class {
34218
34486
  npm_config_cache: npmCacheDir
34219
34487
  };
34220
34488
  const operatorHome = this.options.config.operatorHome ?? homedir10();
34221
- const fingerprint = sessionConfigFingerprint({
34222
- model: configuration.model ?? this.options.config.modelSelection?.model,
34223
- effort: configuration.effort ?? this.options.config.modelSelection?.effort,
34224
- soul: configuration.soul ?? self?.soul,
34225
- agentName: self?.name ?? this.agent.name,
34226
- yoloMode: configuration.yoloMode,
34227
- mcpServers: expectedMountedImportedMcpServerNames({
34228
- operatorHome: this.options.config.operatorHome,
34229
- agentKind: this.options.config.agentKind,
34230
- grantedHostRoutes
34231
- }),
34232
- reviewerHandle: configuration.reviewerHandle
34233
- });
34234
34489
  this.agentEnv = agentEnv;
34235
34490
  const agentArgs = agentArgsWithModelSelection({
34236
34491
  kind: this.options.config.agentKind,
@@ -34241,10 +34496,10 @@ var MonolithCornerTurnLoop = class {
34241
34496
  this.attachmentDir = tmpDir ? join12(tmpDir, "beeline-attachments") : void 0;
34242
34497
  this.sessionScratchDir = tmpDir;
34243
34498
  const homeStateDirs = harnessHomeStateDirs(harnessLabel, agentEnv.HOME ?? operatorHome);
34244
- await Promise.all(homeStateDirs.map((dir) => mkdir13(dir, { recursive: true })));
34499
+ await Promise.all(homeStateDirs.map((dir) => mkdir14(dir, { recursive: true })));
34245
34500
  const attachScratchRoot = this.options.config.agentHomeRoot ?? tmpDir;
34246
34501
  if (attachScratchRoot)
34247
- await mkdir13(attachScratchRoot, { recursive: true });
34502
+ await mkdir14(attachScratchRoot, { recursive: true });
34248
34503
  const spawnCommand = wrapAgentCommand({
34249
34504
  bwrapPath: this.options.config.bwrapPath,
34250
34505
  spec: {
@@ -34280,10 +34535,29 @@ var MonolithCornerTurnLoop = class {
34280
34535
  agentCwd: this.options.worktreePath,
34281
34536
  agentLabel: harnessLabel,
34282
34537
  autoApprovePermissions: true,
34283
- permissionHandler: () => Promise.resolve("allow")
34538
+ permissionHandler: () => Promise.resolve("allow"),
34539
+ onCommands: agentCommandCatalogPublisher({
34540
+ api: this.options.api,
34541
+ agentId: this.agent.publicKey,
34542
+ workspaceId: this.options.workspaceId
34543
+ })
34284
34544
  };
34285
34545
  this.client = (this.options.createAcpClient ?? ((value) => new AcpClient(value)))(clientOptions);
34286
34546
  await this.client.start();
34547
+ const codegraphReady = await prepareCodegraphIndex(this.options.config, this.options.worktreePath);
34548
+ const fingerprint = sessionConfigFingerprint({
34549
+ model: configuration.model ?? this.options.config.modelSelection?.model,
34550
+ effort: configuration.effort ?? this.options.config.modelSelection?.effort,
34551
+ soul: configuration.soul ?? self?.soul,
34552
+ agentName: self?.name ?? this.agent.name,
34553
+ yoloMode: configuration.yoloMode,
34554
+ mcpServers: codegraphFingerprintServers(this.options.config, expectedMountedImportedMcpServerNames({
34555
+ operatorHome: this.options.config.operatorHome,
34556
+ agentKind: this.options.config.agentKind,
34557
+ grantedHostRoutes
34558
+ }), codegraphReady),
34559
+ reviewerHandle: configuration.reviewerHandle
34560
+ });
34287
34561
  const servers = [
34288
34562
  ...repository ? [
34289
34563
  {
@@ -34308,6 +34582,7 @@ var MonolithCornerTurnLoop = class {
34308
34582
  roomId: this.options.parentRoomId,
34309
34583
  workspaceId: this.options.workspaceId,
34310
34584
  cornerId: this.options.cornerId,
34585
+ agentMayCloseCorner: Boolean(repository),
34311
34586
  reviewer: Boolean(reviewerInstruction),
34312
34587
  attachRoot: this.options.worktreePath,
34313
34588
  // The whole per-session overlay, not an enumerated subset: see
@@ -34317,6 +34592,13 @@ var MonolithCornerTurnLoop = class {
34317
34592
  ...this.options.grantRunnerEndpoint ? { grantRunner: this.options.grantRunnerEndpoint } : {}
34318
34593
  })
34319
34594
  ];
34595
+ if (codegraphReady) {
34596
+ const codegraph = codegraphMcpServer(this.options.config, this.options.worktreePath, {
34597
+ readonly: false
34598
+ });
34599
+ if (codegraph)
34600
+ servers.push(codegraph);
34601
+ }
34320
34602
  const youtube = youtubeMcpServer(this.options.config, this.options.youtubeAccessToken);
34321
34603
  if (youtube)
34322
34604
  servers.push(youtube);
@@ -34357,21 +34639,57 @@ var MonolithCornerTurnLoop = class {
34357
34639
  "This is a no-code corner with no repository checkout and no GitHub workflow.",
34358
34640
  "Work in this corner's writable workspace. Use write_scratch_file or ordinary tools to create files, then post_artifact with the path to send them back to the corner.",
34359
34641
  "Do not initialize a repository, create a branch, commit, push, open a pull request, or wait for GitHub checks.",
34360
- // This lane has no pull request URL and no merge card, so the
34361
- // artifacts and the tag ARE the completion signal. Without the
34362
- // tag the person who asked is never told the work finished.
34363
- this.options.requesterHandle ? `Deliver the result as artifacts: post_artifact everything the objective asked for, then finish by replying with @${this.options.requesterHandle} and one line on what you posted. That reply is this corner's only completion signal.` : `Deliver the result as artifacts: post_artifact everything the objective asked for, then finish by replying with one line on what you posted. That reply is this corner's only completion signal.`
34642
+ // This lane has no pull request URL and no merge card, so its
34643
+ // attached final reply reports delivery to the requester. The
34644
+ // corner remains open until a human explicitly closes it.
34645
+ this.options.requesterHandle ? `Deliver the result as artifacts: post_artifact everything the objective asked for. Finish the turn by replying with @${this.options.requesterHandle} and one line on what you posted. The corner stays open until a human explicitly closes it.` : `Deliver the result as artifacts: post_artifact everything the objective asked for. Finish the turn by replying with one line on what you posted. The corner stays open until a human explicitly closes it.`
34364
34646
  ]
34365
34647
  ].filter(Boolean).join("\n\n")
34366
34648
  });
34367
34649
  this.sessionId = opened.sessionId;
34368
34650
  this.sessionFingerprint = fingerprint;
34651
+ this.sessionCodegraphReady = codegraphReady;
34369
34652
  if (selection) {
34370
34653
  const options = filterAllowedModelConfigOptions(parseAdvertisedConfigOptions(opened.raw, selection.model));
34371
34654
  await applyAgentModelSelection(this.client, opened.sessionId, options, selection);
34372
34655
  }
34373
34656
  return opened.sessionId;
34374
34657
  }
34658
+ /**
34659
+ * Resolve the review target at prompt time, not session activation time.
34660
+ *
34661
+ * A push can land after a reviewer session starts but before its first
34662
+ * prompt, or while that prompt is running. The active prompt and its bounded
34663
+ * second pass therefore each read the current green lifecycle head. The
34664
+ * server still compares approve_merge's SHA with the current head, so a push
34665
+ * after this read fails closed too.
34666
+ */
34667
+ async activeReviewerInstruction() {
34668
+ const input = this.reviewerInstructionInput;
34669
+ if (!input)
34670
+ return void 0;
34671
+ try {
34672
+ const [restore, localHead] = await Promise.all([
34673
+ this.options.api.execute("getCornerRestoreState", {
34674
+ cornerId: this.options.cornerId
34675
+ }),
34676
+ execFileAsync4("git", ["-C", this.options.worktreePath, "rev-parse", "HEAD"]).then(({ stdout: stdout6 }) => stdout6.trim())
34677
+ ]);
34678
+ const pr = restore.lifecycle?.pr;
34679
+ if (restore.lifecycle?.checks !== "passing" || !pr?.number || !pr.headSha || pr.headSha !== localHead)
34680
+ return CORNER_REVIEWER_UNSTABLE_HEAD_INSTRUCTION;
34681
+ return [
34682
+ "Current stable reviewer target for this active turn; it supersedes any older head in the trigger or transcript:",
34683
+ cornerReviewerInstruction({
34684
+ ...input,
34685
+ pullRequestNumber: pr.number,
34686
+ headSha: pr.headSha
34687
+ })
34688
+ ].join("\n");
34689
+ } catch {
34690
+ return CORNER_REVIEWER_UNSTABLE_HEAD_INSTRUCTION;
34691
+ }
34692
+ }
34375
34693
  /** The scheduler seam: `queue-wait` closes when a slot buys a session. */
34376
34694
  lifecycle(trace) {
34377
34695
  return {
@@ -34429,6 +34747,7 @@ var MonolithCornerTurnLoop = class {
34429
34747
  this.client = void 0;
34430
34748
  this.sessionId = void 0;
34431
34749
  this.sessionFingerprint = void 0;
34750
+ this.sessionCodegraphReady = false;
34432
34751
  if (client?.isAlive)
34433
34752
  await client.stop();
34434
34753
  this.pinnedProviderOverride = next;
@@ -34448,7 +34767,7 @@ var MonolithCornerTurnLoop = class {
34448
34767
  ...this.turnTraceSink ? { sink: this.turnTraceSink } : {}
34449
34768
  });
34450
34769
  }
34451
- async prompt(requestId, trigger, attachments = [], requestedById, restates) {
34770
+ async prompt(requestId, trigger, attachments = [], requestedById, restates, sourceMessageId) {
34452
34771
  const { api, cornerId } = this.options;
34453
34772
  if (this.stoppedTurns.has(requestId))
34454
34773
  return;
@@ -34476,10 +34795,11 @@ var MonolithCornerTurnLoop = class {
34476
34795
  throw new Error("corner turn stopped for daemon handoff");
34477
34796
  this.busy = true;
34478
34797
  await this.syncBranch();
34479
- const [conversation, roster, delivered] = await trace.measure("context-fetch", () => Promise.all([
34798
+ const [conversation, roster, delivered, activeReviewerInstruction] = await trace.measure("context-fetch", () => Promise.all([
34480
34799
  api.execute("getRoomConversation", { roomId: cornerId, limit: 200 }),
34481
34800
  this.roster(),
34482
- this.attachmentDir && attachments.length ? deliverAttachments(attachments, join12(this.attachmentDir, requestId.replace(/[^\w-]/g, "_")), this.options.fetchImpl) : Promise.resolve([])
34801
+ this.attachmentDir && attachments.length ? deliverAttachments(attachments, join12(this.attachmentDir, requestId.replace(/[^\w-]/g, "_")), this.options.fetchImpl) : Promise.resolve([]),
34802
+ this.activeReviewerInstruction()
34483
34803
  ]));
34484
34804
  const names = new Map(roster.members.map((member) => [member.identityId, member.name]));
34485
34805
  const requestedBy = requester && !requester.name && names.get(requester.pubkey) ? { ...requester, name: names.get(requester.pubkey) } : requester;
@@ -34487,7 +34807,10 @@ var MonolithCornerTurnLoop = class {
34487
34807
  this.currentTurn = { requestId, requester: requestedBy };
34488
34808
  const transcriptRows = conversation.items.slice(-120).map((message) => ({
34489
34809
  id: message.id,
34490
- line: `${names.get(message.authorId) ?? "Beeline"} [${message.type}]: ${message.body}`
34810
+ line: [
34811
+ ...message.type === "message" ? [`[message id: ${message.id}]`] : [],
34812
+ `${names.get(message.authorId) ?? "Beeline"} [${message.type}]: ${message.body}`
34813
+ ].join("\n")
34491
34814
  }));
34492
34815
  const buildPrompt = () => [
34493
34816
  this.turnIdentityInstructions,
@@ -34495,12 +34818,14 @@ var MonolithCornerTurnLoop = class {
34495
34818
  ${this.options.objective}`,
34496
34819
  WarmTranscript.render(this.warmTranscript.select(this.sessionId, transcriptRows), "Corner transcript:", "New in the corner since your last turn (the earlier transcript is already in this session):"),
34497
34820
  roomMentionDirectory(roster, this.agent.publicKey),
34821
+ activeReviewerInstruction,
34498
34822
  [
34823
+ ...sourceMessageId ? [`Reaction target message id: ${sourceMessageId}`] : [],
34499
34824
  `Newest trigger:
34500
34825
  ${trigger}`,
34501
34826
  ...attachmentPromptLines(attachments, delivered, this.acceptsImages())
34502
34827
  ].join("\n"),
34503
- this.options.repository ? "Continue the objective. Obey the PR checks and human hold rules in your session instructions." : "Continue the objective. Attach completed files before calling close_corner.",
34828
+ this.options.repository ? "Continue the objective. Obey the PR checks and human hold rules in your session instructions." : "Continue the objective. Attach completed files; only a human can close this corner.",
34504
34829
  MAINTAIN_ASSIGNED_IDENTITY_DIRECTIVE
34505
34830
  ].filter(Boolean).join("\n\n");
34506
34831
  const stream = new AgentTurnStream({
@@ -34641,7 +34966,9 @@ ${trigger}`,
34641
34966
  this.lastDeliveryNudgeState = deliveryState;
34642
34967
  replyBeforeNudge = durableReplyText(result.agentText);
34643
34968
  await flushToolCalls(result.toolCalls, "");
34644
- result = await runPrompt(this.reviewerHandle ? this.cornerTurnEndNudge : checksTurn ? CORNER_YOLO_MERGE_NUDGE : CORNER_DELIVERY_NUDGE);
34969
+ if (this.reviewerInstructionInput)
34970
+ await this.syncBranch();
34971
+ result = await runPrompt(this.reviewerHandle ? await this.activeReviewerInstruction() ?? this.cornerTurnEndNudge : checksTurn ? CORNER_YOLO_MERGE_NUDGE : CORNER_DELIVERY_NUDGE);
34645
34972
  trace.promptSettled();
34646
34973
  explained = await this.explainEmpty(result);
34647
34974
  }
@@ -34779,6 +35106,16 @@ ${trigger}`,
34779
35106
  onError: (error) => console.error("[thin-core] corner command failed", error),
34780
35107
  stop: (requestId) => this.stopTurn(requestId),
34781
35108
  closed: async () => {
35109
+ if (this.closePushed) {
35110
+ this.closePushed = false;
35111
+ await this.harvest;
35112
+ await this.options.onCloseRequested();
35113
+ return true;
35114
+ }
35115
+ const now2 = Date.now();
35116
+ if (this.lastCloseCheck !== 0 && now2 - this.lastCloseCheck < this.closePollMs)
35117
+ return false;
35118
+ this.lastCloseCheck = now2;
34782
35119
  const state = await api.execute("getCornerRestoreState", { cornerId });
34783
35120
  if (!state.closeRequested)
34784
35121
  return false;
@@ -34786,7 +35123,9 @@ ${trigger}`,
34786
35123
  await this.options.onCloseRequested();
34787
35124
  return true;
34788
35125
  },
34789
- run: (command) => this.prompt(command.turnRequestId, command.source.body, command.source.attachments, command.source.authorId, command.reason === "corner_check" ? [command.source.body] : void 0)
35126
+ run: (command) => this.prompt(command.turnRequestId, command.source.body, command.source.attachments, command.source.authorId, command.reason === "corner_check" ? [command.source.body] : void 0, command.source.type === "message" ? command.sourceMessageId : void 0).finally(() => {
35127
+ this.lastCloseCheck = 0;
35128
+ })
34790
35129
  });
34791
35130
  } finally {
34792
35131
  this.options.grantRunner?.unregister(cornerId);
@@ -35221,7 +35560,7 @@ var SessionScheduler = class {
35221
35560
  var REMOVAL_CONFIRMATION_READS = 2;
35222
35561
  var ROOM_JOIN_CONCURRENCY = 4;
35223
35562
  var DEFAULT_ROOM_WATCHDOG_STALE_MS = 9e4;
35224
- var DEFAULT_RECONCILE_HEARTBEAT_MS = 6e4;
35563
+ var DEFAULT_RECONCILE_HEARTBEAT_MS = 10 * 6e4;
35225
35564
  var DEFAULT_DRAIN_DEADLINE_MS = 30 * 6e4;
35226
35565
  var CORNER_BRANCH_DELETE_ATTEMPTS = 3;
35227
35566
  var DiscoveryWakes = class {
@@ -35261,33 +35600,33 @@ function isStandingCornerStartFault(error) {
35261
35600
  async function materializeCornerWorktree(input) {
35262
35601
  const remote = roomCheckoutRemote(input.remote);
35263
35602
  const repositoryHash = createHash7("sha256").update(remote).digest("hex").slice(0, 24);
35264
- const gitCommonDir = resolve23(input.supervisorRoot, "beeline", "repositories", `${repositoryHash}.git`);
35265
- const path = resolve23(input.supervisorRoot, "beeline", "corners", input.cornerId);
35266
- await mkdir14(dirname12(gitCommonDir), { recursive: true, mode: 448 });
35267
- await mkdir14(dirname12(path), { recursive: true, mode: 448 });
35603
+ const gitCommonDir = resolve24(input.supervisorRoot, "beeline", "repositories", `${repositoryHash}.git`);
35604
+ const path = resolve24(input.supervisorRoot, "beeline", "corners", input.cornerId);
35605
+ await mkdir15(dirname13(gitCommonDir), { recursive: true, mode: 448 });
35606
+ await mkdir15(dirname13(path), { recursive: true, mode: 448 });
35268
35607
  const authEnv = githubGitEnv(input.token);
35269
- if (!existsSync6(resolve23(gitCommonDir, "HEAD"))) {
35270
- await execFileAsync4("git", ["clone", "--bare", remote, gitCommonDir], {
35608
+ if (!existsSync6(resolve24(gitCommonDir, "HEAD"))) {
35609
+ await execFileAsync5("git", ["clone", "--bare", remote, gitCommonDir], {
35271
35610
  env: authEnv,
35272
35611
  maxBuffer: 4 * 1024 * 1024
35273
35612
  });
35274
35613
  }
35275
- await execFileAsync4("git", [
35614
+ await execFileAsync5("git", [
35276
35615
  `--git-dir=${gitCommonDir}`,
35277
35616
  "fetch",
35278
35617
  "--prune",
35279
35618
  "origin",
35280
35619
  `+refs/heads/${input.targetBranch}:refs/remotes/origin/${input.targetBranch}`
35281
35620
  ], { env: authEnv, maxBuffer: 4 * 1024 * 1024 });
35282
- const restored = await execFileAsync4("git", [
35621
+ const restored = await execFileAsync5("git", [
35283
35622
  `--git-dir=${gitCommonDir}`,
35284
35623
  "fetch",
35285
35624
  "origin",
35286
35625
  `+refs/heads/${input.featureBranch}:refs/remotes/origin/${input.featureBranch}`
35287
35626
  ], { env: authEnv, maxBuffer: 4 * 1024 * 1024 }).then(() => true, () => false);
35288
- if (!existsSync6(resolve23(path, ".git"))) {
35627
+ if (!existsSync6(resolve24(path, ".git"))) {
35289
35628
  await rm5(path, { recursive: true, force: true });
35290
- await execFileAsync4("git", [
35629
+ await execFileAsync5("git", [
35291
35630
  `--git-dir=${gitCommonDir}`,
35292
35631
  "worktree",
35293
35632
  "add",
@@ -35304,14 +35643,14 @@ async function materializeCornerWorktree(input) {
35304
35643
  console.log(`[thin-core] corner ${input.cornerId} warm node_modules: ${seed.reason}${seed.detail ? ` (${seed.detail})` : ""}`);
35305
35644
  }
35306
35645
  }
35307
- await execFileAsync4("git", [
35646
+ await execFileAsync5("git", [
35308
35647
  `--git-dir=${gitCommonDir}`,
35309
35648
  "config",
35310
35649
  "extensions.worktreeConfig",
35311
35650
  "true"
35312
35651
  ]);
35313
- await execFileAsync4("git", ["-C", path, "config", "--worktree", "core.bare", "false"]);
35314
- await execFileAsync4("git", [
35652
+ await execFileAsync5("git", ["-C", path, "config", "--worktree", "core.bare", "false"]);
35653
+ await execFileAsync5("git", [
35315
35654
  "-C",
35316
35655
  path,
35317
35656
  "config",
@@ -35319,7 +35658,7 @@ async function materializeCornerWorktree(input) {
35319
35658
  "credential.https://github.com.helper",
35320
35659
  "!f() { echo username=x-access-token; echo password=$GH_TOKEN; }; f"
35321
35660
  ]);
35322
- await execFileAsync4("git", [
35661
+ await execFileAsync5("git", [
35323
35662
  "-C",
35324
35663
  path,
35325
35664
  "config",
@@ -35327,7 +35666,7 @@ async function materializeCornerWorktree(input) {
35327
35666
  "user.name",
35328
35667
  input.committer.name
35329
35668
  ]);
35330
- await execFileAsync4("git", [
35669
+ await execFileAsync5("git", [
35331
35670
  "-C",
35332
35671
  path,
35333
35672
  "config",
@@ -35335,8 +35674,8 @@ async function materializeCornerWorktree(input) {
35335
35674
  "user.email",
35336
35675
  `${input.committer.publicKey.slice(0, 16)}@users.noreply.github.com`
35337
35676
  ]);
35338
- const top = await execFileAsync4("git", ["-C", path, "rev-parse", "--show-toplevel"]);
35339
- if (resolve23(top.stdout.trim()) !== resolve23(path)) {
35677
+ const top = await execFileAsync5("git", ["-C", path, "rev-parse", "--show-toplevel"]);
35678
+ if (resolve24(top.stdout.trim()) !== resolve24(path)) {
35340
35679
  throw new Error(`corner worktree escaped its isolated root: ${top.stdout.trim()}`);
35341
35680
  }
35342
35681
  return { path, gitCommonDir };
@@ -35360,7 +35699,7 @@ async function removeCornerWorktreeAndBranches(worktree) {
35360
35699
  const worktreeExists = existsSync6(worktree.path);
35361
35700
  const localExists = await gitRefExists(worktree.gitCommonDir, localRef);
35362
35701
  if (worktreeExists) {
35363
- const checkedOut = await execFileAsync4("git", [
35702
+ const checkedOut = await execFileAsync5("git", [
35364
35703
  "-C",
35365
35704
  worktree.path,
35366
35705
  "symbolic-ref",
@@ -35370,7 +35709,7 @@ async function removeCornerWorktreeAndBranches(worktree) {
35370
35709
  if (checkedOut.stdout.trim() !== localRef) {
35371
35710
  throw new Error(`corner worktree branch mismatch: expected ${localRef}`);
35372
35711
  }
35373
- await execFileAsync4("git", [
35712
+ await execFileAsync5("git", [
35374
35713
  `--git-dir=${worktree.gitCommonDir}`,
35375
35714
  "worktree",
35376
35715
  "remove",
@@ -35390,7 +35729,7 @@ async function removeCornerWorktreeAndBranches(worktree) {
35390
35729
  }
35391
35730
  await deleteExactRemoteBranch(worktree.gitCommonDir, remoteRef, worktree.token);
35392
35731
  if (await gitRefExists(worktree.gitCommonDir, localRef)) {
35393
- await execFileAsync4("git", [
35732
+ await execFileAsync5("git", [
35394
35733
  `--git-dir=${worktree.gitCommonDir}`,
35395
35734
  "branch",
35396
35735
  "--delete",
@@ -35400,10 +35739,10 @@ async function removeCornerWorktreeAndBranches(worktree) {
35400
35739
  ]);
35401
35740
  }
35402
35741
  }
35403
- var execFileAsync4 = promisify4(execFile6);
35742
+ var execFileAsync5 = promisify5(execFile7);
35404
35743
  async function removeCornerScratchWorkspace(input) {
35405
- const expected = resolve23(input.roomRoot, "scratch");
35406
- if (resolve23(input.scratchPath) !== expected) {
35744
+ const expected = resolve24(input.roomRoot, "scratch");
35745
+ if (resolve24(input.scratchPath) !== expected) {
35407
35746
  throw new Error(`refusing to remove scratch outside corner ${input.cornerId}`);
35408
35747
  }
35409
35748
  await rm5(expected, { recursive: true, force: true });
@@ -35417,6 +35756,19 @@ var RoomRuntimeCoordinator = class {
35417
35756
  /** Close/reconcile leftovers retried until local and remote refs are gone. */
35418
35757
  pendingCornerReaps = /* @__PURE__ */ new Map();
35419
35758
  startingCorners = /* @__PURE__ */ new Set();
35759
+ /**
35760
+ * Rooms whose start is in flight. `running` is not set until the checkout
35761
+ * clone finishes, and three callers race for it — the membership push, the
35762
+ * reconcile sweep, and the watchdog restart — so without this two checkouts
35763
+ * run in the same shared directory and the second start orphans the first
35764
+ * loop's AbortController.
35765
+ */
35766
+ startingRooms = /* @__PURE__ */ new Set();
35767
+ /** Pushed membership changes awaiting a bounded apply, latest per Room. */
35768
+ pendingMembershipEvents = /* @__PURE__ */ new Map();
35769
+ membershipDrain;
35770
+ /** Set by `shutdown`: a pushed event may no longer start anything. */
35771
+ stopped = false;
35420
35772
  /** Corners whose start failure has already been said out loud, once each. */
35421
35773
  reportedCornerStartFailures = /* @__PURE__ */ new Set();
35422
35774
  /** Standing workspace-configuration faults, keyed by the config that failed. */
@@ -35433,10 +35785,11 @@ var RoomRuntimeCoordinator = class {
35433
35785
  workspaceRemovalConfirmations = 0;
35434
35786
  roomRemovalConfirmations = /* @__PURE__ */ new Map();
35435
35787
  confirmationPending = false;
35436
- /** Agent-directed discovery wakes (#1369), counted instead of flagged: a
35437
- * wake that arrives while a reconcile is already running must survive its
35438
- * start-pass clearing, or a corner opened mid-reconcile waits a heartbeat
35439
- * for a wake the daemon already received. */
35788
+ /** Unscoped agent-directed discovery wakes (#1369), counted instead of
35789
+ * flagged: a wake that arrives while a reconcile is already running must
35790
+ * survive its start-pass clearing, or a reconnect mid-reconcile waits a
35791
+ * heartbeat for a wake the daemon already received. A scoped membership
35792
+ * event applies incrementally and never touches this latch. */
35440
35793
  discoveryWakes = new DiscoveryWakes();
35441
35794
  /** One command-grant runner per daemon; Rooms and corners register their checkouts on it. */
35442
35795
  grantRunner;
@@ -35460,11 +35813,20 @@ var RoomRuntimeCoordinator = class {
35460
35813
  });
35461
35814
  this.grantRunnerServer = new GrantRunnerServer(this.grantRunner);
35462
35815
  this.connectorUsage = new ConnectorUsageRecorder();
35463
- this.options.daemonApi.setRoomsChangedListener?.(() => {
35464
- this.discoveryWakes.wake();
35816
+ this.options.daemonApi.setRoomsChangedListener?.((event) => {
35817
+ const roomId = event?.roomId;
35818
+ if (!roomId) {
35819
+ this.discoveryWakes.wake();
35820
+ return;
35821
+ }
35822
+ this.queueMembershipEvent({ ...event, roomId });
35823
+ });
35824
+ this.options.daemonApi.setCornerCompleteListener?.((roomId) => {
35825
+ void this.applyCornerComplete(roomId).catch((error) => console.error("[thin-core] live corner-complete apply failed", error));
35465
35826
  });
35466
35827
  this.options.daemonApi.setConfigChangedListener?.(() => {
35467
35828
  void this.scheduler.suspendIdle().catch((error) => console.error("[body] config-change session restart failed", error));
35829
+ void Promise.resolve(this.options.onConfigChanged?.()).catch((error) => console.error("[body] config-change catalog refresh failed", error));
35468
35830
  });
35469
35831
  this.options.daemonApi.setHiccupRestartListener?.((attempt) => {
35470
35832
  this.options.onHiccupRestart?.(attempt);
@@ -35590,17 +35952,7 @@ var RoomRuntimeCoordinator = class {
35590
35952
  this.confirmationPending = true;
35591
35953
  continue;
35592
35954
  }
35593
- running.controller.abort();
35594
- await running.promise.catch(() => void 0);
35595
- try {
35596
- if (running.worktree)
35597
- await this.reapCornerWorktree(running.worktree);
35598
- else if (running.scratch)
35599
- await this.reapCornerScratch(running.scratch);
35600
- } catch (error) {
35601
- console.error(`[thin-core] corner ${channelId} cleanup failed; will retry:`, error);
35602
- this.confirmationPending = true;
35603
- }
35955
+ await this.stopRunning(channelId, running);
35604
35956
  }
35605
35957
  await mapWithConcurrency(desiredTopRooms, ROOM_JOIN_CONCURRENCY, async (roomId) => {
35606
35958
  if (this.running.has(roomId))
@@ -35620,17 +35972,97 @@ var RoomRuntimeCoordinator = class {
35620
35972
  this.discoveryWakes.completeReconcile(coveredWakes);
35621
35973
  return "member";
35622
35974
  }
35975
+ /**
35976
+ * Pushed membership changes are applied at the same bound the reconcile pass
35977
+ * uses. One `rooms-changed` per row means a single human action (adding an
35978
+ * agent to a Room inherits a membership per corner under it) arrives as a
35979
+ * burst; unbounded, that burst is exactly the concurrent restore reads and
35980
+ * worktree checkouts this change exists to stop.
35981
+ */
35982
+ queueMembershipEvent(event) {
35983
+ if (this.stopped)
35984
+ return;
35985
+ this.pendingMembershipEvents.set(event.roomId, event);
35986
+ this.membershipDrain ??= this.drainMembershipEvents().finally(() => {
35987
+ this.membershipDrain = void 0;
35988
+ });
35989
+ }
35990
+ async drainMembershipEvents() {
35991
+ while (this.pendingMembershipEvents.size) {
35992
+ const batch = [...this.pendingMembershipEvents.values()];
35993
+ this.pendingMembershipEvents.clear();
35994
+ await mapWithConcurrency(batch, ROOM_JOIN_CONCURRENCY, (event) => this.applyMembershipEvent(event).catch((error) => {
35995
+ console.error("[thin-core] live membership apply failed", error);
35996
+ this.discoveryWakes.wake();
35997
+ }));
35998
+ }
35999
+ }
36000
+ /**
36001
+ * Incremental apply of one scoped membership push.
36002
+ * An unscoped wake still uses the slow reconcile as recovery.
36003
+ */
36004
+ async applyMembershipEvent(event) {
36005
+ const roomId = event.roomId;
36006
+ if (!roomId) {
36007
+ this.discoveryWakes.wake();
36008
+ return;
36009
+ }
36010
+ if (event.removed === true) {
36011
+ const running = this.running.get(roomId);
36012
+ if (running)
36013
+ await this.stopRunning(roomId, running);
36014
+ this.monolithCornerParents.delete(roomId);
36015
+ return;
36016
+ }
36017
+ if (event.archived === true)
36018
+ return;
36019
+ this.roomRemovalConfirmations.delete(roomId);
36020
+ if (this.running.has(roomId) || this.startingCorners.has(roomId))
36021
+ return;
36022
+ if (event.parentRoomId) {
36023
+ this.monolithCornerParents.set(roomId, event.parentRoomId);
36024
+ if (!event.openedBy) {
36025
+ this.discoveryWakes.wake();
36026
+ return;
36027
+ }
36028
+ await this.startCorner({
36029
+ cornerId: roomId,
36030
+ parentRoomId: event.parentRoomId,
36031
+ openedBy: event.openedBy
36032
+ });
36033
+ if (!this.running.has(roomId))
36034
+ this.discoveryWakes.wake();
36035
+ return;
36036
+ }
36037
+ await this.startRoom(roomId);
36038
+ }
36039
+ async applyCornerComplete(cornerId) {
36040
+ this.running.get(cornerId)?.body.requestClose?.();
36041
+ }
36042
+ async stopRunning(channelId, running) {
36043
+ running.controller.abort();
36044
+ await running.promise.catch(() => void 0);
36045
+ try {
36046
+ if (running.worktree)
36047
+ await this.reapCornerWorktree(running.worktree);
36048
+ else if (running.scratch)
36049
+ await this.reapCornerScratch(running.scratch);
36050
+ } catch (error) {
36051
+ console.error(`[thin-core] corner ${channelId} cleanup failed; will retry:`, error);
36052
+ this.confirmationPending = true;
36053
+ }
36054
+ }
35623
36055
  roomRecord(roomId) {
35624
36056
  return this.runtime.rooms.find((room) => room.channelId === roomId);
35625
36057
  }
35626
36058
  roomRoot(roomId) {
35627
- return this.roomRecord(roomId)?.root ?? resolve23(dirname12(this.configPath), "rooms", roomId);
36059
+ return this.roomRecord(roomId)?.root ?? resolve24(dirname13(this.configPath), "rooms", roomId);
35628
36060
  }
35629
36061
  roomAgentHomeRoot(workspaceRoot, required = false) {
35630
36062
  const flag = process.env.BUZZY_BODY_ROOM_HOME;
35631
36063
  if (!required && flag === "0")
35632
36064
  return void 0;
35633
- const home = resolve23(workspaceRoot, "agent-home");
36065
+ const home = resolve24(workspaceRoot, "agent-home");
35634
36066
  if (!required && flag !== "1" && !existsSync6(home) && existsSync6(workspaceRoot))
35635
36067
  return void 0;
35636
36068
  try {
@@ -35646,10 +36078,10 @@ var RoomRuntimeCoordinator = class {
35646
36078
  return {
35647
36079
  ...this.baseConfig,
35648
36080
  workspaceRoot,
35649
- agentPrivateRoot: resolve23(workspaceRoot, "agent-private"),
35650
- agentMemoryRoot: resolve23(dirname12(this.configPath), "memory"),
35651
- openRouterRoutingCacheDir: openRouterRoutingCacheDir(dirname12(this.configPath)),
35652
- turnTraceDir: turnTraceDirectory(dirname12(this.configPath)),
36081
+ agentPrivateRoot: resolve24(workspaceRoot, "agent-private"),
36082
+ agentMemoryRoot: resolve24(dirname13(this.configPath), "memory"),
36083
+ openRouterRoutingCacheDir: openRouterRoutingCacheDir(dirname13(this.configPath)),
36084
+ turnTraceDir: turnTraceDirectory(dirname13(this.configPath)),
35653
36085
  ...agentHomeRoot ? { agentHomeRoot } : {}
35654
36086
  };
35655
36087
  }
@@ -35671,6 +36103,16 @@ var RoomRuntimeCoordinator = class {
35671
36103
  });
35672
36104
  }
35673
36105
  async startRoom(roomId) {
36106
+ if (this.running.has(roomId) || this.startingRooms.has(roomId))
36107
+ return;
36108
+ this.startingRooms.add(roomId);
36109
+ try {
36110
+ await this.startRoomOnce(roomId);
36111
+ } finally {
36112
+ this.startingRooms.delete(roomId);
36113
+ }
36114
+ }
36115
+ async startRoomOnce(roomId) {
35674
36116
  const controller = new AbortController();
35675
36117
  const cwd = await this.materializeRoomCheckout(roomId);
35676
36118
  const grantRunnerEndpoint = await this.grantRunnerEndpoint();
@@ -35703,6 +36145,11 @@ var RoomRuntimeCoordinator = class {
35703
36145
  if (this.running.get(roomId)?.body === loop)
35704
36146
  this.running.delete(roomId);
35705
36147
  });
36148
+ if (this.stopped) {
36149
+ controller.abort();
36150
+ await promise;
36151
+ return;
36152
+ }
35706
36153
  this.running.set(roomId, {
35707
36154
  body: loop,
35708
36155
  controller,
@@ -35727,17 +36174,17 @@ var RoomRuntimeCoordinator = class {
35727
36174
  const remote = roomCheckoutRemote(repository.remote);
35728
36175
  const targetBranch = repository.targetBranch || "main";
35729
36176
  const checkoutId = createHash7("sha256").update(`${remote}\0${targetBranch}`).digest("hex").slice(0, 24);
35730
- const path = resolve23(this.runtime.supervisorRoot, "beeline", "room-checkouts", checkoutId);
35731
- await mkdir14(dirname12(path), { recursive: true, mode: 448 });
36177
+ const path = resolve24(this.runtime.supervisorRoot, "beeline", "room-checkouts", checkoutId);
36178
+ await mkdir15(dirname13(path), { recursive: true, mode: 448 });
35732
36179
  const token = remote.startsWith("https://github.com/") ? await this.options.daemonApi.execute("getRoomGitHubToken", { roomId }) : void 0;
35733
36180
  const env = token ? githubGitEnv(token.token) : process.env;
35734
- if (!existsSync6(resolve23(path, ".git"))) {
35735
- await execFileAsync4("git", ["clone", "--no-checkout", remote, path], {
36181
+ if (!existsSync6(resolve24(path, ".git"))) {
36182
+ await execFileAsync5("git", ["clone", "--no-checkout", remote, path], {
35736
36183
  env,
35737
36184
  maxBuffer: 4 * 1024 * 1024
35738
36185
  });
35739
36186
  }
35740
- await execFileAsync4("git", [
36187
+ await execFileAsync5("git", [
35741
36188
  "-C",
35742
36189
  path,
35743
36190
  "fetch",
@@ -35745,7 +36192,7 @@ var RoomRuntimeCoordinator = class {
35745
36192
  "origin",
35746
36193
  `+refs/heads/${targetBranch}:refs/remotes/origin/${targetBranch}`
35747
36194
  ], { env, maxBuffer: 4 * 1024 * 1024 });
35748
- await execFileAsync4("git", ["-C", path, "checkout", "--detach", "--force", `origin/${targetBranch}`], {
36195
+ await execFileAsync5("git", ["-C", path, "checkout", "--detach", "--force", `origin/${targetBranch}`], {
35749
36196
  env,
35750
36197
  maxBuffer: 4 * 1024 * 1024
35751
36198
  });
@@ -35763,7 +36210,8 @@ var RoomRuntimeCoordinator = class {
35763
36210
  roomId: corner.parentRoomId
35764
36211
  })
35765
36212
  ]);
35766
- configKey = cornerStartConfigKey(repository, restore.objective ?? "");
36213
+ const objective = (restore.objective ?? "").trim() || (restore.kind === "human" ? (restore.title ?? "").trim() : "");
36214
+ configKey = cornerStartConfigKey(repository, objective);
35767
36215
  const previousStanding = this.standingCornerStartFaults.get(corner.cornerId);
35768
36216
  if (previousStanding === configKey)
35769
36217
  return;
@@ -35777,7 +36225,6 @@ var RoomRuntimeCoordinator = class {
35777
36225
  if (repository.resolution === "repository" && (!repository.remote || !repository.key)) {
35778
36226
  throw new Error("corner parent Room has an incomplete repository binding");
35779
36227
  }
35780
- const objective = (restore.objective ?? "").trim();
35781
36228
  if (!objective)
35782
36229
  throw new Error("corner has no authoritative objective fact");
35783
36230
  const repositoryBacked = repository.resolution === "repository" && restore.lane !== "no_code";
@@ -35793,10 +36240,10 @@ var RoomRuntimeCoordinator = class {
35793
36240
  featureBranch,
35794
36241
  token: granted.token
35795
36242
  }) : void 0;
35796
- const workspacePath = worktree?.path ?? resolve23(this.roomRoot(corner.cornerId), "scratch");
36243
+ const workspacePath = worktree?.path ?? resolve24(this.roomRoot(corner.cornerId), "scratch");
35797
36244
  if (!worktree)
35798
- await mkdir14(workspacePath, { recursive: true, mode: 448 });
35799
- const isOpener = !corner.openedBy || corner.openedBy === this.agent.publicKey;
36245
+ await mkdir15(workspacePath, { recursive: true, mode: 448 });
36246
+ const isOpener = corner.openedBy === this.agent.publicKey;
35800
36247
  if (worktree && shouldPostInitialCornerWorkingState(restore, isOpener)) {
35801
36248
  await this.options.daemonApi.execute("postCornerRemoteState", {
35802
36249
  cornerId: corner.cornerId,
@@ -35852,6 +36299,11 @@ var RoomRuntimeCoordinator = class {
35852
36299
  this.running.delete(corner.cornerId);
35853
36300
  }
35854
36301
  });
36302
+ if (this.stopped) {
36303
+ controller.abort();
36304
+ await promise;
36305
+ return;
36306
+ }
35855
36307
  this.running.set(corner.cornerId, {
35856
36308
  body: loop,
35857
36309
  controller,
@@ -35997,18 +36449,28 @@ var RoomRuntimeCoordinator = class {
35997
36449
  }
35998
36450
  }
35999
36451
  async shutdown() {
36452
+ this.stopped = true;
36453
+ this.pendingMembershipEvents.clear();
36454
+ const deadlineAt = Math.min(this.now() + this.drainDeadlineMs, this.drainDeadlineAt ?? Number.POSITIVE_INFINITY);
36455
+ const untilDeadline = async (work) => {
36456
+ let timer;
36457
+ const deadline = new Promise((resolveDeadline) => {
36458
+ timer = setTimeout(() => resolveDeadline("deadline"), Math.max(0, deadlineAt - this.now()));
36459
+ });
36460
+ try {
36461
+ return await Promise.race([work, deadline]);
36462
+ } finally {
36463
+ if (timer)
36464
+ clearTimeout(timer);
36465
+ }
36466
+ };
36467
+ if (this.membershipDrain)
36468
+ await untilDeadline(this.membershipDrain);
36000
36469
  const rooms = [...this.running.values()];
36001
36470
  for (const room of rooms)
36002
36471
  room.controller.abort();
36003
36472
  const drained = Promise.all(rooms.map((room) => room.promise.catch(() => void 0)));
36004
- const deadlineAt = Math.min(this.now() + this.drainDeadlineMs, this.drainDeadlineAt ?? Number.POSITIVE_INFINITY);
36005
- let timer;
36006
- const deadline = new Promise((resolveDeadline) => {
36007
- timer = setTimeout(() => resolveDeadline("deadline"), Math.max(0, deadlineAt - this.now()));
36008
- });
36009
- const result = await Promise.race([drained.then(() => "drained"), deadline]);
36010
- if (timer)
36011
- clearTimeout(timer);
36473
+ const result = await untilDeadline(drained.then(() => "drained"));
36012
36474
  if (result === "deadline") {
36013
36475
  await Promise.allSettled(rooms.map((room) => room.body.forceRecoverRoom()));
36014
36476
  await drained;
@@ -36044,7 +36506,7 @@ function githubGitEnv(token) {
36044
36506
  }
36045
36507
  async function gitRefExists(gitCommonDir, ref) {
36046
36508
  try {
36047
- await execFileAsync4("git", [`--git-dir=${gitCommonDir}`, "show-ref", "--verify", "--quiet", ref]);
36509
+ await execFileAsync5("git", [`--git-dir=${gitCommonDir}`, "show-ref", "--verify", "--quiet", ref]);
36048
36510
  return true;
36049
36511
  } catch (error) {
36050
36512
  if (error.code === 1)
@@ -36054,7 +36516,7 @@ async function gitRefExists(gitCommonDir, ref) {
36054
36516
  }
36055
36517
  async function repositoryHeadRef(gitCommonDir) {
36056
36518
  try {
36057
- const head = await execFileAsync4("git", [
36519
+ const head = await execFileAsync5("git", [
36058
36520
  `--git-dir=${gitCommonDir}`,
36059
36521
  "symbolic-ref",
36060
36522
  "--quiet",
@@ -36074,7 +36536,7 @@ ${gitStderr(error)}`);
36074
36536
  }
36075
36537
  async function deleteExactRemoteBranch(gitCommonDir, remoteRef, token, options = {}) {
36076
36538
  const authEnv = githubGitEnv(token);
36077
- const listRemote = () => execFileAsync4("git", [`--git-dir=${gitCommonDir}`, "ls-remote", "--heads", "origin", remoteRef], {
36539
+ const listRemote = () => execFileAsync5("git", [`--git-dir=${gitCommonDir}`, "ls-remote", "--heads", "origin", remoteRef], {
36078
36540
  env: authEnv,
36079
36541
  maxBuffer: 4 * 1024 * 1024
36080
36542
  });
@@ -36090,7 +36552,7 @@ async function deleteExactRemoteBranch(gitCommonDir, remoteRef, token, options =
36090
36552
  try {
36091
36553
  const remote = await listRemote();
36092
36554
  if (remote.stdout.trim()) {
36093
- await execFileAsync4("git", [`--git-dir=${gitCommonDir}`, "push", "origin", `:${remoteRef}`], {
36555
+ await execFileAsync5("git", [`--git-dir=${gitCommonDir}`, "push", "origin", `:${remoteRef}`], {
36094
36556
  env: authEnv,
36095
36557
  maxBuffer: 4 * 1024 * 1024
36096
36558
  });
@@ -36185,17 +36647,17 @@ var ThinDaemonCore = class {
36185
36647
  };
36186
36648
 
36187
36649
  // apps/body/dist/agent-retirement.js
36188
- import { mkdir as mkdir16, rename as rename5 } from "node:fs/promises";
36189
- import { dirname as dirname14, resolve as resolve25 } from "node:path";
36650
+ import { mkdir as mkdir17, rename as rename5 } from "node:fs/promises";
36651
+ import { dirname as dirname15, resolve as resolve26 } from "node:path";
36190
36652
 
36191
36653
  // apps/body/dist/systemd.js
36192
- import { execFile as execFile7 } from "node:child_process";
36193
- import { mkdir as mkdir15, readFile as readFile9, stat as stat3, writeFile as writeFile10 } from "node:fs/promises";
36654
+ import { execFile as execFile8 } from "node:child_process";
36655
+ import { mkdir as mkdir16, readFile as readFile10, stat as stat3, writeFile as writeFile10 } from "node:fs/promises";
36194
36656
  import { homedir as homedir11 } from "node:os";
36195
- import { dirname as dirname13, resolve as resolve24 } from "node:path";
36657
+ import { dirname as dirname14, resolve as resolve25 } from "node:path";
36196
36658
  import { setTimeout as sleep } from "node:timers/promises";
36197
- import { promisify as promisify5 } from "node:util";
36198
- var execFileAsync5 = promisify5(execFile7);
36659
+ import { promisify as promisify6 } from "node:util";
36660
+ var execFileAsync6 = promisify6(execFile8);
36199
36661
  var DELIBERATE_REMOVAL_EXIT_STATUS = 78;
36200
36662
  var DAEMON_DISTRESS_EXIT_STATUS = 77;
36201
36663
  var UNKNOWN_AGENT_EXIT_STATUS = 79;
@@ -36227,8 +36689,13 @@ TimeoutStartSec=90s
36227
36689
  TimeoutStopSec=10min
36228
36690
  KillMode=control-group
36229
36691
  UMask=0077
36230
- NoNewPrivileges=yes
36231
- PrivateTmp=yes
36692
+ # A desktop-launched user manager may inherit Ubuntu's unprivileged_userns
36693
+ # AppArmor profile. Without an explicit transition every agent inherits it too,
36694
+ # so /usr/bin/bwrap cannot enter its package-provided bwrap profile.
36695
+ # Do not set NoNewPrivileges or PrivateTmp on this outer service: systemd applies
36696
+ # either before AppArmorProfile, which blocks this transition. Bubblewrap sets
36697
+ # no-new-privs and a private /tmp inside each agent sandbox it creates.
36698
+ AppArmorProfile=-unconfined
36232
36699
 
36233
36700
  [Install]
36234
36701
  WantedBy=default.target
@@ -36236,9 +36703,9 @@ WantedBy=default.target
36236
36703
  }
36237
36704
  function isCanonicalInstalledLauncher(env = process.env, invocationPath = process.argv[1]) {
36238
36705
  const home = env.HOME?.trim() || homedir11();
36239
- const expectedLibDir = resolve24(home, ".local", "lib", "beeline");
36706
+ const expectedLibDir = resolve25(home, ".local", "lib", "beeline");
36240
36707
  const expectedPrefix = `${expectedLibDir}/`;
36241
- return resolve24(env.BEELINE_LIB_DIR?.trim() || "/") === expectedLibDir && Boolean(invocationPath) && resolve24(invocationPath).startsWith(expectedPrefix);
36708
+ return resolve25(env.BEELINE_LIB_DIR?.trim() || "/") === expectedLibDir && Boolean(invocationPath) && resolve25(invocationPath).startsWith(expectedPrefix);
36242
36709
  }
36243
36710
  function assertCanonicalInstalledLauncher(env, invocationPath) {
36244
36711
  if (isCanonicalInstalledLauncher(env, invocationPath))
@@ -36246,12 +36713,12 @@ function assertCanonicalInstalledLauncher(env, invocationPath) {
36246
36713
  throw new Error("refusing to modify the shared Beeline systemd unit outside the canonical ~/.local/bin/beeline launcher");
36247
36714
  }
36248
36715
  function systemdUserUnitPath(env = process.env) {
36249
- const configRoot = env.XDG_CONFIG_HOME?.trim() || resolve24(homedir11(), ".config");
36250
- return resolve24(configRoot, "systemd", "user", SYSTEMD_UNIT_NAME);
36716
+ const configRoot = env.XDG_CONFIG_HOME?.trim() || resolve25(homedir11(), ".config");
36717
+ return resolve25(configRoot, "systemd", "user", SYSTEMD_UNIT_NAME);
36251
36718
  }
36252
36719
  function systemdBrokerUnitPath(env = process.env) {
36253
- const configRoot = env.XDG_CONFIG_HOME?.trim() || resolve24(homedir11(), ".config");
36254
- return resolve24(configRoot, "systemd", "user", TRUSTY_SQUIRE_BROKER_UNIT_NAME);
36720
+ const configRoot = env.XDG_CONFIG_HOME?.trim() || resolve25(homedir11(), ".config");
36721
+ return resolve25(configRoot, "systemd", "user", TRUSTY_SQUIRE_BROKER_UNIT_NAME);
36255
36722
  }
36256
36723
  async function installTrustySquireBrokerService(options = {}) {
36257
36724
  const env = options.env ?? process.env;
@@ -36260,9 +36727,9 @@ async function installTrustySquireBrokerService(options = {}) {
36260
36727
  ensureSquireHostDir(home);
36261
36728
  const path = systemdBrokerUnitPath(env);
36262
36729
  const content = trustySquireBrokerUnit();
36263
- const existing = await readFile9(path, "utf8").catch(() => "");
36730
+ const existing = await readFile10(path, "utf8").catch(() => "");
36264
36731
  if (existing !== content) {
36265
- await mkdir15(dirname13(path), { recursive: true, mode: 448 });
36732
+ await mkdir16(dirname14(path), { recursive: true, mode: 448 });
36266
36733
  await writeFile10(path, content, { mode: 384 });
36267
36734
  }
36268
36735
  const run2 = options.run ?? runSystemctl;
@@ -36271,7 +36738,7 @@ async function installTrustySquireBrokerService(options = {}) {
36271
36738
  }
36272
36739
  var AGENT_SERVICE = /^beeline-agent@([0-9a-f]{64})\.service$/i;
36273
36740
  var runSystemctl = async (args) => {
36274
- const result = await execFileAsync5("systemctl", ["--user", ...args], {
36741
+ const result = await execFileAsync6("systemctl", ["--user", ...args], {
36275
36742
  timeout: SYSTEMD_COMMAND_TIMEOUT_MS,
36276
36743
  encoding: "utf8"
36277
36744
  });
@@ -36284,9 +36751,9 @@ async function installAgentService(publicKey, options = {}) {
36284
36751
  assertCanonicalInstalledLauncher(env, options.invocationPath);
36285
36752
  const path = systemdUserUnitPath(env);
36286
36753
  const content = agentServiceUnit();
36287
- const existing = await readFile9(path, "utf8").catch(() => "");
36754
+ const existing = await readFile10(path, "utf8").catch(() => "");
36288
36755
  if (existing !== content) {
36289
- await mkdir15(dirname13(path), { recursive: true, mode: 448 });
36756
+ await mkdir16(dirname14(path), { recursive: true, mode: 448 });
36290
36757
  await writeFile10(path, content, { mode: 384 });
36291
36758
  }
36292
36759
  const run2 = options.run ?? runSystemctl;
@@ -36407,7 +36874,7 @@ async function reconcileAgentServices(options = {}) {
36407
36874
  async function notify(fields) {
36408
36875
  if (process.env.BEELINE_MANAGED_BY_SYSTEMD !== "1")
36409
36876
  return;
36410
- await execFileAsync5("systemd-notify", fields, { timeout: SYSTEMD_COMMAND_TIMEOUT_MS });
36877
+ await execFileAsync6("systemd-notify", fields, { timeout: SYSTEMD_COMMAND_TIMEOUT_MS });
36411
36878
  }
36412
36879
  async function extendSystemdStartTimeout(ms) {
36413
36880
  await notify([`EXTEND_TIMEOUT_USEC=${Math.max(0, Math.round(ms)) * 1e3}`]).catch(() => void 0);
@@ -36426,15 +36893,15 @@ var SystemdNotifier = class {
36426
36893
 
36427
36894
  // apps/body/dist/agent-retirement.js
36428
36895
  async function retireRemovedAgent(runtime, options = {}) {
36429
- const deletedRoot = resolve25(runtime.supervisorRoot, "beeline", "deleted-runtimes");
36430
- const target = resolve25(deletedRoot, `${runtime.agent.publicKey}-${Date.now()}`);
36896
+ const deletedRoot = resolve26(runtime.supervisorRoot, "beeline", "deleted-runtimes");
36897
+ const target = resolve26(deletedRoot, `${runtime.agent.publicKey}-${Date.now()}`);
36431
36898
  return relocateAgentRuntime(runtime, target, {
36432
36899
  ...options.run ? { run: options.run } : {}
36433
36900
  });
36434
36901
  }
36435
36902
  async function relocateAgentRuntime(runtime, target, options = {}) {
36436
36903
  const source = runtimeDirectory(runtime.supervisorRoot, runtime.agent.publicKey);
36437
- const destination = resolve25(target);
36904
+ const destination = resolve26(target);
36438
36905
  if (destination === source || destination.startsWith(`${source}/`)) {
36439
36906
  throw new Error("agent runtime destination must be outside the live runtime");
36440
36907
  }
@@ -36443,13 +36910,13 @@ async function relocateAgentRuntime(runtime, target, options = {}) {
36443
36910
  ...options.run ? { run: options.run } : {}
36444
36911
  });
36445
36912
  }
36446
- await mkdir16(dirname14(destination), { recursive: true, mode: 448 });
36913
+ await mkdir17(dirname15(destination), { recursive: true, mode: 448 });
36447
36914
  await rename5(source, destination);
36448
36915
  return destination;
36449
36916
  }
36450
36917
 
36451
36918
  // apps/body/dist/start-command.js
36452
- import { basename as basename7, dirname as dirname18 } from "node:path";
36919
+ import { basename as basename7, dirname as dirname19 } from "node:path";
36453
36920
  var import_picocolors2 = __toESM(require_picocolors(), 1);
36454
36921
 
36455
36922
  // apps/body/dist/self-update-cli.js
@@ -36460,21 +36927,21 @@ init_self_update_manifest();
36460
36927
  // apps/body/dist/managed-update.js
36461
36928
  init_self_update();
36462
36929
  import { spawn as spawn9 } from "node:child_process";
36463
- import { mkdir as mkdir19, rm as rm7, stat as stat4, writeFile as writeFile13 } from "node:fs/promises";
36464
- import { dirname as dirname17, resolve as resolve28 } from "node:path";
36930
+ import { mkdir as mkdir20, rm as rm7, stat as stat4, writeFile as writeFile13 } from "node:fs/promises";
36931
+ import { dirname as dirname18, resolve as resolve29 } from "node:path";
36465
36932
 
36466
36933
  // apps/body/dist/update-rollback-alert.js
36467
- import { mkdir as mkdir18, readFile as readFile11, rename as rename7, unlink as unlink4, writeFile as writeFile12 } from "node:fs/promises";
36468
- import { dirname as dirname16, resolve as resolve27 } from "node:path";
36934
+ import { mkdir as mkdir19, readFile as readFile12, rename as rename7, unlink as unlink4, writeFile as writeFile12 } from "node:fs/promises";
36935
+ import { dirname as dirname17, resolve as resolve28 } from "node:path";
36469
36936
  var REPORT_INTERVAL_MS = 60 * 60 * 1e3;
36470
36937
  var lastLogged = /* @__PURE__ */ new Map();
36471
36938
  function updateRollbackAlertPath(runtimeDir) {
36472
- return resolve27(runtimeDir, "update-rollback-alert.json");
36939
+ return resolve28(runtimeDir, "update-rollback-alert.json");
36473
36940
  }
36474
36941
  async function writeAlert(runtimeDir, alert) {
36475
36942
  const path = updateRollbackAlertPath(runtimeDir);
36476
36943
  const staged = `${path}.${process.pid}.tmp`;
36477
- await mkdir18(dirname16(path), { recursive: true });
36944
+ await mkdir19(dirname17(path), { recursive: true });
36478
36945
  await writeFile12(staged, `${JSON.stringify(alert, null, 2)}
36479
36946
  `, { mode: 384 });
36480
36947
  await rename7(staged, path);
@@ -36487,7 +36954,7 @@ async function queueUpdateRollbackAlert(runtimeDir, releaseId, now2 = Date.now()
36487
36954
  }
36488
36955
  async function readUpdateRollbackAlert(runtimeDir) {
36489
36956
  try {
36490
- const value = JSON.parse(await readFile11(updateRollbackAlertPath(runtimeDir), "utf8"));
36957
+ const value = JSON.parse(await readFile12(updateRollbackAlertPath(runtimeDir), "utf8"));
36491
36958
  if (value.version !== 1 || typeof value.releaseId !== "string")
36492
36959
  return void 0;
36493
36960
  return value;
@@ -36538,13 +37005,13 @@ var LOCK_STALE_MS = UPDATE_WORKER_DEADLINE_MS + 5 * 6e4;
36538
37005
  var DEFAULT_UPDATE_INITIAL_DELAY_MS = 0;
36539
37006
  async function withInstallLock(layout, work, options = {}) {
36540
37007
  const now2 = options.now ?? Date.now;
36541
- const lock = resolve28(layout.releasesRoot, ".state", "install.lock");
37008
+ const lock = resolve29(layout.releasesRoot, ".state", "install.lock");
36542
37009
  const deadline = now2() + (options.waitMs ?? 1e4);
36543
- await mkdir19(dirname17(lock), { recursive: true });
37010
+ await mkdir20(dirname18(lock), { recursive: true });
36544
37011
  for (; ; ) {
36545
37012
  try {
36546
- await mkdir19(lock);
36547
- await writeFile13(resolve28(lock, "owner"), `${process.pid}
37013
+ await mkdir20(lock);
37014
+ await writeFile13(resolve29(lock, "owner"), `${process.pid}
36548
37015
  ${now2()}
36549
37016
  `, "utf8");
36550
37017
  break;
@@ -36668,7 +37135,7 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
36668
37135
  if (!attempt || attempt.releaseId !== desiredRelease || attempt.status !== "pending") {
36669
37136
  const from = await readInstalledBundleIdentity({
36670
37137
  ...this.#layout,
36671
- libDir: resolve28(this.#layout.releasesRoot, this.#loadedRelease)
37138
+ libDir: resolve29(this.#layout.releasesRoot, this.#loadedRelease)
36672
37139
  }).catch(() => void 0) ?? {};
36673
37140
  const to = await readInstalledBundleIdentity(this.#layout).catch(() => void 0) ?? {};
36674
37141
  const record3 = {
@@ -36955,7 +37422,7 @@ async function proveLoadedReleaseReady(layout, runtimeDir, loadedRelease, option
36955
37422
  });
36956
37423
  if (!accepted)
36957
37424
  return false;
36958
- await writeFile13(resolve28(runtimeDir, "daemon-ready.json"), `${JSON.stringify({
37425
+ await writeFile13(resolve29(runtimeDir, "daemon-ready.json"), `${JSON.stringify({
36959
37426
  readyAt: (options.now ?? Date.now)(),
36960
37427
  loadedRelease,
36961
37428
  functionalProof: options.functionalProof
@@ -37201,7 +37668,7 @@ async function startStoredRuntime(configPath, opts = {}, dependencyOverrides = {
37201
37668
  return { status: "started", pid };
37202
37669
  }
37203
37670
  function agentStartId(configPath) {
37204
- return basename7(dirname18(configPath));
37671
+ return basename7(dirname19(configPath));
37205
37672
  }
37206
37673
  async function startRuntime(configPath, spinnerHandle) {
37207
37674
  const report = (text2) => spinnerHandle ? spinnerHandle.message(text2) : console.log(text2);
@@ -37267,7 +37734,7 @@ async function runStartCommand(args, interactiveUi, dependencyOverrides = {}) {
37267
37734
  for (const path of unique) {
37268
37735
  const id = agentStartId(path);
37269
37736
  const spinnerHandle = interactiveUi ? spinner() : void 0;
37270
- spinnerHandle?.start(`Starting ${dirname18(path)}\u2026`);
37737
+ spinnerHandle?.start(`Starting ${dirname19(path)}\u2026`);
37271
37738
  try {
37272
37739
  const outcome = await deps.startOne(path, spinnerHandle);
37273
37740
  const report = { id, path, ...outcome };
@@ -37301,9 +37768,9 @@ async function runStartCommand(args, interactiveUi, dependencyOverrides = {}) {
37301
37768
  // apps/body/dist/connect-command.js
37302
37769
  import { spawn as spawn11 } from "node:child_process";
37303
37770
  import { createHash as createHash9, randomUUID as randomUUID6 } from "node:crypto";
37304
- import { chmod as chmod7, mkdir as mkdir20, readFile as readFile12, unlink as unlink5, writeFile as writeFile14 } from "node:fs/promises";
37771
+ import { chmod as chmod7, mkdir as mkdir21, readFile as readFile13, unlink as unlink5, writeFile as writeFile14 } from "node:fs/promises";
37305
37772
  import { homedir as homedir13, hostname as hostname2 } from "node:os";
37306
- import { dirname as dirname19, resolve as resolve29 } from "node:path";
37773
+ import { dirname as dirname20, resolve as resolve30 } from "node:path";
37307
37774
  import { stdin as stdin3, stdout as stdout4 } from "node:process";
37308
37775
 
37309
37776
  // apps/body/dist/clack-support.js
@@ -37621,6 +38088,9 @@ init_self_update();
37621
38088
  init_self_update_manifest();
37622
38089
  var CONNECT_HARNESSES = AUTO_DETECT_AGENT_KINDS;
37623
38090
  var AGENT_NAME_MAX_LENGTH = 32;
38091
+ function isInteractiveConnectTerminal(input = stdin3) {
38092
+ return input.isTTY === true;
38093
+ }
37624
38094
  function isReasonableAgentName(value) {
37625
38095
  const normalized = value.trim().replace(/\s+/g, " ");
37626
38096
  return normalized.length > 0 && normalized.length <= AGENT_NAME_MAX_LENGTH && new RegExp("^\\p{L}[\\p{L}\\p{M}'\u2019 -]*$", "u").test(normalized);
@@ -37784,8 +38254,6 @@ async function collectConnectWizard(prompts = clackPrompts, loadModels = loadCon
37784
38254
  return (picked ?? "").trim();
37785
38255
  };
37786
38256
  const askEffort = async (catalog2, forHarness, model2, forProvider, forKey) => {
37787
- if (!catalog2.effort)
37788
- return void 0;
37789
38257
  let axis = catalog2.effort;
37790
38258
  if (model2 !== catalog2.currentValue) {
37791
38259
  const reread = await loadModels({
@@ -37797,7 +38265,7 @@ async function collectConnectWizard(prompts = clackPrompts, loadModels = loadCon
37797
38265
  }).catch(() => void 0);
37798
38266
  axis = reread?.effort ?? axis;
37799
38267
  }
37800
- if (!axis.options.length)
38268
+ if (!axis?.options.length)
37801
38269
  return void 0;
37802
38270
  const picked = await prompts.select({
37803
38271
  message: brass("Choose reasoning effort"),
@@ -37931,19 +38399,19 @@ function parseConnectSubscriptions(value) {
37931
38399
  ];
37932
38400
  }
37933
38401
  async function readMachineId(env = process.env) {
37934
- const configDir = resolve29(env.XDG_CONFIG_HOME ?? resolve29(homedir13(), ".config"), "beeline");
37935
- const machineIdPath = resolve29(configDir, "machine-id");
38402
+ const configDir = resolve30(env.XDG_CONFIG_HOME ?? resolve30(homedir13(), ".config"), "beeline");
38403
+ const machineIdPath = resolve30(configDir, "machine-id");
37936
38404
  let machineId;
37937
38405
  let machineName = hostname2();
37938
38406
  try {
37939
- const existing = await readFile12(machineIdPath, "utf8");
38407
+ const existing = await readFile13(machineIdPath, "utf8");
37940
38408
  machineId = existing.trim();
37941
38409
  if (!/^[0-9a-f-]{32,}$/.test(machineId))
37942
38410
  throw new Error("invalid persisted machine id");
37943
38411
  } catch {
37944
38412
  machineId = randomUUID6();
37945
38413
  try {
37946
- await mkdir20(configDir, { recursive: true, mode: 448 });
38414
+ await mkdir21(configDir, { recursive: true, mode: 448 });
37947
38415
  await writeFile14(machineIdPath, `${machineId}
37948
38416
  `, { mode: 384 });
37949
38417
  } catch {
@@ -38005,7 +38473,7 @@ async function installCurrentRelease(fetchImpl) {
38005
38473
  });
38006
38474
  await activateRelease(layout, releaseId);
38007
38475
  return {
38008
- binary: resolve29(layout.binDir, "beeline"),
38476
+ binary: resolve30(layout.binDir, "beeline"),
38009
38477
  version: published.version ?? releaseId
38010
38478
  };
38011
38479
  }
@@ -38028,7 +38496,7 @@ function providerEnvironment(selection) {
38028
38496
  };
38029
38497
  }
38030
38498
  async function writePrivateJson(path, value) {
38031
- await mkdir20(dirname19(path), { recursive: true, mode: 448 });
38499
+ await mkdir21(dirname20(path), { recursive: true, mode: 448 });
38032
38500
  await writeFile14(path, `${JSON.stringify(value, null, 2)}
38033
38501
  `, { mode: 384 });
38034
38502
  await chmod7(path, 384);
@@ -38037,8 +38505,8 @@ async function writeProviderEnv(selection, agentPubkey) {
38037
38505
  const values = providerEnvironment(selection);
38038
38506
  if (Object.keys(values).length === 0)
38039
38507
  return void 0;
38040
- const path = resolve29(defaultSupervisorRoot(process.env), "beeline", "connect", `${agentPubkey}.env`);
38041
- await mkdir20(dirname19(path), { recursive: true, mode: 448 });
38508
+ const path = resolve30(defaultSupervisorRoot(process.env), "beeline", "connect", `${agentPubkey}.env`);
38509
+ await mkdir21(dirname20(path), { recursive: true, mode: 448 });
38042
38510
  const contents = Object.entries(values).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join("\n");
38043
38511
  await writeFile14(path, `${contents}
38044
38512
  `, { mode: 384 });
@@ -38080,7 +38548,7 @@ async function brassSpinner(message, action, completion) {
38080
38548
  }
38081
38549
  }
38082
38550
  async function runConnectCommand(code, options = {}) {
38083
- if (!stdin3.isTTY || !stdout4.isTTY) {
38551
+ if (!isInteractiveConnectTerminal()) {
38084
38552
  throw new Error("`usebeeline connect` needs an interactive terminal");
38085
38553
  }
38086
38554
  const restoreRails = paintWizardBrass();
@@ -38107,7 +38575,7 @@ async function runConnectWizard(code, fetchImpl, eventSubscriptions, accessPolic
38107
38575
  await finishConnectedAgentPairing(baseUrl, pairingCode, grant.workspace_joined, eventSubscriptions, fetchImpl);
38108
38576
  const installedRelease = await brassSpinner("Installing the Beeline daemon\u2026", () => installCurrentRelease(fetchImpl), (release) => `Installed Beeline helper ${release.version}`);
38109
38577
  const llmEnvFile = await writeProviderEnv(selection, grant.agent_pubkey);
38110
- const grantPath = resolve29(defaultSupervisorRoot(process.env), "beeline", "connect", `grant-${process.pid}-${Date.now()}.json`);
38578
+ const grantPath = resolve30(defaultSupervisorRoot(process.env), "beeline", "connect", `grant-${process.pid}-${Date.now()}.json`);
38111
38579
  await writePrivateJson(grantPath, {
38112
38580
  agentSecretKey: grant.agent_secret_key,
38113
38581
  bodySecretKey: grant.body_secret_key,
@@ -38192,18 +38660,18 @@ async function runConnectFinishCommand(path) {
38192
38660
  throw new Error("connect-finish may run only from the canonical installed Beeline launcher");
38193
38661
  }
38194
38662
  try {
38195
- const grant = JSON.parse(await readFile12(resolve29(path), "utf8"));
38663
+ const grant = JSON.parse(await readFile13(resolve30(path), "utf8"));
38196
38664
  if (!isDevicePairingGrant(grant))
38197
38665
  throw new Error("device connection grant is invalid");
38198
38666
  const connected = await completeDevicePairing(grant);
38199
- await unlink5(resolve29(path));
38200
- const providerEnv = grant.llmEnvFile ? await readFile12(grant.llmEnvFile, "utf8").catch(() => "") : "";
38667
+ await unlink5(resolve30(path));
38668
+ const providerEnv = grant.llmEnvFile ? await readFile13(grant.llmEnvFile, "utf8").catch(() => "") : "";
38201
38669
  const apiKey = (/^OPENROUTER_API_KEY=(\S+)/m.exec(providerEnv)?.[1] ?? "").replace(/^["']|["']$/g, "");
38202
38670
  const model = openRouterModelId(grant.model, { OPENROUTER_API_KEY: apiKey });
38203
38671
  if (model) {
38204
38672
  const decision2 = await resolveOpenRouterRouting({
38205
38673
  model,
38206
- cacheDir: openRouterRoutingCacheDir(dirname19(connected.configPath)),
38674
+ cacheDir: openRouterRoutingCacheDir(dirname20(connected.configPath)),
38207
38675
  ...apiKey ? { apiKey } : {},
38208
38676
  probeTimeoutMs: 1e4
38209
38677
  });
@@ -38221,16 +38689,16 @@ async function runConnectFinishCommand(path) {
38221
38689
  init_self_update();
38222
38690
 
38223
38691
  // apps/body/dist/daemon-failure.js
38224
- import { mkdir as mkdir21, readFile as readFile13, rename as rename8, rm as rm8, writeFile as writeFile15 } from "node:fs/promises";
38225
- import { dirname as dirname20, resolve as resolve30 } from "node:path";
38692
+ import { mkdir as mkdir22, readFile as readFile14, rename as rename8, rm as rm8, writeFile as writeFile15 } from "node:fs/promises";
38693
+ import { dirname as dirname21, resolve as resolve31 } from "node:path";
38226
38694
  var DAEMON_FAILURE_LIMIT = 3;
38227
38695
  var DAEMON_FAILURE_WINDOW_MS = 5 * 6e4;
38228
38696
  function daemonFailurePath(runtimeDir) {
38229
- return resolve30(runtimeDir, "daemon-distress.json");
38697
+ return resolve31(runtimeDir, "daemon-distress.json");
38230
38698
  }
38231
38699
  async function readFailureRecord(runtimeDir) {
38232
38700
  try {
38233
- const value = JSON.parse(await readFile13(daemonFailurePath(runtimeDir), "utf8"));
38701
+ const value = JSON.parse(await readFile14(daemonFailurePath(runtimeDir), "utf8"));
38234
38702
  if (value.version !== 1 || !Array.isArray(value.failures) || value.failures.some((failure2) => typeof failure2 !== "number") || typeof value.lastError !== "string") {
38235
38703
  return void 0;
38236
38704
  }
@@ -38242,7 +38710,7 @@ async function readFailureRecord(runtimeDir) {
38242
38710
  async function writeFailureRecord(runtimeDir, record3) {
38243
38711
  const path = daemonFailurePath(runtimeDir);
38244
38712
  const staged = `${path}.${process.pid}.tmp`;
38245
- await mkdir21(dirname20(path), { recursive: true, mode: 448 });
38713
+ await mkdir22(dirname21(path), { recursive: true, mode: 448 });
38246
38714
  await writeFile15(staged, `${JSON.stringify(record3, null, 2)}
38247
38715
  `, { mode: 384 });
38248
38716
  await rename8(staged, path);
@@ -38265,9 +38733,9 @@ async function clearDaemonStartFailures(runtimeDir) {
38265
38733
  }
38266
38734
 
38267
38735
  // apps/body/dist/update-functional-probe.js
38268
- import { mkdir as mkdir22, rm as rm9 } from "node:fs/promises";
38736
+ import { mkdir as mkdir23, rm as rm9 } from "node:fs/promises";
38269
38737
  import { homedir as homedir14 } from "node:os";
38270
- import { resolve as resolve31 } from "node:path";
38738
+ import { resolve as resolve32 } from "node:path";
38271
38739
  var UPDATE_PROBE_SESSION_TIMEOUT_MS = 1e4;
38272
38740
  var UPDATE_PROBE_SESSION_OPEN_TIMEOUT_MS = 2e4;
38273
38741
  var UPDATE_PROBE_TURN_TIMEOUT_MS = 45e3;
@@ -38387,11 +38855,11 @@ async function runUpdateFunctionalProbe(input) {
38387
38855
  modelAnswerReason: `${detail} (the current release has the same host sandbox failure)`
38388
38856
  };
38389
38857
  }
38390
- const root = input.probeRoot ?? resolve31(input.runtimeDir, "update-functional-probe");
38391
- const cwd = resolve31(root, "checkout");
38392
- const homeRoot = resolve31(root, "agent-home");
38858
+ const root = input.probeRoot ?? resolve32(input.runtimeDir, "update-functional-probe");
38859
+ const cwd = resolve32(root, "checkout");
38860
+ const homeRoot = resolve32(root, "agent-home");
38393
38861
  await rm9(root, { recursive: true, force: true });
38394
- await mkdir22(cwd, { recursive: true, mode: 448 });
38862
+ await mkdir23(cwd, { recursive: true, mode: 448 });
38395
38863
  let client;
38396
38864
  try {
38397
38865
  const agentEnv = {
@@ -38421,7 +38889,7 @@ async function runUpdateFunctionalProbe(input) {
38421
38889
  const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
38422
38890
  const operatorHome = input.config.operatorHome ?? homedir14();
38423
38891
  const homeStateDirs = harnessHomeStateDirs(harnessLabel, agentEnv.HOME ?? operatorHome);
38424
- await Promise.all(homeStateDirs.map((dir) => mkdir22(dir, { recursive: true })));
38892
+ await Promise.all(homeStateDirs.map((dir) => mkdir23(dir, { recursive: true })));
38425
38893
  spawnCommand = wrapAgentCommand({
38426
38894
  bwrapPath: input.config.bwrapPath,
38427
38895
  spec: {
@@ -38571,7 +39039,7 @@ async function runUpdateFunctionalProbe(input) {
38571
39039
 
38572
39040
  // apps/body/dist/current-release-probe.js
38573
39041
  import { spawn as spawn12 } from "node:child_process";
38574
- import { dirname as dirname21, join as join14 } from "node:path";
39042
+ import { dirname as dirname22, join as join14 } from "node:path";
38575
39043
  init_self_update();
38576
39044
  var CURRENT_RELEASE_PROBE_TIMEOUT_MS = 12e4;
38577
39045
  var UPDATE_PROBE_COMMAND = "update-probe";
@@ -38620,7 +39088,7 @@ async function probeReleaseInSubprocess(input) {
38620
39088
  return { kind: "unavailable", reason: `release ${input.releaseId} has no runnable CLI entrypoint` };
38621
39089
  }
38622
39090
  const timeoutMs = input.timeoutMs ?? CURRENT_RELEASE_PROBE_TIMEOUT_MS;
38623
- return new Promise((resolve35) => {
39091
+ return new Promise((resolve36) => {
38624
39092
  const child = spawn12(input.execPath ?? process.execPath, [entrypoint, UPDATE_PROBE_COMMAND, "--config", input.runtimeConfigPath], { env: input.env ?? process.env, stdio: ["ignore", "pipe", "pipe"] });
38625
39093
  let stdout6 = "";
38626
39094
  let stderr = "";
@@ -38630,7 +39098,7 @@ async function probeReleaseInSubprocess(input) {
38630
39098
  return;
38631
39099
  settled = true;
38632
39100
  clearTimeout(timer);
38633
- resolve35(outcome);
39101
+ resolve36(outcome);
38634
39102
  };
38635
39103
  const timer = setTimeout(() => {
38636
39104
  child.kill("SIGKILL");
@@ -38677,7 +39145,7 @@ async function runUpdateProbeCommand(args, options = {}) {
38677
39145
  const runtime = await readRuntimeRecord(configPath);
38678
39146
  const agent = runtimeAgentCommand(runtime);
38679
39147
  const config = loadBodyConfig({
38680
- workspaceRoot: join14(dirname21(configPath), "workspace"),
39148
+ workspaceRoot: join14(dirname22(configPath), "workspace"),
38681
39149
  llmEnvFile: runtime.llmEnvFile,
38682
39150
  env: { ...env, BUZZ_AGENT_BIN: agent.command, BUZZ_DEV_MCP_BIN: runtime.mcpBinary },
38683
39151
  agent
@@ -38695,7 +39163,7 @@ async function runUpdateProbeCommand(args, options = {}) {
38695
39163
  }
38696
39164
  const layout = beelineInstallLayout(env);
38697
39165
  const releaseId = (layout && await activeReleaseId(layout).catch(() => void 0)) ?? "unknown";
38698
- const runtimeDir = dirname21(configPath);
39166
+ const runtimeDir = dirname22(configPath);
38699
39167
  const outcome = await probeOutcome(() => (options.probe ?? runUpdateFunctionalProbe)({
38700
39168
  config,
38701
39169
  runtimeDir,
@@ -38710,8 +39178,8 @@ async function runUpdateProbeCommand(args, options = {}) {
38710
39178
  }
38711
39179
 
38712
39180
  // apps/body/dist/release-status.js
38713
- import { readFile as readFile14, readdir as readdir6, rename as rename9, writeFile as writeFile16 } from "node:fs/promises";
38714
- import { resolve as resolve32 } from "node:path";
39181
+ import { readFile as readFile15, readdir as readdir6, rename as rename9, writeFile as writeFile16 } from "node:fs/promises";
39182
+ import { resolve as resolve33 } from "node:path";
38715
39183
  var DAEMON_RELEASE_STATUS_FILE = "release-status.json";
38716
39184
  var RELEASE_VERSION = /^v\d+\.\d+\.\d+$/;
38717
39185
  var SOURCE_SHA = /^[0-9a-f]{7,64}$/;
@@ -38728,7 +39196,7 @@ async function writeDaemonReleaseStatus(runtimeDir, agentPubkey, identity, optio
38728
39196
  pid: options.pid ?? process.pid,
38729
39197
  readyAt: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
38730
39198
  };
38731
- const target = resolve32(runtimeDir, DAEMON_RELEASE_STATUS_FILE);
39199
+ const target = resolve33(runtimeDir, DAEMON_RELEASE_STATUS_FILE);
38732
39200
  const temporary = `${target}.${status.pid}.tmp`;
38733
39201
  await writeFile16(temporary, `${JSON.stringify(status, null, 2)}
38734
39202
  `, { mode: 384 });
@@ -38738,7 +39206,7 @@ async function writeDaemonReleaseStatus(runtimeDir, agentPubkey, identity, optio
38738
39206
 
38739
39207
  // apps/body/dist/scratch-sweep.js
38740
39208
  import { lstat as lstat5, readdir as readdir7, rmdir, unlink as unlink6 } from "node:fs/promises";
38741
- import { resolve as resolve33 } from "node:path";
39209
+ import { resolve as resolve34 } from "node:path";
38742
39210
  var DEFAULT_SCRATCH_TTL_HOURS = 72;
38743
39211
  var NEVER_SWEEP_SUBDIR_NAMES = new Set(HOME_SUBDIRS.filter((name) => name !== "tmp"));
38744
39212
  function scratchTtlMs(env = process.env) {
@@ -38747,7 +39215,7 @@ function scratchTtlMs(env = process.env) {
38747
39215
  return (Number.isFinite(hours) && hours > 0 ? hours : DEFAULT_SCRATCH_TTL_HOURS) * 60 * 60 * 1e3;
38748
39216
  }
38749
39217
  async function discoverAttachScratchRoots(runtimeDir) {
38750
- const roomsDir = resolve33(runtimeDir, "rooms");
39218
+ const roomsDir = resolve34(runtimeDir, "rooms");
38751
39219
  let entries;
38752
39220
  try {
38753
39221
  entries = await readdir7(roomsDir, { withFileTypes: true });
@@ -38758,7 +39226,7 @@ async function discoverAttachScratchRoots(runtimeDir) {
38758
39226
  for (const entry of entries) {
38759
39227
  if (!entry.isDirectory())
38760
39228
  continue;
38761
- const home = resolve33(roomsDir, entry.name, "agent-home");
39229
+ const home = resolve34(roomsDir, entry.name, "agent-home");
38762
39230
  const stats = await lstat5(home).catch(() => void 0);
38763
39231
  if (stats?.isDirectory())
38764
39232
  roots.push(home);
@@ -38777,7 +39245,7 @@ async function removeStaleFiles(dir, cutoffMs, protectNamesHere) {
38777
39245
  for (const entry of entries) {
38778
39246
  if (protectNamesHere && NEVER_SWEEP_SUBDIR_NAMES.has(entry.name))
38779
39247
  continue;
38780
- const path = resolve33(dir, entry.name);
39248
+ const path = resolve34(dir, entry.name);
38781
39249
  const stats = await lstat5(path).catch(() => void 0);
38782
39250
  if (!stats || stats.isSymbolicLink())
38783
39251
  continue;
@@ -38865,7 +39333,7 @@ var DaemonExitError = class extends Error {
38865
39333
  };
38866
39334
  async function runStoredDaemon(pathOrPointer) {
38867
39335
  const configPath = await resolveRuntimeConfigPath(pathOrPointer);
38868
- daemonFailureRuntimeDir = dirname22(configPath);
39336
+ daemonFailureRuntimeDir = dirname23(configPath);
38869
39337
  const accessMigration = await migrateRuntimeRecordAccessPolicy(configPath);
38870
39338
  let runtime = accessMigration.runtime;
38871
39339
  if (!runtime.transport) {
@@ -38884,7 +39352,7 @@ async function runStoredDaemon(pathOrPointer) {
38884
39352
  BUZZ_DEV_MCP_BIN: runtime.mcpBinary
38885
39353
  };
38886
39354
  const config = loadBodyConfig({
38887
- workspaceRoot: resolve34(dirname22(configPath), "workspace"),
39355
+ workspaceRoot: resolve35(dirname23(configPath), "workspace"),
38888
39356
  llmEnvFile: runtime.llmEnvFile,
38889
39357
  env,
38890
39358
  agent
@@ -38919,7 +39387,7 @@ async function runStoredDaemon(pathOrPointer) {
38919
39387
  const stop = () => controller.abort();
38920
39388
  process.once("SIGINT", stop);
38921
39389
  process.once("SIGTERM", stop);
38922
- const runtimeDir = dirname22(configPath);
39390
+ const runtimeDir = dirname23(configPath);
38923
39391
  const layout = beelineInstallLayout(process.env);
38924
39392
  const notifier = new SystemdNotifier();
38925
39393
  let rollbackAlertDrain;
@@ -38967,10 +39435,27 @@ async function runStoredDaemon(pathOrPointer) {
38967
39435
  scratchSweepTimer.unref();
38968
39436
  let ready = false;
38969
39437
  let connectorLoop;
39438
+ let catalogRefresh;
39439
+ const refreshCatalog = () => {
39440
+ catalogRefresh ??= syncAgentModelCatalog({
39441
+ api: daemonApi,
39442
+ agent,
39443
+ agentEnv: config.agentEnv,
39444
+ agentId: runtime.agent.publicKey,
39445
+ workspaceId: runtime.communityId,
39446
+ runtimeDir,
39447
+ ...runtime.modelSelection ? { runtimeSelection: runtime.modelSelection } : {},
39448
+ force: true
39449
+ }).then(() => void 0).finally(() => {
39450
+ catalogRefresh = void 0;
39451
+ });
39452
+ return catalogRefresh;
39453
+ };
38970
39454
  let stoppingStatus = "daemon stopped";
38971
39455
  try {
38972
39456
  const core = new ThinDaemonCore(runtime, configPath, config, {
38973
39457
  daemonApi,
39458
+ onConfigChanged: refreshCatalog,
38974
39459
  onHiccupRestart: (attempt) => {
38975
39460
  const delay = hiccupBackoffMs(attempt);
38976
39461
  console.warn(`[thin-core] hiccup restart attempt ${attempt}; exiting so systemd can start a fresh helper`);
@@ -39057,6 +39542,7 @@ async function runStoredDaemon(pathOrPointer) {
39057
39542
  agentId: runtime.agent.publicKey,
39058
39543
  log: (message) => console.log(`[body] connector: ${message}`)
39059
39544
  });
39545
+ daemonApi.setConnectorAssignmentListener(() => connectorLoop?.wake());
39060
39546
  connectorLoop.start();
39061
39547
  },
39062
39548
  onProgress: async (status) => {
@@ -39123,7 +39609,7 @@ async function main() {
39123
39609
  const roomId = roomFlag >= 0 ? args[roomFlag + 1] : void 0;
39124
39610
  if (!configPath || !roomId)
39125
39611
  throw new Error("corner-read-token requires --config and --room");
39126
- const activated = await activateDaemonTransport(resolve34(configPath));
39612
+ const activated = await activateDaemonTransport(resolve35(configPath));
39127
39613
  if (!activated)
39128
39614
  throw new Error("corner-read-token requires monolith transport");
39129
39615
  const credential = await activated.client.execute("getRoomGitHubToken", { roomId });
@@ -39177,14 +39663,14 @@ async function main() {
39177
39663
  }
39178
39664
  if (!configPath && agentPubkey) {
39179
39665
  const configs = await findAgentRuntimeConfigPaths(process.env, process.cwd());
39180
- configPath = configs.find((candidate) => dirname22(candidate).endsWith(agentPubkey));
39666
+ configPath = configs.find((candidate) => dirname23(candidate).endsWith(agentPubkey));
39181
39667
  }
39182
39668
  if (!configPath && agentPubkey) {
39183
39669
  throw new DaemonExitError(`unknown agent ${agentPubkey}: no durable runtime exists; refusing systemd restart loop`, UNKNOWN_AGENT_EXIT_STATUS);
39184
39670
  }
39185
39671
  if (!configPath)
39186
39672
  throw new Error("daemon requires --config <runtime.json> or --agent <pubkey>");
39187
- await runStoredDaemon(resolve34(configPath));
39673
+ await runStoredDaemon(resolve35(configPath));
39188
39674
  return;
39189
39675
  }
39190
39676
  if (command === "update") {
@@ -39205,7 +39691,7 @@ async function main() {
39205
39691
  if (!agentPubkey)
39206
39692
  throw new Error("stop requires --agent <pubkey>");
39207
39693
  const configs = await findAgentRuntimeConfigPaths(process.env, process.cwd());
39208
- const configPath = configs.find((candidate) => dirname22(candidate).endsWith(agentPubkey));
39694
+ const configPath = configs.find((candidate) => dirname23(candidate).endsWith(agentPubkey));
39209
39695
  if (!configPath)
39210
39696
  throw new Error(`no stored runtime found for agent ${agentPubkey}`);
39211
39697
  const runtime = await readRuntimeRecord(configPath);