usebeeline 0.0.121 → 0.0.122

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 +1083 -655
  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,
@@ -16226,8 +16236,8 @@ async function syncAgentModelCatalog(input) {
16226
16236
  }
16227
16237
 
16228
16238
  // 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";
16239
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "node:fs";
16240
+ import { dirname as dirname4, join as join3 } from "node:path";
16231
16241
 
16232
16242
  // apps/body/dist/google-workspace-client.js
16233
16243
  function credentialsTokenSource(credentials) {
@@ -16601,14 +16611,170 @@ function googleWorkspaceClient(tokenSource, transport = defaultGoogleApiTranspor
16601
16611
  };
16602
16612
  }
16603
16613
 
16614
+ // apps/body/dist/connector-google.js
16615
+ var GOOGLE_TOOL_SCOPES = {
16616
+ "google-gmail": [
16617
+ "https://www.googleapis.com/auth/gmail.send",
16618
+ "https://www.googleapis.com/auth/gmail.readonly",
16619
+ "https://www.googleapis.com/auth/gmail.compose"
16620
+ ],
16621
+ "google-calendar": ["https://www.googleapis.com/auth/calendar.events", "https://www.googleapis.com/auth/calendar.readonly"],
16622
+ "google-drive": ["https://www.googleapis.com/auth/drive.readonly"],
16623
+ "google-youtube": [
16624
+ "https://www.googleapis.com/auth/youtube.readonly",
16625
+ "https://www.googleapis.com/auth/yt-analytics.readonly"
16626
+ ]
16627
+ };
16628
+ function isGoogleToolConnectorType(type) {
16629
+ return type in GOOGLE_TOOL_SCOPES;
16630
+ }
16631
+ async function readGoogleCredentialsFromVault(mcp) {
16632
+ if (!mcp)
16633
+ return { source: "unavailable", reason: "no Trusty Squire connector is paired" };
16634
+ let raw;
16635
+ try {
16636
+ raw = await mcp.call("google_oauth_credentials", {});
16637
+ } catch (error) {
16638
+ const detail = describe(error);
16639
+ return {
16640
+ source: "unavailable",
16641
+ reason: `Squire has no Google OAuth grant on record yet (connect your Google account in Squire first: ${detail})`
16642
+ };
16643
+ }
16644
+ const record3 = raw && typeof raw === "object" ? raw : {};
16645
+ const inner = record3.credentials && typeof record3.credentials === "object" ? record3.credentials : record3;
16646
+ const accessToken = inner.accessToken ?? inner.access_token;
16647
+ if (!accessToken || typeof accessToken !== "string") {
16648
+ return { source: "unavailable", reason: "Squire returned a Google grant without an access token" };
16649
+ }
16650
+ return {
16651
+ source: "squire",
16652
+ credentials: {
16653
+ accessToken,
16654
+ refreshToken: typeof record3.refreshToken === "string" ? record3.refreshToken : typeof record3.refresh_token === "string" ? record3.refresh_token : void 0,
16655
+ expiresAt: typeof record3.expiresAt === "number" ? record3.expiresAt : void 0,
16656
+ accountEmail: typeof record3.accountEmail === "string" ? record3.accountEmail : typeof record3.email === "string" ? record3.email : void 0
16657
+ }
16658
+ };
16659
+ }
16660
+ function manualGoogleCredentialsSearchPaths(home) {
16661
+ return [join3(home, "google-credentials.json")];
16662
+ }
16663
+ function loadManualGoogleCredentials(home, env = process.env) {
16664
+ if (env.BEELINE_GOOGLE_ACCESS_TOKEN) {
16665
+ return {
16666
+ source: "manual",
16667
+ credentials: { accessToken: env.BEELINE_GOOGLE_ACCESS_TOKEN }
16668
+ };
16669
+ }
16670
+ for (const path of manualGoogleCredentialsSearchPaths(home)) {
16671
+ try {
16672
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
16673
+ if (typeof parsed.accessToken === "string" || typeof parsed.access_token === "string") {
16674
+ return {
16675
+ source: "manual",
16676
+ credentials: {
16677
+ accessToken: parsed.accessToken ?? parsed.access_token,
16678
+ refreshToken: typeof parsed.refreshToken === "string" ? parsed.refreshToken : void 0,
16679
+ expiresAt: typeof parsed.expiresAt === "number" ? parsed.expiresAt : void 0,
16680
+ accountEmail: typeof parsed.accountEmail === "string" ? parsed.accountEmail : void 0
16681
+ }
16682
+ };
16683
+ }
16684
+ } catch {
16685
+ }
16686
+ }
16687
+ return {
16688
+ source: "manual-missing",
16689
+ 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.`
16690
+ };
16691
+ }
16692
+ function persistManualGoogleCredentials(home, credentials) {
16693
+ const path = manualGoogleCredentialsSearchPaths(home)[0];
16694
+ mkdirSync2(dirname4(path), { recursive: true, mode: 448 });
16695
+ writeFileSync(path, `${JSON.stringify({
16696
+ accessToken: credentials.accessToken,
16697
+ ...credentials.refreshToken ? { refreshToken: credentials.refreshToken } : {},
16698
+ ...credentials.expiresAt ? { expiresAt: credentials.expiresAt } : {},
16699
+ ...credentials.accountEmail ? { accountEmail: credentials.accountEmail } : {}
16700
+ })}
16701
+ `, { encoding: "utf8", mode: 384 });
16702
+ return path;
16703
+ }
16704
+ var step = (label, status, extra) => ({
16705
+ label,
16706
+ status,
16707
+ ...extra
16708
+ });
16709
+ function outputTail(text2, maxChars = 800) {
16710
+ const trimmed = text2.trim();
16711
+ return trimmed.length <= maxChars ? trimmed : `\u2026${trimmed.slice(-maxChars)}`;
16712
+ }
16713
+ async function installGoogleTool(options) {
16714
+ const steps = [step("helper reached", "done")];
16715
+ const emit = () => options.onProgress?.([...steps]);
16716
+ const push = (next) => {
16717
+ steps.push(next);
16718
+ emit();
16719
+ };
16720
+ const fail = (label, reason, output) => {
16721
+ push(step(label, "failed", { reason, ...output ? { output } : {} }));
16722
+ return { status: "error", steps, errorMessage: reason };
16723
+ };
16724
+ emit();
16725
+ if (!isGoogleToolConnectorType(options.connectorType)) {
16726
+ return fail("connector type", `${options.connectorType} is not a Google tool connector`);
16727
+ }
16728
+ push(step("Google credentials resolved", "running", { output: "resolving\u2026" }));
16729
+ const resolved = options.resolvedCredentials ? await options.resolvedCredentials : options.resolveCredentials ? await options.resolveCredentials() : await (async () => {
16730
+ const oneClick = await readGoogleCredentialsFromVault(options.squire);
16731
+ if (oneClick.source === "squire")
16732
+ return oneClick;
16733
+ return loadManualGoogleCredentials(options.home, options.env);
16734
+ })();
16735
+ if (!("credentials" in resolved)) {
16736
+ const reason = resolved.reason;
16737
+ steps[1] = step("Google credentials resolved", "failed", { reason, output: outputTail(reason) });
16738
+ emit();
16739
+ return { status: "error", steps, errorMessage: reason };
16740
+ }
16741
+ steps[1] = step("Google credentials resolved", "done", {
16742
+ output: resolved.source === "squire" ? "one-click: read the Google grant from Trusty Squire" : "manual: local google-credentials.json"
16743
+ });
16744
+ emit();
16745
+ const client = options.client ?? googleWorkspaceClient(refreshableTokenSource(resolved.credentials, options.env?.BEELINE_GOOGLE_CLIENT_ID, options.env?.BEELINE_GOOGLE_CLIENT_SECRET));
16746
+ push(step("authorized with Google", "running", { output: "verifying the grant with Google\u2026" }));
16747
+ const verify = await client.verify();
16748
+ if (!verify.ok) {
16749
+ return fail("authorized with Google", verify.reason, outputTail(verify.reason));
16750
+ }
16751
+ steps[2] = step("authorized with Google", "done", {
16752
+ ...verify.account ? { output: `signed in as ${verify.account}` } : {}
16753
+ });
16754
+ emit();
16755
+ push(step("tools enabled", "done", {
16756
+ output: `${GOOGLE_TOOL_SCOPES[options.connectorType].length} Google scopes granted`
16757
+ }));
16758
+ persistManualGoogleCredentials(options.home, resolved.credentials);
16759
+ return {
16760
+ status: "connected",
16761
+ steps,
16762
+ ...verify.account ? { signedInAs: verify.account } : {}
16763
+ };
16764
+ }
16765
+ function describe(error) {
16766
+ return error instanceof Error ? error.message : String(error);
16767
+ }
16768
+
16604
16769
  // apps/body/dist/connector-squire.js
16605
16770
  import { execFile, spawn as spawn5 } from "node:child_process";
16606
16771
  import { createHash as createHash2 } from "node:crypto";
16607
- import { lstatSync as lstatSync3, readFileSync as readFileSync3, realpathSync, rmSync } from "node:fs";
16772
+ import { lstatSync as lstatSync3, readFileSync as readFileSync4, realpathSync, rmSync } from "node:fs";
16608
16773
  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";
16774
+ import { basename as basename4, dirname as dirname5, join as join4, resolve as resolve7 } from "node:path";
16610
16775
  var SQUIRE_MCP_NAME = "@trusty-squire/mcp";
16611
- var SQUIRE_CONNECT_PACKAGE = `${SQUIRE_MCP_NAME}@latest`;
16776
+ var SQUIRE_CONNECT_VERSION = "1.1.16-rc.4";
16777
+ var SQUIRE_CONNECT_PACKAGE = `${SQUIRE_MCP_NAME}@${SQUIRE_CONNECT_VERSION}`;
16612
16778
  var activeConnectSession;
16613
16779
  function squireConnectSession() {
16614
16780
  return activeConnectSession;
@@ -16644,9 +16810,9 @@ function squireProfilePathIdentity(profileDir) {
16644
16810
  let candidate = absolute;
16645
16811
  for (; ; ) {
16646
16812
  try {
16647
- return join3(realpathSync.native(candidate), ...suffix.reverse());
16813
+ return join4(realpathSync.native(candidate), ...suffix.reverse());
16648
16814
  } catch {
16649
- const parent = dirname4(candidate);
16815
+ const parent = dirname5(candidate);
16650
16816
  if (parent === candidate)
16651
16817
  return absolute;
16652
16818
  suffix.push(basename4(candidate));
@@ -16656,20 +16822,14 @@ function squireProfilePathIdentity(profileDir) {
16656
16822
  }
16657
16823
  function squireProfileLockPath(profileDir = squireChromeProfileDir(), lockRoot = tmpdir2()) {
16658
16824
  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);
16825
+ return join4(lockRoot, `trusty-squire-profile-${digest}.lock`);
16666
16826
  }
16667
16827
  function squireForeignClaimAction(owner) {
16668
16828
  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
16829
  }
16670
16830
  function readLinuxStartTime(pid) {
16671
16831
  try {
16672
- const stat5 = readFileSync3(`/proc/${pid}/stat`, "utf8");
16832
+ const stat5 = readFileSync4(`/proc/${pid}/stat`, "utf8");
16673
16833
  const close = stat5.lastIndexOf(")");
16674
16834
  return close < 0 ? void 0 : stat5.slice(close + 2).split(" ")[19];
16675
16835
  } catch {
@@ -16688,8 +16848,8 @@ function lockOwnerIsAlive(owner) {
16688
16848
  }
16689
16849
  function readLockFileOwner(lockPath) {
16690
16850
  try {
16691
- const target = lstatSync3(lockPath).isDirectory() ? join3(lockPath, "owner.json") : lockPath;
16692
- const parsed = JSON.parse(readFileSync3(target, "utf8"));
16851
+ const target = lstatSync3(lockPath).isDirectory() ? join4(lockPath, "owner.json") : lockPath;
16852
+ const parsed = JSON.parse(readFileSync4(target, "utf8"));
16693
16853
  if (typeof parsed.host !== "string" || typeof parsed.pid !== "number")
16694
16854
  return void 0;
16695
16855
  return {
@@ -16727,10 +16887,10 @@ function reclaimSquireProfileClaim(options) {
16727
16887
  options?.log?.(`cleared dead trusty-squire on-disk browser claim (pid ${owner.pid} gone)`);
16728
16888
  return { kind: "reclaimed-dead", owner };
16729
16889
  }
16730
- var defaultShellRunner = (command, args) => new Promise((resolve35) => {
16890
+ var defaultShellRunner = (command, args) => new Promise((resolve36) => {
16731
16891
  execFile(command, [...args], { timeout: 12e4, maxBuffer: 4 * 1024 * 1024, encoding: "utf8" }, (error, stdout6, stderr) => {
16732
16892
  const code = error?.code;
16733
- resolve35({
16893
+ resolve36({
16734
16894
  code: typeof code === "number" ? code : error ? 1 : 0,
16735
16895
  stdout: String(stdout6 ?? ""),
16736
16896
  stderr: String(stderr ?? "")
@@ -16755,7 +16915,7 @@ function killConnectTree(child) {
16755
16915
  }
16756
16916
  child.kill();
16757
16917
  }
16758
- var defaultStreamedRunner = (command, args, env) => new Promise((resolve35) => {
16918
+ var defaultStreamedRunner = (command, args, env) => new Promise((resolve36) => {
16759
16919
  const child = spawn5(command, args, {
16760
16920
  stdio: ["ignore", "pipe", "pipe"],
16761
16921
  env: env ?? squireConnectProcessEnv(),
@@ -16764,10 +16924,12 @@ var defaultStreamedRunner = (command, args, env) => new Promise((resolve35) => {
16764
16924
  let stdout6 = "";
16765
16925
  let stderr = "";
16766
16926
  let resolved = false;
16927
+ let consumed = 0;
16928
+ let report;
16767
16929
  const safetyTimer = setTimeout(() => {
16768
16930
  if (!resolved) {
16769
16931
  resolved = true;
16770
- resolve35({ stdout: stdout6, stderr, pid: child.pid ?? void 0, signIn: void 0, abort: () => {
16932
+ resolve36({ stdout: stdout6, stderr, pid: child.pid ?? void 0, report, abort: () => {
16771
16933
  } });
16772
16934
  }
16773
16935
  killConnectTree(child);
@@ -16785,128 +16947,163 @@ var defaultStreamedRunner = (command, args, env) => new Promise((resolve35) => {
16785
16947
  const finish = (result) => {
16786
16948
  if (!resolved) {
16787
16949
  resolved = true;
16788
- resolve35(result);
16950
+ resolve36(result);
16789
16951
  }
16790
16952
  };
16791
16953
  const settle = (result) => {
16792
16954
  clearTimeout(safetyTimer);
16793
16955
  finish(result);
16794
16956
  };
16795
- const checkOutput = () => {
16796
- const signIn = parseConnectOutput(stdout6) ?? parseConnectOutput(stderr);
16797
- if (signIn) {
16798
- finish({ stdout: stdout6, stderr, signIn, abort });
16957
+ const consumeReports = (final) => {
16958
+ const pending = stdout6.slice(consumed);
16959
+ const lastNewline = pending.lastIndexOf("\n");
16960
+ if (lastNewline < 0 && !final)
16961
+ return;
16962
+ const complete = lastNewline < 0 ? pending : pending.slice(0, lastNewline);
16963
+ for (const line of complete.split("\n")) {
16964
+ if (line.trim() === "")
16965
+ continue;
16966
+ const parsed = parseConnectReport(line);
16967
+ if (parsed)
16968
+ report = parsed;
16969
+ }
16970
+ consumed += lastNewline < 0 ? pending.length : lastNewline + 1;
16971
+ };
16972
+ const publishSignIn = () => {
16973
+ consumeReports(false);
16974
+ if (report && isPublishableConnectReport(report)) {
16975
+ finish({ stdout: stdout6, stderr, pid: child.pid ?? void 0, report, abort });
16799
16976
  }
16800
16977
  };
16801
- const finalSignIn = () => parseConnectOutput(`${stdout6}
16802
- `) ?? parseConnectOutput(`${stderr}
16803
- `);
16804
16978
  child.stdout?.on("data", (chunk) => {
16805
16979
  stdout6 += String(chunk);
16806
- checkOutput();
16980
+ publishSignIn();
16807
16981
  });
16808
16982
  child.stderr?.on("data", (chunk) => {
16809
16983
  stderr += String(chunk);
16810
- checkOutput();
16811
16984
  });
16812
16985
  child.on("close", () => {
16986
+ consumeReports(true);
16813
16987
  settle({
16814
16988
  stdout: stdout6,
16815
16989
  stderr,
16816
16990
  pid: child.pid ?? void 0,
16817
- signIn: finalSignIn(),
16991
+ report,
16818
16992
  abort: () => {
16819
16993
  }
16820
16994
  });
16821
16995
  });
16822
16996
  child.on("error", () => {
16823
- settle({ stdout: stdout6, stderr, pid: child.pid ?? void 0, signIn: void 0, abort: () => {
16997
+ settle({ stdout: stdout6, stderr, pid: child.pid ?? void 0, report, abort: () => {
16824
16998
  } });
16825
16999
  });
16826
17000
  });
16827
- var step = (label, status, reason) => ({
17001
+ var step2 = (label, status, reason) => ({
16828
17002
  label,
16829
17003
  status,
16830
17004
  ...reason ? { reason } : {}
16831
17005
  });
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
- }
17006
+ function isRecord(value) {
17007
+ return value !== null && typeof value === "object" && !Array.isArray(value);
16841
17008
  }
16842
- function isForeignUrl(url) {
17009
+ function connectAccount(raw) {
17010
+ if (!isRecord(raw) || typeof raw.id !== "string")
17011
+ return null;
17012
+ return {
17013
+ id: raw.id,
17014
+ // An absent or unreadable `providers` is `null` (could not look), never
17015
+ // `[]` (looked and found nothing).
17016
+ providers: Array.isArray(raw.providers) ? raw.providers.filter((provider) => typeof provider === "string") : null
17017
+ };
17018
+ }
17019
+ function parseConnectReport(line) {
17020
+ let parsed;
16843
17021
  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";
17022
+ parsed = JSON.parse(line);
16851
17023
  } catch {
16852
- return true;
17024
+ return void 0;
16853
17025
  }
17026
+ if (!isRecord(parsed) || typeof parsed.state !== "string")
17027
+ return void 0;
17028
+ return {
17029
+ state: parsed.state,
17030
+ terminal: parsed.terminal === true,
17031
+ reason: typeof parsed.reason === "string" ? parsed.reason : null,
17032
+ sign_in_url: typeof parsed.sign_in_url === "string" ? parsed.sign_in_url : null,
17033
+ account: connectAccount(parsed.account),
17034
+ holder: parsed.holder,
17035
+ browser_location: parsed.browser_location
17036
+ };
16854
17037
  }
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
- }
17038
+ function isOutstandingSignIn(report) {
17039
+ if (report.terminal || report.state !== "needs-sign-in" || !report.sign_in_url)
17040
+ return false;
17041
+ const location = report.browser_location;
17042
+ if (!isRecord(location) || typeof location.kind !== "string")
17043
+ return false;
17044
+ return location.kind !== "none" && location.kind !== "unreachable";
17045
+ }
17046
+ function isPublishableConnectReport(report) {
17047
+ return report.terminal || isOutstandingSignIn(report);
17048
+ }
17049
+ var CONNECT_REASON_LINES = {
17050
+ provider_session_missing: "the bot's Chrome profile has no live provider session",
17051
+ requested_provider_missing: "the provider sign-in you asked to refresh did not complete",
17052
+ account_mismatch: "this machine is bound to a different account",
17053
+ profile_unverifiable: "the bot's Chrome profile could not be verified",
17054
+ install_expired: "the sign-in page expired before it was used",
17055
+ cached_cookie_evidence: "the shared profile already carried the session",
17056
+ run_failed: "the connect run failed before it could finish"
17057
+ };
17058
+ function holderLine(raw) {
17059
+ if (!isRecord(raw))
17060
+ return void 0;
17061
+ if (raw.kind === "other") {
17062
+ const pid = typeof raw.pid === "number" ? raw.pid : void 0;
17063
+ return pid === void 0 ? "another Trusty Squire session is using the browser" : squireForeignClaimAction({ host: hostname(), pid, startTime: null });
16872
17064
  }
16873
- if (carry !== void 0)
16874
- lines.push(carry);
16875
- return lines;
17065
+ if (raw.kind === "unknown")
17066
+ return "another process may be using the browser";
17067
+ return void 0;
16876
17068
  }
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);
17069
+ function connectBlockedLine(report) {
17070
+ if (report.state === "connected" || isOutstandingSignIn(report))
17071
+ return void 0;
17072
+ if (report.reason && CONNECT_REASON_LINES[report.reason]) {
17073
+ return CONNECT_REASON_LINES[report.reason];
16890
17074
  }
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 };
17075
+ const location = isRecord(report.browser_location) ? report.browser_location : void 0;
17076
+ if (location?.kind === "unreachable") {
17077
+ return "the sign-in page could not be shown on this machine";
16902
17078
  }
16903
- return void 0;
16904
- }
16905
- function parseConnectAlreadyConnected(output) {
16906
- return /Already connected \(/i.test(output) && /config refreshed/i.test(output);
17079
+ if (report.state === "needs-sign-in") {
17080
+ return "the connect run ended before the sign-in was completed";
17081
+ }
17082
+ const holder = holderLine(report.holder);
17083
+ if (holder)
17084
+ return holder;
17085
+ return `Trusty Squire reported the connect state "${report.state}"`;
16907
17086
  }
16908
- function withoutForceReloginHint(output) {
16909
- return output.split("\n").filter((line) => !/--force-relogin/.test(line)).join("\n").trim();
17087
+ function connectBrowserLocation(raw) {
17088
+ if (!isRecord(raw))
17089
+ return void 0;
17090
+ switch (raw.kind) {
17091
+ case "host_screen":
17092
+ return { kind: "host_screen" };
17093
+ case "virtual":
17094
+ return typeof raw.url === "string" ? { kind: "virtual", url: raw.url } : void 0;
17095
+ case "unreachable":
17096
+ return {
17097
+ kind: "unreachable",
17098
+ reason: typeof raw.reason === "string" ? raw.reason : "the sign-in page could not be shown"
17099
+ };
17100
+ case "none":
17101
+ return { kind: "none" };
17102
+ case "unknown":
17103
+ return typeof raw.reason === "string" ? { kind: "unknown", reason: raw.reason } : void 0;
17104
+ default:
17105
+ return void 0;
17106
+ }
16910
17107
  }
16911
17108
  async function installedSquireVersion(run2, spec = SQUIRE_CONNECT_PACKAGE, preferOnline = false) {
16912
17109
  const probe = await run2("npx", [
@@ -16919,7 +17116,7 @@ async function installedSquireVersion(run2, spec = SQUIRE_CONNECT_PACKAGE, prefe
16919
17116
  return version;
16920
17117
  }
16921
17118
  async function currentSquireRelease(run2) {
16922
- const probe = await run2("npm", ["view", SQUIRE_MCP_NAME, "version"]);
17119
+ const probe = await run2("npm", ["view", SQUIRE_CONNECT_PACKAGE, "version"]);
16923
17120
  if (probe.code !== 0)
16924
17121
  return void 0;
16925
17122
  return probe.stdout.match(/\d+\.\d+\.\d+[^\s]*/)?.[0];
@@ -16951,9 +17148,6 @@ async function resolveSquireConnectSpec(run2) {
16951
17148
  reResolved: true
16952
17149
  };
16953
17150
  }
16954
- function parseSignedInAs(output) {
16955
- return output.match(/signed in as ([^\s,;]+)/i)?.[1];
16956
- }
16957
17151
  async function installSquire(options) {
16958
17152
  const profileDir = options.profileDir ?? squireChromeProfileDir();
16959
17153
  const lockRoot = options.lockRoot ?? tmpdir2();
@@ -16961,14 +17155,14 @@ async function installSquire(options) {
16961
17155
  const streamRun = options.streamRun ?? defaultStreamedRunner;
16962
17156
  const log2 = options.log ?? (() => {
16963
17157
  });
16964
- const steps = [step("helper reached", "done")];
17158
+ const steps = [step2("helper reached", "done")];
16965
17159
  const emit = () => options.onProgress?.([...steps]);
16966
17160
  const push = (next) => {
16967
17161
  steps.push(next);
16968
17162
  emit();
16969
17163
  };
16970
17164
  const fail = (reason) => {
16971
- steps.push(step("waiting for sign-in", "pending"));
17165
+ steps.push(step2("waiting for sign-in", "pending"));
16972
17166
  emit();
16973
17167
  return { status: "error", steps, errorMessage: reason };
16974
17168
  };
@@ -16982,62 +17176,62 @@ async function installSquire(options) {
16982
17176
  ...previousPid !== void 0 ? { ourPids: [previousPid] } : {}
16983
17177
  });
16984
17178
  if (claim.kind === "blocked-foreign") {
16985
- push(step("trusty-squire installed", "failed", claim.action));
17179
+ push(step2("trusty-squire installed", "failed", claim.action));
16986
17180
  return fail(claim.action);
16987
17181
  }
16988
17182
  const resolution = await resolveSquireConnectSpec(run2);
16989
17183
  if (resolution.reResolved) {
16990
17184
  log2(`trusty-squire stale copy ${resolution.resolvedVersion ?? "unknown"} re-resolved against current release ${resolution.currentRelease}`);
16991
17185
  }
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
- }
17186
+ const install = await streamRun("npx", [...resolution.npxArgs, "connect", "--target=codex", "--json"], squireConnectProcessEnv(profileDir));
17187
+ const report = install.report;
17009
17188
  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"));
17189
+ if (!report) {
17190
+ const reason = "Trusty Squire did not report a connect result";
17191
+ push(step2("trusty-squire installed", "failed", reason));
17192
+ return fail(reason);
17193
+ }
17194
+ if (report.state === "needs-sign-in" && !report.sign_in_url) {
17195
+ const reason = "Trusty Squire reported a sign-in without a page";
17196
+ push(step2("trusty-squire installed", "failed", reason));
17197
+ return fail(reason);
17198
+ }
17199
+ if (report.state !== "connected" && !isOutstandingSignIn(report)) {
17200
+ const reason = connectBlockedLine(report) ?? "Trusty Squire could not connect";
17201
+ push(step2("trusty-squire installed", "failed", reason));
17202
+ return fail(reason);
17203
+ }
17204
+ push(step2(`trusty-squire${version ? ` ${version}` : ""} installed`, "done"));
17205
+ const location = connectBrowserLocation(report.browser_location);
17206
+ const signIn = isOutstandingSignIn(report) && report.sign_in_url ? {
17207
+ method: "streamed-page",
17208
+ url: report.sign_in_url,
17209
+ ...location ? { browserLocation: location } : {}
17210
+ } : void 0;
17211
+ push(step2("waiting for sign-in", signIn ? "pending" : "done"));
17015
17212
  const pair = await pairSquire(options.mcp, options.workspaceId);
17016
17213
  if (!pair.ok) {
17017
- push(step("paired to workspace", "failed", pair.reason));
17214
+ push(step2("paired to workspace", "failed", pair.reason));
17018
17215
  return {
17019
17216
  status: "installing",
17020
17217
  steps,
17021
- signIn,
17022
- ...version ? { squireVersion: version } : {},
17023
- ...signedInAs ? { signedInAs } : {}
17218
+ ...signIn ? { signIn } : {},
17219
+ ...version ? { squireVersion: version } : {}
17024
17220
  };
17025
17221
  }
17026
- push(step("paired to workspace", "done"));
17222
+ push(step2("paired to workspace", "done"));
17027
17223
  if (signIn) {
17028
17224
  return {
17029
17225
  status: "installing",
17030
17226
  steps,
17031
17227
  signIn,
17032
- ...version ? { squireVersion: version } : {},
17033
- ...signedInAs ? { signedInAs } : {}
17228
+ ...version ? { squireVersion: version } : {}
17034
17229
  };
17035
17230
  }
17036
17231
  return {
17037
17232
  status: "connected",
17038
17233
  steps,
17039
- ...version ? { squireVersion: version } : {},
17040
- ...signedInAs ? { signedInAs } : {}
17234
+ ...version ? { squireVersion: version } : {}
17041
17235
  };
17042
17236
  }
17043
17237
  async function pairSquire(mcp, _workspaceId) {
@@ -17115,174 +17309,11 @@ async function revokeGrants(mcp, ref) {
17115
17309
  failed += 1;
17116
17310
  else
17117
17311
  revoked += 1;
17118
- } catch {
17119
- failed += 1;
17120
- }
17121
- }
17122
- return { revoked, failed };
17123
- }
17124
-
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);
17312
+ } catch {
17313
+ failed += 1;
17314
+ }
17315
+ }
17316
+ return { revoked, failed };
17286
17317
  }
17287
17318
 
17288
17319
  // apps/body/dist/squire-mcp-client.js
@@ -17378,12 +17409,12 @@ var StdioSquireMcpClient = class {
17378
17409
  const child = this.child;
17379
17410
  if (!child)
17380
17411
  return Promise.reject(new Error("Squire MCP session is not running"));
17381
- return new Promise((resolve35, reject) => {
17412
+ return new Promise((resolve36, reject) => {
17382
17413
  const timer = setTimeout(() => {
17383
17414
  this.pending.delete(id);
17384
17415
  reject(new Error(`${method} timed out`));
17385
17416
  }, method === "initialize" ? INITIALIZE_TIMEOUT_MS : CALL_TIMEOUT_MS);
17386
- this.pending.set(id, { resolve: resolve35, reject, timer });
17417
+ this.pending.set(id, { resolve: resolve36, reject, timer });
17387
17418
  child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}
17388
17419
  `);
17389
17420
  });
@@ -17426,7 +17457,8 @@ function defaultSquireMcpClient() {
17426
17457
  }
17427
17458
 
17428
17459
  // apps/body/dist/connector-assignments.js
17429
- var CONNECTOR_POLL_INTERVAL_MS = 1e4;
17460
+ var CONNECTOR_POLL_INTERVAL_MS = 5 * 6e4;
17461
+ var CONNECT_WATCH_INTERVAL_MS = 2e3;
17430
17462
  var CEREMONY_EXPIRED = "the Trusty Squire sign-in page expired before it was used \xB7 tap Retry to open a new one";
17431
17463
  var ConnectorAssignmentLoop = class {
17432
17464
  agentId;
@@ -17441,6 +17473,8 @@ var ConnectorAssignmentLoop = class {
17441
17473
  schedule;
17442
17474
  cancel;
17443
17475
  timer;
17476
+ /** Armed only while this helper owns a live sign-in ceremony. */
17477
+ connectWatch;
17444
17478
  started = false;
17445
17479
  stopped = false;
17446
17480
  /** One install at a time per connector; other polls skip it. */
@@ -17476,12 +17510,56 @@ var ConnectorAssignmentLoop = class {
17476
17510
  void this.runOnce();
17477
17511
  this.timer = this.schedule(() => this.poll(), this.intervalMs);
17478
17512
  }
17513
+ /** Event-driven drain. The interval stays the recovery net. */
17514
+ wake() {
17515
+ if (this.stopped)
17516
+ return;
17517
+ void this.runOnce();
17518
+ }
17479
17519
  stop() {
17480
17520
  this.stopped = true;
17481
17521
  if (this.timer !== void 0) {
17482
17522
  this.cancel(this.timer);
17483
17523
  this.timer = void 0;
17484
17524
  }
17525
+ if (this.connectWatch !== void 0) {
17526
+ this.cancel(this.connectWatch);
17527
+ this.connectWatch = void 0;
17528
+ }
17529
+ }
17530
+ /**
17531
+ * Watch the sign-in this helper is holding open. The phone paints
17532
+ * `installing` for the whole ceremony and only a drain that reaches
17533
+ * Squire's already-connected short-circuit flips the row to `connected`;
17534
+ * nothing on the wire announces that the human finished, so the connect
17535
+ * process exiting is the signal. Drain on that, and the row settles on the
17536
+ * same cadence it always has instead of waiting out the recovery poll.
17537
+ */
17538
+ watchConnectSignIn() {
17539
+ if (this.stopped || this.connectWatch !== void 0)
17540
+ return;
17541
+ if (!this.connectCeremonyLive())
17542
+ return;
17543
+ this.connectWatch = this.schedule(() => this.checkConnectSignIn(), CONNECT_WATCH_INTERVAL_MS);
17544
+ }
17545
+ /** A ceremony of ours still worth waiting on: claimed, unspent, alive. */
17546
+ connectCeremonyLive() {
17547
+ const claim = squireConnectSession();
17548
+ if (!claim)
17549
+ return false;
17550
+ if (Date.now() - claim.claimedAt >= CONNECT_TIMEOUT_MS)
17551
+ return false;
17552
+ return isProcessAlive(claim.pid);
17553
+ }
17554
+ checkConnectSignIn() {
17555
+ this.connectWatch = void 0;
17556
+ if (this.stopped)
17557
+ return;
17558
+ if (this.connectCeremonyLive()) {
17559
+ this.connectWatch = this.schedule(() => this.checkConnectSignIn(), CONNECT_WATCH_INTERVAL_MS);
17560
+ return;
17561
+ }
17562
+ void this.runOnce();
17485
17563
  }
17486
17564
  /** One interval tick: poll, then re-arm. */
17487
17565
  poll() {
@@ -17548,8 +17626,6 @@ var ConnectorAssignmentLoop = class {
17548
17626
  const oneClick = await readGoogleCredentialsFromVault(this.squire());
17549
17627
  if (oneClick.source === "squire")
17550
17628
  return oneClick;
17551
- if (isSquireBrowserSessionFailure(oneClick.reason))
17552
- return oneClick;
17553
17629
  } catch (error) {
17554
17630
  this.log(`google one-click grant lookup failed: ${describe2(error)}`);
17555
17631
  }
@@ -17605,6 +17681,7 @@ var ConnectorAssignmentLoop = class {
17605
17681
  if (claim && !spent && isProcessAlive(claim.pid)) {
17606
17682
  if (!await this.rearmedByHuman(connectorId)) {
17607
17683
  this.log(`trusty-squire connect still waiting for sign-in (pid ${String(claim.pid)}); leaving it alone`);
17684
+ this.watchConnectSignIn();
17608
17685
  return;
17609
17686
  }
17610
17687
  this.log("trusty-squire re-pair requested; superseding the connect holding the browser");
@@ -17646,8 +17723,7 @@ var ConnectorAssignmentLoop = class {
17646
17723
  await this.api.execute("installConnector", {
17647
17724
  agentId: this.agentId,
17648
17725
  connectorId,
17649
- ...result.squireVersion ? { squireVersion: result.squireVersion } : {},
17650
- ...result.signedInAs ? { signedInAs: result.signedInAs } : {}
17726
+ ...result.squireVersion ? { squireVersion: result.squireVersion } : {}
17651
17727
  });
17652
17728
  await this.reportVault(connectorId);
17653
17729
  return;
@@ -17657,9 +17733,9 @@ var ConnectorAssignmentLoop = class {
17657
17733
  connectorId,
17658
17734
  steps: result.steps,
17659
17735
  signIn: result.signIn ?? null,
17660
- ...result.squireVersion ? { squireVersion: result.squireVersion } : {},
17661
- ...result.signedInAs ? { signedInAs: result.signedInAs } : {}
17736
+ ...result.squireVersion ? { squireVersion: result.squireVersion } : {}
17662
17737
  });
17738
+ this.watchConnectSignIn();
17663
17739
  }
17664
17740
  /** This connector's own row, or undefined when the server cannot answer. */
17665
17741
  async connectorRow(connectorId) {
@@ -23759,9 +23835,9 @@ function isHex32(input) {
23759
23835
  return true;
23760
23836
  }
23761
23837
  var verifiedSymbol = /* @__PURE__ */ Symbol("verified");
23762
- var isRecord = (obj) => obj instanceof Object;
23838
+ var isRecord2 = (obj) => obj instanceof Object;
23763
23839
  function validateEvent(event) {
23764
- if (!isRecord(event))
23840
+ if (!isRecord2(event))
23765
23841
  return false;
23766
23842
  if (typeof event.kind !== "number")
23767
23843
  return false;
@@ -26685,6 +26761,15 @@ var AGENT_REMOVED_CODE = "agent_removed";
26685
26761
  function isAgentRemovedError(error) {
26686
26762
  return error instanceof DaemonApiError && error.status === 403 && error.code === AGENT_REMOVED_CODE;
26687
26763
  }
26764
+ function membershipChange(event) {
26765
+ return {
26766
+ ...typeof event.roomId === "string" && event.roomId ? { roomId: event.roomId } : {},
26767
+ ...typeof event.parentRoomId === "string" ? { parentRoomId: event.parentRoomId } : {},
26768
+ ...typeof event.openedBy === "string" ? { openedBy: event.openedBy } : {},
26769
+ ...event.archived === true ? { archived: true } : {},
26770
+ ...event.removed === true ? { removed: true } : {}
26771
+ };
26772
+ }
26688
26773
  function endpoint(origin, path) {
26689
26774
  return new URL(path, `${origin}/`).toString();
26690
26775
  }
@@ -26712,6 +26797,8 @@ var DaemonApiClient = class {
26712
26797
  roomsChangedListener;
26713
26798
  configChangedListener;
26714
26799
  hiccupRestartListener;
26800
+ connectorAssignmentListener;
26801
+ cornerCompleteListener;
26715
26802
  constructor(baseUrl, daemonToken, agentId, fetchImpl = fetch, webSocketFactory = (url, protocols) => new wrapper_default(url, protocols)) {
26716
26803
  this.baseUrl = baseUrl;
26717
26804
  this.daemonToken = daemonToken;
@@ -26759,8 +26846,8 @@ var DaemonApiClient = class {
26759
26846
  };
26760
26847
  }
26761
26848
  /** 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. */
26849
+ * Room/corner memberships changed — a scoped event applies incrementally;
26850
+ * an unscoped wake (reconnect) still runs the recovery reconcile. */
26764
26851
  setRoomsChangedListener(listener) {
26765
26852
  this.roomsChangedListener = listener;
26766
26853
  }
@@ -26774,6 +26861,14 @@ var DaemonApiClient = class {
26774
26861
  setHiccupRestartListener(listener) {
26775
26862
  this.hiccupRestartListener = listener;
26776
26863
  }
26864
+ /** Connect / pending_ops drain wake. Never a catalog. */
26865
+ setConnectorAssignmentListener(listener) {
26866
+ this.connectorAssignmentListener = listener;
26867
+ }
26868
+ /** corner-complete on the subscribed corner — close now, poll is recovery. */
26869
+ setCornerCompleteListener(listener) {
26870
+ this.cornerCompleteListener = listener;
26871
+ }
26777
26872
  updateLiveCursor(roomId, cursor3) {
26778
26873
  const room = this.liveRooms.get(roomId);
26779
26874
  if (room && cursor3)
@@ -26815,6 +26910,7 @@ var DaemonApiClient = class {
26815
26910
  for (const roomId of this.liveRooms.keys())
26816
26911
  this.sendLiveSubscription(roomId);
26817
26912
  this.roomsChangedListener?.();
26913
+ this.connectorAssignmentListener?.();
26818
26914
  };
26819
26915
  socket.onmessage = (message) => {
26820
26916
  let value;
@@ -26827,7 +26923,15 @@ var DaemonApiClient = class {
26827
26923
  return;
26828
26924
  const event = value;
26829
26925
  if (event.type === "rooms-changed") {
26830
- this.roomsChangedListener?.();
26926
+ this.roomsChangedListener?.(membershipChange(event));
26927
+ return;
26928
+ }
26929
+ if (event.type === "corner-complete" && typeof event.roomId === "string") {
26930
+ this.cornerCompleteListener?.(event.roomId);
26931
+ return;
26932
+ }
26933
+ if (event.type === "connector-assignment") {
26934
+ this.connectorAssignmentListener?.();
26831
26935
  return;
26832
26936
  }
26833
26937
  if (event.type === "config-changed") {
@@ -26954,12 +27058,12 @@ async function activateDaemonTransport(path, fetchImpl = fetch) {
26954
27058
  }
26955
27059
 
26956
27060
  // apps/body/dist/room-runtime.js
26957
- import { execFile as execFile6 } from "node:child_process";
27061
+ import { execFile as execFile7 } from "node:child_process";
26958
27062
  import { createHash as createHash7 } from "node:crypto";
26959
27063
  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";
27064
+ import { mkdir as mkdir15, rm as rm5 } from "node:fs/promises";
27065
+ import { dirname as dirname13, resolve as resolve24 } from "node:path";
27066
+ import { promisify as promisify5 } from "node:util";
26963
27067
 
26964
27068
  // apps/body/dist/grant-runner.js
26965
27069
  import { execFile as execFile3 } from "node:child_process";
@@ -27297,9 +27401,10 @@ function sandboxMountPlan(spec) {
27297
27401
  ...spec.additionalWritablePaths ?? [],
27298
27402
  ...harnessState
27299
27403
  ] : (
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.
27404
+ // A Room keeps source files read-only. Its explicit capabilities are
27405
+ // limited to harness state, agent-private paths, and release-owned
27406
+ // generated state such as the repository's .codegraph index; callers
27407
+ // must name each path. /tmp remains private.
27303
27408
  [...spec.additionalWritablePaths ?? [], ...harnessState]
27304
27409
  ));
27305
27410
  const tmpRestores = normalize([
@@ -27426,8 +27531,8 @@ function detectBwrapSandbox(options = {}) {
27426
27531
  });
27427
27532
  const result = run2(probe.command, probe.args);
27428
27533
  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." : "";
27534
+ const detail = (result.stderr ?? "").trim().split("\n").filter(Boolean).pop() ?? `exit ${result.status}`;
27535
+ 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
27536
  return {
27432
27537
  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
27538
  };
@@ -27501,6 +27606,7 @@ var GRANT_COMMAND_OUTPUT_CAP_BYTES = 64 * 1024;
27501
27606
  var ARGV_MAX_WORDS = 256;
27502
27607
  var ARGV_WORD_MAX_LENGTH = 4096;
27503
27608
  var WRITE_REFUSED = /Read-only file system|EROFS/;
27609
+ var SANDBOX_STARTED_SCRIPT = ['printf "%s\\n" "$1" >&2', "shift", 'exec "$@"'].join("\n");
27504
27610
  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
27611
  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
27612
  function operatorSecretResolver(env = process.env) {
@@ -27624,7 +27730,9 @@ var GrantCommandRunner = class {
27624
27730
  ...Object.fromEntries(secrets)
27625
27731
  };
27626
27732
  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);
27733
+ const runsOnHost = surfaceAllows(policy.surface, "run-host-command");
27734
+ const sandboxMarker = runsOnHost ? void 0 : `[beeline-sandbox-started:${randomBytes6(16).toString("hex")}]`;
27735
+ const spawn13 = runsOnHost ? { command: argv[0], args: argv.slice(1) } : roomSandboxCommand(policy, room.cwd, argv, sandboxMarker);
27628
27736
  const outcome = await new Promise((resolveRun) => {
27629
27737
  const child = execFile3(spawn13.command, spawn13.args, {
27630
27738
  cwd: room.cwd,
@@ -27645,7 +27753,13 @@ var GrantCommandRunner = class {
27645
27753
  });
27646
27754
  });
27647
27755
  });
27648
- const writeRefused = !surfaceAllows(policy.surface, "run-host-command") && WRITE_REFUSED.test(outcome.output);
27756
+ const sandboxStarted = sandboxMarker ? outcome.output.includes(sandboxMarker) : true;
27757
+ if (sandboxMarker && sandboxStarted) {
27758
+ outcome.output = outcome.output.replace(`${sandboxMarker}
27759
+ `, "").replace(sandboxMarker, "");
27760
+ }
27761
+ const writeRefused = !runsOnHost && WRITE_REFUSED.test(outcome.output);
27762
+ const sandboxFailure = !runsOnHost && !sandboxStarted;
27649
27763
  const output = capOutput(scrubSecrets(writeRefused ? `${outcome.output.trimEnd()}
27650
27764
  ${ROOM_WRITE_REFUSED_NOTE}` : outcome.output, secrets), cap);
27651
27765
  const turn = room.turn();
@@ -27653,7 +27767,7 @@ ${ROOM_WRITE_REFUSED_NOTE}` : outcome.output, secrets), cap);
27653
27767
  pubkey: grant.requestedBy,
27654
27768
  ...grant.requestedByName ? { name: grant.requestedByName } : {}
27655
27769
  };
27656
- const status = outcome.timedOut ? "timed out" : outcome.exitCode === null ? "error" : `exit ${outcome.exitCode}`;
27770
+ const status = sandboxFailure ? "sandbox failed" : outcome.timedOut ? "timed out" : outcome.exitCode === null ? "error" : `exit ${outcome.exitCode}`;
27657
27771
  await this.options.api.execute("postAgentActivity", {
27658
27772
  agentId: this.options.agentId,
27659
27773
  roomId: input.roomId,
@@ -27677,7 +27791,8 @@ ${ROOM_WRITE_REFUSED_NOTE}` : outcome.output, secrets), cap);
27677
27791
  ...outcome.signal ? { signal: outcome.signal } : {},
27678
27792
  timedOut: outcome.timedOut,
27679
27793
  output,
27680
- ...writeRefused ? { writeRefused: true } : {}
27794
+ ...writeRefused ? { writeRefused: true } : {},
27795
+ ...sandboxFailure ? { sandboxFailure: true } : {}
27681
27796
  };
27682
27797
  }
27683
27798
  /**
@@ -27719,7 +27834,7 @@ function scriptCandidates(cwd, scratch, argv) {
27719
27834
  ];
27720
27835
  return [...new Set(paths)];
27721
27836
  }
27722
- function roomSandboxCommand(policy, cwd, argv) {
27837
+ function roomSandboxCommand(policy, cwd, argv, sandboxMarker) {
27723
27838
  if (!policy.bwrapPath)
27724
27839
  throw new Error(ROOM_SANDBOX_UNAVAILABLE);
27725
27840
  return wrapAgentCommand({
@@ -27734,8 +27849,8 @@ function roomSandboxCommand(policy, cwd, argv) {
27734
27849
  ...policy.scratch ? { tmpDir: policy.scratch } : {},
27735
27850
  ...policy.maskPaths ? { maskPaths: policy.maskPaths } : {}
27736
27851
  },
27737
- command: argv[0],
27738
- args: argv.slice(1)
27852
+ command: "/bin/sh",
27853
+ args: ["-c", SANDBOX_STARTED_SCRIPT, "beeline-grant-sandbox", sandboxMarker, ...argv]
27739
27854
  });
27740
27855
  }
27741
27856
  var GrantRunnerServer = class {
@@ -28050,13 +28165,13 @@ async function runServerCommandIntake(options) {
28050
28165
  }
28051
28166
  }
28052
28167
  options.onPoll?.();
28053
- const reconcile = await new Promise((resolve35) => {
28168
+ const reconcile = await new Promise((resolve36) => {
28054
28169
  const done = (needed) => {
28055
28170
  if (timer)
28056
28171
  clearTimeout(timer);
28057
28172
  signal?.removeEventListener("abort", aborted);
28058
28173
  wake = void 0;
28059
- resolve35(needed);
28174
+ resolve36(needed);
28060
28175
  };
28061
28176
  const aborted = () => done(false);
28062
28177
  wake = done;
@@ -28081,12 +28196,12 @@ async function runServerCommandIntake(options) {
28081
28196
  }
28082
28197
 
28083
28198
  // apps/body/dist/monolith-corner-turn.js
28084
- import { execFile as execFile5 } from "node:child_process";
28199
+ import { execFile as execFile6 } from "node:child_process";
28085
28200
  import { createHash as createHash6 } from "node:crypto";
28086
- import { mkdir as mkdir13 } from "node:fs/promises";
28201
+ import { mkdir as mkdir14 } from "node:fs/promises";
28087
28202
  import { homedir as homedir10 } from "node:os";
28088
28203
  import { join as join12 } from "node:path";
28089
- import { promisify as promisify3 } from "node:util";
28204
+ import { promisify as promisify4 } from "node:util";
28090
28205
 
28091
28206
  // apps/body/dist/agent-home.js
28092
28207
  var import_yaml2 = __toESM(require_dist(), 1);
@@ -28896,6 +29011,9 @@ import { basename as basename5, dirname as dirname10, join as join8, relative as
28896
29011
  import { readFileSync as readFileSync6 } from "node:fs";
28897
29012
  import { resolve as resolve13 } from "node:path";
28898
29013
 
29014
+ // packages/api-contract/dist/phone-types.js
29015
+ var MESSAGE_REACTION_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F389}", "\u{1F440}", "\u2705"];
29016
+
28899
29017
  // packages/api-contract/dist/workbench.js
28900
29018
  var CONNECTABLE_CONNECTOR_KINDS = [
28901
29019
  "trusty-squire",
@@ -28942,11 +29060,13 @@ var BEELINE_ROOM_CAPABILITIES = [
28942
29060
  "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
29061
  "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
29062
  "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.",
29063
+ "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
29064
  "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
29065
  BEELINE_AMBIENT_CONNECTOR_CAPABILITY,
28947
29066
  "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
29067
  "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
29068
  "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.",
29069
+ `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
29070
  `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
29071
  "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
29072
  "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 +29086,7 @@ var BEELINE_DM_CAPABILITIES = [
28966
29086
  "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
29087
  "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
29088
  "Tag the person only when you need a decision or input, or when the task they asked for is finished.",
29089
+ `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
29090
  "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
29091
  "Never claim an action or reply happened unless the prompt or a tool result proves it."
28971
29092
  ].join(" ");
@@ -30807,12 +30928,22 @@ function harnessStateDirsFromEnv(env) {
30807
30928
  return { stateDirs, ...tmp ? { tmpDir: resolve16(tmp) } : {} };
30808
30929
  }
30809
30930
 
30931
+ // apps/body/dist/agent-command-catalog.js
30932
+ function agentCommandCatalogPublisher(input) {
30933
+ return (commands) => {
30934
+ void input.api.execute("postAgentCommands", {
30935
+ agentId: input.agentId,
30936
+ workspaceId: input.workspaceId,
30937
+ commands
30938
+ }).catch(input.report ?? ((error) => console.error("[body] agent command catalog publish failed:", error)));
30939
+ };
30940
+ }
30941
+
30810
30942
  // apps/body/dist/attachment-delivery.js
30811
30943
  import { mkdir as mkdir7, writeFile as writeFile8 } from "node:fs/promises";
30812
30944
  import { basename as basename6, extname, join as join9 } from "node:path";
30813
30945
  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`;
30946
+ var EXPIRED_REASON = "expired: these attachment bytes are past their retention window";
30816
30947
  var FETCH_TIMEOUT_MS = 3e4;
30817
30948
  var BoundedSizeError = class extends Error {
30818
30949
  bytes;
@@ -31996,9 +32127,91 @@ function youtubeMcpServer(config, accessToken) {
31996
32127
  };
31997
32128
  }
31998
32129
 
32130
+ // apps/body/dist/codegraph.js
32131
+ import { execFile as execFile5 } from "node:child_process";
32132
+ import { access, appendFile, mkdir as mkdir9, readFile as readFile7 } from "node:fs/promises";
32133
+ import { dirname as dirname11, isAbsolute as isAbsolute3, resolve as resolve19 } from "node:path";
32134
+ import { promisify as promisify3 } from "node:util";
32135
+ var execFileAsync3 = promisify3(execFile5);
32136
+ var CODEGRAPH_MCP_SERVER_NAME = "codegraph";
32137
+ var CODEGRAPH_INDEX_PATH = ".codegraph/codegraph.db";
32138
+ var CODEGRAPH_PREPARE_TIMEOUT_MS = 12e4;
32139
+ function codegraphIndexDirectory(cwd) {
32140
+ return resolve19(cwd, dirname11(CODEGRAPH_INDEX_PATH));
32141
+ }
32142
+ function codegraphMcpServer(config, cwd, options) {
32143
+ if (!config.codegraphCommand)
32144
+ return void 0;
32145
+ return {
32146
+ name: CODEGRAPH_MCP_SERVER_NAME,
32147
+ command: config.codegraphCommand,
32148
+ args: ["serve", "--mcp", "--path", resolve19(cwd), ...options.readonly ? ["--no-watch"] : []],
32149
+ env: [
32150
+ { name: "CODEGRAPH_TELEMETRY", value: "0" },
32151
+ // One MCP child per harness session is easier to contain than a daemon
32152
+ // that can outlive the Room/corner whose index and permissions it used.
32153
+ { name: "CODEGRAPH_NO_DAEMON", value: "1" }
32154
+ ]
32155
+ };
32156
+ }
32157
+ function codegraphFingerprintServers(config, servers, mounted = Boolean(config.codegraphCommand)) {
32158
+ return config.codegraphCommand && mounted ? [...servers, CODEGRAPH_MCP_SERVER_NAME] : [...servers];
32159
+ }
32160
+ async function isGitWorktree(cwd) {
32161
+ try {
32162
+ const { stdout: stdout6 } = await execFileAsync3("git", ["rev-parse", "--show-toplevel"], {
32163
+ cwd,
32164
+ timeout: 1e4
32165
+ });
32166
+ return resolve19(stdout6.trim()) === resolve19(cwd);
32167
+ } catch {
32168
+ return false;
32169
+ }
32170
+ }
32171
+ async function excludeIndexFromGitStatus(cwd) {
32172
+ try {
32173
+ const { stdout: stdout6 } = await execFileAsync3("git", ["rev-parse", "--git-path", "info/exclude"], {
32174
+ cwd,
32175
+ timeout: 1e4
32176
+ });
32177
+ const gitPath = stdout6.trim();
32178
+ if (!gitPath)
32179
+ return;
32180
+ const path = isAbsolute3(gitPath) ? gitPath : resolve19(cwd, gitPath);
32181
+ await mkdir9(dirname11(path), { recursive: true });
32182
+ const existing = await readFile7(path, "utf8").catch(() => "");
32183
+ if (existing.split("\n").includes(".codegraph/"))
32184
+ return;
32185
+ await appendFile(path, `${existing && !existing.endsWith("\n") ? "\n" : ""}.codegraph/
32186
+ `);
32187
+ } catch {
32188
+ }
32189
+ }
32190
+ async function prepareCodegraphIndex(config, cwd) {
32191
+ const command = config.codegraphCommand;
32192
+ if (!command || !await isGitWorktree(cwd))
32193
+ return false;
32194
+ await excludeIndexFromGitStatus(cwd);
32195
+ const indexPath = resolve19(cwd, CODEGRAPH_INDEX_PATH);
32196
+ const indexed = await access(indexPath).then(() => true, () => false);
32197
+ const args = indexed ? ["sync", "--quiet", cwd] : ["init", "--yes", cwd];
32198
+ try {
32199
+ await execFileAsync3(command, args, {
32200
+ cwd,
32201
+ env: { ...process.env, CODEGRAPH_TELEMETRY: "0", CODEGRAPH_NO_DAEMON: "1" },
32202
+ timeout: CODEGRAPH_PREPARE_TIMEOUT_MS,
32203
+ maxBuffer: 4 * 1024 * 1024
32204
+ });
32205
+ return true;
32206
+ } catch (error) {
32207
+ console.warn(`[body] CodeGraph ${args[0]} failed for ${cwd}; continuing with Beeline read tools:`, error);
32208
+ return false;
32209
+ }
32210
+ }
32211
+
31999
32212
  // 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";
32213
+ import { readdir as readdir3, readFile as readFile8 } from "node:fs/promises";
32214
+ import { resolve as resolve20 } from "node:path";
32002
32215
  function summarizeProviderError(errorMessage2) {
32003
32216
  const trimmed = errorMessage2.trim();
32004
32217
  const statusMatch = /^(\d{3}):\s*([\s\S]*)$/.exec(trimmed);
@@ -32019,7 +32232,7 @@ function summarizeProviderError(errorMessage2) {
32019
32232
  }
32020
32233
  async function sessionFileFromMap(home, sessionId) {
32021
32234
  try {
32022
- const raw = await readFile7(resolve19(home, ".pi", "pi-acp", "session-map.json"), "utf8");
32235
+ const raw = await readFile8(resolve20(home, ".pi", "pi-acp", "session-map.json"), "utf8");
32023
32236
  const map = JSON.parse(raw);
32024
32237
  const file = map.sessions?.[sessionId]?.sessionFile;
32025
32238
  return typeof file === "string" && file ? file : void 0;
@@ -32028,7 +32241,7 @@ async function sessionFileFromMap(home, sessionId) {
32028
32241
  }
32029
32242
  }
32030
32243
  async function sessionFileFromLayout(piDir, sessionId) {
32031
- const sessionsRoot = resolve19(piDir, "sessions");
32244
+ const sessionsRoot = resolve20(piDir, "sessions");
32032
32245
  const suffix = `_${sessionId}.jsonl`;
32033
32246
  let projects;
32034
32247
  try {
@@ -32037,7 +32250,7 @@ async function sessionFileFromLayout(piDir, sessionId) {
32037
32250
  return void 0;
32038
32251
  }
32039
32252
  for (const project of projects) {
32040
- const dir = resolve19(sessionsRoot, project);
32253
+ const dir = resolve20(sessionsRoot, project);
32041
32254
  let files;
32042
32255
  try {
32043
32256
  files = await readdir3(dir);
@@ -32046,7 +32259,7 @@ async function sessionFileFromLayout(piDir, sessionId) {
32046
32259
  }
32047
32260
  const match = files.find((file) => file.endsWith(suffix));
32048
32261
  if (match)
32049
- return resolve19(dir, match);
32262
+ return resolve20(dir, match);
32050
32263
  }
32051
32264
  return void 0;
32052
32265
  }
@@ -32064,7 +32277,7 @@ async function readPiTurnRecord(input) {
32064
32277
  return void 0;
32065
32278
  let raw;
32066
32279
  try {
32067
- raw = await readFile7(file, "utf8");
32280
+ raw = await readFile8(file, "utf8");
32068
32281
  } catch {
32069
32282
  return void 0;
32070
32283
  }
@@ -32220,8 +32433,8 @@ async function withTurnReceiptHeartbeat(api, receipt, task, onHeartbeatError) {
32220
32433
  }
32221
32434
 
32222
32435
  // 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";
32436
+ import { appendFile as appendFile2, mkdir as mkdir10, readdir as readdir4, rm as rm3 } from "node:fs/promises";
32437
+ import { resolve as resolve21 } from "node:path";
32225
32438
  import { performance as performance2 } from "node:perf_hooks";
32226
32439
  var TURN_PHASES = [
32227
32440
  /** Enqueued on `SessionScheduler` until this turn holds a slot. A capacity wait lives here. */
@@ -32451,7 +32664,7 @@ function formatTurnTraceLine(record3) {
32451
32664
  return [head, ...record3.attempts.map((attempt) => formatTurnAttempt(attempt))].join("\n ");
32452
32665
  }
32453
32666
  function turnTraceDirectory(runtimeDir) {
32454
- return resolve20(runtimeDir, "turn-traces");
32667
+ return resolve21(runtimeDir, "turn-traces");
32455
32668
  }
32456
32669
  var TURN_TRACE_RETENTION_DAYS = 7;
32457
32670
  function traceFileName(date) {
@@ -32468,14 +32681,14 @@ var TurnTraceFile = class {
32468
32681
  this.options = options;
32469
32682
  }
32470
32683
  path(now2 = (this.options.clock ?? (() => /* @__PURE__ */ new Date()))()) {
32471
- return resolve20(this.directory, traceFileName(now2));
32684
+ return resolve21(this.directory, traceFileName(now2));
32472
32685
  }
32473
32686
  write(record3) {
32474
32687
  this.tail = this.tail.catch(() => void 0).then(async () => {
32475
32688
  const now2 = (this.options.clock ?? (() => /* @__PURE__ */ new Date()))();
32476
32689
  const path = this.path(now2);
32477
- await mkdir9(this.directory, { recursive: true, mode: 448 });
32478
- await appendFile(path, `${JSON.stringify(record3)}
32690
+ await mkdir10(this.directory, { recursive: true, mode: 448 });
32691
+ await appendFile2(path, `${JSON.stringify(record3)}
32479
32692
  `, { mode: 384 });
32480
32693
  await this.prune(now2);
32481
32694
  const log2 = this.options.log ?? ((line) => console.log(line));
@@ -32492,16 +32705,16 @@ var TurnTraceFile = class {
32492
32705
  this.prunedDay = day;
32493
32706
  const cutoff = traceFileName(new Date(now2.getTime() - TURN_TRACE_RETENTION_DAYS * 864e5));
32494
32707
  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 })));
32708
+ 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
32709
  }
32497
32710
  };
32498
32711
 
32499
32712
  // 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";
32713
+ import { chmod as chmod4, mkdir as mkdir11, writeFile as writeFile9 } from "node:fs/promises";
32714
+ import { delimiter as delimiter2, resolve as resolve22 } from "node:path";
32502
32715
  async function installCornerGitHubWrappers(input) {
32503
- const bin = resolve21(input.root, "beeline-github-bin");
32504
- await mkdir10(bin, { recursive: true, mode: 448 });
32716
+ const bin = resolve22(input.root, "beeline-github-bin");
32717
+ await mkdir11(bin, { recursive: true, mode: 448 });
32505
32718
  const common = {
32506
32719
  node: process.execPath,
32507
32720
  cli: input.cliEntrypoint,
@@ -32510,13 +32723,13 @@ async function installCornerGitHubWrappers(input) {
32510
32723
  featureBranch: input.featureBranch,
32511
32724
  targetBranch: input.targetBranch
32512
32725
  };
32513
- await writeLauncher(resolve21(bin, "git"), {
32726
+ await writeLauncher(resolve22(bin, "git"), {
32514
32727
  ...common,
32515
32728
  command: input.gitBinary,
32516
32729
  launcher: "git"
32517
32730
  });
32518
32731
  if (input.ghBinary)
32519
- await writeLauncher(resolve21(bin, "gh"), {
32732
+ await writeLauncher(resolve22(bin, "gh"), {
32520
32733
  ...common,
32521
32734
  command: input.ghBinary,
32522
32735
  launcher: "gh"
@@ -32653,23 +32866,23 @@ process.exit(result.status ?? 1);
32653
32866
  // apps/body/dist/warm-node-modules.js
32654
32867
  import { createHash as createHash5, randomUUID as randomUUID5 } from "node:crypto";
32655
32868
  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";
32869
+ 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";
32870
+ import { dirname as dirname12, join as join10, resolve as resolve23 } from "node:path";
32658
32871
  var STORE_FORMAT = "v1";
32659
32872
  var STAGING_PREFIX = ".beeline-warm-";
32660
32873
  var STAGING_SWEEP_MS = 24 * 60 * 60 * 1e3;
32661
32874
  var WARM_STORE_MAX_ENTRIES = 3;
32662
32875
  function sharedNpmCacheDir(supervisorRoot) {
32663
- return resolve22(supervisorRoot, "beeline", "npm-cache");
32876
+ return resolve23(supervisorRoot, "beeline", "npm-cache");
32664
32877
  }
32665
32878
  function warmNodeModulesStoreDir(supervisorRoot) {
32666
- return resolve22(supervisorRoot, "beeline", "node-modules");
32879
+ return resolve23(supervisorRoot, "beeline", "node-modules");
32667
32880
  }
32668
32881
  function isPlanRefusal(value) {
32669
32882
  return "failure" in value;
32670
32883
  }
32671
32884
  async function readWarmPlan(worktreePath) {
32672
- const lockfile = await readFile8(resolve22(worktreePath, "package-lock.json")).catch(() => void 0);
32885
+ const lockfile = await readFile9(resolve23(worktreePath, "package-lock.json")).catch(() => void 0);
32673
32886
  if (!lockfile)
32674
32887
  return { failure: "no-lockfile" };
32675
32888
  const packages = parseLockfilePackages(lockfile);
@@ -32708,11 +32921,11 @@ async function seedWarmNodeModules(input) {
32708
32921
  return { reason: plan.failure, ...plan.detail ? { detail: plan.detail } : {} };
32709
32922
  }
32710
32923
  for (const tree of plan.trees) {
32711
- if (await pathExists(resolve22(input.worktreePath, tree))) {
32924
+ if (await pathExists(resolve23(input.worktreePath, tree))) {
32712
32925
  return { reason: "present", key: plan.key };
32713
32926
  }
32714
32927
  }
32715
- const entry = resolve22(input.storeRoot, plan.key);
32928
+ const entry = resolve23(input.storeRoot, plan.key);
32716
32929
  if (!await isDirectory(entry))
32717
32930
  return { reason: "cold", key: plan.key };
32718
32931
  const [storeDevice, checkoutDevice] = await Promise.all([
@@ -32722,19 +32935,19 @@ async function seedWarmNodeModules(input) {
32722
32935
  if (storeDevice === void 0 || storeDevice !== checkoutDevice) {
32723
32936
  return { reason: "cross-device", key: plan.key };
32724
32937
  }
32725
- const staging = resolve22(input.storeRoot, `${STAGING_PREFIX}${process.pid}.${randomUUID5()}`);
32938
+ const staging = resolve23(input.storeRoot, `${STAGING_PREFIX}${process.pid}.${randomUUID5()}`);
32726
32939
  const placed = [];
32727
32940
  const now2 = (input.now ?? Date.now)();
32728
32941
  try {
32729
32942
  await sweepStaleStaging(input.storeRoot, now2);
32730
32943
  await utimes(entry, now2 / 1e3, now2 / 1e3).catch(() => void 0);
32731
32944
  for (const tree of plan.trees) {
32732
- await cloneTree(resolve22(entry, tree), resolve22(staging, tree), seedFile);
32945
+ await cloneTree(resolve23(entry, tree), resolve23(staging, tree), seedFile);
32733
32946
  }
32734
32947
  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);
32948
+ const target = resolve23(input.worktreePath, tree);
32949
+ await mkdir12(dirname12(target), { recursive: true });
32950
+ await rename4(resolve23(staging, tree), target);
32738
32951
  placed.push(target);
32739
32952
  }
32740
32953
  return { reason: "seeded", key: plan.key };
@@ -32752,11 +32965,11 @@ async function harvestWarmNodeModules(input) {
32752
32965
  if (isPlanRefusal(plan)) {
32753
32966
  return { reason: plan.failure, ...plan.detail ? { detail: plan.detail } : {} };
32754
32967
  }
32755
- const entry = resolve22(input.storeRoot, plan.key);
32968
+ const entry = resolve23(input.storeRoot, plan.key);
32756
32969
  if (await pathExists(entry))
32757
32970
  return { reason: "already-warm", key: plan.key };
32758
32971
  for (const tree of plan.trees) {
32759
- if (!await isDirectory(resolve22(input.worktreePath, tree))) {
32972
+ if (!await isDirectory(resolve23(input.worktreePath, tree))) {
32760
32973
  return { reason: "no-node-modules", key: plan.key, detail: tree };
32761
32974
  }
32762
32975
  }
@@ -32768,12 +32981,12 @@ async function harvestWarmNodeModules(input) {
32768
32981
  detail: `${missing.length} absent, e.g. ${missing.slice(0, 3).join(", ")}`
32769
32982
  };
32770
32983
  }
32771
- const staging = resolve22(input.storeRoot, `${STAGING_PREFIX}${process.pid}.${randomUUID5()}`);
32984
+ const staging = resolve23(input.storeRoot, `${STAGING_PREFIX}${process.pid}.${randomUUID5()}`);
32772
32985
  try {
32773
- await mkdir11(input.storeRoot, { recursive: true, mode: 493 });
32986
+ await mkdir12(input.storeRoot, { recursive: true, mode: 493 });
32774
32987
  await sweepStaleStaging(input.storeRoot, (input.now ?? Date.now)());
32775
32988
  for (const tree of plan.trees) {
32776
- await cloneTree(resolve22(input.worktreePath, tree), resolve22(staging, tree), harvestFile);
32989
+ await cloneTree(resolve23(input.worktreePath, tree), resolve23(staging, tree), harvestFile);
32777
32990
  }
32778
32991
  await input.onCopied?.();
32779
32992
  if (!await stagedTreeIsPublishable(input.worktreePath, staging, plan.key)) {
@@ -32796,16 +33009,16 @@ async function stagedTreeIsPublishable(worktreePath, staging, key) {
32796
33009
  return false;
32797
33010
  const hidden = join10("node_modules", ".package-lock.json");
32798
33011
  const [copied, current] = await Promise.all([
32799
- readFile8(resolve22(staging, hidden)).catch(() => void 0),
32800
- readFile8(resolve22(worktreePath, hidden)).catch(() => void 0)
33012
+ readFile9(resolve23(staging, hidden)).catch(() => void 0),
33013
+ readFile9(resolve23(worktreePath, hidden)).catch(() => void 0)
32801
33014
  ]);
32802
33015
  if (!copied || !current || !copied.equals(current))
32803
33016
  return false;
32804
33017
  return (await missingInstalledPackages(worktreePath, staging)).length === 0;
32805
33018
  }
32806
33019
  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));
33020
+ const wanted = parseLockfilePackages(await readFile9(resolve23(worktreePath, "package-lock.json")).catch(() => void 0));
33021
+ const installed = parseLockfilePackages(await readFile9(resolve23(treeRoot, "node_modules", ".package-lock.json")).catch(() => void 0));
32809
33022
  if (!wanted)
32810
33023
  return ["package-lock.json is unreadable"];
32811
33024
  if (!installed)
@@ -32825,7 +33038,7 @@ async function missingInstalledPackages(worktreePath, treeRoot = worktreePath) {
32825
33038
  required.push(path);
32826
33039
  }
32827
33040
  await mapWithLimit(required, INTEGRITY_READ_CONCURRENCY, async (path) => {
32828
- if (!(path in installed) || !await isInstalledPackage(resolve22(treeRoot, path))) {
33041
+ if (!(path in installed) || !await isInstalledPackage(resolve23(treeRoot, path))) {
32829
33042
  missing.push(path);
32830
33043
  }
32831
33044
  });
@@ -32864,7 +33077,7 @@ function isContainedTreePath(value) {
32864
33077
  return isContainedPath(value) && value.split("/").pop() === "node_modules";
32865
33078
  }
32866
33079
  async function cloneTree(source, target, file, topLevel = true) {
32867
- await mkdir11(target, { recursive: true, mode: 493 });
33080
+ await mkdir12(target, { recursive: true, mode: 493 });
32868
33081
  for (const entry of await readdir5(source, { withFileTypes: true })) {
32869
33082
  const from = join10(source, entry.name);
32870
33083
  const to = join10(target, entry.name);
@@ -32933,7 +33146,7 @@ function describe3(error) {
32933
33146
  }
32934
33147
 
32935
33148
  // apps/body/dist/monolith-room-turn.js
32936
- import { mkdir as mkdir12 } from "node:fs/promises";
33149
+ import { mkdir as mkdir13 } from "node:fs/promises";
32937
33150
  import { homedir as homedir9 } from "node:os";
32938
33151
  import { join as join11 } from "node:path";
32939
33152
 
@@ -33020,6 +33233,8 @@ var MonolithRoomTurnLoop = class {
33020
33233
  sessionId;
33021
33234
  /** The configuration the live session baked in; a change invalidates it. */
33022
33235
  sessionFingerprint;
33236
+ /** Whether CodeGraph preparation succeeded for the live session. */
33237
+ sessionCodegraphReady = false;
33023
33238
  /** The live session's environment, read back for pi's own turn record. */
33024
33239
  agentEnv = {};
33025
33240
  /** OpenRouter providers this activation pinned, in order (C92). */
@@ -33168,6 +33383,7 @@ var MonolithRoomTurnLoop = class {
33168
33383
  this.client = void 0;
33169
33384
  this.sessionId = void 0;
33170
33385
  this.sessionFingerprint = void 0;
33386
+ this.sessionCodegraphReady = false;
33171
33387
  this.pinnedProviderOverride = void 0;
33172
33388
  if (client?.isAlive)
33173
33389
  await client.stop();
@@ -33199,11 +33415,11 @@ var MonolithRoomTurnLoop = class {
33199
33415
  effort: configuration.effort ?? this.options.config.modelSelection?.effort,
33200
33416
  soul: configuration.soul ?? self?.soul,
33201
33417
  agentName: self?.name ?? this.agent.name,
33202
- mcpServers: expectedMountedImportedMcpServerNames({
33418
+ mcpServers: codegraphFingerprintServers(this.options.config, expectedMountedImportedMcpServerNames({
33203
33419
  operatorHome: this.options.config.operatorHome,
33204
33420
  agentKind: this.options.config.agentKind,
33205
33421
  grantedHostRoutes
33206
- })
33422
+ }), this.sessionCodegraphReady)
33207
33423
  });
33208
33424
  }
33209
33425
  async grantedHostRoutes() {
@@ -33237,7 +33453,7 @@ var MonolithRoomTurnLoop = class {
33237
33453
  ]);
33238
33454
  const self = roster.members.find((member) => member.identityId === this.agent.publicKey);
33239
33455
  const directMessage = Array.isArray(repositoryState.directParticipants) && repositoryState.directParticipants.length === 2;
33240
- await mkdir12(this.options.cwd, { recursive: true });
33456
+ await mkdir13(this.options.cwd, { recursive: true });
33241
33457
  const selectionModel = configuration.model ?? this.options.config.modelSelection?.model;
33242
33458
  const selectionEffort = configuration.effort ?? this.options.config.modelSelection?.effort;
33243
33459
  const selection = selectionModel || selectionEffort ? { model: selectionModel, effort: selectionEffort } : void 0;
@@ -33264,17 +33480,6 @@ var MonolithRoomTurnLoop = class {
33264
33480
  command
33265
33481
  });
33266
33482
  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
33483
  this.agentEnv = agentEnv;
33279
33484
  const agentArgs = agentArgsWithModelSelection({
33280
33485
  kind: this.options.config.agentKind,
@@ -33286,10 +33491,22 @@ var MonolithRoomTurnLoop = class {
33286
33491
  this.sessionScratchDir = tmpDir;
33287
33492
  this.sessionStateDirs = stateDirs;
33288
33493
  const homeStateDirs = harnessHomeStateDirs(harnessLabel, agentEnv.HOME ?? operatorHome);
33289
- await Promise.all(homeStateDirs.map((dir) => mkdir12(dir, { recursive: true })));
33494
+ await Promise.all(homeStateDirs.map((dir) => mkdir13(dir, { recursive: true })));
33290
33495
  const attachScratchRoot = this.options.config.agentHomeRoot ?? tmpDir;
33291
33496
  if (attachScratchRoot)
33292
- await mkdir12(attachScratchRoot, { recursive: true });
33497
+ await mkdir13(attachScratchRoot, { recursive: true });
33498
+ const codegraphReady = await prepareCodegraphIndex(this.options.config, this.options.cwd);
33499
+ const fingerprint = sessionConfigFingerprint({
33500
+ model: configuration.model ?? this.options.config.modelSelection?.model,
33501
+ effort: configuration.effort ?? this.options.config.modelSelection?.effort,
33502
+ soul: configuration.soul ?? self?.soul,
33503
+ agentName: self?.name ?? this.agent.name,
33504
+ mcpServers: codegraphFingerprintServers(this.options.config, expectedMountedImportedMcpServerNames({
33505
+ operatorHome: this.options.config.operatorHome,
33506
+ agentKind: this.options.config.agentKind,
33507
+ grantedHostRoutes
33508
+ }), codegraphReady)
33509
+ });
33293
33510
  const spawnCommand = wrapAgentCommand({
33294
33511
  bwrapPath: this.options.config.bwrapPath,
33295
33512
  spec: {
@@ -33300,6 +33517,7 @@ var MonolithRoomTurnLoop = class {
33300
33517
  ...tmpDir ? { tmpDir } : {},
33301
33518
  additionalWritablePaths: [
33302
33519
  ...attachScratchRoot ? [attachScratchRoot] : [],
33520
+ ...codegraphReady ? [codegraphIndexDirectory(this.options.cwd)] : [],
33303
33521
  ...grantedSquireHostBindPaths({
33304
33522
  operatorHome,
33305
33523
  agentKind: this.options.config.agentKind,
@@ -33324,9 +33542,16 @@ var MonolithRoomTurnLoop = class {
33324
33542
  attachScratchRoot,
33325
33543
  turnContextPath: this.commandContext.path,
33326
33544
  directMessage,
33327
- ...this.options.grantRunnerEndpoint ? { grantRunner: this.options.grantRunnerEndpoint } : {}
33545
+ ...this.options.grantRunnerEndpoint && this.options.config.bwrapPath ? { grantRunner: this.options.grantRunnerEndpoint } : {}
33328
33546
  })
33329
33547
  ];
33548
+ if (codegraphReady) {
33549
+ const codegraph = codegraphMcpServer(this.options.config, this.options.cwd, {
33550
+ readonly: true
33551
+ });
33552
+ if (codegraph)
33553
+ servers.push(codegraph);
33554
+ }
33330
33555
  const youtube = youtubeMcpServer(this.options.config, this.options.youtubeAccessToken);
33331
33556
  if (youtube)
33332
33557
  servers.push(youtube);
@@ -33358,7 +33583,12 @@ var MonolithRoomTurnLoop = class {
33358
33583
  // (`config.ts`), which is exactly when `wrapAgentCommand` above wraps.
33359
33584
  osSandbox: Boolean(this.options.config.bwrapPath),
33360
33585
  autoApprovePermissions: false,
33361
- permissionAllowlist: (request) => isRoomMcpPermissionRequest(request, mountedServers, hostServers)
33586
+ permissionAllowlist: (request) => isRoomMcpPermissionRequest(request, mountedServers, hostServers),
33587
+ onCommands: agentCommandCatalogPublisher({
33588
+ api: this.options.api,
33589
+ agentId: this.agent.publicKey,
33590
+ workspaceId: this.options.workspaceId
33591
+ })
33362
33592
  };
33363
33593
  this.client = (this.options.createAcpClient ?? ((value) => new AcpClient(value)))(clientOptions);
33364
33594
  await this.client.start();
@@ -33390,6 +33620,7 @@ var MonolithRoomTurnLoop = class {
33390
33620
  });
33391
33621
  this.sessionId = opened.sessionId;
33392
33622
  this.sessionFingerprint = fingerprint;
33623
+ this.sessionCodegraphReady = codegraphReady;
33393
33624
  if (selection) {
33394
33625
  const options = filterAllowedModelConfigOptions(parseAdvertisedConfigOptions(opened.raw, selection.model));
33395
33626
  await applyAgentModelSelection(this.client, opened.sessionId, options, selection);
@@ -33453,6 +33684,7 @@ var MonolithRoomTurnLoop = class {
33453
33684
  this.client = void 0;
33454
33685
  this.sessionId = void 0;
33455
33686
  this.sessionFingerprint = void 0;
33687
+ this.sessionCodegraphReady = false;
33456
33688
  if (client?.isAlive)
33457
33689
  await client.stop();
33458
33690
  this.pinnedProviderOverride = next;
@@ -33545,7 +33777,7 @@ var MonolithRoomTurnLoop = class {
33545
33777
  const names = new Map(roster.members.map((member) => [member.identityId, member.name]));
33546
33778
  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
33779
  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())
33780
+ 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
33781
  }));
33550
33782
  const grantDecision = this.commandContext.current?.action === "resume";
33551
33783
  const resumedRequestId = grantDecision ? this.pausedOnGrantRequestId : void 0;
@@ -33565,7 +33797,7 @@ ${JSON.stringify((corners.corners ?? []).filter((corner) => !corner.archived))}`
33565
33797
  MAINTAIN_ASSIGNED_IDENTITY_DIRECTIVE
33566
33798
  ].join(" "),
33567
33799
  `Current task selected by the server from ${inboxItemAuthorName(item, names)}:`,
33568
- roomMessagePrompt("", inboxItemPromptBody(item), item.attachments, delivered, this.acceptsImages())
33800
+ roomMessagePrompt("", inboxItemPromptBody(item), item.attachments, delivered, this.acceptsImages(), item.type === "message" ? item.id : void 0)
33569
33801
  ].filter(Boolean).join("\n\n");
33570
33802
  const stream = new AgentTurnStream({
33571
33803
  api,
@@ -33608,7 +33840,7 @@ ${JSON.stringify((corners.corners ?? []).filter((corner) => !corner.archived))}`
33608
33840
  "The previous run was cancelled because its harness could not accept every live steer.",
33609
33841
  "Resume the same turn. Keep the original request and everything that happened before it was cancelled.",
33610
33842
  "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())),
33843
+ ...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
33844
  "Continue now and answer the updated request without erasing the earlier context."
33613
33845
  ].join("\n\n");
33614
33846
  }
@@ -33766,14 +33998,18 @@ function openCornerToolCall(calls) {
33766
33998
  function openedACorner(call) {
33767
33999
  return !!call && isCompletedToolCall(call);
33768
34000
  }
33769
- function roomMessagePrompt(author, body, attachments, delivered, harnessAcceptsImages = true) {
34001
+ function roomMessagePrompt(author, body, attachments, delivered, harnessAcceptsImages = true, messageId) {
33770
34002
  const message = body.trim() || "(shared attachments)";
33771
34003
  const rendered = author ? `${author}: ${message}` : message;
33772
- return [rendered, ...attachmentPromptLines(attachments, delivered, harnessAcceptsImages)].join("\n");
34004
+ return [
34005
+ ...messageId ? [`[message id: ${messageId}]`] : [],
34006
+ rendered,
34007
+ ...attachmentPromptLines(attachments, delivered, harnessAcceptsImages)
34008
+ ].join("\n");
33773
34009
  }
33774
34010
 
33775
34011
  // apps/body/dist/monolith-corner-turn.js
33776
- var execFileAsync3 = promisify3(execFile5);
34012
+ var execFileAsync4 = promisify4(execFile6);
33777
34013
  var TOOL_ARGUMENT_MAX_BYTES = 1200;
33778
34014
  var TOOL_OUTPUT_MAX_BYTES = 3200;
33779
34015
  var TOOL_PATH_LIMIT = 12;
@@ -33816,8 +34052,8 @@ function isCornerChecksTurn(trigger, restates) {
33816
34052
  async function cornerUndeliveredRepositoryState(worktreePath, featureBranch, targetBranch) {
33817
34053
  try {
33818
34054
  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", [
34055
+ execFileAsync4("git", ["-C", worktreePath, "status", "--porcelain=v1"]),
34056
+ featureBranch ? firstResolvedRemoteRef(worktreePath, [featureBranch, targetBranch]).then((remoteRef) => remoteRef ? execFileAsync4("git", [
33821
34057
  "-C",
33822
34058
  worktreePath,
33823
34059
  "rev-list",
@@ -33837,7 +34073,7 @@ async function firstResolvedRemoteRef(worktreePath, branches) {
33837
34073
  if (!branch)
33838
34074
  continue;
33839
34075
  const ref = `refs/remotes/origin/${branch}`;
33840
- const resolved = await execFileAsync3("git", ["-C", worktreePath, "rev-parse", "--verify", ref]).then(() => true).catch(() => false);
34076
+ const resolved = await execFileAsync4("git", ["-C", worktreePath, "rev-parse", "--verify", ref]).then(() => true).catch(() => false);
33841
34077
  if (resolved)
33842
34078
  return ref;
33843
34079
  }
@@ -33945,7 +34181,7 @@ async function cornerToolActivity(call, worktreePath, requestedBy) {
33945
34181
  let title = oneLine(redactToolDetail(call.title ?? "")) || `${operation} tool`;
33946
34182
  if (isSuccessfulCommit(call)) {
33947
34183
  try {
33948
- const shown = await execFileAsync3("git", ["-C", worktreePath, "show", "--format=%s", "--name-only", "--no-renames", "HEAD"], { maxBuffer: 1024 * 1024 });
34184
+ const shown = await execFileAsync4("git", ["-C", worktreePath, "show", "--format=%s", "--name-only", "--no-renames", "HEAD"], { maxBuffer: 1024 * 1024 });
33949
34185
  const lines = shown.stdout.split(/\r?\n/);
33950
34186
  const subject = oneLine(lines.shift() ?? "commit");
33951
34187
  const files = new Set(lines.map(oneLine).filter(Boolean));
@@ -33967,20 +34203,36 @@ async function cornerToolActivity(call, worktreePath, requestedBy) {
33967
34203
  ...paths.length ? { files: paths.map((path) => ({ path })) } : {}
33968
34204
  };
33969
34205
  }
34206
+ var CORNER_CLOSE_POLL_BASE_MS = 10 * 6e4;
34207
+ function cornerClosePollMs(random = Math.random) {
34208
+ return CORNER_CLOSE_POLL_BASE_MS + Math.floor(random() * 3e3);
34209
+ }
33970
34210
  var MonolithCornerTurnLoop = class {
33971
34211
  options;
33972
34212
  commandContext;
33973
34213
  agent;
33974
34214
  wakeIntake;
33975
- /** Called by the daemon's one slow workspace reconciliation sweep. */
34215
+ /**
34216
+ * Called by the daemon's one slow workspace reconciliation sweep, and by the
34217
+ * fast reconcile a socket reconnect arms. A `corner-complete` published while
34218
+ * that socket was down is never replayed, so the sweep clears the close
34219
+ * throttle too: the durable read is what recovers the frame nobody heard.
34220
+ *
34221
+ * The wake is the intake loop's own stable notify and is handed back exactly
34222
+ * once, so it is kept: clearing it here left `requestClose` waking nothing
34223
+ * after the first sweep, and a pushed `corner-complete` then waited out the
34224
+ * idle timer. Intake clears it itself when it exits.
34225
+ */
33976
34226
  requestReconciliation() {
34227
+ this.lastCloseCheck = 0;
33977
34228
  this.wakeIntake?.();
33978
- this.wakeIntake = void 0;
33979
34229
  }
33980
34230
  client;
33981
34231
  sessionId;
33982
34232
  /** The configuration the live session baked in; a change invalidates it. */
33983
34233
  sessionFingerprint;
34234
+ /** Whether CodeGraph preparation succeeded for the live session. */
34235
+ sessionCodegraphReady = false;
33984
34236
  /** What this exact ACP session has already been prompted with (`warm-transcript.ts`). */
33985
34237
  warmTranscript = new WarmTranscript();
33986
34238
  /** The live session's environment, read back for pi's own turn record. */
@@ -34023,10 +34275,17 @@ var MonolithCornerTurnLoop = class {
34023
34275
  * by the same replay window the inbox already de-duplicates over.
34024
34276
  */
34025
34277
  stoppedTurns = /* @__PURE__ */ new Set();
34278
+ /** Live corner-complete: close now, do not wait for the recovery GET. */
34279
+ closePushed = false;
34280
+ /** Last durable close read; 0 means the first check still runs. */
34281
+ lastCloseCheck = 0;
34282
+ /** This corner's own jittered recovery interval, drawn once. */
34283
+ closePollMs;
34026
34284
  /** In-flight warm-store harvest, awaited at shutdown and never by a turn. */
34027
34285
  harvest;
34028
34286
  constructor(options) {
34029
34287
  this.options = options;
34288
+ this.closePollMs = options.closePollMs ?? cornerClosePollMs();
34030
34289
  this.agent = runtimeIdentity(options.runtime.agent);
34031
34290
  this.commandContext = new CommandExecutionContext(options.config.agentHomeRoot);
34032
34291
  this.options = { ...options, api: this.commandContext.bind(options.api) };
@@ -34046,6 +34305,11 @@ var MonolithCornerTurnLoop = class {
34046
34305
  isBusy() {
34047
34306
  return this.busy;
34048
34307
  }
34308
+ /** corner-complete on the wire: wake intake so `closed()` reaps now. */
34309
+ requestClose() {
34310
+ this.closePushed = true;
34311
+ this.wakeIntake?.();
34312
+ }
34049
34313
  refreshPersonaForSoulUpdate() {
34050
34314
  return this.options.scheduler.suspend(this.options.cornerId);
34051
34315
  }
@@ -34091,6 +34355,7 @@ var MonolithCornerTurnLoop = class {
34091
34355
  this.client = void 0;
34092
34356
  this.sessionId = void 0;
34093
34357
  this.sessionFingerprint = void 0;
34358
+ this.sessionCodegraphReady = false;
34094
34359
  this.pinnedProviderOverride = void 0;
34095
34360
  if (client?.isAlive)
34096
34361
  await client.stop();
@@ -34117,11 +34382,11 @@ var MonolithCornerTurnLoop = class {
34117
34382
  soul: configuration.soul ?? self?.soul,
34118
34383
  agentName: self?.name ?? this.agent.name,
34119
34384
  yoloMode: configuration.yoloMode,
34120
- mcpServers: expectedMountedImportedMcpServerNames({
34385
+ mcpServers: codegraphFingerprintServers(this.options.config, expectedMountedImportedMcpServerNames({
34121
34386
  operatorHome: this.options.config.operatorHome,
34122
34387
  agentKind: this.options.config.agentKind,
34123
34388
  grantedHostRoutes
34124
- }),
34389
+ }), this.sessionCodegraphReady),
34125
34390
  reviewerHandle: configuration.reviewerHandle
34126
34391
  });
34127
34392
  }
@@ -34169,7 +34434,7 @@ var MonolithCornerTurnLoop = class {
34169
34434
  }
34170
34435
  const selfReviewerInstruction = cornerSelfReviewerInstruction(reviewerInput);
34171
34436
  this.cornerTurnEndNudge = reviewerInstruction ?? selfReviewerInstruction ?? cornerMergeInstruction(configuration.yoloMode, configuration.reviewerHandle);
34172
- await mkdir13(this.options.worktreePath, { recursive: true });
34437
+ await mkdir14(this.options.worktreePath, { recursive: true });
34173
34438
  const selection = configuration.model || configuration.effort ? { model: configuration.model, effort: configuration.effort } : this.options.config.modelSelection;
34174
34439
  const homeOverlay = this.options.config.agentHomeRoot ? await prepareRoomAgentHome({
34175
34440
  root: this.options.config.agentHomeRoot,
@@ -34195,8 +34460,8 @@ var MonolithCornerTurnLoop = class {
34195
34460
  const repository = this.options.repository;
34196
34461
  let githubEnv = repository ? { GH_TOKEN: repository.githubToken, GITHUB_TOKEN: repository.githubToken } : {};
34197
34462
  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);
34463
+ const gitBinary = (await execFileAsync4("which", ["git"])).stdout.trim();
34464
+ const ghBinary = await execFileAsync4("which", ["gh"]).then((result) => result.stdout.trim()).catch(() => void 0);
34200
34465
  githubEnv = await installCornerGitHubWrappers({
34201
34466
  root: this.options.config.agentHomeRoot,
34202
34467
  runtimeConfigPath: this.options.config.runtimeConfigPath,
@@ -34210,7 +34475,7 @@ var MonolithCornerTurnLoop = class {
34210
34475
  });
34211
34476
  }
34212
34477
  const npmCacheDir = sharedNpmCacheDir(this.options.runtime.supervisorRoot);
34213
- await mkdir13(npmCacheDir, { recursive: true, mode: 448 });
34478
+ await mkdir14(npmCacheDir, { recursive: true, mode: 448 });
34214
34479
  const agentEnv = {
34215
34480
  ...this.options.config.agentEnv,
34216
34481
  ...homeOverlay,
@@ -34218,19 +34483,6 @@ var MonolithCornerTurnLoop = class {
34218
34483
  npm_config_cache: npmCacheDir
34219
34484
  };
34220
34485
  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
34486
  this.agentEnv = agentEnv;
34235
34487
  const agentArgs = agentArgsWithModelSelection({
34236
34488
  kind: this.options.config.agentKind,
@@ -34241,10 +34493,10 @@ var MonolithCornerTurnLoop = class {
34241
34493
  this.attachmentDir = tmpDir ? join12(tmpDir, "beeline-attachments") : void 0;
34242
34494
  this.sessionScratchDir = tmpDir;
34243
34495
  const homeStateDirs = harnessHomeStateDirs(harnessLabel, agentEnv.HOME ?? operatorHome);
34244
- await Promise.all(homeStateDirs.map((dir) => mkdir13(dir, { recursive: true })));
34496
+ await Promise.all(homeStateDirs.map((dir) => mkdir14(dir, { recursive: true })));
34245
34497
  const attachScratchRoot = this.options.config.agentHomeRoot ?? tmpDir;
34246
34498
  if (attachScratchRoot)
34247
- await mkdir13(attachScratchRoot, { recursive: true });
34499
+ await mkdir14(attachScratchRoot, { recursive: true });
34248
34500
  const spawnCommand = wrapAgentCommand({
34249
34501
  bwrapPath: this.options.config.bwrapPath,
34250
34502
  spec: {
@@ -34280,10 +34532,29 @@ var MonolithCornerTurnLoop = class {
34280
34532
  agentCwd: this.options.worktreePath,
34281
34533
  agentLabel: harnessLabel,
34282
34534
  autoApprovePermissions: true,
34283
- permissionHandler: () => Promise.resolve("allow")
34535
+ permissionHandler: () => Promise.resolve("allow"),
34536
+ onCommands: agentCommandCatalogPublisher({
34537
+ api: this.options.api,
34538
+ agentId: this.agent.publicKey,
34539
+ workspaceId: this.options.workspaceId
34540
+ })
34284
34541
  };
34285
34542
  this.client = (this.options.createAcpClient ?? ((value) => new AcpClient(value)))(clientOptions);
34286
34543
  await this.client.start();
34544
+ const codegraphReady = await prepareCodegraphIndex(this.options.config, this.options.worktreePath);
34545
+ const fingerprint = sessionConfigFingerprint({
34546
+ model: configuration.model ?? this.options.config.modelSelection?.model,
34547
+ effort: configuration.effort ?? this.options.config.modelSelection?.effort,
34548
+ soul: configuration.soul ?? self?.soul,
34549
+ agentName: self?.name ?? this.agent.name,
34550
+ yoloMode: configuration.yoloMode,
34551
+ mcpServers: codegraphFingerprintServers(this.options.config, expectedMountedImportedMcpServerNames({
34552
+ operatorHome: this.options.config.operatorHome,
34553
+ agentKind: this.options.config.agentKind,
34554
+ grantedHostRoutes
34555
+ }), codegraphReady),
34556
+ reviewerHandle: configuration.reviewerHandle
34557
+ });
34287
34558
  const servers = [
34288
34559
  ...repository ? [
34289
34560
  {
@@ -34317,6 +34588,13 @@ var MonolithCornerTurnLoop = class {
34317
34588
  ...this.options.grantRunnerEndpoint ? { grantRunner: this.options.grantRunnerEndpoint } : {}
34318
34589
  })
34319
34590
  ];
34591
+ if (codegraphReady) {
34592
+ const codegraph = codegraphMcpServer(this.options.config, this.options.worktreePath, {
34593
+ readonly: false
34594
+ });
34595
+ if (codegraph)
34596
+ servers.push(codegraph);
34597
+ }
34320
34598
  const youtube = youtubeMcpServer(this.options.config, this.options.youtubeAccessToken);
34321
34599
  if (youtube)
34322
34600
  servers.push(youtube);
@@ -34357,15 +34635,17 @@ var MonolithCornerTurnLoop = class {
34357
34635
  "This is a no-code corner with no repository checkout and no GitHub workflow.",
34358
34636
  "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
34637
  "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.`
34638
+ // This lane has no pull request URL and no merge card, so its
34639
+ // attached final reply is the requester-facing completion
34640
+ // signal. close_corner is still the lifecycle completion: it
34641
+ // archives the corner and reaps this scratch workspace.
34642
+ this.options.requesterHandle ? `Deliver the result as artifacts: post_artifact everything the objective asked for, then call close_corner. Finish the turn by replying with @${this.options.requesterHandle} and one line on what you posted; that attached reply is the requester-facing completion signal.` : `Deliver the result as artifacts: post_artifact everything the objective asked for, then call close_corner. Finish the turn by replying with one line on what you posted; that attached reply is the requester-facing completion signal.`
34364
34643
  ]
34365
34644
  ].filter(Boolean).join("\n\n")
34366
34645
  });
34367
34646
  this.sessionId = opened.sessionId;
34368
34647
  this.sessionFingerprint = fingerprint;
34648
+ this.sessionCodegraphReady = codegraphReady;
34369
34649
  if (selection) {
34370
34650
  const options = filterAllowedModelConfigOptions(parseAdvertisedConfigOptions(opened.raw, selection.model));
34371
34651
  await applyAgentModelSelection(this.client, opened.sessionId, options, selection);
@@ -34429,6 +34709,7 @@ var MonolithCornerTurnLoop = class {
34429
34709
  this.client = void 0;
34430
34710
  this.sessionId = void 0;
34431
34711
  this.sessionFingerprint = void 0;
34712
+ this.sessionCodegraphReady = false;
34432
34713
  if (client?.isAlive)
34433
34714
  await client.stop();
34434
34715
  this.pinnedProviderOverride = next;
@@ -34448,7 +34729,7 @@ var MonolithCornerTurnLoop = class {
34448
34729
  ...this.turnTraceSink ? { sink: this.turnTraceSink } : {}
34449
34730
  });
34450
34731
  }
34451
- async prompt(requestId, trigger, attachments = [], requestedById, restates) {
34732
+ async prompt(requestId, trigger, attachments = [], requestedById, restates, sourceMessageId) {
34452
34733
  const { api, cornerId } = this.options;
34453
34734
  if (this.stoppedTurns.has(requestId))
34454
34735
  return;
@@ -34487,7 +34768,10 @@ var MonolithCornerTurnLoop = class {
34487
34768
  this.currentTurn = { requestId, requester: requestedBy };
34488
34769
  const transcriptRows = conversation.items.slice(-120).map((message) => ({
34489
34770
  id: message.id,
34490
- line: `${names.get(message.authorId) ?? "Beeline"} [${message.type}]: ${message.body}`
34771
+ line: [
34772
+ ...message.type === "message" ? [`[message id: ${message.id}]`] : [],
34773
+ `${names.get(message.authorId) ?? "Beeline"} [${message.type}]: ${message.body}`
34774
+ ].join("\n")
34491
34775
  }));
34492
34776
  const buildPrompt = () => [
34493
34777
  this.turnIdentityInstructions,
@@ -34496,6 +34780,7 @@ ${this.options.objective}`,
34496
34780
  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
34781
  roomMentionDirectory(roster, this.agent.publicKey),
34498
34782
  [
34783
+ ...sourceMessageId ? [`Reaction target message id: ${sourceMessageId}`] : [],
34499
34784
  `Newest trigger:
34500
34785
  ${trigger}`,
34501
34786
  ...attachmentPromptLines(attachments, delivered, this.acceptsImages())
@@ -34779,6 +35064,16 @@ ${trigger}`,
34779
35064
  onError: (error) => console.error("[thin-core] corner command failed", error),
34780
35065
  stop: (requestId) => this.stopTurn(requestId),
34781
35066
  closed: async () => {
35067
+ if (this.closePushed) {
35068
+ this.closePushed = false;
35069
+ await this.harvest;
35070
+ await this.options.onCloseRequested();
35071
+ return true;
35072
+ }
35073
+ const now2 = Date.now();
35074
+ if (this.lastCloseCheck !== 0 && now2 - this.lastCloseCheck < this.closePollMs)
35075
+ return false;
35076
+ this.lastCloseCheck = now2;
34782
35077
  const state = await api.execute("getCornerRestoreState", { cornerId });
34783
35078
  if (!state.closeRequested)
34784
35079
  return false;
@@ -34786,7 +35081,9 @@ ${trigger}`,
34786
35081
  await this.options.onCloseRequested();
34787
35082
  return true;
34788
35083
  },
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)
35084
+ 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(() => {
35085
+ this.lastCloseCheck = 0;
35086
+ })
34790
35087
  });
34791
35088
  } finally {
34792
35089
  this.options.grantRunner?.unregister(cornerId);
@@ -35221,7 +35518,7 @@ var SessionScheduler = class {
35221
35518
  var REMOVAL_CONFIRMATION_READS = 2;
35222
35519
  var ROOM_JOIN_CONCURRENCY = 4;
35223
35520
  var DEFAULT_ROOM_WATCHDOG_STALE_MS = 9e4;
35224
- var DEFAULT_RECONCILE_HEARTBEAT_MS = 6e4;
35521
+ var DEFAULT_RECONCILE_HEARTBEAT_MS = 10 * 6e4;
35225
35522
  var DEFAULT_DRAIN_DEADLINE_MS = 30 * 6e4;
35226
35523
  var CORNER_BRANCH_DELETE_ATTEMPTS = 3;
35227
35524
  var DiscoveryWakes = class {
@@ -35261,33 +35558,33 @@ function isStandingCornerStartFault(error) {
35261
35558
  async function materializeCornerWorktree(input) {
35262
35559
  const remote = roomCheckoutRemote(input.remote);
35263
35560
  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 });
35561
+ const gitCommonDir = resolve24(input.supervisorRoot, "beeline", "repositories", `${repositoryHash}.git`);
35562
+ const path = resolve24(input.supervisorRoot, "beeline", "corners", input.cornerId);
35563
+ await mkdir15(dirname13(gitCommonDir), { recursive: true, mode: 448 });
35564
+ await mkdir15(dirname13(path), { recursive: true, mode: 448 });
35268
35565
  const authEnv = githubGitEnv(input.token);
35269
- if (!existsSync6(resolve23(gitCommonDir, "HEAD"))) {
35270
- await execFileAsync4("git", ["clone", "--bare", remote, gitCommonDir], {
35566
+ if (!existsSync6(resolve24(gitCommonDir, "HEAD"))) {
35567
+ await execFileAsync5("git", ["clone", "--bare", remote, gitCommonDir], {
35271
35568
  env: authEnv,
35272
35569
  maxBuffer: 4 * 1024 * 1024
35273
35570
  });
35274
35571
  }
35275
- await execFileAsync4("git", [
35572
+ await execFileAsync5("git", [
35276
35573
  `--git-dir=${gitCommonDir}`,
35277
35574
  "fetch",
35278
35575
  "--prune",
35279
35576
  "origin",
35280
35577
  `+refs/heads/${input.targetBranch}:refs/remotes/origin/${input.targetBranch}`
35281
35578
  ], { env: authEnv, maxBuffer: 4 * 1024 * 1024 });
35282
- const restored = await execFileAsync4("git", [
35579
+ const restored = await execFileAsync5("git", [
35283
35580
  `--git-dir=${gitCommonDir}`,
35284
35581
  "fetch",
35285
35582
  "origin",
35286
35583
  `+refs/heads/${input.featureBranch}:refs/remotes/origin/${input.featureBranch}`
35287
35584
  ], { env: authEnv, maxBuffer: 4 * 1024 * 1024 }).then(() => true, () => false);
35288
- if (!existsSync6(resolve23(path, ".git"))) {
35585
+ if (!existsSync6(resolve24(path, ".git"))) {
35289
35586
  await rm5(path, { recursive: true, force: true });
35290
- await execFileAsync4("git", [
35587
+ await execFileAsync5("git", [
35291
35588
  `--git-dir=${gitCommonDir}`,
35292
35589
  "worktree",
35293
35590
  "add",
@@ -35304,14 +35601,14 @@ async function materializeCornerWorktree(input) {
35304
35601
  console.log(`[thin-core] corner ${input.cornerId} warm node_modules: ${seed.reason}${seed.detail ? ` (${seed.detail})` : ""}`);
35305
35602
  }
35306
35603
  }
35307
- await execFileAsync4("git", [
35604
+ await execFileAsync5("git", [
35308
35605
  `--git-dir=${gitCommonDir}`,
35309
35606
  "config",
35310
35607
  "extensions.worktreeConfig",
35311
35608
  "true"
35312
35609
  ]);
35313
- await execFileAsync4("git", ["-C", path, "config", "--worktree", "core.bare", "false"]);
35314
- await execFileAsync4("git", [
35610
+ await execFileAsync5("git", ["-C", path, "config", "--worktree", "core.bare", "false"]);
35611
+ await execFileAsync5("git", [
35315
35612
  "-C",
35316
35613
  path,
35317
35614
  "config",
@@ -35319,7 +35616,7 @@ async function materializeCornerWorktree(input) {
35319
35616
  "credential.https://github.com.helper",
35320
35617
  "!f() { echo username=x-access-token; echo password=$GH_TOKEN; }; f"
35321
35618
  ]);
35322
- await execFileAsync4("git", [
35619
+ await execFileAsync5("git", [
35323
35620
  "-C",
35324
35621
  path,
35325
35622
  "config",
@@ -35327,7 +35624,7 @@ async function materializeCornerWorktree(input) {
35327
35624
  "user.name",
35328
35625
  input.committer.name
35329
35626
  ]);
35330
- await execFileAsync4("git", [
35627
+ await execFileAsync5("git", [
35331
35628
  "-C",
35332
35629
  path,
35333
35630
  "config",
@@ -35335,8 +35632,8 @@ async function materializeCornerWorktree(input) {
35335
35632
  "user.email",
35336
35633
  `${input.committer.publicKey.slice(0, 16)}@users.noreply.github.com`
35337
35634
  ]);
35338
- const top = await execFileAsync4("git", ["-C", path, "rev-parse", "--show-toplevel"]);
35339
- if (resolve23(top.stdout.trim()) !== resolve23(path)) {
35635
+ const top = await execFileAsync5("git", ["-C", path, "rev-parse", "--show-toplevel"]);
35636
+ if (resolve24(top.stdout.trim()) !== resolve24(path)) {
35340
35637
  throw new Error(`corner worktree escaped its isolated root: ${top.stdout.trim()}`);
35341
35638
  }
35342
35639
  return { path, gitCommonDir };
@@ -35360,7 +35657,7 @@ async function removeCornerWorktreeAndBranches(worktree) {
35360
35657
  const worktreeExists = existsSync6(worktree.path);
35361
35658
  const localExists = await gitRefExists(worktree.gitCommonDir, localRef);
35362
35659
  if (worktreeExists) {
35363
- const checkedOut = await execFileAsync4("git", [
35660
+ const checkedOut = await execFileAsync5("git", [
35364
35661
  "-C",
35365
35662
  worktree.path,
35366
35663
  "symbolic-ref",
@@ -35370,7 +35667,7 @@ async function removeCornerWorktreeAndBranches(worktree) {
35370
35667
  if (checkedOut.stdout.trim() !== localRef) {
35371
35668
  throw new Error(`corner worktree branch mismatch: expected ${localRef}`);
35372
35669
  }
35373
- await execFileAsync4("git", [
35670
+ await execFileAsync5("git", [
35374
35671
  `--git-dir=${worktree.gitCommonDir}`,
35375
35672
  "worktree",
35376
35673
  "remove",
@@ -35390,7 +35687,7 @@ async function removeCornerWorktreeAndBranches(worktree) {
35390
35687
  }
35391
35688
  await deleteExactRemoteBranch(worktree.gitCommonDir, remoteRef, worktree.token);
35392
35689
  if (await gitRefExists(worktree.gitCommonDir, localRef)) {
35393
- await execFileAsync4("git", [
35690
+ await execFileAsync5("git", [
35394
35691
  `--git-dir=${worktree.gitCommonDir}`,
35395
35692
  "branch",
35396
35693
  "--delete",
@@ -35400,10 +35697,10 @@ async function removeCornerWorktreeAndBranches(worktree) {
35400
35697
  ]);
35401
35698
  }
35402
35699
  }
35403
- var execFileAsync4 = promisify4(execFile6);
35700
+ var execFileAsync5 = promisify5(execFile7);
35404
35701
  async function removeCornerScratchWorkspace(input) {
35405
- const expected = resolve23(input.roomRoot, "scratch");
35406
- if (resolve23(input.scratchPath) !== expected) {
35702
+ const expected = resolve24(input.roomRoot, "scratch");
35703
+ if (resolve24(input.scratchPath) !== expected) {
35407
35704
  throw new Error(`refusing to remove scratch outside corner ${input.cornerId}`);
35408
35705
  }
35409
35706
  await rm5(expected, { recursive: true, force: true });
@@ -35417,6 +35714,19 @@ var RoomRuntimeCoordinator = class {
35417
35714
  /** Close/reconcile leftovers retried until local and remote refs are gone. */
35418
35715
  pendingCornerReaps = /* @__PURE__ */ new Map();
35419
35716
  startingCorners = /* @__PURE__ */ new Set();
35717
+ /**
35718
+ * Rooms whose start is in flight. `running` is not set until the checkout
35719
+ * clone finishes, and three callers race for it — the membership push, the
35720
+ * reconcile sweep, and the watchdog restart — so without this two checkouts
35721
+ * run in the same shared directory and the second start orphans the first
35722
+ * loop's AbortController.
35723
+ */
35724
+ startingRooms = /* @__PURE__ */ new Set();
35725
+ /** Pushed membership changes awaiting a bounded apply, latest per Room. */
35726
+ pendingMembershipEvents = /* @__PURE__ */ new Map();
35727
+ membershipDrain;
35728
+ /** Set by `shutdown`: a pushed event may no longer start anything. */
35729
+ stopped = false;
35420
35730
  /** Corners whose start failure has already been said out loud, once each. */
35421
35731
  reportedCornerStartFailures = /* @__PURE__ */ new Set();
35422
35732
  /** Standing workspace-configuration faults, keyed by the config that failed. */
@@ -35433,10 +35743,11 @@ var RoomRuntimeCoordinator = class {
35433
35743
  workspaceRemovalConfirmations = 0;
35434
35744
  roomRemovalConfirmations = /* @__PURE__ */ new Map();
35435
35745
  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. */
35746
+ /** Unscoped agent-directed discovery wakes (#1369), counted instead of
35747
+ * flagged: a wake that arrives while a reconcile is already running must
35748
+ * survive its start-pass clearing, or a reconnect mid-reconcile waits a
35749
+ * heartbeat for a wake the daemon already received. A scoped membership
35750
+ * event applies incrementally and never touches this latch. */
35440
35751
  discoveryWakes = new DiscoveryWakes();
35441
35752
  /** One command-grant runner per daemon; Rooms and corners register their checkouts on it. */
35442
35753
  grantRunner;
@@ -35460,8 +35771,16 @@ var RoomRuntimeCoordinator = class {
35460
35771
  });
35461
35772
  this.grantRunnerServer = new GrantRunnerServer(this.grantRunner);
35462
35773
  this.connectorUsage = new ConnectorUsageRecorder();
35463
- this.options.daemonApi.setRoomsChangedListener?.(() => {
35464
- this.discoveryWakes.wake();
35774
+ this.options.daemonApi.setRoomsChangedListener?.((event) => {
35775
+ const roomId = event?.roomId;
35776
+ if (!roomId) {
35777
+ this.discoveryWakes.wake();
35778
+ return;
35779
+ }
35780
+ this.queueMembershipEvent({ ...event, roomId });
35781
+ });
35782
+ this.options.daemonApi.setCornerCompleteListener?.((roomId) => {
35783
+ void this.applyCornerComplete(roomId).catch((error) => console.error("[thin-core] live corner-complete apply failed", error));
35465
35784
  });
35466
35785
  this.options.daemonApi.setConfigChangedListener?.(() => {
35467
35786
  void this.scheduler.suspendIdle().catch((error) => console.error("[body] config-change session restart failed", error));
@@ -35590,17 +35909,7 @@ var RoomRuntimeCoordinator = class {
35590
35909
  this.confirmationPending = true;
35591
35910
  continue;
35592
35911
  }
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
- }
35912
+ await this.stopRunning(channelId, running);
35604
35913
  }
35605
35914
  await mapWithConcurrency(desiredTopRooms, ROOM_JOIN_CONCURRENCY, async (roomId) => {
35606
35915
  if (this.running.has(roomId))
@@ -35620,17 +35929,97 @@ var RoomRuntimeCoordinator = class {
35620
35929
  this.discoveryWakes.completeReconcile(coveredWakes);
35621
35930
  return "member";
35622
35931
  }
35932
+ /**
35933
+ * Pushed membership changes are applied at the same bound the reconcile pass
35934
+ * uses. One `rooms-changed` per row means a single human action (adding an
35935
+ * agent to a Room inherits a membership per corner under it) arrives as a
35936
+ * burst; unbounded, that burst is exactly the concurrent restore reads and
35937
+ * worktree checkouts this change exists to stop.
35938
+ */
35939
+ queueMembershipEvent(event) {
35940
+ if (this.stopped)
35941
+ return;
35942
+ this.pendingMembershipEvents.set(event.roomId, event);
35943
+ this.membershipDrain ??= this.drainMembershipEvents().finally(() => {
35944
+ this.membershipDrain = void 0;
35945
+ });
35946
+ }
35947
+ async drainMembershipEvents() {
35948
+ while (this.pendingMembershipEvents.size) {
35949
+ const batch = [...this.pendingMembershipEvents.values()];
35950
+ this.pendingMembershipEvents.clear();
35951
+ await mapWithConcurrency(batch, ROOM_JOIN_CONCURRENCY, (event) => this.applyMembershipEvent(event).catch((error) => {
35952
+ console.error("[thin-core] live membership apply failed", error);
35953
+ this.discoveryWakes.wake();
35954
+ }));
35955
+ }
35956
+ }
35957
+ /**
35958
+ * Incremental apply of one scoped membership push.
35959
+ * An unscoped wake still uses the slow reconcile as recovery.
35960
+ */
35961
+ async applyMembershipEvent(event) {
35962
+ const roomId = event.roomId;
35963
+ if (!roomId) {
35964
+ this.discoveryWakes.wake();
35965
+ return;
35966
+ }
35967
+ if (event.removed === true) {
35968
+ const running = this.running.get(roomId);
35969
+ if (running)
35970
+ await this.stopRunning(roomId, running);
35971
+ this.monolithCornerParents.delete(roomId);
35972
+ return;
35973
+ }
35974
+ if (event.archived === true)
35975
+ return;
35976
+ this.roomRemovalConfirmations.delete(roomId);
35977
+ if (this.running.has(roomId) || this.startingCorners.has(roomId))
35978
+ return;
35979
+ if (event.parentRoomId) {
35980
+ this.monolithCornerParents.set(roomId, event.parentRoomId);
35981
+ if (!event.openedBy) {
35982
+ this.discoveryWakes.wake();
35983
+ return;
35984
+ }
35985
+ await this.startCorner({
35986
+ cornerId: roomId,
35987
+ parentRoomId: event.parentRoomId,
35988
+ openedBy: event.openedBy
35989
+ });
35990
+ if (!this.running.has(roomId))
35991
+ this.discoveryWakes.wake();
35992
+ return;
35993
+ }
35994
+ await this.startRoom(roomId);
35995
+ }
35996
+ async applyCornerComplete(cornerId) {
35997
+ this.running.get(cornerId)?.body.requestClose?.();
35998
+ }
35999
+ async stopRunning(channelId, running) {
36000
+ running.controller.abort();
36001
+ await running.promise.catch(() => void 0);
36002
+ try {
36003
+ if (running.worktree)
36004
+ await this.reapCornerWorktree(running.worktree);
36005
+ else if (running.scratch)
36006
+ await this.reapCornerScratch(running.scratch);
36007
+ } catch (error) {
36008
+ console.error(`[thin-core] corner ${channelId} cleanup failed; will retry:`, error);
36009
+ this.confirmationPending = true;
36010
+ }
36011
+ }
35623
36012
  roomRecord(roomId) {
35624
36013
  return this.runtime.rooms.find((room) => room.channelId === roomId);
35625
36014
  }
35626
36015
  roomRoot(roomId) {
35627
- return this.roomRecord(roomId)?.root ?? resolve23(dirname12(this.configPath), "rooms", roomId);
36016
+ return this.roomRecord(roomId)?.root ?? resolve24(dirname13(this.configPath), "rooms", roomId);
35628
36017
  }
35629
36018
  roomAgentHomeRoot(workspaceRoot, required = false) {
35630
36019
  const flag = process.env.BUZZY_BODY_ROOM_HOME;
35631
36020
  if (!required && flag === "0")
35632
36021
  return void 0;
35633
- const home = resolve23(workspaceRoot, "agent-home");
36022
+ const home = resolve24(workspaceRoot, "agent-home");
35634
36023
  if (!required && flag !== "1" && !existsSync6(home) && existsSync6(workspaceRoot))
35635
36024
  return void 0;
35636
36025
  try {
@@ -35646,10 +36035,10 @@ var RoomRuntimeCoordinator = class {
35646
36035
  return {
35647
36036
  ...this.baseConfig,
35648
36037
  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)),
36038
+ agentPrivateRoot: resolve24(workspaceRoot, "agent-private"),
36039
+ agentMemoryRoot: resolve24(dirname13(this.configPath), "memory"),
36040
+ openRouterRoutingCacheDir: openRouterRoutingCacheDir(dirname13(this.configPath)),
36041
+ turnTraceDir: turnTraceDirectory(dirname13(this.configPath)),
35653
36042
  ...agentHomeRoot ? { agentHomeRoot } : {}
35654
36043
  };
35655
36044
  }
@@ -35671,6 +36060,16 @@ var RoomRuntimeCoordinator = class {
35671
36060
  });
35672
36061
  }
35673
36062
  async startRoom(roomId) {
36063
+ if (this.running.has(roomId) || this.startingRooms.has(roomId))
36064
+ return;
36065
+ this.startingRooms.add(roomId);
36066
+ try {
36067
+ await this.startRoomOnce(roomId);
36068
+ } finally {
36069
+ this.startingRooms.delete(roomId);
36070
+ }
36071
+ }
36072
+ async startRoomOnce(roomId) {
35674
36073
  const controller = new AbortController();
35675
36074
  const cwd = await this.materializeRoomCheckout(roomId);
35676
36075
  const grantRunnerEndpoint = await this.grantRunnerEndpoint();
@@ -35703,6 +36102,11 @@ var RoomRuntimeCoordinator = class {
35703
36102
  if (this.running.get(roomId)?.body === loop)
35704
36103
  this.running.delete(roomId);
35705
36104
  });
36105
+ if (this.stopped) {
36106
+ controller.abort();
36107
+ await promise;
36108
+ return;
36109
+ }
35706
36110
  this.running.set(roomId, {
35707
36111
  body: loop,
35708
36112
  controller,
@@ -35727,17 +36131,17 @@ var RoomRuntimeCoordinator = class {
35727
36131
  const remote = roomCheckoutRemote(repository.remote);
35728
36132
  const targetBranch = repository.targetBranch || "main";
35729
36133
  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 });
36134
+ const path = resolve24(this.runtime.supervisorRoot, "beeline", "room-checkouts", checkoutId);
36135
+ await mkdir15(dirname13(path), { recursive: true, mode: 448 });
35732
36136
  const token = remote.startsWith("https://github.com/") ? await this.options.daemonApi.execute("getRoomGitHubToken", { roomId }) : void 0;
35733
36137
  const env = token ? githubGitEnv(token.token) : process.env;
35734
- if (!existsSync6(resolve23(path, ".git"))) {
35735
- await execFileAsync4("git", ["clone", "--no-checkout", remote, path], {
36138
+ if (!existsSync6(resolve24(path, ".git"))) {
36139
+ await execFileAsync5("git", ["clone", "--no-checkout", remote, path], {
35736
36140
  env,
35737
36141
  maxBuffer: 4 * 1024 * 1024
35738
36142
  });
35739
36143
  }
35740
- await execFileAsync4("git", [
36144
+ await execFileAsync5("git", [
35741
36145
  "-C",
35742
36146
  path,
35743
36147
  "fetch",
@@ -35745,7 +36149,7 @@ var RoomRuntimeCoordinator = class {
35745
36149
  "origin",
35746
36150
  `+refs/heads/${targetBranch}:refs/remotes/origin/${targetBranch}`
35747
36151
  ], { env, maxBuffer: 4 * 1024 * 1024 });
35748
- await execFileAsync4("git", ["-C", path, "checkout", "--detach", "--force", `origin/${targetBranch}`], {
36152
+ await execFileAsync5("git", ["-C", path, "checkout", "--detach", "--force", `origin/${targetBranch}`], {
35749
36153
  env,
35750
36154
  maxBuffer: 4 * 1024 * 1024
35751
36155
  });
@@ -35763,7 +36167,8 @@ var RoomRuntimeCoordinator = class {
35763
36167
  roomId: corner.parentRoomId
35764
36168
  })
35765
36169
  ]);
35766
- configKey = cornerStartConfigKey(repository, restore.objective ?? "");
36170
+ const objective = (restore.objective ?? "").trim() || (restore.kind === "human" ? (restore.title ?? "").trim() : "");
36171
+ configKey = cornerStartConfigKey(repository, objective);
35767
36172
  const previousStanding = this.standingCornerStartFaults.get(corner.cornerId);
35768
36173
  if (previousStanding === configKey)
35769
36174
  return;
@@ -35777,7 +36182,6 @@ var RoomRuntimeCoordinator = class {
35777
36182
  if (repository.resolution === "repository" && (!repository.remote || !repository.key)) {
35778
36183
  throw new Error("corner parent Room has an incomplete repository binding");
35779
36184
  }
35780
- const objective = (restore.objective ?? "").trim();
35781
36185
  if (!objective)
35782
36186
  throw new Error("corner has no authoritative objective fact");
35783
36187
  const repositoryBacked = repository.resolution === "repository" && restore.lane !== "no_code";
@@ -35793,10 +36197,10 @@ var RoomRuntimeCoordinator = class {
35793
36197
  featureBranch,
35794
36198
  token: granted.token
35795
36199
  }) : void 0;
35796
- const workspacePath = worktree?.path ?? resolve23(this.roomRoot(corner.cornerId), "scratch");
36200
+ const workspacePath = worktree?.path ?? resolve24(this.roomRoot(corner.cornerId), "scratch");
35797
36201
  if (!worktree)
35798
- await mkdir14(workspacePath, { recursive: true, mode: 448 });
35799
- const isOpener = !corner.openedBy || corner.openedBy === this.agent.publicKey;
36202
+ await mkdir15(workspacePath, { recursive: true, mode: 448 });
36203
+ const isOpener = corner.openedBy === this.agent.publicKey;
35800
36204
  if (worktree && shouldPostInitialCornerWorkingState(restore, isOpener)) {
35801
36205
  await this.options.daemonApi.execute("postCornerRemoteState", {
35802
36206
  cornerId: corner.cornerId,
@@ -35852,6 +36256,11 @@ var RoomRuntimeCoordinator = class {
35852
36256
  this.running.delete(corner.cornerId);
35853
36257
  }
35854
36258
  });
36259
+ if (this.stopped) {
36260
+ controller.abort();
36261
+ await promise;
36262
+ return;
36263
+ }
35855
36264
  this.running.set(corner.cornerId, {
35856
36265
  body: loop,
35857
36266
  controller,
@@ -35997,18 +36406,28 @@ var RoomRuntimeCoordinator = class {
35997
36406
  }
35998
36407
  }
35999
36408
  async shutdown() {
36409
+ this.stopped = true;
36410
+ this.pendingMembershipEvents.clear();
36411
+ const deadlineAt = Math.min(this.now() + this.drainDeadlineMs, this.drainDeadlineAt ?? Number.POSITIVE_INFINITY);
36412
+ const untilDeadline = async (work) => {
36413
+ let timer;
36414
+ const deadline = new Promise((resolveDeadline) => {
36415
+ timer = setTimeout(() => resolveDeadline("deadline"), Math.max(0, deadlineAt - this.now()));
36416
+ });
36417
+ try {
36418
+ return await Promise.race([work, deadline]);
36419
+ } finally {
36420
+ if (timer)
36421
+ clearTimeout(timer);
36422
+ }
36423
+ };
36424
+ if (this.membershipDrain)
36425
+ await untilDeadline(this.membershipDrain);
36000
36426
  const rooms = [...this.running.values()];
36001
36427
  for (const room of rooms)
36002
36428
  room.controller.abort();
36003
36429
  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);
36430
+ const result = await untilDeadline(drained.then(() => "drained"));
36012
36431
  if (result === "deadline") {
36013
36432
  await Promise.allSettled(rooms.map((room) => room.body.forceRecoverRoom()));
36014
36433
  await drained;
@@ -36044,7 +36463,7 @@ function githubGitEnv(token) {
36044
36463
  }
36045
36464
  async function gitRefExists(gitCommonDir, ref) {
36046
36465
  try {
36047
- await execFileAsync4("git", [`--git-dir=${gitCommonDir}`, "show-ref", "--verify", "--quiet", ref]);
36466
+ await execFileAsync5("git", [`--git-dir=${gitCommonDir}`, "show-ref", "--verify", "--quiet", ref]);
36048
36467
  return true;
36049
36468
  } catch (error) {
36050
36469
  if (error.code === 1)
@@ -36054,7 +36473,7 @@ async function gitRefExists(gitCommonDir, ref) {
36054
36473
  }
36055
36474
  async function repositoryHeadRef(gitCommonDir) {
36056
36475
  try {
36057
- const head = await execFileAsync4("git", [
36476
+ const head = await execFileAsync5("git", [
36058
36477
  `--git-dir=${gitCommonDir}`,
36059
36478
  "symbolic-ref",
36060
36479
  "--quiet",
@@ -36074,7 +36493,7 @@ ${gitStderr(error)}`);
36074
36493
  }
36075
36494
  async function deleteExactRemoteBranch(gitCommonDir, remoteRef, token, options = {}) {
36076
36495
  const authEnv = githubGitEnv(token);
36077
- const listRemote = () => execFileAsync4("git", [`--git-dir=${gitCommonDir}`, "ls-remote", "--heads", "origin", remoteRef], {
36496
+ const listRemote = () => execFileAsync5("git", [`--git-dir=${gitCommonDir}`, "ls-remote", "--heads", "origin", remoteRef], {
36078
36497
  env: authEnv,
36079
36498
  maxBuffer: 4 * 1024 * 1024
36080
36499
  });
@@ -36090,7 +36509,7 @@ async function deleteExactRemoteBranch(gitCommonDir, remoteRef, token, options =
36090
36509
  try {
36091
36510
  const remote = await listRemote();
36092
36511
  if (remote.stdout.trim()) {
36093
- await execFileAsync4("git", [`--git-dir=${gitCommonDir}`, "push", "origin", `:${remoteRef}`], {
36512
+ await execFileAsync5("git", [`--git-dir=${gitCommonDir}`, "push", "origin", `:${remoteRef}`], {
36094
36513
  env: authEnv,
36095
36514
  maxBuffer: 4 * 1024 * 1024
36096
36515
  });
@@ -36185,17 +36604,17 @@ var ThinDaemonCore = class {
36185
36604
  };
36186
36605
 
36187
36606
  // 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";
36607
+ import { mkdir as mkdir17, rename as rename5 } from "node:fs/promises";
36608
+ import { dirname as dirname15, resolve as resolve26 } from "node:path";
36190
36609
 
36191
36610
  // 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";
36611
+ import { execFile as execFile8 } from "node:child_process";
36612
+ import { mkdir as mkdir16, readFile as readFile10, stat as stat3, writeFile as writeFile10 } from "node:fs/promises";
36194
36613
  import { homedir as homedir11 } from "node:os";
36195
- import { dirname as dirname13, resolve as resolve24 } from "node:path";
36614
+ import { dirname as dirname14, resolve as resolve25 } from "node:path";
36196
36615
  import { setTimeout as sleep } from "node:timers/promises";
36197
- import { promisify as promisify5 } from "node:util";
36198
- var execFileAsync5 = promisify5(execFile7);
36616
+ import { promisify as promisify6 } from "node:util";
36617
+ var execFileAsync6 = promisify6(execFile8);
36199
36618
  var DELIBERATE_REMOVAL_EXIT_STATUS = 78;
36200
36619
  var DAEMON_DISTRESS_EXIT_STATUS = 77;
36201
36620
  var UNKNOWN_AGENT_EXIT_STATUS = 79;
@@ -36227,8 +36646,13 @@ TimeoutStartSec=90s
36227
36646
  TimeoutStopSec=10min
36228
36647
  KillMode=control-group
36229
36648
  UMask=0077
36230
- NoNewPrivileges=yes
36231
- PrivateTmp=yes
36649
+ # A desktop-launched user manager may inherit Ubuntu's unprivileged_userns
36650
+ # AppArmor profile. Without an explicit transition every agent inherits it too,
36651
+ # so /usr/bin/bwrap cannot enter its package-provided bwrap profile.
36652
+ # Do not set NoNewPrivileges or PrivateTmp on this outer service: systemd applies
36653
+ # either before AppArmorProfile, which blocks this transition. Bubblewrap sets
36654
+ # no-new-privs and a private /tmp inside each agent sandbox it creates.
36655
+ AppArmorProfile=-unconfined
36232
36656
 
36233
36657
  [Install]
36234
36658
  WantedBy=default.target
@@ -36236,9 +36660,9 @@ WantedBy=default.target
36236
36660
  }
36237
36661
  function isCanonicalInstalledLauncher(env = process.env, invocationPath = process.argv[1]) {
36238
36662
  const home = env.HOME?.trim() || homedir11();
36239
- const expectedLibDir = resolve24(home, ".local", "lib", "beeline");
36663
+ const expectedLibDir = resolve25(home, ".local", "lib", "beeline");
36240
36664
  const expectedPrefix = `${expectedLibDir}/`;
36241
- return resolve24(env.BEELINE_LIB_DIR?.trim() || "/") === expectedLibDir && Boolean(invocationPath) && resolve24(invocationPath).startsWith(expectedPrefix);
36665
+ return resolve25(env.BEELINE_LIB_DIR?.trim() || "/") === expectedLibDir && Boolean(invocationPath) && resolve25(invocationPath).startsWith(expectedPrefix);
36242
36666
  }
36243
36667
  function assertCanonicalInstalledLauncher(env, invocationPath) {
36244
36668
  if (isCanonicalInstalledLauncher(env, invocationPath))
@@ -36246,12 +36670,12 @@ function assertCanonicalInstalledLauncher(env, invocationPath) {
36246
36670
  throw new Error("refusing to modify the shared Beeline systemd unit outside the canonical ~/.local/bin/beeline launcher");
36247
36671
  }
36248
36672
  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);
36673
+ const configRoot = env.XDG_CONFIG_HOME?.trim() || resolve25(homedir11(), ".config");
36674
+ return resolve25(configRoot, "systemd", "user", SYSTEMD_UNIT_NAME);
36251
36675
  }
36252
36676
  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);
36677
+ const configRoot = env.XDG_CONFIG_HOME?.trim() || resolve25(homedir11(), ".config");
36678
+ return resolve25(configRoot, "systemd", "user", TRUSTY_SQUIRE_BROKER_UNIT_NAME);
36255
36679
  }
36256
36680
  async function installTrustySquireBrokerService(options = {}) {
36257
36681
  const env = options.env ?? process.env;
@@ -36260,9 +36684,9 @@ async function installTrustySquireBrokerService(options = {}) {
36260
36684
  ensureSquireHostDir(home);
36261
36685
  const path = systemdBrokerUnitPath(env);
36262
36686
  const content = trustySquireBrokerUnit();
36263
- const existing = await readFile9(path, "utf8").catch(() => "");
36687
+ const existing = await readFile10(path, "utf8").catch(() => "");
36264
36688
  if (existing !== content) {
36265
- await mkdir15(dirname13(path), { recursive: true, mode: 448 });
36689
+ await mkdir16(dirname14(path), { recursive: true, mode: 448 });
36266
36690
  await writeFile10(path, content, { mode: 384 });
36267
36691
  }
36268
36692
  const run2 = options.run ?? runSystemctl;
@@ -36271,7 +36695,7 @@ async function installTrustySquireBrokerService(options = {}) {
36271
36695
  }
36272
36696
  var AGENT_SERVICE = /^beeline-agent@([0-9a-f]{64})\.service$/i;
36273
36697
  var runSystemctl = async (args) => {
36274
- const result = await execFileAsync5("systemctl", ["--user", ...args], {
36698
+ const result = await execFileAsync6("systemctl", ["--user", ...args], {
36275
36699
  timeout: SYSTEMD_COMMAND_TIMEOUT_MS,
36276
36700
  encoding: "utf8"
36277
36701
  });
@@ -36284,9 +36708,9 @@ async function installAgentService(publicKey, options = {}) {
36284
36708
  assertCanonicalInstalledLauncher(env, options.invocationPath);
36285
36709
  const path = systemdUserUnitPath(env);
36286
36710
  const content = agentServiceUnit();
36287
- const existing = await readFile9(path, "utf8").catch(() => "");
36711
+ const existing = await readFile10(path, "utf8").catch(() => "");
36288
36712
  if (existing !== content) {
36289
- await mkdir15(dirname13(path), { recursive: true, mode: 448 });
36713
+ await mkdir16(dirname14(path), { recursive: true, mode: 448 });
36290
36714
  await writeFile10(path, content, { mode: 384 });
36291
36715
  }
36292
36716
  const run2 = options.run ?? runSystemctl;
@@ -36407,7 +36831,7 @@ async function reconcileAgentServices(options = {}) {
36407
36831
  async function notify(fields) {
36408
36832
  if (process.env.BEELINE_MANAGED_BY_SYSTEMD !== "1")
36409
36833
  return;
36410
- await execFileAsync5("systemd-notify", fields, { timeout: SYSTEMD_COMMAND_TIMEOUT_MS });
36834
+ await execFileAsync6("systemd-notify", fields, { timeout: SYSTEMD_COMMAND_TIMEOUT_MS });
36411
36835
  }
36412
36836
  async function extendSystemdStartTimeout(ms) {
36413
36837
  await notify([`EXTEND_TIMEOUT_USEC=${Math.max(0, Math.round(ms)) * 1e3}`]).catch(() => void 0);
@@ -36426,15 +36850,15 @@ var SystemdNotifier = class {
36426
36850
 
36427
36851
  // apps/body/dist/agent-retirement.js
36428
36852
  async function retireRemovedAgent(runtime, options = {}) {
36429
- const deletedRoot = resolve25(runtime.supervisorRoot, "beeline", "deleted-runtimes");
36430
- const target = resolve25(deletedRoot, `${runtime.agent.publicKey}-${Date.now()}`);
36853
+ const deletedRoot = resolve26(runtime.supervisorRoot, "beeline", "deleted-runtimes");
36854
+ const target = resolve26(deletedRoot, `${runtime.agent.publicKey}-${Date.now()}`);
36431
36855
  return relocateAgentRuntime(runtime, target, {
36432
36856
  ...options.run ? { run: options.run } : {}
36433
36857
  });
36434
36858
  }
36435
36859
  async function relocateAgentRuntime(runtime, target, options = {}) {
36436
36860
  const source = runtimeDirectory(runtime.supervisorRoot, runtime.agent.publicKey);
36437
- const destination = resolve25(target);
36861
+ const destination = resolve26(target);
36438
36862
  if (destination === source || destination.startsWith(`${source}/`)) {
36439
36863
  throw new Error("agent runtime destination must be outside the live runtime");
36440
36864
  }
@@ -36443,13 +36867,13 @@ async function relocateAgentRuntime(runtime, target, options = {}) {
36443
36867
  ...options.run ? { run: options.run } : {}
36444
36868
  });
36445
36869
  }
36446
- await mkdir16(dirname14(destination), { recursive: true, mode: 448 });
36870
+ await mkdir17(dirname15(destination), { recursive: true, mode: 448 });
36447
36871
  await rename5(source, destination);
36448
36872
  return destination;
36449
36873
  }
36450
36874
 
36451
36875
  // apps/body/dist/start-command.js
36452
- import { basename as basename7, dirname as dirname18 } from "node:path";
36876
+ import { basename as basename7, dirname as dirname19 } from "node:path";
36453
36877
  var import_picocolors2 = __toESM(require_picocolors(), 1);
36454
36878
 
36455
36879
  // apps/body/dist/self-update-cli.js
@@ -36460,21 +36884,21 @@ init_self_update_manifest();
36460
36884
  // apps/body/dist/managed-update.js
36461
36885
  init_self_update();
36462
36886
  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";
36887
+ import { mkdir as mkdir20, rm as rm7, stat as stat4, writeFile as writeFile13 } from "node:fs/promises";
36888
+ import { dirname as dirname18, resolve as resolve29 } from "node:path";
36465
36889
 
36466
36890
  // 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";
36891
+ import { mkdir as mkdir19, readFile as readFile12, rename as rename7, unlink as unlink4, writeFile as writeFile12 } from "node:fs/promises";
36892
+ import { dirname as dirname17, resolve as resolve28 } from "node:path";
36469
36893
  var REPORT_INTERVAL_MS = 60 * 60 * 1e3;
36470
36894
  var lastLogged = /* @__PURE__ */ new Map();
36471
36895
  function updateRollbackAlertPath(runtimeDir) {
36472
- return resolve27(runtimeDir, "update-rollback-alert.json");
36896
+ return resolve28(runtimeDir, "update-rollback-alert.json");
36473
36897
  }
36474
36898
  async function writeAlert(runtimeDir, alert) {
36475
36899
  const path = updateRollbackAlertPath(runtimeDir);
36476
36900
  const staged = `${path}.${process.pid}.tmp`;
36477
- await mkdir18(dirname16(path), { recursive: true });
36901
+ await mkdir19(dirname17(path), { recursive: true });
36478
36902
  await writeFile12(staged, `${JSON.stringify(alert, null, 2)}
36479
36903
  `, { mode: 384 });
36480
36904
  await rename7(staged, path);
@@ -36487,7 +36911,7 @@ async function queueUpdateRollbackAlert(runtimeDir, releaseId, now2 = Date.now()
36487
36911
  }
36488
36912
  async function readUpdateRollbackAlert(runtimeDir) {
36489
36913
  try {
36490
- const value = JSON.parse(await readFile11(updateRollbackAlertPath(runtimeDir), "utf8"));
36914
+ const value = JSON.parse(await readFile12(updateRollbackAlertPath(runtimeDir), "utf8"));
36491
36915
  if (value.version !== 1 || typeof value.releaseId !== "string")
36492
36916
  return void 0;
36493
36917
  return value;
@@ -36538,13 +36962,13 @@ var LOCK_STALE_MS = UPDATE_WORKER_DEADLINE_MS + 5 * 6e4;
36538
36962
  var DEFAULT_UPDATE_INITIAL_DELAY_MS = 0;
36539
36963
  async function withInstallLock(layout, work, options = {}) {
36540
36964
  const now2 = options.now ?? Date.now;
36541
- const lock = resolve28(layout.releasesRoot, ".state", "install.lock");
36965
+ const lock = resolve29(layout.releasesRoot, ".state", "install.lock");
36542
36966
  const deadline = now2() + (options.waitMs ?? 1e4);
36543
- await mkdir19(dirname17(lock), { recursive: true });
36967
+ await mkdir20(dirname18(lock), { recursive: true });
36544
36968
  for (; ; ) {
36545
36969
  try {
36546
- await mkdir19(lock);
36547
- await writeFile13(resolve28(lock, "owner"), `${process.pid}
36970
+ await mkdir20(lock);
36971
+ await writeFile13(resolve29(lock, "owner"), `${process.pid}
36548
36972
  ${now2()}
36549
36973
  `, "utf8");
36550
36974
  break;
@@ -36668,7 +37092,7 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
36668
37092
  if (!attempt || attempt.releaseId !== desiredRelease || attempt.status !== "pending") {
36669
37093
  const from = await readInstalledBundleIdentity({
36670
37094
  ...this.#layout,
36671
- libDir: resolve28(this.#layout.releasesRoot, this.#loadedRelease)
37095
+ libDir: resolve29(this.#layout.releasesRoot, this.#loadedRelease)
36672
37096
  }).catch(() => void 0) ?? {};
36673
37097
  const to = await readInstalledBundleIdentity(this.#layout).catch(() => void 0) ?? {};
36674
37098
  const record3 = {
@@ -36955,7 +37379,7 @@ async function proveLoadedReleaseReady(layout, runtimeDir, loadedRelease, option
36955
37379
  });
36956
37380
  if (!accepted)
36957
37381
  return false;
36958
- await writeFile13(resolve28(runtimeDir, "daemon-ready.json"), `${JSON.stringify({
37382
+ await writeFile13(resolve29(runtimeDir, "daemon-ready.json"), `${JSON.stringify({
36959
37383
  readyAt: (options.now ?? Date.now)(),
36960
37384
  loadedRelease,
36961
37385
  functionalProof: options.functionalProof
@@ -37201,7 +37625,7 @@ async function startStoredRuntime(configPath, opts = {}, dependencyOverrides = {
37201
37625
  return { status: "started", pid };
37202
37626
  }
37203
37627
  function agentStartId(configPath) {
37204
- return basename7(dirname18(configPath));
37628
+ return basename7(dirname19(configPath));
37205
37629
  }
37206
37630
  async function startRuntime(configPath, spinnerHandle) {
37207
37631
  const report = (text2) => spinnerHandle ? spinnerHandle.message(text2) : console.log(text2);
@@ -37267,7 +37691,7 @@ async function runStartCommand(args, interactiveUi, dependencyOverrides = {}) {
37267
37691
  for (const path of unique) {
37268
37692
  const id = agentStartId(path);
37269
37693
  const spinnerHandle = interactiveUi ? spinner() : void 0;
37270
- spinnerHandle?.start(`Starting ${dirname18(path)}\u2026`);
37694
+ spinnerHandle?.start(`Starting ${dirname19(path)}\u2026`);
37271
37695
  try {
37272
37696
  const outcome = await deps.startOne(path, spinnerHandle);
37273
37697
  const report = { id, path, ...outcome };
@@ -37301,9 +37725,9 @@ async function runStartCommand(args, interactiveUi, dependencyOverrides = {}) {
37301
37725
  // apps/body/dist/connect-command.js
37302
37726
  import { spawn as spawn11 } from "node:child_process";
37303
37727
  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";
37728
+ import { chmod as chmod7, mkdir as mkdir21, readFile as readFile13, unlink as unlink5, writeFile as writeFile14 } from "node:fs/promises";
37305
37729
  import { homedir as homedir13, hostname as hostname2 } from "node:os";
37306
- import { dirname as dirname19, resolve as resolve29 } from "node:path";
37730
+ import { dirname as dirname20, resolve as resolve30 } from "node:path";
37307
37731
  import { stdin as stdin3, stdout as stdout4 } from "node:process";
37308
37732
 
37309
37733
  // apps/body/dist/clack-support.js
@@ -37621,6 +38045,9 @@ init_self_update();
37621
38045
  init_self_update_manifest();
37622
38046
  var CONNECT_HARNESSES = AUTO_DETECT_AGENT_KINDS;
37623
38047
  var AGENT_NAME_MAX_LENGTH = 32;
38048
+ function isInteractiveConnectTerminal(input = stdin3) {
38049
+ return input.isTTY === true;
38050
+ }
37624
38051
  function isReasonableAgentName(value) {
37625
38052
  const normalized = value.trim().replace(/\s+/g, " ");
37626
38053
  return normalized.length > 0 && normalized.length <= AGENT_NAME_MAX_LENGTH && new RegExp("^\\p{L}[\\p{L}\\p{M}'\u2019 -]*$", "u").test(normalized);
@@ -37931,19 +38358,19 @@ function parseConnectSubscriptions(value) {
37931
38358
  ];
37932
38359
  }
37933
38360
  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");
38361
+ const configDir = resolve30(env.XDG_CONFIG_HOME ?? resolve30(homedir13(), ".config"), "beeline");
38362
+ const machineIdPath = resolve30(configDir, "machine-id");
37936
38363
  let machineId;
37937
38364
  let machineName = hostname2();
37938
38365
  try {
37939
- const existing = await readFile12(machineIdPath, "utf8");
38366
+ const existing = await readFile13(machineIdPath, "utf8");
37940
38367
  machineId = existing.trim();
37941
38368
  if (!/^[0-9a-f-]{32,}$/.test(machineId))
37942
38369
  throw new Error("invalid persisted machine id");
37943
38370
  } catch {
37944
38371
  machineId = randomUUID6();
37945
38372
  try {
37946
- await mkdir20(configDir, { recursive: true, mode: 448 });
38373
+ await mkdir21(configDir, { recursive: true, mode: 448 });
37947
38374
  await writeFile14(machineIdPath, `${machineId}
37948
38375
  `, { mode: 384 });
37949
38376
  } catch {
@@ -38005,7 +38432,7 @@ async function installCurrentRelease(fetchImpl) {
38005
38432
  });
38006
38433
  await activateRelease(layout, releaseId);
38007
38434
  return {
38008
- binary: resolve29(layout.binDir, "beeline"),
38435
+ binary: resolve30(layout.binDir, "beeline"),
38009
38436
  version: published.version ?? releaseId
38010
38437
  };
38011
38438
  }
@@ -38028,7 +38455,7 @@ function providerEnvironment(selection) {
38028
38455
  };
38029
38456
  }
38030
38457
  async function writePrivateJson(path, value) {
38031
- await mkdir20(dirname19(path), { recursive: true, mode: 448 });
38458
+ await mkdir21(dirname20(path), { recursive: true, mode: 448 });
38032
38459
  await writeFile14(path, `${JSON.stringify(value, null, 2)}
38033
38460
  `, { mode: 384 });
38034
38461
  await chmod7(path, 384);
@@ -38037,8 +38464,8 @@ async function writeProviderEnv(selection, agentPubkey) {
38037
38464
  const values = providerEnvironment(selection);
38038
38465
  if (Object.keys(values).length === 0)
38039
38466
  return void 0;
38040
- const path = resolve29(defaultSupervisorRoot(process.env), "beeline", "connect", `${agentPubkey}.env`);
38041
- await mkdir20(dirname19(path), { recursive: true, mode: 448 });
38467
+ const path = resolve30(defaultSupervisorRoot(process.env), "beeline", "connect", `${agentPubkey}.env`);
38468
+ await mkdir21(dirname20(path), { recursive: true, mode: 448 });
38042
38469
  const contents = Object.entries(values).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join("\n");
38043
38470
  await writeFile14(path, `${contents}
38044
38471
  `, { mode: 384 });
@@ -38080,7 +38507,7 @@ async function brassSpinner(message, action, completion) {
38080
38507
  }
38081
38508
  }
38082
38509
  async function runConnectCommand(code, options = {}) {
38083
- if (!stdin3.isTTY || !stdout4.isTTY) {
38510
+ if (!isInteractiveConnectTerminal()) {
38084
38511
  throw new Error("`usebeeline connect` needs an interactive terminal");
38085
38512
  }
38086
38513
  const restoreRails = paintWizardBrass();
@@ -38107,7 +38534,7 @@ async function runConnectWizard(code, fetchImpl, eventSubscriptions, accessPolic
38107
38534
  await finishConnectedAgentPairing(baseUrl, pairingCode, grant.workspace_joined, eventSubscriptions, fetchImpl);
38108
38535
  const installedRelease = await brassSpinner("Installing the Beeline daemon\u2026", () => installCurrentRelease(fetchImpl), (release) => `Installed Beeline helper ${release.version}`);
38109
38536
  const llmEnvFile = await writeProviderEnv(selection, grant.agent_pubkey);
38110
- const grantPath = resolve29(defaultSupervisorRoot(process.env), "beeline", "connect", `grant-${process.pid}-${Date.now()}.json`);
38537
+ const grantPath = resolve30(defaultSupervisorRoot(process.env), "beeline", "connect", `grant-${process.pid}-${Date.now()}.json`);
38111
38538
  await writePrivateJson(grantPath, {
38112
38539
  agentSecretKey: grant.agent_secret_key,
38113
38540
  bodySecretKey: grant.body_secret_key,
@@ -38192,18 +38619,18 @@ async function runConnectFinishCommand(path) {
38192
38619
  throw new Error("connect-finish may run only from the canonical installed Beeline launcher");
38193
38620
  }
38194
38621
  try {
38195
- const grant = JSON.parse(await readFile12(resolve29(path), "utf8"));
38622
+ const grant = JSON.parse(await readFile13(resolve30(path), "utf8"));
38196
38623
  if (!isDevicePairingGrant(grant))
38197
38624
  throw new Error("device connection grant is invalid");
38198
38625
  const connected = await completeDevicePairing(grant);
38199
- await unlink5(resolve29(path));
38200
- const providerEnv = grant.llmEnvFile ? await readFile12(grant.llmEnvFile, "utf8").catch(() => "") : "";
38626
+ await unlink5(resolve30(path));
38627
+ const providerEnv = grant.llmEnvFile ? await readFile13(grant.llmEnvFile, "utf8").catch(() => "") : "";
38201
38628
  const apiKey = (/^OPENROUTER_API_KEY=(\S+)/m.exec(providerEnv)?.[1] ?? "").replace(/^["']|["']$/g, "");
38202
38629
  const model = openRouterModelId(grant.model, { OPENROUTER_API_KEY: apiKey });
38203
38630
  if (model) {
38204
38631
  const decision2 = await resolveOpenRouterRouting({
38205
38632
  model,
38206
- cacheDir: openRouterRoutingCacheDir(dirname19(connected.configPath)),
38633
+ cacheDir: openRouterRoutingCacheDir(dirname20(connected.configPath)),
38207
38634
  ...apiKey ? { apiKey } : {},
38208
38635
  probeTimeoutMs: 1e4
38209
38636
  });
@@ -38221,16 +38648,16 @@ async function runConnectFinishCommand(path) {
38221
38648
  init_self_update();
38222
38649
 
38223
38650
  // 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";
38651
+ import { mkdir as mkdir22, readFile as readFile14, rename as rename8, rm as rm8, writeFile as writeFile15 } from "node:fs/promises";
38652
+ import { dirname as dirname21, resolve as resolve31 } from "node:path";
38226
38653
  var DAEMON_FAILURE_LIMIT = 3;
38227
38654
  var DAEMON_FAILURE_WINDOW_MS = 5 * 6e4;
38228
38655
  function daemonFailurePath(runtimeDir) {
38229
- return resolve30(runtimeDir, "daemon-distress.json");
38656
+ return resolve31(runtimeDir, "daemon-distress.json");
38230
38657
  }
38231
38658
  async function readFailureRecord(runtimeDir) {
38232
38659
  try {
38233
- const value = JSON.parse(await readFile13(daemonFailurePath(runtimeDir), "utf8"));
38660
+ const value = JSON.parse(await readFile14(daemonFailurePath(runtimeDir), "utf8"));
38234
38661
  if (value.version !== 1 || !Array.isArray(value.failures) || value.failures.some((failure2) => typeof failure2 !== "number") || typeof value.lastError !== "string") {
38235
38662
  return void 0;
38236
38663
  }
@@ -38242,7 +38669,7 @@ async function readFailureRecord(runtimeDir) {
38242
38669
  async function writeFailureRecord(runtimeDir, record3) {
38243
38670
  const path = daemonFailurePath(runtimeDir);
38244
38671
  const staged = `${path}.${process.pid}.tmp`;
38245
- await mkdir21(dirname20(path), { recursive: true, mode: 448 });
38672
+ await mkdir22(dirname21(path), { recursive: true, mode: 448 });
38246
38673
  await writeFile15(staged, `${JSON.stringify(record3, null, 2)}
38247
38674
  `, { mode: 384 });
38248
38675
  await rename8(staged, path);
@@ -38265,9 +38692,9 @@ async function clearDaemonStartFailures(runtimeDir) {
38265
38692
  }
38266
38693
 
38267
38694
  // apps/body/dist/update-functional-probe.js
38268
- import { mkdir as mkdir22, rm as rm9 } from "node:fs/promises";
38695
+ import { mkdir as mkdir23, rm as rm9 } from "node:fs/promises";
38269
38696
  import { homedir as homedir14 } from "node:os";
38270
- import { resolve as resolve31 } from "node:path";
38697
+ import { resolve as resolve32 } from "node:path";
38271
38698
  var UPDATE_PROBE_SESSION_TIMEOUT_MS = 1e4;
38272
38699
  var UPDATE_PROBE_SESSION_OPEN_TIMEOUT_MS = 2e4;
38273
38700
  var UPDATE_PROBE_TURN_TIMEOUT_MS = 45e3;
@@ -38387,11 +38814,11 @@ async function runUpdateFunctionalProbe(input) {
38387
38814
  modelAnswerReason: `${detail} (the current release has the same host sandbox failure)`
38388
38815
  };
38389
38816
  }
38390
- const root = input.probeRoot ?? resolve31(input.runtimeDir, "update-functional-probe");
38391
- const cwd = resolve31(root, "checkout");
38392
- const homeRoot = resolve31(root, "agent-home");
38817
+ const root = input.probeRoot ?? resolve32(input.runtimeDir, "update-functional-probe");
38818
+ const cwd = resolve32(root, "checkout");
38819
+ const homeRoot = resolve32(root, "agent-home");
38393
38820
  await rm9(root, { recursive: true, force: true });
38394
- await mkdir22(cwd, { recursive: true, mode: 448 });
38821
+ await mkdir23(cwd, { recursive: true, mode: 448 });
38395
38822
  let client;
38396
38823
  try {
38397
38824
  const agentEnv = {
@@ -38421,7 +38848,7 @@ async function runUpdateFunctionalProbe(input) {
38421
38848
  const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
38422
38849
  const operatorHome = input.config.operatorHome ?? homedir14();
38423
38850
  const homeStateDirs = harnessHomeStateDirs(harnessLabel, agentEnv.HOME ?? operatorHome);
38424
- await Promise.all(homeStateDirs.map((dir) => mkdir22(dir, { recursive: true })));
38851
+ await Promise.all(homeStateDirs.map((dir) => mkdir23(dir, { recursive: true })));
38425
38852
  spawnCommand = wrapAgentCommand({
38426
38853
  bwrapPath: input.config.bwrapPath,
38427
38854
  spec: {
@@ -38571,7 +38998,7 @@ async function runUpdateFunctionalProbe(input) {
38571
38998
 
38572
38999
  // apps/body/dist/current-release-probe.js
38573
39000
  import { spawn as spawn12 } from "node:child_process";
38574
- import { dirname as dirname21, join as join14 } from "node:path";
39001
+ import { dirname as dirname22, join as join14 } from "node:path";
38575
39002
  init_self_update();
38576
39003
  var CURRENT_RELEASE_PROBE_TIMEOUT_MS = 12e4;
38577
39004
  var UPDATE_PROBE_COMMAND = "update-probe";
@@ -38620,7 +39047,7 @@ async function probeReleaseInSubprocess(input) {
38620
39047
  return { kind: "unavailable", reason: `release ${input.releaseId} has no runnable CLI entrypoint` };
38621
39048
  }
38622
39049
  const timeoutMs = input.timeoutMs ?? CURRENT_RELEASE_PROBE_TIMEOUT_MS;
38623
- return new Promise((resolve35) => {
39050
+ return new Promise((resolve36) => {
38624
39051
  const child = spawn12(input.execPath ?? process.execPath, [entrypoint, UPDATE_PROBE_COMMAND, "--config", input.runtimeConfigPath], { env: input.env ?? process.env, stdio: ["ignore", "pipe", "pipe"] });
38625
39052
  let stdout6 = "";
38626
39053
  let stderr = "";
@@ -38630,7 +39057,7 @@ async function probeReleaseInSubprocess(input) {
38630
39057
  return;
38631
39058
  settled = true;
38632
39059
  clearTimeout(timer);
38633
- resolve35(outcome);
39060
+ resolve36(outcome);
38634
39061
  };
38635
39062
  const timer = setTimeout(() => {
38636
39063
  child.kill("SIGKILL");
@@ -38677,7 +39104,7 @@ async function runUpdateProbeCommand(args, options = {}) {
38677
39104
  const runtime = await readRuntimeRecord(configPath);
38678
39105
  const agent = runtimeAgentCommand(runtime);
38679
39106
  const config = loadBodyConfig({
38680
- workspaceRoot: join14(dirname21(configPath), "workspace"),
39107
+ workspaceRoot: join14(dirname22(configPath), "workspace"),
38681
39108
  llmEnvFile: runtime.llmEnvFile,
38682
39109
  env: { ...env, BUZZ_AGENT_BIN: agent.command, BUZZ_DEV_MCP_BIN: runtime.mcpBinary },
38683
39110
  agent
@@ -38695,7 +39122,7 @@ async function runUpdateProbeCommand(args, options = {}) {
38695
39122
  }
38696
39123
  const layout = beelineInstallLayout(env);
38697
39124
  const releaseId = (layout && await activeReleaseId(layout).catch(() => void 0)) ?? "unknown";
38698
- const runtimeDir = dirname21(configPath);
39125
+ const runtimeDir = dirname22(configPath);
38699
39126
  const outcome = await probeOutcome(() => (options.probe ?? runUpdateFunctionalProbe)({
38700
39127
  config,
38701
39128
  runtimeDir,
@@ -38710,8 +39137,8 @@ async function runUpdateProbeCommand(args, options = {}) {
38710
39137
  }
38711
39138
 
38712
39139
  // 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";
39140
+ import { readFile as readFile15, readdir as readdir6, rename as rename9, writeFile as writeFile16 } from "node:fs/promises";
39141
+ import { resolve as resolve33 } from "node:path";
38715
39142
  var DAEMON_RELEASE_STATUS_FILE = "release-status.json";
38716
39143
  var RELEASE_VERSION = /^v\d+\.\d+\.\d+$/;
38717
39144
  var SOURCE_SHA = /^[0-9a-f]{7,64}$/;
@@ -38728,7 +39155,7 @@ async function writeDaemonReleaseStatus(runtimeDir, agentPubkey, identity, optio
38728
39155
  pid: options.pid ?? process.pid,
38729
39156
  readyAt: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
38730
39157
  };
38731
- const target = resolve32(runtimeDir, DAEMON_RELEASE_STATUS_FILE);
39158
+ const target = resolve33(runtimeDir, DAEMON_RELEASE_STATUS_FILE);
38732
39159
  const temporary = `${target}.${status.pid}.tmp`;
38733
39160
  await writeFile16(temporary, `${JSON.stringify(status, null, 2)}
38734
39161
  `, { mode: 384 });
@@ -38738,7 +39165,7 @@ async function writeDaemonReleaseStatus(runtimeDir, agentPubkey, identity, optio
38738
39165
 
38739
39166
  // apps/body/dist/scratch-sweep.js
38740
39167
  import { lstat as lstat5, readdir as readdir7, rmdir, unlink as unlink6 } from "node:fs/promises";
38741
- import { resolve as resolve33 } from "node:path";
39168
+ import { resolve as resolve34 } from "node:path";
38742
39169
  var DEFAULT_SCRATCH_TTL_HOURS = 72;
38743
39170
  var NEVER_SWEEP_SUBDIR_NAMES = new Set(HOME_SUBDIRS.filter((name) => name !== "tmp"));
38744
39171
  function scratchTtlMs(env = process.env) {
@@ -38747,7 +39174,7 @@ function scratchTtlMs(env = process.env) {
38747
39174
  return (Number.isFinite(hours) && hours > 0 ? hours : DEFAULT_SCRATCH_TTL_HOURS) * 60 * 60 * 1e3;
38748
39175
  }
38749
39176
  async function discoverAttachScratchRoots(runtimeDir) {
38750
- const roomsDir = resolve33(runtimeDir, "rooms");
39177
+ const roomsDir = resolve34(runtimeDir, "rooms");
38751
39178
  let entries;
38752
39179
  try {
38753
39180
  entries = await readdir7(roomsDir, { withFileTypes: true });
@@ -38758,7 +39185,7 @@ async function discoverAttachScratchRoots(runtimeDir) {
38758
39185
  for (const entry of entries) {
38759
39186
  if (!entry.isDirectory())
38760
39187
  continue;
38761
- const home = resolve33(roomsDir, entry.name, "agent-home");
39188
+ const home = resolve34(roomsDir, entry.name, "agent-home");
38762
39189
  const stats = await lstat5(home).catch(() => void 0);
38763
39190
  if (stats?.isDirectory())
38764
39191
  roots.push(home);
@@ -38777,7 +39204,7 @@ async function removeStaleFiles(dir, cutoffMs, protectNamesHere) {
38777
39204
  for (const entry of entries) {
38778
39205
  if (protectNamesHere && NEVER_SWEEP_SUBDIR_NAMES.has(entry.name))
38779
39206
  continue;
38780
- const path = resolve33(dir, entry.name);
39207
+ const path = resolve34(dir, entry.name);
38781
39208
  const stats = await lstat5(path).catch(() => void 0);
38782
39209
  if (!stats || stats.isSymbolicLink())
38783
39210
  continue;
@@ -38865,7 +39292,7 @@ var DaemonExitError = class extends Error {
38865
39292
  };
38866
39293
  async function runStoredDaemon(pathOrPointer) {
38867
39294
  const configPath = await resolveRuntimeConfigPath(pathOrPointer);
38868
- daemonFailureRuntimeDir = dirname22(configPath);
39295
+ daemonFailureRuntimeDir = dirname23(configPath);
38869
39296
  const accessMigration = await migrateRuntimeRecordAccessPolicy(configPath);
38870
39297
  let runtime = accessMigration.runtime;
38871
39298
  if (!runtime.transport) {
@@ -38884,7 +39311,7 @@ async function runStoredDaemon(pathOrPointer) {
38884
39311
  BUZZ_DEV_MCP_BIN: runtime.mcpBinary
38885
39312
  };
38886
39313
  const config = loadBodyConfig({
38887
- workspaceRoot: resolve34(dirname22(configPath), "workspace"),
39314
+ workspaceRoot: resolve35(dirname23(configPath), "workspace"),
38888
39315
  llmEnvFile: runtime.llmEnvFile,
38889
39316
  env,
38890
39317
  agent
@@ -38919,7 +39346,7 @@ async function runStoredDaemon(pathOrPointer) {
38919
39346
  const stop = () => controller.abort();
38920
39347
  process.once("SIGINT", stop);
38921
39348
  process.once("SIGTERM", stop);
38922
- const runtimeDir = dirname22(configPath);
39349
+ const runtimeDir = dirname23(configPath);
38923
39350
  const layout = beelineInstallLayout(process.env);
38924
39351
  const notifier = new SystemdNotifier();
38925
39352
  let rollbackAlertDrain;
@@ -39057,6 +39484,7 @@ async function runStoredDaemon(pathOrPointer) {
39057
39484
  agentId: runtime.agent.publicKey,
39058
39485
  log: (message) => console.log(`[body] connector: ${message}`)
39059
39486
  });
39487
+ daemonApi.setConnectorAssignmentListener(() => connectorLoop?.wake());
39060
39488
  connectorLoop.start();
39061
39489
  },
39062
39490
  onProgress: async (status) => {
@@ -39123,7 +39551,7 @@ async function main() {
39123
39551
  const roomId = roomFlag >= 0 ? args[roomFlag + 1] : void 0;
39124
39552
  if (!configPath || !roomId)
39125
39553
  throw new Error("corner-read-token requires --config and --room");
39126
- const activated = await activateDaemonTransport(resolve34(configPath));
39554
+ const activated = await activateDaemonTransport(resolve35(configPath));
39127
39555
  if (!activated)
39128
39556
  throw new Error("corner-read-token requires monolith transport");
39129
39557
  const credential = await activated.client.execute("getRoomGitHubToken", { roomId });
@@ -39177,14 +39605,14 @@ async function main() {
39177
39605
  }
39178
39606
  if (!configPath && agentPubkey) {
39179
39607
  const configs = await findAgentRuntimeConfigPaths(process.env, process.cwd());
39180
- configPath = configs.find((candidate) => dirname22(candidate).endsWith(agentPubkey));
39608
+ configPath = configs.find((candidate) => dirname23(candidate).endsWith(agentPubkey));
39181
39609
  }
39182
39610
  if (!configPath && agentPubkey) {
39183
39611
  throw new DaemonExitError(`unknown agent ${agentPubkey}: no durable runtime exists; refusing systemd restart loop`, UNKNOWN_AGENT_EXIT_STATUS);
39184
39612
  }
39185
39613
  if (!configPath)
39186
39614
  throw new Error("daemon requires --config <runtime.json> or --agent <pubkey>");
39187
- await runStoredDaemon(resolve34(configPath));
39615
+ await runStoredDaemon(resolve35(configPath));
39188
39616
  return;
39189
39617
  }
39190
39618
  if (command === "update") {
@@ -39205,7 +39633,7 @@ async function main() {
39205
39633
  if (!agentPubkey)
39206
39634
  throw new Error("stop requires --agent <pubkey>");
39207
39635
  const configs = await findAgentRuntimeConfigPaths(process.env, process.cwd());
39208
- const configPath = configs.find((candidate) => dirname22(candidate).endsWith(agentPubkey));
39636
+ const configPath = configs.find((candidate) => dirname23(candidate).endsWith(agentPubkey));
39209
39637
  if (!configPath)
39210
39638
  throw new Error(`no stored runtime found for agent ${agentPubkey}`);
39211
39639
  const runtime = await readRuntimeRecord(configPath);