usebeeline 0.0.36 → 0.0.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/usebeeline.mjs +1608 -469
  2. package/package.json +1 -1
@@ -307,14 +307,14 @@ __export(self_update_exports, {
307
307
  writeUpdateAttemptFixture: () => writeUpdateAttemptFixture,
308
308
  writeUpdateState: () => writeUpdateState
309
309
  });
310
- import { createHash as createHash2 } from "node:crypto";
310
+ import { createHash as createHash3 } from "node:crypto";
311
311
  import { constants as fsConstants } from "node:fs";
312
- import { access, chmod as chmod3, lstat as lstat2, mkdir as mkdir8, open, readFile as readFile4, rename as rename3, rm as rm4, symlink as symlink2, writeFile as writeFile5 } from "node:fs/promises";
312
+ import { access, chmod as chmod3, lstat as lstat2, mkdir as mkdir10, open, readFile as readFile8, rename as rename3, rm as rm4, symlink as symlink2, writeFile as writeFile8 } from "node:fs/promises";
313
313
  import { spawn as spawn4 } from "node:child_process";
314
314
  import { homedir as homedir9 } from "node:os";
315
- import { dirname as dirname8, join as join3, resolve as resolve14 } from "node:path";
315
+ import { dirname as dirname9, join as join6, resolve as resolve18 } from "node:path";
316
316
  function anchorLayout(rawLibDir) {
317
- const libDir = resolve14(rawLibDir);
317
+ const libDir = resolve18(rawLibDir);
318
318
  const segments = libDir.split(/[/\\]/);
319
319
  const idx = segments.lastIndexOf(RELEASES_SEGMENT);
320
320
  if (idx >= 2 && segments[idx - 1] === "lib") {
@@ -330,9 +330,9 @@ function anchorLayout(rawLibDir) {
330
330
  // prefix's bin, NOT <prefix>/lib/bin — deriving it one level up was the
331
331
  // defect that made activateRelease write its stable forwarders where
332
332
  // nothing executed them, leaving stale raw wrappers in <prefix>/bin.
333
- binDir: resolve14(libDir, "../../bin"),
333
+ binDir: resolve18(libDir, "../../bin"),
334
334
  libDir,
335
- releasesRoot: resolve14(libDir, `../${RELEASES_SEGMENT}`)
335
+ releasesRoot: resolve18(libDir, `../${RELEASES_SEGMENT}`)
336
336
  };
337
337
  }
338
338
  function beelineInstallLayout(env = process.env) {
@@ -343,7 +343,7 @@ function beelineInstallLayout(env = process.env) {
343
343
  }
344
344
  function defaultBeelineInstallLayout(env = process.env) {
345
345
  const home = env.HOME?.trim() || homedir9();
346
- return anchorLayout(resolve14(home, ".local", "lib", "beeline"));
346
+ return anchorLayout(resolve18(home, ".local", "lib", "beeline"));
347
347
  }
348
348
  function hostPlatformKey() {
349
349
  const os = process.platform === "linux" ? "linux" : process.platform === "darwin" ? "darwin" : "";
@@ -353,13 +353,13 @@ function hostPlatformKey() {
353
353
  return `${os}-${arch}`;
354
354
  }
355
355
  function bundleJsonCandidates(bundleDir) {
356
- return [join3(bundleDir, "lib", "beeline", "bundle.json"), join3(bundleDir, "bundle.json")];
356
+ return [join6(bundleDir, "lib", "beeline", "bundle.json"), join6(bundleDir, "bundle.json")];
357
357
  }
358
358
  async function readBundleJson(bundleDir) {
359
359
  let raw;
360
360
  for (const candidate of bundleJsonCandidates(bundleDir)) {
361
361
  try {
362
- raw = await readFile4(candidate, "utf8");
362
+ raw = await readFile8(candidate, "utf8");
363
363
  break;
364
364
  } catch {
365
365
  }
@@ -377,18 +377,18 @@ async function readBundleJson(bundleDir) {
377
377
  }
378
378
  }
379
379
  function updateStatePath(layout) {
380
- return join3(layout.releasesRoot, ".state", "update-state.json");
380
+ return join6(layout.releasesRoot, ".state", "update-state.json");
381
381
  }
382
382
  async function readUpdateState(layout) {
383
383
  try {
384
- return JSON.parse(await readFile4(updateStatePath(layout), "utf8"));
384
+ return JSON.parse(await readFile8(updateStatePath(layout), "utf8"));
385
385
  } catch {
386
386
  return {};
387
387
  }
388
388
  }
389
389
  async function writeUpdateState(layout, state) {
390
- await mkdir8(join3(layout.releasesRoot, ".state"), { recursive: true });
391
- await writeFile5(updateStatePath(layout), `${JSON.stringify(state, null, 2)}
390
+ await mkdir10(join6(layout.releasesRoot, ".state"), { recursive: true });
391
+ await writeFile8(updateStatePath(layout), `${JSON.stringify(state, null, 2)}
392
392
  `, "utf8");
393
393
  }
394
394
  async function readInstalledBundleIdentity(layout, _state = {}) {
@@ -469,7 +469,7 @@ function run(command, args, timeoutMs) {
469
469
  });
470
470
  }
471
471
  function entrypointCandidates(bundleDir) {
472
- return [join3(bundleDir, BUNDLE_ENTRYPOINT), join3(bundleDir, "beeline-cli.mjs")];
472
+ return [join6(bundleDir, BUNDLE_ENTRYPOINT), join6(bundleDir, "beeline-cli.mjs")];
473
473
  }
474
474
  async function resolveBundleEntrypoint(bundleDir) {
475
475
  for (const candidate of entrypointCandidates(bundleDir)) {
@@ -491,18 +491,18 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
491
491
  const fetchImpl = opts.fetchImpl ?? fetch;
492
492
  const log = opts.logger ?? ((line) => console.log(`[body] self-update: ${line}`));
493
493
  const releaseId = sanitizeReleaseId(published.commit ?? published.version ?? `release-${Date.now()}`);
494
- const releaseDir = join3(layout.releasesRoot, releaseId);
495
- const okMarker = join3(releaseDir, ".stage-ok");
494
+ const releaseDir = join6(layout.releasesRoot, releaseId);
495
+ const okMarker = join6(releaseDir, ".stage-ok");
496
496
  let previouslyVerified = false;
497
497
  try {
498
- const recorded = await readFile4(okMarker, "utf8");
498
+ const recorded = await readFile8(okMarker, "utf8");
499
499
  if (recorded.trim() === published.sha256)
500
500
  return releaseId;
501
501
  previouslyVerified = true;
502
502
  } catch {
503
503
  }
504
- await mkdir8(releaseDir, { recursive: true });
505
- const tempArchive = join3(layout.releasesRoot, `.download-${releaseId}-${process.pid}.tar.gz`);
504
+ await mkdir10(releaseDir, { recursive: true });
505
+ const tempArchive = join6(layout.releasesRoot, `.download-${releaseId}-${process.pid}.tar.gz`);
506
506
  try {
507
507
  log(`downloading ${published.file}`);
508
508
  const response = await fetchImpl(archiveUrlFor(manifestUrl, published.file), {
@@ -511,7 +511,7 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
511
511
  if (!response.ok || !response.body) {
512
512
  throw new Error(`downloading ${published.file} failed: HTTP ${response.status}`);
513
513
  }
514
- const hash = createHash2("sha256");
514
+ const hash = createHash3("sha256");
515
515
  const chunks = [];
516
516
  for await (const chunk of response.body) {
517
517
  const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
@@ -522,7 +522,7 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
522
522
  if (actual !== published.sha256.toLowerCase()) {
523
523
  throw new Error(`checksum mismatch for ${published.file}: expected ${published.sha256}, got ${actual} \u2014 aborting without touching the installed bundle`);
524
524
  }
525
- await writeFile5(tempArchive, Buffer.concat(chunks), { mode: 384 });
525
+ await writeFile8(tempArchive, Buffer.concat(chunks), { mode: 384 });
526
526
  const entries = (await new Promise((resolveList, rejectList) => {
527
527
  const child = spawn4("tar", ["-tzf", tempArchive], { stdio: ["ignore", "pipe", "inherit"] });
528
528
  let out = "";
@@ -542,18 +542,18 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
542
542
  throw new Error(`extracting bundle failed: ${extract2.stderr}`);
543
543
  for (const relative3 of requiredBundlePaths()) {
544
544
  try {
545
- await access(join3(releaseDir, relative3), fsConstants.F_OK);
545
+ await access(join6(releaseDir, relative3), fsConstants.F_OK);
546
546
  } catch {
547
547
  throw new Error(`staged bundle is missing ${relative3}`);
548
548
  }
549
549
  }
550
550
  if (opts.smokeTestCli !== false) {
551
- const probe = await run(process.execPath, [join3(releaseDir, BUNDLE_ENTRYPOINT), "--version"], 6e4);
551
+ const probe = await run(process.execPath, [join6(releaseDir, BUNDLE_ENTRYPOINT), "--version"], 6e4);
552
552
  if (probe.status !== 0) {
553
553
  throw new Error(`staged bundle failed its startup smoke test (--version exited ${probe.status})${probe.stderr ? `: ${probe.stderr.trim()}` : ""}`);
554
554
  }
555
555
  }
556
- await writeFile5(okMarker, `${published.sha256}
556
+ await writeFile8(okMarker, `${published.sha256}
557
557
  `, "utf8");
558
558
  log(`staged release ${releaseId} (sha256 verified)`);
559
559
  return releaseId;
@@ -581,26 +581,26 @@ function forwarderScript(tool) {
581
581
  }
582
582
  async function replaceFile(path, contents, mode) {
583
583
  const temp = `${path}.new-${process.pid}`;
584
- await writeFile5(temp, contents, { mode });
584
+ await writeFile8(temp, contents, { mode });
585
585
  await chmod3(temp, mode);
586
586
  await rename3(temp, path);
587
587
  }
588
588
  async function activateRelease(layout, releaseId) {
589
- const releaseDir = join3(layout.releasesRoot, releaseId);
590
- await access(join3(releaseDir, BUNDLE_ENTRYPOINT), fsConstants.F_OK);
591
- await mkdir8(layout.releasesRoot, { recursive: true });
592
- await mkdir8(layout.binDir, { recursive: true });
589
+ const releaseDir = join6(layout.releasesRoot, releaseId);
590
+ await access(join6(releaseDir, BUNDLE_ENTRYPOINT), fsConstants.F_OK);
591
+ await mkdir10(layout.releasesRoot, { recursive: true });
592
+ await mkdir10(layout.binDir, { recursive: true });
593
593
  let previousReleaseId = await activeReleaseId(layout);
594
594
  const kind = await pathKind(layout.libDir);
595
595
  if (kind === "directory") {
596
596
  const legacyIdentity = await readBundleJson(layout.libDir);
597
597
  const legacyId = sanitizeReleaseId(legacyIdentity?.commit ?? legacyIdentity?.version ?? `legacy-${Date.now()}`);
598
- const legacyDir = join3(layout.releasesRoot, legacyId);
598
+ const legacyDir = join6(layout.releasesRoot, legacyId);
599
599
  try {
600
600
  await access(legacyDir, fsConstants.F_OK);
601
601
  previousReleaseId = `${legacyId}-${Date.now()}`;
602
- await rename3(layout.libDir, join3(layout.releasesRoot, previousReleaseId));
603
- await normalizeLegacyBundleShape(join3(layout.releasesRoot, previousReleaseId));
602
+ await rename3(layout.libDir, join6(layout.releasesRoot, previousReleaseId));
603
+ await normalizeLegacyBundleShape(join6(layout.releasesRoot, previousReleaseId));
604
604
  } catch {
605
605
  await rename3(layout.libDir, legacyDir);
606
606
  await normalizeLegacyBundleShape(legacyDir);
@@ -609,18 +609,18 @@ async function activateRelease(layout, releaseId) {
609
609
  }
610
610
  const tempLink = `${layout.libDir}.new-${process.pid}`;
611
611
  await rm4(tempLink, { force: true });
612
- await symlink2(join3("beeline-releases", releaseId), tempLink);
612
+ await symlink2(join6("beeline-releases", releaseId), tempLink);
613
613
  await rename3(tempLink, layout.libDir);
614
- await fsyncDir(dirname8(layout.libDir));
614
+ await fsyncDir(dirname9(layout.libDir));
615
615
  await writeBinForwarders(layout, releaseDir);
616
616
  return { previousReleaseId };
617
617
  }
618
618
  async function normalizeLegacyBundleShape(bundleDir) {
619
- const innerLib = join3(bundleDir, "lib", "beeline");
619
+ const innerLib = join6(bundleDir, "lib", "beeline");
620
620
  let anyFlat = false;
621
621
  for (const name of LEGACY_FLAT_BUNDLE_FILES) {
622
622
  try {
623
- await access(join3(bundleDir, name), fsConstants.F_OK);
623
+ await access(join6(bundleDir, name), fsConstants.F_OK);
624
624
  anyFlat = true;
625
625
  break;
626
626
  } catch {
@@ -628,62 +628,62 @@ async function normalizeLegacyBundleShape(bundleDir) {
628
628
  }
629
629
  if (!anyFlat)
630
630
  return;
631
- await mkdir8(innerLib, { recursive: true });
631
+ await mkdir10(innerLib, { recursive: true });
632
632
  for (const name of LEGACY_FLAT_BUNDLE_FILES) {
633
633
  try {
634
- await access(join3(innerLib, name), fsConstants.F_OK);
634
+ await access(join6(innerLib, name), fsConstants.F_OK);
635
635
  continue;
636
636
  } catch {
637
637
  }
638
- await rename3(join3(bundleDir, name), join3(innerLib, name)).catch(() => void 0);
638
+ await rename3(join6(bundleDir, name), join6(innerLib, name)).catch(() => void 0);
639
639
  }
640
640
  }
641
641
  async function writeBinForwarders(layout, activeBundleRoot) {
642
642
  for (const tool of FORWARDER_TOOLS) {
643
- const target = join3(activeBundleRoot, "bin", tool);
643
+ const target = join6(activeBundleRoot, "bin", tool);
644
644
  try {
645
645
  await access(target, fsConstants.X_OK);
646
646
  } catch {
647
647
  continue;
648
648
  }
649
- await replaceFile(join3(layout.binDir, tool), forwarderScript(tool), 493);
649
+ await replaceFile(join6(layout.binDir, tool), forwarderScript(tool), 493);
650
650
  }
651
651
  }
652
652
  async function repairInstallForwarders(layout, opts = {}) {
653
653
  if (await pathKind(layout.libDir) !== "symlink")
654
654
  return false;
655
- const forwarderPath = join3(layout.binDir, "beeline");
655
+ const forwarderPath = join6(layout.binDir, "beeline");
656
656
  let current;
657
657
  try {
658
- current = await readFile4(forwarderPath, "utf8");
658
+ current = await readFile8(forwarderPath, "utf8");
659
659
  } catch {
660
660
  current = void 0;
661
661
  }
662
662
  if (current === forwarderScript("beeline"))
663
663
  return false;
664
- await mkdir8(layout.binDir, { recursive: true });
664
+ await mkdir10(layout.binDir, { recursive: true });
665
665
  await writeBinForwarders(layout, layout.libDir);
666
666
  opts.logger?.(`[body] self-update: repaired <prefix>/bin forwarders to follow the active-bundle anchor (${layout.libDir})`);
667
667
  return true;
668
668
  }
669
669
  async function rollbackToPreviousRelease(layout, previousReleaseId) {
670
- const releaseDir = join3(layout.releasesRoot, previousReleaseId);
670
+ const releaseDir = join6(layout.releasesRoot, previousReleaseId);
671
671
  const entrypoint = await resolveBundleEntrypoint(releaseDir);
672
672
  if (!entrypoint) {
673
673
  throw new Error(`release ${previousReleaseId} has no runnable CLI entrypoint`);
674
674
  }
675
675
  const tempLink = `${layout.libDir}.rollback-${process.pid}`;
676
676
  await rm4(tempLink, { force: true });
677
- await symlink2(join3("beeline-releases", previousReleaseId), tempLink);
677
+ await symlink2(join6("beeline-releases", previousReleaseId), tempLink);
678
678
  await rename3(tempLink, layout.libDir);
679
- await fsyncDir(dirname8(layout.libDir));
679
+ await fsyncDir(dirname9(layout.libDir));
680
680
  }
681
681
  function updateAttemptPath(layout) {
682
- return join3(layout.releasesRoot, ".state", "update-attempt.json");
682
+ return join6(layout.releasesRoot, ".state", "update-attempt.json");
683
683
  }
684
684
  async function readUpdateAttempt(layout) {
685
685
  try {
686
- const raw = JSON.parse(await readFile4(updateAttemptPath(layout), "utf8"));
686
+ const raw = JSON.parse(await readFile8(updateAttemptPath(layout), "utf8"));
687
687
  if (raw.version !== 1 || typeof raw.appliedAt !== "number" || typeof raw.confirmBy !== "number" || typeof raw.releaseId !== "string" || !["pending", "confirmed", "reverted"].includes(raw.status)) {
688
688
  return void 0;
689
689
  }
@@ -693,10 +693,10 @@ async function readUpdateAttempt(layout) {
693
693
  }
694
694
  }
695
695
  async function writeUpdateAttempt(layout, record2) {
696
- await mkdir8(join3(layout.releasesRoot, ".state"), { recursive: true });
696
+ await mkdir10(join6(layout.releasesRoot, ".state"), { recursive: true });
697
697
  const path = updateAttemptPath(layout);
698
698
  const staged = `${path}.${process.pid}.tmp`;
699
- await writeFile5(staged, `${JSON.stringify(record2, null, 2)}
699
+ await writeFile8(staged, `${JSON.stringify(record2, null, 2)}
700
700
  `, { mode: 384 });
701
701
  await rename3(staged, path);
702
702
  }
@@ -955,8 +955,8 @@ var init_self_update = __esm({
955
955
  });
956
956
 
957
957
  // apps/body/dist/cli.js
958
- import { dirname as dirname13, resolve as resolve21 } from "node:path";
959
- import { readFile as readFile9, unlink as unlink3, writeFile as writeFile11 } from "node:fs/promises";
958
+ import { dirname as dirname14, resolve as resolve25 } from "node:path";
959
+ import { readFile as readFile13, unlink as unlink3, writeFile as writeFile14 } from "node:fs/promises";
960
960
  import { stdin as stdin4, stdout as stdout5 } from "node:process";
961
961
 
962
962
  // node_modules/@clack/core/dist/index.mjs
@@ -2954,6 +2954,12 @@ function cornerAutonomyModeCandidates(agentCommand) {
2954
2954
  return [];
2955
2955
  return ["agent", "edit", "code"];
2956
2956
  }
2957
+ function roomModeCandidates(agentCommand, options = {}) {
2958
+ if (options.osSandbox && agentCommand && /(^|[/\\])codex-acp(\.[a-z]+)?$/i.test(agentCommand)) {
2959
+ return ["agent-full-access"];
2960
+ }
2961
+ return ["read-only", "readonly"];
2962
+ }
2957
2963
 
2958
2964
  // apps/body/dist/harness-tool-scope.js
2959
2965
  var CLAUDE_TOOL_SCOPE_SETTINGS = { disableClaudeAiConnectors: true };
@@ -3093,6 +3099,9 @@ function agentMessageChunkText(update) {
3093
3099
  }
3094
3100
  var CHUNK_CONTINUES_PREVIOUS_WORD = /^[\s'\u2018\u2019\u02bc.,!?;:%)\]}-]/;
3095
3101
  var PI_ACP_HARNESS = /(^|[/\\])pi-acp(?:\.[a-z]+)?$/i;
3102
+ function isPiAcpHarness(agentLabel) {
3103
+ return Boolean(agentLabel && PI_ACP_HARNESS.test(agentLabel));
3104
+ }
3096
3105
  function withoutOneTrailingLineEnding(text2) {
3097
3106
  if (/\r?\n\r?\n$/.test(text2))
3098
3107
  return text2;
@@ -3160,6 +3169,19 @@ function finalAgentMessageText(updates, agentLabel) {
3160
3169
  return "";
3161
3170
  return last;
3162
3171
  }
3172
+ function describeEmptyTurn(result, agentLabel) {
3173
+ const counts = /* @__PURE__ */ new Map();
3174
+ for (const { update } of result.updates) {
3175
+ const kind = typeof update.sessionUpdate === "string" ? update.sessionUpdate : "unknown";
3176
+ if (kind === "session_info_update" || kind === "available_commands_update")
3177
+ continue;
3178
+ counts.set(kind, (counts.get(kind) ?? 0) + 1);
3179
+ }
3180
+ const stream = counts.size ? `the stream carried only ${[...counts].map(([kind, count]) => `${kind}\xD7${count}`).join(", ")}` : "the stream carried no content updates";
3181
+ const lastRun = agentMessageRuns(result.updates, agentLabel).at(-1);
3182
+ const narration = lastRun && isPureRetryNarration(lastRun) ? `; the last message was retry narration "${lastRun.trim().slice(0, 80)}"` : "";
3183
+ return `harness ended the turn (${result.stopReason}) with no answer text; ${stream}${narration}`;
3184
+ }
3163
3185
  function updateText(update) {
3164
3186
  const content = update.content;
3165
3187
  if (typeof content === "string")
@@ -3180,7 +3202,7 @@ var THOUGHT_UPDATE_TYPES = /* @__PURE__ */ new Set([
3180
3202
  ]);
3181
3203
  function agentStreamSnapshot(updates, agentLabel) {
3182
3204
  const completedMessages = [];
3183
- let messageText = "";
3205
+ let messageText2 = "";
3184
3206
  let lastWasMessage = false;
3185
3207
  let thoughtText = "";
3186
3208
  let thoughtRunOpen = false;
@@ -3190,20 +3212,20 @@ function agentStreamSnapshot(updates, agentLabel) {
3190
3212
  const delta = normalizeStreamDelta(agentMessageChunkText(update), agentLabel);
3191
3213
  if (!delta)
3192
3214
  continue;
3193
- if (!lastWasMessage && messageText && !/\s$/.test(messageText) && !CHUNK_CONTINUES_PREVIOUS_WORD.test(delta)) {
3194
- if (!completedMessages.includes(messageText))
3195
- completedMessages.push(messageText);
3196
- messageText = "";
3215
+ if (!lastWasMessage && messageText2 && !/\s$/.test(messageText2) && !CHUNK_CONTINUES_PREVIOUS_WORD.test(delta)) {
3216
+ if (!completedMessages.includes(messageText2))
3217
+ completedMessages.push(messageText2);
3218
+ messageText2 = "";
3197
3219
  }
3198
- messageText += delta;
3220
+ messageText2 += delta;
3199
3221
  lastWasMessage = true;
3200
3222
  thoughtRunOpen = false;
3201
3223
  continue;
3202
3224
  }
3203
- if (lastWasMessage && messageText) {
3204
- if (!completedMessages.includes(messageText))
3205
- completedMessages.push(messageText);
3206
- messageText = "";
3225
+ if (lastWasMessage && messageText2) {
3226
+ if (!completedMessages.includes(messageText2))
3227
+ completedMessages.push(messageText2);
3228
+ messageText2 = "";
3207
3229
  }
3208
3230
  lastWasMessage = false;
3209
3231
  if (THOUGHT_UPDATE_TYPES.has(kind)) {
@@ -3219,7 +3241,7 @@ function agentStreamSnapshot(updates, agentLabel) {
3219
3241
  }
3220
3242
  }
3221
3243
  return {
3222
- messageText,
3244
+ messageText: messageText2,
3223
3245
  ...thoughtText || completedMessages.length ? { thoughtText: thoughtText || completedMessages.at(-1) } : {}
3224
3246
  };
3225
3247
  }
@@ -3268,6 +3290,7 @@ var AcpClient = class extends EventEmitter {
3268
3290
  sessionCommands = /* @__PURE__ */ new Map();
3269
3291
  supportsStandardSteering = false;
3270
3292
  supportsSessionLoading = false;
3293
+ supportsImagePrompts = false;
3271
3294
  alive = false;
3272
3295
  /** Bounded tail of recent stderr, so a spawn/exit failure's rejection text
3273
3296
  * carries the real reason (e.g. a harness's own "missing API key" notice)
@@ -3280,6 +3303,7 @@ var AcpClient = class extends EventEmitter {
3280
3303
  agentCwd;
3281
3304
  inheritProcessEnv;
3282
3305
  autoApprove;
3306
+ osSandbox;
3283
3307
  permissionHandler;
3284
3308
  permissionAllowlist;
3285
3309
  constructor(opts) {
@@ -3295,6 +3319,7 @@ var AcpClient = class extends EventEmitter {
3295
3319
  this.agentCwd = opts.agentCwd;
3296
3320
  this.inheritProcessEnv = opts.inheritProcessEnv ?? process.env.BUZZY_BODY_AGENT_ENV_INHERIT === "1";
3297
3321
  this.autoApprove = opts.autoApprovePermissions ?? true;
3322
+ this.osSandbox = opts.osSandbox ?? false;
3298
3323
  this.permissionHandler = opts.permissionHandler;
3299
3324
  this.permissionAllowlist = opts.permissionAllowlist;
3300
3325
  }
@@ -3354,6 +3379,7 @@ var AcpClient = class extends EventEmitter {
3354
3379
  this.supportsStandardSteering = steering?.supported === true;
3355
3380
  const agentCapabilities = initResult.agentCapabilities;
3356
3381
  this.supportsSessionLoading = agentCapabilities?.loadSession === true;
3382
+ this.supportsImagePrompts = agentCapabilities?.promptCapabilities?.image === true;
3357
3383
  this.emit("initialized", initResult);
3358
3384
  this.notify("notifications/initialized", {});
3359
3385
  }
@@ -3400,6 +3426,10 @@ var AcpClient = class extends EventEmitter {
3400
3426
  canLoadSession() {
3401
3427
  return this.supportsSessionLoading;
3402
3428
  }
3429
+ /** Whether this live ACP process accepts `image` prompt blocks (`promptCapabilities.image`). */
3430
+ canPromptWithImages() {
3431
+ return this.supportsImagePrompts;
3432
+ }
3403
3433
  async sessionNew(opts) {
3404
3434
  const params = {
3405
3435
  cwd: opts.cwd,
@@ -3459,7 +3489,7 @@ var AcpClient = class extends EventEmitter {
3459
3489
  if (!mode)
3460
3490
  return;
3461
3491
  const modes = raw.modes;
3462
- const candidates = mode === "readonly" ? ["read-only", "readonly"] : cornerAutonomyModeCandidates(this.agentLabel);
3492
+ const candidates = mode === "readonly" ? roomModeCandidates(this.agentLabel, { osSandbox: this.osSandbox }) : cornerAutonomyModeCandidates(this.agentLabel);
3463
3493
  const target = modes?.availableModes?.find((candidate) => candidate.id && candidates.includes(candidate.id))?.id;
3464
3494
  if (!target || modes?.currentModeId === target)
3465
3495
  return;
@@ -3506,7 +3536,7 @@ var AcpClient = class extends EventEmitter {
3506
3536
  try {
3507
3537
  const result = await this.request("session/prompt", {
3508
3538
  sessionId,
3509
- prompt: [{ type: "text", text: text2 }]
3539
+ prompt: typeof text2 === "string" ? [{ type: "text", text: text2 }] : [...text2]
3510
3540
  }, timeoutMs, (id) => {
3511
3541
  requestId = id;
3512
3542
  });
@@ -3706,7 +3736,7 @@ var AcpClient = class extends EventEmitter {
3706
3736
  const current = this.activeRunIds.get(sessionId);
3707
3737
  if (current)
3708
3738
  return Promise.resolve(current);
3709
- return new Promise((resolve22, reject) => {
3739
+ return new Promise((resolve26, reject) => {
3710
3740
  const onUpdate = (update) => {
3711
3741
  if (update.sessionId !== sessionId)
3712
3742
  return;
@@ -3714,7 +3744,7 @@ var AcpClient = class extends EventEmitter {
3714
3744
  if (!runId)
3715
3745
  return;
3716
3746
  cleanup();
3717
- resolve22(runId);
3747
+ resolve26(runId);
3718
3748
  };
3719
3749
  const timer = setTimeout(() => {
3720
3750
  cleanup();
@@ -3734,23 +3764,23 @@ var AcpClient = class extends EventEmitter {
3734
3764
  const tracked = metadataKey ? this.toolCallMetadata.get(metadataKey) : void 0;
3735
3765
  if (tracked)
3736
3766
  p.toolCall = { ...tracked, ...p.toolCall };
3737
- let decision = this.autoApprove ? "allow" : "reject";
3767
+ let decision2 = this.autoApprove ? "allow" : "reject";
3738
3768
  if (!this.autoApprove && this.permissionAllowlist) {
3739
3769
  try {
3740
- decision = this.permissionAllowlist(p) ? "allow" : "reject";
3770
+ decision2 = this.permissionAllowlist(p) ? "allow" : "reject";
3741
3771
  } catch (error) {
3742
3772
  this.emit("permission/error", error);
3743
- decision = "reject";
3773
+ decision2 = "reject";
3744
3774
  }
3745
3775
  } else if (this.permissionHandler) {
3746
3776
  try {
3747
- decision = await this.permissionHandler(p);
3777
+ decision2 = await this.permissionHandler(p);
3748
3778
  } catch (error) {
3749
3779
  this.emit("permission/error", error);
3750
- decision = "reject";
3780
+ decision2 = "reject";
3751
3781
  }
3752
3782
  }
3753
- if (decision === "allow") {
3783
+ if (decision2 === "allow") {
3754
3784
  const allow = p?.options?.find((o) => o.kind === "allow_once" || o.kind === "allow_always") ?? p?.options?.[0];
3755
3785
  if (allow?.optionId) {
3756
3786
  if (metadataKey)
@@ -3798,13 +3828,13 @@ var AcpClient = class extends EventEmitter {
3798
3828
  }
3799
3829
  const id = this.nextId++;
3800
3830
  const payload = { jsonrpc: "2.0", id, method, params };
3801
- return new Promise((resolve22, reject) => {
3831
+ return new Promise((resolve26, reject) => {
3802
3832
  const timer = setTimeout(() => {
3803
3833
  this.pending.delete(id);
3804
3834
  reject(new AcpRequestTimeoutError(method, timeoutMs, this.stderrTail, Boolean(onStart)));
3805
3835
  }, timeoutMs);
3806
3836
  this.pending.set(id, {
3807
- resolve: resolve22,
3837
+ resolve: resolve26,
3808
3838
  reject,
3809
3839
  timer,
3810
3840
  method,
@@ -3874,6 +3904,18 @@ function advertisedChoiceId(choice) {
3874
3904
  return choice.value;
3875
3905
  return void 0;
3876
3906
  }
3907
+ function sortModelChoicesNewestFirst(choices) {
3908
+ return choices.map((choice, index) => ({ choice, index })).sort((left, right) => {
3909
+ const leftParts = left.choice.id.match(/\d+/g)?.map(Number) ?? [];
3910
+ const rightParts = right.choice.id.match(/\d+/g)?.map(Number) ?? [];
3911
+ for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) {
3912
+ const delta = (rightParts[index] ?? -1) - (leftParts[index] ?? -1);
3913
+ if (delta !== 0)
3914
+ return delta;
3915
+ }
3916
+ return left.index - right.index;
3917
+ }).map(({ choice }) => choice);
3918
+ }
3877
3919
  function parseAdvertisedConfigOptions(raw, preferredModelId, includeLaunchEffort = false) {
3878
3920
  const configOptions = raw?.configOptions;
3879
3921
  const result = [];
@@ -3903,7 +3945,7 @@ function parseAdvertisedConfigOptions(raw, preferredModelId, includeLaunchEffort
3903
3945
  id,
3904
3946
  category,
3905
3947
  ...typeof record2.currentValue === "string" ? { currentValue: record2.currentValue } : {},
3906
- options: choices
3948
+ options: category === "model" ? sortModelChoicesNewestFirst(choices) : choices
3907
3949
  });
3908
3950
  }
3909
3951
  const models = raw?.models;
@@ -3929,7 +3971,7 @@ function parseAdvertisedConfigOptions(raw, preferredModelId, includeLaunchEffort
3929
3971
  id: GROK_SESSION_MODEL_AXIS_ID,
3930
3972
  category: "model",
3931
3973
  ...currentModelId ? { currentValue: currentModelId } : {},
3932
- options: modelChoices
3974
+ options: sortModelChoicesNewestFirst(modelChoices)
3933
3975
  });
3934
3976
  }
3935
3977
  const effortModelId = preferredModelId ?? currentModelId;
@@ -4210,30 +4252,433 @@ async function applyRuntimeModelPreflight(config, agent, selection, validate = v
4210
4252
  config.modelUnavailable = await revalidateRuntimeModelSelection(agent, config.agentEnv, selection, validate);
4211
4253
  }
4212
4254
 
4213
- // apps/body/dist/room-runtime.js
4214
- import { execFile as execFile3 } from "node:child_process";
4255
+ // apps/body/dist/model-catalog-sync.js
4215
4256
  import { createHash } from "node:crypto";
4257
+ import { readFile, writeFile } from "node:fs/promises";
4258
+ import { resolve as resolve5 } from "node:path";
4259
+ var MODEL_CATALOG_PROBE_TIMEOUT_MS = 3e4;
4260
+ var MODEL_CATALOG_HASH_FILE = "model-catalog.sha256";
4261
+ function modelCatalogHash(options, selection) {
4262
+ return createHash("sha256").update(JSON.stringify({ options, selection: selection ?? null })).digest("hex");
4263
+ }
4264
+ async function withTimeout(work, timeoutMs, what) {
4265
+ let timer;
4266
+ const expiry = new Promise((_, reject) => {
4267
+ timer = setTimeout(() => reject(new Error(`${what} timed out after ${timeoutMs}ms`)), timeoutMs);
4268
+ });
4269
+ try {
4270
+ return await Promise.race([work, expiry]);
4271
+ } finally {
4272
+ clearTimeout(timer);
4273
+ work.catch(() => void 0);
4274
+ }
4275
+ }
4276
+ async function syncAgentModelCatalog(input) {
4277
+ const log = input.log ?? ((line) => console.log(line));
4278
+ const fetchCatalog = input.fetchCatalog ?? fetchAgentModelCatalog;
4279
+ const hashPath = resolve5(input.runtimeDir, MODEL_CATALOG_HASH_FILE);
4280
+ try {
4281
+ const configuration = await input.api.execute("getAgentConfiguration", {
4282
+ agentId: input.agentId
4283
+ });
4284
+ const selection = configuration.model || configuration.effort ? {
4285
+ ...configuration.model ? { model: configuration.model } : {},
4286
+ ...configuration.effort ? { effort: configuration.effort } : {}
4287
+ } : input.runtimeSelection;
4288
+ const { catalog } = await withTimeout(fetchCatalog(input.agent, input.agentEnv, selection), input.timeoutMs ?? MODEL_CATALOG_PROBE_TIMEOUT_MS, "model catalog probe");
4289
+ const hash = modelCatalogHash(catalog, selection);
4290
+ const previous = await readFile(hashPath, "utf8").catch(() => "");
4291
+ if (previous.trim() === hash)
4292
+ return "unchanged";
4293
+ await input.api.execute("postAgentModelCatalog", {
4294
+ agentId: input.agentId,
4295
+ workspaceId: input.workspaceId,
4296
+ // `fetchAgentModelCatalog` already applied the category allow-list.
4297
+ options: catalog,
4298
+ ...selection ? { selection } : {}
4299
+ });
4300
+ await writeFile(hashPath, `${hash}
4301
+ `, { mode: 384 });
4302
+ log(`[body] model catalog posted: ${catalog.length} axis(es)` + (selection?.model ? `, model ${selection.model}` : "") + (selection?.effort ? `, effort ${selection.effort}` : ""));
4303
+ return "posted";
4304
+ } catch (error) {
4305
+ log(`[body] model catalog not posted this activation: ${error instanceof Error ? error.message : String(error)}`);
4306
+ return "failed";
4307
+ }
4308
+ }
4309
+
4310
+ // apps/body/dist/room-runtime.js
4311
+ import { execFile as execFile4 } from "node:child_process";
4312
+ import { createHash as createHash2 } from "node:crypto";
4216
4313
  import { existsSync as existsSync4, mkdirSync } from "node:fs";
4217
- import { mkdir as mkdir5, rm as rm3 } from "node:fs/promises";
4218
- import { dirname as dirname4, resolve as resolve10 } from "node:path";
4314
+ import { mkdir as mkdir8, rm as rm3 } from "node:fs/promises";
4315
+ import { dirname as dirname6, resolve as resolve15 } from "node:path";
4219
4316
  import { promisify as promisify3 } from "node:util";
4220
4317
 
4318
+ // apps/body/dist/grant-runner.js
4319
+ import { execFile } from "node:child_process";
4320
+ import { randomBytes } from "node:crypto";
4321
+ import { readFile as readFile3 } from "node:fs/promises";
4322
+ import { createServer } from "node:http";
4323
+ import { dirname as dirname3, resolve as resolve7 } from "node:path";
4324
+
4325
+ // packages/api-contract/dist/agent-grants.js
4326
+ var AGENT_GRANT_KINDS = ["path", "host", "secret", "device", "budget", "command"];
4327
+ var SHELL_METACHARACTERS = /[;&|<>$`\\'"(){}\n\r\t*?[\]~#!]/;
4328
+ var SECRET_NAME = /^[A-Z][A-Z0-9_]{0,63}$/;
4329
+ function parseCommandGrantTarget(target) {
4330
+ if (typeof target !== "string" || !target.trim()) {
4331
+ throw new Error("command target is required");
4332
+ }
4333
+ if (target !== target.trim() || /\s{2,}/.test(target)) {
4334
+ throw new Error("command target must be one line with single spaces");
4335
+ }
4336
+ if (SHELL_METACHARACTERS.test(target)) {
4337
+ throw new Error("command target must not contain shell metacharacters");
4338
+ }
4339
+ const words = target.split(" ");
4340
+ const argv = [];
4341
+ const secrets = [];
4342
+ for (let index = 0; index < words.length; index += 1) {
4343
+ const word = words[index];
4344
+ if (word === "--with") {
4345
+ const name = words[index + 1];
4346
+ if (!name || !SECRET_NAME.test(name)) {
4347
+ throw new Error("--with must name one UPPER_CASE secret");
4348
+ }
4349
+ if (!secrets.includes(name))
4350
+ secrets.push(name);
4351
+ index += 1;
4352
+ continue;
4353
+ }
4354
+ if (secrets.length)
4355
+ throw new Error("--with suffixes must come after the command words");
4356
+ argv.push(word);
4357
+ }
4358
+ if (!argv.length)
4359
+ throw new Error("command target must name a command");
4360
+ return { argv, secrets };
4361
+ }
4362
+ function commandGrantMatches(rule, requested) {
4363
+ if (requested.length < rule.argv.length)
4364
+ return false;
4365
+ return rule.argv.every((word, index) => requested[index] === word);
4366
+ }
4367
+ var DECISION_LINE = new RegExp(`^(.+?) (approved once|approved|declined) (${AGENT_GRANT_KINDS.join("|")}) (.+)$`, "s");
4368
+ function parseGrantDecisionLine(body) {
4369
+ const match = DECISION_LINE.exec(body);
4370
+ if (!match)
4371
+ return void 0;
4372
+ const decision2 = match[2] === "approved once" ? "once" : match[2] === "approved" ? "always" : "deny";
4373
+ return {
4374
+ deciderName: match[1],
4375
+ decision: decision2,
4376
+ kind: match[3],
4377
+ target: match[4]
4378
+ };
4379
+ }
4380
+
4381
+ // apps/body/dist/provider-key-store.js
4382
+ import { chmod, mkdir, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
4383
+ import { homedir as homedir2 } from "node:os";
4384
+ import { dirname as dirname2, resolve as resolve6 } from "node:path";
4385
+ var PROVIDER_KEY_ENV_VARS = {
4386
+ openrouter: "OPENROUTER_API_KEY",
4387
+ openai: "OPENAI_API_KEY",
4388
+ anthropic: "ANTHROPIC_API_KEY",
4389
+ google: "GOOGLE_API_KEY",
4390
+ xai: "XAI_API_KEY"
4391
+ };
4392
+ var GOOGLE_ENV_ALIAS = "GEMINI_API_KEY";
4393
+ function providerKeyStorePath(env = process.env) {
4394
+ const configRoot = env.XDG_CONFIG_HOME?.trim() || resolve6(homedir2(), ".config");
4395
+ return resolve6(configRoot, "beeline", "providers.json");
4396
+ }
4397
+ async function readProviderKeyStore(env = process.env) {
4398
+ const path = providerKeyStorePath(env);
4399
+ const raw = await readFile2(path, "utf8").catch(() => void 0);
4400
+ if (!raw)
4401
+ return {};
4402
+ try {
4403
+ const parsed = JSON.parse(raw);
4404
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
4405
+ return {};
4406
+ const entries = Object.entries(parsed).filter((entry) => entry[0] in PROVIDER_KEY_ENV_VARS && typeof entry[1] === "string" && entry[1].length > 0);
4407
+ return Object.fromEntries(entries);
4408
+ } catch {
4409
+ return {};
4410
+ }
4411
+ }
4412
+ async function readSavedProviderKey(provider, env = process.env) {
4413
+ return (await readProviderKeyStore(env))[provider];
4414
+ }
4415
+ async function saveProviderKey(provider, key, env = process.env) {
4416
+ const path = providerKeyStorePath(env);
4417
+ const store = { ...await readProviderKeyStore(env), [provider]: key };
4418
+ await mkdir(dirname2(path), { recursive: true, mode: 448 });
4419
+ await writeFile2(path, `${JSON.stringify(store, null, 2)}
4420
+ `, { mode: 384 });
4421
+ await chmod(path, 384);
4422
+ }
4423
+ function providerKeyFromEnvironment(provider, env = process.env) {
4424
+ const primary = env[PROVIDER_KEY_ENV_VARS[provider]]?.trim();
4425
+ if (primary)
4426
+ return primary;
4427
+ if (provider === "google")
4428
+ return env[GOOGLE_ENV_ALIAS]?.trim() || void 0;
4429
+ return void 0;
4430
+ }
4431
+ function maskProviderKey(key) {
4432
+ const trimmed = key.trim();
4433
+ if (trimmed.length <= 9)
4434
+ return "\u2026";
4435
+ return `${trimmed.slice(0, 6)}\u2026${trimmed.slice(-3)}`;
4436
+ }
4437
+
4438
+ // apps/body/dist/grant-runner.js
4439
+ var GRANT_COMMAND_TIMEOUT_MS = 10 * 6e4;
4440
+ var GRANT_COMMAND_OUTPUT_CAP_BYTES = 64 * 1024;
4441
+ var ARGV_MAX_WORDS = 256;
4442
+ var ARGV_WORD_MAX_LENGTH = 4096;
4443
+ function operatorSecretResolver(env = process.env) {
4444
+ const providerByEnvVar = new Map(Object.entries(PROVIDER_KEY_ENV_VARS).map(([provider, envVar]) => [envVar, provider]));
4445
+ return async (name) => {
4446
+ const provider = providerByEnvVar.get(name);
4447
+ if (provider) {
4448
+ const saved = (await readProviderKeyStore(env))[provider];
4449
+ if (saved)
4450
+ return saved;
4451
+ }
4452
+ const raw = await readFile3(resolve7(dirname3(providerKeyStorePath(env)), "secrets.json"), "utf8").catch(() => void 0);
4453
+ if (!raw)
4454
+ return void 0;
4455
+ try {
4456
+ const parsed = JSON.parse(raw);
4457
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
4458
+ return void 0;
4459
+ const value = parsed[name];
4460
+ return typeof value === "string" && value.length > 0 ? value : void 0;
4461
+ } catch {
4462
+ return void 0;
4463
+ }
4464
+ };
4465
+ }
4466
+ function validateGrantArgv(value) {
4467
+ if (!Array.isArray(value) || value.length === 0)
4468
+ throw new Error("argv must be a non-empty array");
4469
+ if (value.length > ARGV_MAX_WORDS)
4470
+ throw new Error(`argv exceeds ${ARGV_MAX_WORDS} words`);
4471
+ return value.map((word) => {
4472
+ if (typeof word !== "string" || !word)
4473
+ throw new Error("argv words must be non-empty strings");
4474
+ if (word.length > ARGV_WORD_MAX_LENGTH || /[\0\r\n]/.test(word)) {
4475
+ throw new Error("argv words must be single-line and bounded");
4476
+ }
4477
+ return word;
4478
+ });
4479
+ }
4480
+ function matchCommandGrant(grants, workspaceId, argv) {
4481
+ for (const grant of grants) {
4482
+ if (grant.kind !== "command" || grant.workspaceId !== workspaceId)
4483
+ continue;
4484
+ let rule;
4485
+ try {
4486
+ rule = parseCommandGrantTarget(grant.target);
4487
+ } catch {
4488
+ continue;
4489
+ }
4490
+ if (commandGrantMatches(rule, argv))
4491
+ return { grant, rule };
4492
+ }
4493
+ return void 0;
4494
+ }
4495
+ function capOutput(value, cap) {
4496
+ if (Buffer.byteLength(value) <= cap)
4497
+ return value;
4498
+ const half = Math.floor(cap / 2);
4499
+ const bytes = Buffer.from(value);
4500
+ return `${bytes.subarray(0, half).toString("utf8")}
4501
+ \u2026[${bytes.length - cap} bytes omitted]\u2026
4502
+ ${bytes.subarray(bytes.length - half).toString("utf8")}`;
4503
+ }
4504
+ function scrubSecrets(value, secrets) {
4505
+ let scrubbed = value;
4506
+ for (const [name, secret] of secrets) {
4507
+ if (secret.length >= 4)
4508
+ scrubbed = scrubbed.split(secret).join(`[${name}]`);
4509
+ }
4510
+ return scrubbed;
4511
+ }
4512
+ var GrantCommandRunner = class {
4513
+ options;
4514
+ rooms = /* @__PURE__ */ new Map();
4515
+ resolveSecret;
4516
+ constructor(options) {
4517
+ this.options = options;
4518
+ this.resolveSecret = options.resolveSecret ?? operatorSecretResolver(options.env ?? process.env);
4519
+ }
4520
+ register(roomId, room) {
4521
+ this.rooms.set(roomId, room);
4522
+ }
4523
+ unregister(roomId) {
4524
+ this.rooms.delete(roomId);
4525
+ }
4526
+ async run(input) {
4527
+ if (typeof input.roomId !== "string" || !input.roomId)
4528
+ throw new Error("roomId is required");
4529
+ const room = this.rooms.get(input.roomId);
4530
+ if (!room)
4531
+ throw new Error("this daemon is not serving that Room");
4532
+ const argv = validateGrantArgv(input.argv);
4533
+ const live = await this.options.api.execute("listAgentGrants", { agentId: this.options.agentId });
4534
+ const match = matchCommandGrant(live.grants, room.workspaceId, argv);
4535
+ if (!match) {
4536
+ throw new Error(`no approved command grant matches: ${argv.join(" ")}. Ask with request_grant kind=command first.`);
4537
+ }
4538
+ const { grant, rule } = match;
4539
+ const secrets = /* @__PURE__ */ new Map();
4540
+ for (const name of rule.secrets) {
4541
+ const value = await this.resolveSecret(name);
4542
+ if (!value)
4543
+ throw new Error(`secret ${name} is not in the operator key store`);
4544
+ secrets.set(name, value);
4545
+ }
4546
+ if (grant.status === "once") {
4547
+ await this.options.api.execute("consumeAgentGrant", { grantId: grant.grantId });
4548
+ }
4549
+ const source = this.options.env ?? process.env;
4550
+ const env = {
4551
+ ...source.PATH ? { PATH: source.PATH } : {},
4552
+ ...source.HOME ? { HOME: source.HOME } : {},
4553
+ ...Object.fromEntries(secrets)
4554
+ };
4555
+ const cap = this.options.outputCapBytes ?? GRANT_COMMAND_OUTPUT_CAP_BYTES;
4556
+ const outcome = await new Promise((resolveRun) => {
4557
+ const child = execFile(argv[0], argv.slice(1), {
4558
+ cwd: room.cwd,
4559
+ env,
4560
+ timeout: this.options.timeoutMs ?? GRANT_COMMAND_TIMEOUT_MS,
4561
+ killSignal: "SIGKILL",
4562
+ maxBuffer: cap * 4,
4563
+ encoding: "utf8"
4564
+ }, (error, stdout6, stderr) => {
4565
+ const combined = [stdout6, stderr].filter(Boolean).join(stderr && stdout6 ? "\n" : "");
4566
+ const failure = error;
4567
+ const spawnFailure = Boolean(failure && typeof failure.code === "string");
4568
+ resolveRun({
4569
+ exitCode: spawnFailure ? null : child.exitCode ?? (typeof failure?.code === "number" ? failure.code : 0),
4570
+ ...failure?.signal ? { signal: failure.signal } : {},
4571
+ timedOut: Boolean(failure?.killed && failure?.signal === "SIGKILL"),
4572
+ output: spawnFailure ? `${combined}${combined ? "\n" : ""}${failure.message}` : combined
4573
+ });
4574
+ });
4575
+ });
4576
+ const output = capOutput(scrubSecrets(outcome.output, secrets), cap);
4577
+ const turn = room.turn();
4578
+ const requester = turn?.requester ?? {
4579
+ pubkey: grant.requestedBy,
4580
+ ...grant.requestedByName ? { name: grant.requestedByName } : {}
4581
+ };
4582
+ const status = outcome.timedOut ? "timed out" : outcome.exitCode === null ? "error" : `exit ${outcome.exitCode}`;
4583
+ await this.options.api.execute("postAgentActivity", {
4584
+ agentId: this.options.agentId,
4585
+ roomId: input.roomId,
4586
+ requestId: turn?.requestId ?? `grant:${grant.grantId}`,
4587
+ activity: [
4588
+ {
4589
+ kind: "tool",
4590
+ title: `ran ${argv.join(" ")} under grant ${grant.grantId} \xB7 asked by ${requester.name ?? requester.pubkey.slice(0, 12)}`,
4591
+ operation: "execute",
4592
+ status,
4593
+ command: argv.join(" "),
4594
+ ...output ? { output } : {},
4595
+ requestedBy: requester
4596
+ }
4597
+ ]
4598
+ });
4599
+ return {
4600
+ grantId: grant.grantId,
4601
+ exitCode: outcome.exitCode,
4602
+ ...outcome.signal ? { signal: outcome.signal } : {},
4603
+ timedOut: outcome.timedOut,
4604
+ output
4605
+ };
4606
+ }
4607
+ };
4608
+ var GrantRunnerServer = class {
4609
+ runner;
4610
+ server;
4611
+ endpoint;
4612
+ constructor(runner) {
4613
+ this.runner = runner;
4614
+ }
4615
+ async start() {
4616
+ if (this.endpoint)
4617
+ return this.endpoint;
4618
+ const token = randomBytes(32).toString("base64url");
4619
+ const server = createServer((request, response) => {
4620
+ void this.handle(request, response, token);
4621
+ });
4622
+ await new Promise((resolveListen, reject) => {
4623
+ server.once("error", reject);
4624
+ server.listen(0, "127.0.0.1", () => resolveListen());
4625
+ });
4626
+ server.unref?.();
4627
+ const address = server.address();
4628
+ this.server = server;
4629
+ this.endpoint = { url: `http://127.0.0.1:${address.port}`, token };
4630
+ return this.endpoint;
4631
+ }
4632
+ async close() {
4633
+ const server = this.server;
4634
+ this.server = void 0;
4635
+ this.endpoint = void 0;
4636
+ if (!server)
4637
+ return;
4638
+ await new Promise((resolveClose) => server.close(() => resolveClose()));
4639
+ }
4640
+ async handle(request, response, token) {
4641
+ const send = (status, body) => {
4642
+ response.writeHead(status, { "content-type": "application/json" });
4643
+ response.end(JSON.stringify(body));
4644
+ };
4645
+ if (request.headers.authorization !== `Bearer ${token}`) {
4646
+ send(401, { error: "grant runner token required" });
4647
+ return;
4648
+ }
4649
+ if (request.method !== "POST" || request.url !== "/run") {
4650
+ send(404, { error: "not found" });
4651
+ return;
4652
+ }
4653
+ const chunks = [];
4654
+ for await (const chunk of request)
4655
+ chunks.push(chunk);
4656
+ try {
4657
+ const body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
4658
+ send(200, await this.runner.run({ roomId: body.roomId, argv: body.argv }));
4659
+ } catch (error) {
4660
+ send(400, { error: error instanceof Error ? error.message : String(error) });
4661
+ }
4662
+ }
4663
+ };
4664
+
4221
4665
  // apps/body/dist/monolith-corner-turn.js
4222
- import { execFile as execFile2 } from "node:child_process";
4223
- import { mkdir as mkdir3 } from "node:fs/promises";
4224
- import { homedir as homedir5 } from "node:os";
4666
+ import { execFile as execFile3 } from "node:child_process";
4667
+ import { mkdir as mkdir6 } from "node:fs/promises";
4668
+ import { homedir as homedir6 } from "node:os";
4669
+ import { join as join4 } from "node:path";
4225
4670
  import { promisify as promisify2 } from "node:util";
4226
4671
 
4227
4672
  // apps/body/dist/agent-home.js
4228
4673
  import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
4229
4674
  import { randomUUID } from "node:crypto";
4230
- import { chmod, copyFile, lstat, mkdir, readdir, realpath, rename, rm as rm2, symlink, unlink, writeFile } from "node:fs/promises";
4231
- import { homedir as homedir2 } from "node:os";
4232
- import { basename as basename3, dirname as dirname2, relative, resolve as resolve6, sep } from "node:path";
4675
+ import { chmod as chmod2, copyFile, lstat, mkdir as mkdir3, readdir, realpath, rename, rm as rm2, symlink, unlink, writeFile as writeFile4 } from "node:fs/promises";
4676
+ import { homedir as homedir3 } from "node:os";
4677
+ import { basename as basename3, dirname as dirname4, relative, resolve as resolve10, sep } from "node:path";
4233
4678
 
4234
4679
  // apps/body/dist/beeline-skill.js
4235
4680
  import { readFileSync as readFileSync3 } from "node:fs";
4236
- import { resolve as resolve5 } from "node:path";
4681
+ import { resolve as resolve8 } from "node:path";
4237
4682
  var USING_BEELINE_SKILL_NAME = "using-beeline";
4238
4683
  var BEELINE_ROOM_CAPABILITIES = [
4239
4684
  "The repository filesystem is read-only in this Room session.",
@@ -4241,9 +4686,10 @@ var BEELINE_ROOM_CAPABILITIES = [
4241
4686
  "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.",
4242
4687
  "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.",
4243
4688
  "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.",
4689
+ "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.",
4244
4690
  "To send a file, call beeline-agent attach_file with a path inside your checkout; it is attached to your reply.",
4245
4691
  "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.",
4246
- "When repository work is needed, you MUST call beeline-agent open_corner with a one-paragraph summary of the complete objective. The host-governed call is the only way to start write work.",
4692
+ "When repository work is needed, you MUST call beeline-agent open_corner with a complete objective of no more than 24 words. The host-governed call is the only way to start write work.",
4247
4693
  "Never claim an action or reply happened unless the prompt or a tool result proves it."
4248
4694
  ].join(" ");
4249
4695
  var BEELINE_DM_CAPABILITIES = [
@@ -4251,6 +4697,7 @@ var BEELINE_DM_CAPABILITIES = [
4251
4697
  "This Room is strictly conversational: there is no repository binding and no corner can be opened from here.",
4252
4698
  "The repository filesystem is read-only in this session.",
4253
4699
  "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.",
4700
+ "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.",
4254
4701
  "To send a file, call beeline-agent attach_file with a path inside your checkout; it is attached to your reply.",
4255
4702
  "Tag the person only when you need a decision or input, or when the task they asked for is finished.",
4256
4703
  "Never claim an action or reply happened unless the prompt or a tool result proves it."
@@ -4275,7 +4722,7 @@ function runningBeelineReleaseId(env = process.env, read = (path) => readFileSyn
4275
4722
  const lib = env.BEELINE_LIB_DIR;
4276
4723
  if (!lib)
4277
4724
  return "source";
4278
- const manifest = JSON.parse(read(resolve5(lib, "bundle.json")));
4725
+ const manifest = JSON.parse(read(resolve8(lib, "bundle.json")));
4279
4726
  return [manifest.version, manifest.commit].filter(Boolean).join("-") || "source";
4280
4727
  } catch {
4281
4728
  return "source";
@@ -4311,6 +4758,226 @@ var SQUIRE_GOVERNED_TOOLS = [
4311
4758
  ];
4312
4759
  var SQUIRE_GOVERNED_TOOL_SET = new Set(SQUIRE_GOVERNED_TOOLS);
4313
4760
 
4761
+ // apps/body/dist/openrouter-routing.js
4762
+ import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
4763
+ import { resolve as resolve9 } from "node:path";
4764
+ var OPENROUTER_ENDPOINTS_BASE_URL = "https://openrouter.ai/api/v1/models";
4765
+ var OPENROUTER_ROUTING_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
4766
+ var OPENROUTER_ROUTING_FETCH_TIMEOUT_MS = 1e4;
4767
+ var OPENROUTER_UPTIME_BAR = 98;
4768
+ var OPENROUTER_UPTIME_BAR_RELAXED = 95;
4769
+ var OPENROUTER_MIN_PROVIDERS = 2;
4770
+ var OPENROUTER_FALLBACK_PROVIDERS = ["deepinfra", "novita"];
4771
+ function openRouterModelId(model, env = {}) {
4772
+ const trimmed = model?.trim();
4773
+ if (!trimmed)
4774
+ return void 0;
4775
+ if (trimmed.startsWith("openrouter/")) {
4776
+ const id = trimmed.slice("openrouter/".length);
4777
+ return id.includes("/") ? id : void 0;
4778
+ }
4779
+ if (env.OPENROUTER_API_KEY?.trim() && /^[^/\s]+\/[^/\s][^\s]*$/.test(trimmed))
4780
+ return trimmed;
4781
+ return void 0;
4782
+ }
4783
+ function parseOpenRouterEndpoints(payload) {
4784
+ const data = payload?.data;
4785
+ const record2 = data && typeof data === "object" ? data : {};
4786
+ const raw = Array.isArray(record2.endpoints) ? record2.endpoints : [];
4787
+ const endpoints = [];
4788
+ for (const entry of raw) {
4789
+ if (!entry || typeof entry !== "object")
4790
+ continue;
4791
+ const endpoint2 = entry;
4792
+ const tag = typeof endpoint2.tag === "string" ? endpoint2.tag : "";
4793
+ const provider = tag.split("/")[0]?.trim() ?? "";
4794
+ if (!provider)
4795
+ continue;
4796
+ const uptime = typeof endpoint2.uptime_last_30m === "number" ? endpoint2.uptime_last_30m : 0;
4797
+ const contextLength = typeof endpoint2.context_length === "number" ? endpoint2.context_length : 0;
4798
+ const supported = Array.isArray(endpoint2.supported_parameters) ? endpoint2.supported_parameters : [];
4799
+ endpoints.push({ provider, uptime, contextLength, tools: supported.includes("tools") });
4800
+ }
4801
+ return {
4802
+ endpoints,
4803
+ contextLength: typeof record2.context_length === "number" ? record2.context_length : advertisedContextLength(endpoints)
4804
+ };
4805
+ }
4806
+ function advertisedContextLength(endpoints) {
4807
+ const counts = /* @__PURE__ */ new Map();
4808
+ for (const endpoint2 of endpoints) {
4809
+ if (!endpoint2.tools || endpoint2.contextLength <= 0)
4810
+ continue;
4811
+ counts.set(endpoint2.contextLength, (counts.get(endpoint2.contextLength) ?? 0) + 1);
4812
+ }
4813
+ let best;
4814
+ for (const [length, count] of counts) {
4815
+ if (best === void 0) {
4816
+ best = length;
4817
+ continue;
4818
+ }
4819
+ const bestCount = counts.get(best) ?? 0;
4820
+ if (count > bestCount || count === bestCount && length > best)
4821
+ best = length;
4822
+ }
4823
+ return best;
4824
+ }
4825
+ function selectReliableOpenRouterProviders(endpoints, contextLength) {
4826
+ const eligible = endpoints.filter((endpoint2) => endpoint2.tools && (contextLength === void 0 || endpoint2.contextLength >= contextLength));
4827
+ const atBar = (bar) => {
4828
+ const seen = /* @__PURE__ */ new Set();
4829
+ return eligible.filter((endpoint2) => endpoint2.uptime >= bar).sort((left, right) => right.uptime - left.uptime).map((endpoint2) => endpoint2.provider).filter((provider) => seen.has(provider) ? false : (seen.add(provider), true));
4830
+ };
4831
+ const strict = atBar(OPENROUTER_UPTIME_BAR);
4832
+ if (strict.length >= OPENROUTER_MIN_PROVIDERS) {
4833
+ return { providers: strict, bar: OPENROUTER_UPTIME_BAR, contextLength };
4834
+ }
4835
+ const relaxed = atBar(OPENROUTER_UPTIME_BAR_RELAXED);
4836
+ if (relaxed.length > 0) {
4837
+ return { providers: relaxed, bar: OPENROUTER_UPTIME_BAR_RELAXED, contextLength };
4838
+ }
4839
+ return { providers: [], bar: null, contextLength };
4840
+ }
4841
+ function openRouterRoutingFor(providers) {
4842
+ return {
4843
+ only: [...providers],
4844
+ order: [...providers],
4845
+ allow_fallbacks: true,
4846
+ require_parameters: true
4847
+ };
4848
+ }
4849
+ function openRouterRoutingCacheDir(runtimeDir) {
4850
+ return resolve9(runtimeDir, "openrouter-routing");
4851
+ }
4852
+ function cachePath(cacheDir, model) {
4853
+ return resolve9(cacheDir, `${model.replace(/[^A-Za-z0-9._-]+/g, "_")}.json`);
4854
+ }
4855
+ async function readCache(cacheDir, model) {
4856
+ try {
4857
+ const parsed = JSON.parse(await readFile4(cachePath(cacheDir, model), "utf8"));
4858
+ if (!parsed || typeof parsed !== "object")
4859
+ return void 0;
4860
+ const cached = parsed;
4861
+ if (cached.model !== model || typeof cached.fetchedAt !== "number" || !Array.isArray(cached.providers) || !cached.providers.every((provider) => typeof provider === "string" && provider.length > 0)) {
4862
+ return void 0;
4863
+ }
4864
+ return {
4865
+ model,
4866
+ fetchedAt: cached.fetchedAt,
4867
+ providers: cached.providers,
4868
+ bar: typeof cached.bar === "number" ? cached.bar : null
4869
+ };
4870
+ } catch {
4871
+ return void 0;
4872
+ }
4873
+ }
4874
+ async function writeCache(cacheDir, value) {
4875
+ await mkdir2(cacheDir, { recursive: true, mode: 448 });
4876
+ await writeFile3(cachePath(cacheDir, value.model), `${JSON.stringify(value, null, 2)}
4877
+ `, {
4878
+ mode: 384
4879
+ });
4880
+ }
4881
+ function decision(model, providers, bar, source, note) {
4882
+ const criteria = bar === null ? "fallback pair" : `uptime \u2265${bar}%, tools`;
4883
+ const suffix = [
4884
+ bar === OPENROUTER_UPTIME_BAR_RELAXED ? "bar lowered: fewer than 2 providers at 98%" : "",
4885
+ source === "cache" ? "cached" : "",
4886
+ source === "stale-cache" ? "stale cache" : "",
4887
+ note ?? ""
4888
+ ].filter(Boolean).join("; ");
4889
+ return {
4890
+ model,
4891
+ routing: openRouterRoutingFor(providers),
4892
+ providers: [...providers],
4893
+ bar,
4894
+ source,
4895
+ line: `[body] openrouter routing for ${model}: ${providers.join(", ")} (${criteria}` + (suffix ? `; ${suffix})` : ")")
4896
+ };
4897
+ }
4898
+ async function resolveOpenRouterRouting(input) {
4899
+ const now2 = input.now ?? Date.now;
4900
+ const cached = await readCache(input.cacheDir, input.model);
4901
+ if (cached && now2() - cached.fetchedAt < OPENROUTER_ROUTING_CACHE_TTL_MS) {
4902
+ return decision(input.model, cached.providers, cached.bar, "cache");
4903
+ }
4904
+ let failure;
4905
+ try {
4906
+ const doFetch = input.fetchImpl ?? fetch;
4907
+ const response = await doFetch(`${OPENROUTER_ENDPOINTS_BASE_URL}/${input.model}/endpoints`, {
4908
+ signal: AbortSignal.timeout(input.timeoutMs ?? OPENROUTER_ROUTING_FETCH_TIMEOUT_MS)
4909
+ });
4910
+ if (!response.ok)
4911
+ throw new Error(`HTTP ${response.status}`);
4912
+ const { endpoints, contextLength } = parseOpenRouterEndpoints(await response.json());
4913
+ if (endpoints.length === 0)
4914
+ throw new Error("no endpoints listed");
4915
+ const selected = selectReliableOpenRouterProviders(endpoints, contextLength);
4916
+ if (selected.providers.length === 0) {
4917
+ return decision(input.model, OPENROUTER_FALLBACK_PROVIDERS, null, "fallback", "no provider met the bar");
4918
+ }
4919
+ await writeCache(input.cacheDir, {
4920
+ model: input.model,
4921
+ fetchedAt: now2(),
4922
+ providers: selected.providers,
4923
+ bar: selected.bar
4924
+ }).catch(() => void 0);
4925
+ return decision(input.model, selected.providers, selected.bar, "live");
4926
+ } catch (error) {
4927
+ failure = error instanceof Error ? error.message : String(error);
4928
+ }
4929
+ if (cached) {
4930
+ return decision(input.model, cached.providers, cached.bar, "stale-cache", `api unreachable: ${failure}`);
4931
+ }
4932
+ return decision(input.model, OPENROUTER_FALLBACK_PROVIDERS, null, "fallback", `api unreachable: ${failure}`);
4933
+ }
4934
+ function withOpenRouterModelRouting(value, pin) {
4935
+ const root = value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
4936
+ const applyOverride = (provider) => {
4937
+ if (!pin)
4938
+ return provider;
4939
+ const overrides = provider.modelOverrides && typeof provider.modelOverrides === "object" && !Array.isArray(provider.modelOverrides) ? { ...provider.modelOverrides } : {};
4940
+ const existing = overrides[pin.model];
4941
+ const override = existing && typeof existing === "object" && !Array.isArray(existing) ? { ...existing } : {};
4942
+ const compat = override.compat && typeof override.compat === "object" && !Array.isArray(override.compat) ? { ...override.compat } : {};
4943
+ override.compat = { ...compat, openRouterRouting: pin.routing };
4944
+ overrides[pin.model] = override;
4945
+ return { ...provider, modelOverrides: overrides };
4946
+ };
4947
+ if (Array.isArray(root.providers)) {
4948
+ if (!pin)
4949
+ return root;
4950
+ const providers2 = root.providers.map((provider) => provider && typeof provider === "object" ? { ...provider } : provider);
4951
+ const index = providers2.findIndex((provider) => provider && typeof provider === "object" && provider.name === "openrouter");
4952
+ const current = applyOverride(index >= 0 ? providers2[index] : { name: "openrouter" });
4953
+ if (index >= 0)
4954
+ providers2[index] = current;
4955
+ else
4956
+ providers2.push(current);
4957
+ root.providers = providers2;
4958
+ return root;
4959
+ }
4960
+ const providers = root.providers && typeof root.providers === "object" ? { ...root.providers } : {};
4961
+ if (pin) {
4962
+ const current = providers.openrouter && typeof providers.openrouter === "object" && !Array.isArray(providers.openrouter) ? { ...providers.openrouter } : {};
4963
+ providers.openrouter = applyOverride(current);
4964
+ }
4965
+ root.providers = providers;
4966
+ return root;
4967
+ }
4968
+ function openRouterRoutingInput(config, selection, fetchImpl) {
4969
+ const model = openRouterModelId(selection?.model, config.agentEnv);
4970
+ if (!model || !config.openRouterRoutingCacheDir)
4971
+ return {};
4972
+ return {
4973
+ openRouterRouting: {
4974
+ model,
4975
+ cacheDir: config.openRouterRoutingCacheDir,
4976
+ ...fetchImpl ? { fetchImpl } : {}
4977
+ }
4978
+ };
4979
+ }
4980
+
4314
4981
  // apps/body/dist/toml-section.js
4315
4982
  function scanLine(line, openString) {
4316
4983
  if (openString.kind) {
@@ -4498,17 +5165,17 @@ var CODEX_ROOM_AGENT_LOCKDOWN_TOML = "[agents]\nenabled = false\n";
4498
5165
  var CODEX_ROOM_WEB_SEARCH_TOML = "[features]\nstandalone_web_search = true\n";
4499
5166
  var HOME_SUBDIRS = ["user", "claude", "codex", "grok", "pi", "state", "cache", "tmp"];
4500
5167
  async function prepareRoomAgentHome(input) {
4501
- const root = resolve6(input.root);
4502
- const operatorHome = input.operatorHome ?? homedir2();
5168
+ const root = resolve10(input.root);
5169
+ const operatorHome = input.operatorHome ?? homedir3();
4503
5170
  try {
4504
- await mkdir(root, { recursive: true, mode: 448 });
5171
+ await mkdir3(root, { recursive: true, mode: 448 });
4505
5172
  const rootStats = await lstat(root);
4506
5173
  if (!rootStats.isDirectory() || rootStats.isSymbolicLink()) {
4507
5174
  throw new AgentHomeSecurityError(`agent home root is not an ordinary directory: ${root}`);
4508
5175
  }
4509
5176
  for (const subdir of HOME_SUBDIRS) {
4510
- const path = resolve6(root, subdir);
4511
- await mkdir(path, { recursive: true, mode: 448 });
5177
+ const path = resolve10(root, subdir);
5178
+ await mkdir3(path, { recursive: true, mode: 448 });
4512
5179
  await assertRealContainedDirectory(path, root);
4513
5180
  }
4514
5181
  } catch (error) {
@@ -4518,14 +5185,14 @@ async function prepareRoomAgentHome(input) {
4518
5185
  return {};
4519
5186
  }
4520
5187
  for (const credential of SHARED_CREDENTIALS) {
4521
- const source = resolve6(operatorHome, credential.source);
4522
- const target = resolve6(root, credential.dir, credential.target);
5188
+ const source = resolve10(operatorHome, credential.source);
5189
+ const target = resolve10(root, credential.dir, credential.target);
4523
5190
  if (!existsSync3(source) || existsSync3(target))
4524
5191
  continue;
4525
5192
  await symlink(source, target).catch(() => void 0);
4526
5193
  }
4527
5194
  const prior = agentHomeProvisionQueues.get(root) ?? Promise.resolve();
4528
- const provision = prior.catch(() => void 0).then(() => provisionAgentSkillsAndMcp(root, operatorHome, input.skillReleaseId ?? runningBeelineReleaseId(), input.failClosed ?? false, input.sharedSkills ?? []));
5195
+ const provision = prior.catch(() => void 0).then(() => provisionAgentSkillsAndMcp(root, operatorHome, input.skillReleaseId ?? runningBeelineReleaseId(), input.failClosed ?? false, input.sharedSkills ?? [], input.openRouterRouting));
4529
5196
  agentHomeProvisionQueues.set(root, provision);
4530
5197
  try {
4531
5198
  await provision;
@@ -4535,19 +5202,19 @@ async function prepareRoomAgentHome(input) {
4535
5202
  }
4536
5203
  return roomAgentHomeEnv(root);
4537
5204
  }
4538
- async function provisionAgentSkillsAndMcp(root, operatorHome, skillReleaseId, failClosed, sharedSkills) {
5205
+ async function provisionAgentSkillsAndMcp(root, operatorHome, skillReleaseId, failClosed, sharedSkills, openRouterRouting) {
4539
5206
  const managedSkills = [
4540
5207
  { name: USING_BEELINE_SKILL_NAME, content: usingBeelineSkillMarkdown(skillReleaseId) }
4541
5208
  ];
4542
5209
  const shared = await resolveSharedSkillSources(operatorHome, sharedSkills);
4543
5210
  for (const dir of AGENT_SKILL_DIRS) {
4544
- const target = resolve6(root, dir, "skills");
5211
+ const target = resolve10(root, dir, "skills");
4545
5212
  await provisionManagedSkillsDir(target, managedSkills, shared, sharedSkills.length === 0);
4546
5213
  }
4547
5214
  for (const config of HARNESS_MCP_CONFIGS) {
4548
5215
  try {
4549
- const source = resolve6(operatorHome, config.toml);
4550
- const target = resolve6(root, config.dir, "config.toml");
5216
+ const source = resolve10(operatorHome, config.toml);
5217
+ const target = resolve10(root, config.dir, "config.toml");
4551
5218
  const mcpSection = existsSync3(source) ? filteredHarnessMcpToml(readFileSync4(source, "utf8")) : void 0;
4552
5219
  const section = config.dir === "codex" ? [CODEX_ROOM_AGENT_LOCKDOWN_TOML, CODEX_ROOM_WEB_SEARCH_TOML, mcpSection].filter(Boolean).join("\n") : mcpSection;
4553
5220
  if (!section) {
@@ -4562,8 +5229,8 @@ async function provisionAgentSkillsAndMcp(root, operatorHome, skillReleaseId, fa
4562
5229
  }
4563
5230
  }
4564
5231
  try {
4565
- const claudeJson = resolve6(operatorHome, ".claude.json");
4566
- const claudeTarget = resolve6(root, "claude", ".claude.json");
5232
+ const claudeJson = resolve10(operatorHome, ".claude.json");
5233
+ const claudeTarget = resolve10(root, "claude", ".claude.json");
4567
5234
  const mcpServers = existsSync3(claudeJson) ? readClaudeUserScopeMcpServers(claudeJson) : void 0;
4568
5235
  if (mcpServers && Object.keys(mcpServers).length > 0) {
4569
5236
  await writeIsolatedHarnessFile(claudeTarget, `${JSON.stringify({ mcpServers }, null, 2)}
@@ -4578,22 +5245,29 @@ async function provisionAgentSkillsAndMcp(root, operatorHome, skillReleaseId, fa
4578
5245
  }
4579
5246
  try {
4580
5247
  const settings2 = { permissions: { allow: ["WebSearch"] } };
4581
- await writeIsolatedHarnessFile(resolve6(root, "claude", "settings.json"), `${JSON.stringify(settings2, null, 2)}
5248
+ await writeIsolatedHarnessFile(resolve10(root, "claude", "settings.json"), `${JSON.stringify(settings2, null, 2)}
4582
5249
  `);
4583
5250
  } catch (error) {
4584
5251
  if (failClosed)
4585
5252
  throw error;
4586
5253
  console.warn("[body] claude web-search settings provisioning failed:", error);
4587
5254
  }
4588
- await provisionPiCustomModelConfig(root, operatorHome, failClosed);
5255
+ await provisionPiCustomModelConfig(root, operatorHome, failClosed, openRouterRouting);
4589
5256
  }
4590
- async function provisionPiCustomModelConfig(root, operatorHome, failClosed) {
4591
- const source = resolve6(operatorHome, PI_CUSTOM_MODEL_CONFIG.source);
4592
- const target = resolve6(root, "pi", PI_CUSTOM_MODEL_CONFIG.target);
5257
+ async function provisionPiCustomModelConfig(root, operatorHome, failClosed, openRouterRouting) {
5258
+ const source = resolve10(operatorHome, PI_CUSTOM_MODEL_CONFIG.source);
5259
+ const target = resolve10(root, "pi", PI_CUSTOM_MODEL_CONFIG.target);
5260
+ let decision2;
5261
+ if (openRouterRouting) {
5262
+ decision2 = await resolveOpenRouterRouting(openRouterRouting);
5263
+ console.log(decision2.line);
5264
+ }
5265
+ const pin = decision2 ? { model: decision2.model, routing: decision2.routing } : void 0;
4593
5266
  try {
4594
5267
  const sourceStats = await lstat(source).catch(() => void 0);
4595
5268
  if (!sourceStats) {
4596
- await unlink(target).catch(() => void 0);
5269
+ await writeIsolatedHarnessFile(target, `${JSON.stringify(withOpenRouterModelRouting({}, pin), null, 2)}
5270
+ `);
4597
5271
  return;
4598
5272
  }
4599
5273
  if (!sourceStats.isFile() || sourceStats.isSymbolicLink() || sourceStats.nlink !== 1) {
@@ -4603,7 +5277,9 @@ async function provisionPiCustomModelConfig(root, operatorHome, failClosed) {
4603
5277
  if (resolvedSource !== source) {
4604
5278
  throw new AgentHomeSecurityError(`Pi custom model config resolves through a link: ${source}`);
4605
5279
  }
4606
- await writeIsolatedHarnessFile(target, readFileSync4(resolvedSource, "utf8"));
5280
+ const sourceValue = JSON.parse(readFileSync4(resolvedSource, "utf8"));
5281
+ await writeIsolatedHarnessFile(target, `${JSON.stringify(withOpenRouterModelRouting(sourceValue, pin), null, 2)}
5282
+ `);
4607
5283
  } catch (error) {
4608
5284
  await unlink(target).catch(() => void 0);
4609
5285
  if (failClosed)
@@ -4637,16 +5313,16 @@ function filteredHarnessMcpToml(source) {
4637
5313
  return extractTomlSections(source, ["mcp_servers"], excluded);
4638
5314
  }
4639
5315
  async function provisionManagedSkillsDir(target, managedSkills, sharedSkills, optionalShares) {
4640
- const parent = dirname2(target);
4641
- await assertRealContainedDirectory(parent, dirname2(parent));
4642
- const staged = resolve6(parent, `.skills.${process.pid}.${randomUUID()}.tmp`);
4643
- await mkdir(staged, { mode: 448 });
5316
+ const parent = dirname4(target);
5317
+ await assertRealContainedDirectory(parent, dirname4(parent));
5318
+ const staged = resolve10(parent, `.skills.${process.pid}.${randomUUID()}.tmp`);
5319
+ await mkdir3(staged, { mode: 448 });
4644
5320
  const names = new Set(managedSkills.map((skill) => skill.name));
4645
5321
  try {
4646
5322
  for (const skill of managedSkills) {
4647
- const skillDir = resolve6(staged, skill.name);
4648
- await mkdir(skillDir, { recursive: true });
4649
- await writeIsolatedHarnessFile(resolve6(skillDir, "SKILL.md"), skill.content);
5323
+ const skillDir = resolve10(staged, skill.name);
5324
+ await mkdir3(skillDir, { recursive: true });
5325
+ await writeIsolatedHarnessFile(resolve10(skillDir, "SKILL.md"), skill.content);
4650
5326
  }
4651
5327
  for (const shared of sharedSkills) {
4652
5328
  if (names.has(shared.name)) {
@@ -4654,7 +5330,7 @@ async function provisionManagedSkillsDir(target, managedSkills, sharedSkills, op
4654
5330
  }
4655
5331
  names.add(shared.name);
4656
5332
  try {
4657
- await copySafeSkillTree(shared.source, resolve6(staged, shared.name), shared.source);
5333
+ await copySafeSkillTree(shared.source, resolve10(staged, shared.name), shared.source);
4658
5334
  } catch (error) {
4659
5335
  if (!optionalShares)
4660
5336
  throw error;
@@ -4675,20 +5351,20 @@ async function resolveSharedSkillSources(operatorHome, names) {
4675
5351
  const seen = /* @__PURE__ */ new Set();
4676
5352
  const resolved = [];
4677
5353
  for (const relativeRoot of OPERATOR_SKILL_SOURCE_DIRS) {
4678
- const sourceRoot = resolve6(operatorHome, relativeRoot);
5354
+ const sourceRoot = resolve10(operatorHome, relativeRoot);
4679
5355
  const rootStats = await lstat(sourceRoot).catch(() => void 0);
4680
5356
  if (!rootStats?.isDirectory() || rootStats.isSymbolicLink())
4681
5357
  continue;
4682
5358
  for (const entry of await readdir(sourceRoot)) {
4683
5359
  if (!isSharedSkillName(entry) || seen.has(entry))
4684
5360
  continue;
4685
- const candidate = resolve6(sourceRoot, entry);
5361
+ const candidate = resolve10(sourceRoot, entry);
4686
5362
  try {
4687
5363
  const candidateStats = await lstat(candidate);
4688
5364
  if (!candidateStats.isDirectory() || candidateStats.isSymbolicLink())
4689
5365
  continue;
4690
5366
  assertContained(sourceRoot, candidate);
4691
- const skillMd = resolve6(candidate, "SKILL.md");
5367
+ const skillMd = resolve10(candidate, "SKILL.md");
4692
5368
  const skillStats = await lstat(skillMd);
4693
5369
  if (!skillStats.isFile() || skillStats.isSymbolicLink() || skillStats.nlink !== 1) {
4694
5370
  throw new Error(`shared skill requires an ordinary SKILL.md: ${entry}`);
@@ -4712,8 +5388,8 @@ async function resolveExplicitSkillSources(operatorHome, names) {
4712
5388
  for (const name of unique) {
4713
5389
  const matches = [];
4714
5390
  for (const relativeRoot of OPERATOR_SKILL_SOURCE_DIRS) {
4715
- const sourceRoot = resolve6(operatorHome, relativeRoot);
4716
- const candidate = resolve6(sourceRoot, name);
5391
+ const sourceRoot = resolve10(operatorHome, relativeRoot);
5392
+ const candidate = resolve10(sourceRoot, name);
4717
5393
  const rootStats = await lstat(sourceRoot).catch(() => void 0);
4718
5394
  const candidateStats = await lstat(candidate).catch(() => void 0);
4719
5395
  if (!candidateStats)
@@ -4730,7 +5406,7 @@ async function resolveExplicitSkillSources(operatorHome, names) {
4730
5406
  if (matches.length !== 1) {
4731
5407
  throw new Error(matches.length === 0 ? `shared skill is unavailable: ${name}` : `shared skill source is ambiguous: ${name}`);
4732
5408
  }
4733
- const skillMd = resolve6(matches[0], "SKILL.md");
5409
+ const skillMd = resolve10(matches[0], "SKILL.md");
4734
5410
  const skillStats = await lstat(skillMd).catch(() => void 0);
4735
5411
  if (!skillStats?.isFile() || skillStats.isSymbolicLink() || skillStats.nlink !== 1) {
4736
5412
  throw new Error(`shared skill requires an ordinary SKILL.md: ${name}`);
@@ -4741,7 +5417,7 @@ async function resolveExplicitSkillSources(operatorHome, names) {
4741
5417
  }
4742
5418
  function assertContained(root, candidate) {
4743
5419
  const rel = relative(root, candidate);
4744
- if (rel === ".." || rel.startsWith(`..${sep}`) || resolve6(root, rel) !== resolve6(candidate)) {
5420
+ if (rel === ".." || rel.startsWith(`..${sep}`) || resolve10(root, rel) !== resolve10(candidate)) {
4745
5421
  throw new Error(`path escapes the agent skill boundary: ${candidate}`);
4746
5422
  }
4747
5423
  }
@@ -4756,7 +5432,7 @@ var BLOCKED_SHARED_FILENAMES = /^(?:\.env(?:\..*)?|auth\.json|\.credentials\.jso
4756
5432
  async function copySafeSkillTree(source, target, sourceRoot) {
4757
5433
  assertContained(sourceRoot, source);
4758
5434
  const resolvedSource = await realpath(source);
4759
- if (resolvedSource !== resolve6(source)) {
5435
+ if (resolvedSource !== resolve10(source)) {
4760
5436
  throw new Error(`shared skill path resolves through a link: ${source}`);
4761
5437
  }
4762
5438
  assertContained(sourceRoot, resolvedSource);
@@ -4767,11 +5443,11 @@ async function copySafeSkillTree(source, target, sourceRoot) {
4767
5443
  throw new Error(`shared skill contains credential or configuration material: ${source}`);
4768
5444
  }
4769
5445
  if (stats.isDirectory()) {
4770
- await mkdir(target, { mode: 448 });
5446
+ await mkdir3(target, { mode: 448 });
4771
5447
  for (const entry of await readdir(resolvedSource)) {
4772
5448
  if (entry === "." || entry === "..")
4773
5449
  throw new Error("invalid shared skill entry");
4774
- await copySafeSkillTree(resolve6(source, entry), resolve6(target, entry), sourceRoot);
5450
+ await copySafeSkillTree(resolve10(source, entry), resolve10(target, entry), sourceRoot);
4775
5451
  }
4776
5452
  return;
4777
5453
  }
@@ -4779,34 +5455,34 @@ async function copySafeSkillTree(source, target, sourceRoot) {
4779
5455
  throw new Error(`shared skill contains a nonordinary file: ${source}`);
4780
5456
  }
4781
5457
  await copyFile(resolvedSource, target);
4782
- await chmod(target, 384);
5458
+ await chmod2(target, 384);
4783
5459
  }
4784
5460
  async function writeIsolatedHarnessFile(path, content) {
4785
- const parent = dirname2(path);
5461
+ const parent = dirname4(path);
4786
5462
  const parentStats = await lstat(parent);
4787
5463
  if (!parentStats.isDirectory() || parentStats.isSymbolicLink()) {
4788
5464
  throw new Error(`isolated harness parent is not a real directory: ${parent}`);
4789
5465
  }
4790
- const temporary = resolve6(parent, `.${basename3(path)}.${process.pid}.${randomUUID()}.tmp`);
5466
+ const temporary = resolve10(parent, `.${basename3(path)}.${process.pid}.${randomUUID()}.tmp`);
4791
5467
  try {
4792
- await writeFile(temporary, content, { mode: 384, flag: "wx" });
4793
- await chmod(temporary, 384);
5468
+ await writeFile4(temporary, content, { mode: 384, flag: "wx" });
5469
+ await chmod2(temporary, 384);
4794
5470
  await rename(temporary, path);
4795
5471
  } finally {
4796
5472
  await unlink(temporary).catch(() => void 0);
4797
5473
  }
4798
5474
  }
4799
5475
  function roomAgentHomeEnv(root) {
4800
- const resolved = resolve6(root);
5476
+ const resolved = resolve10(root);
4801
5477
  return {
4802
- HOME: resolve6(resolved, "user"),
4803
- CLAUDE_CONFIG_DIR: resolve6(resolved, "claude"),
4804
- CODEX_HOME: resolve6(resolved, "codex"),
4805
- GROK_HOME: resolve6(resolved, "grok"),
4806
- PI_CODING_AGENT_DIR: resolve6(resolved, "pi"),
4807
- XDG_STATE_HOME: resolve6(resolved, "state"),
4808
- XDG_CACHE_HOME: resolve6(resolved, "cache"),
4809
- TMPDIR: resolve6(resolved, "tmp")
5478
+ HOME: resolve10(resolved, "user"),
5479
+ CLAUDE_CONFIG_DIR: resolve10(resolved, "claude"),
5480
+ CODEX_HOME: resolve10(resolved, "codex"),
5481
+ GROK_HOME: resolve10(resolved, "grok"),
5482
+ PI_CODING_AGENT_DIR: resolve10(resolved, "pi"),
5483
+ XDG_STATE_HOME: resolve10(resolved, "state"),
5484
+ XDG_CACHE_HOME: resolve10(resolved, "cache"),
5485
+ TMPDIR: resolve10(resolved, "tmp")
4810
5486
  };
4811
5487
  }
4812
5488
  var HARNESS_STATE_ENV_VARS = [
@@ -4823,10 +5499,92 @@ function harnessStateDirsFromEnv(env) {
4823
5499
  for (const name of HARNESS_STATE_ENV_VARS) {
4824
5500
  const value = env[name];
4825
5501
  if (value)
4826
- stateDirs.push(resolve6(value));
5502
+ stateDirs.push(resolve10(value));
4827
5503
  }
4828
5504
  const tmp = env.TMPDIR;
4829
- return { stateDirs, ...tmp ? { tmpDir: resolve6(tmp) } : {} };
5505
+ return { stateDirs, ...tmp ? { tmpDir: resolve10(tmp) } : {} };
5506
+ }
5507
+
5508
+ // apps/body/dist/attachment-delivery.js
5509
+ import { mkdir as mkdir4, writeFile as writeFile5 } from "node:fs/promises";
5510
+ import { basename as basename4, extname, join as join2 } from "node:path";
5511
+ var MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
5512
+ var FETCH_TIMEOUT_MS = 3e4;
5513
+ function safeFileName(attachment, index, taken) {
5514
+ const raw = basename4(attachment.name ?? "").replace(/[^\w.-]+/g, "_").replace(/^\.+/, "");
5515
+ let name = raw || `attachment-${index + 1}`;
5516
+ if (taken.has(name)) {
5517
+ const ext = extname(name);
5518
+ name = `${name.slice(0, name.length - ext.length)}-${index + 1}${ext}`;
5519
+ }
5520
+ taken.add(name);
5521
+ return name;
5522
+ }
5523
+ async function deliverAttachments(attachments, dir, fetchImpl = fetch) {
5524
+ if (!attachments.length)
5525
+ return [];
5526
+ const taken = /* @__PURE__ */ new Set();
5527
+ await mkdir4(dir, { recursive: true });
5528
+ return Promise.all(attachments.map(async (attachment, index) => {
5529
+ const tooLarge = (bytes) => ({
5530
+ attachment,
5531
+ reason: `skipped: ${bytes} bytes exceeds the ${MAX_ATTACHMENT_BYTES}-byte limit`
5532
+ });
5533
+ if (attachment.size && attachment.size > MAX_ATTACHMENT_BYTES)
5534
+ return tooLarge(attachment.size);
5535
+ try {
5536
+ const response = await fetchImpl(attachment.url, {
5537
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
5538
+ });
5539
+ if (!response.ok)
5540
+ throw new Error(`HTTP ${response.status}`);
5541
+ const declared = Number(response.headers.get("content-length") ?? 0);
5542
+ if (declared > MAX_ATTACHMENT_BYTES)
5543
+ return tooLarge(declared);
5544
+ const bytes = Buffer.from(await response.arrayBuffer());
5545
+ if (bytes.length > MAX_ATTACHMENT_BYTES)
5546
+ return tooLarge(bytes.length);
5547
+ const path = join2(dir, safeFileName(attachment, index, taken));
5548
+ await writeFile5(path, bytes);
5549
+ const mimeType = attachment.mimeType ?? response.headers.get("content-type") ?? "";
5550
+ return {
5551
+ attachment,
5552
+ path,
5553
+ ...mimeType.startsWith("image/") ? { image: { data: bytes.toString("base64"), mimeType } } : {}
5554
+ };
5555
+ } catch (error) {
5556
+ return {
5557
+ attachment,
5558
+ reason: `download failed: ${error instanceof Error ? error.message : String(error)}`
5559
+ };
5560
+ }
5561
+ }));
5562
+ }
5563
+ function withoutImageData(delivered) {
5564
+ return delivered.map(({ image: _image, ...rest }) => rest);
5565
+ }
5566
+ function attachmentPromptLines(attachments, delivered) {
5567
+ if (!attachments.length)
5568
+ return [];
5569
+ const byUrl = new Map(delivered?.map((entry) => [entry.attachment.url, entry]) ?? []);
5570
+ return [
5571
+ "Attachments shared with this message (read the local file; the trailing URL is only a reference, do not download it):",
5572
+ ...attachments.map((attachment) => {
5573
+ const kind = attachment.mimeType?.startsWith("image/") ? "image" : "file";
5574
+ const metadata = [attachment.mimeType, attachment.size ? `${attachment.size} bytes` : ""].filter(Boolean).join(", ");
5575
+ const entry = byUrl.get(attachment.url);
5576
+ const location = entry?.path ? `local file ${entry.path}` : entry?.reason ?? "no local copy in this session";
5577
+ return `- ${kind}: ${attachment.name ?? "attachment"}${metadata ? ` (${metadata})` : ""}: ${location} (source ${attachment.url})`;
5578
+ })
5579
+ ];
5580
+ }
5581
+ function attachmentImageBlocks(delivered, harnessAcceptsImages) {
5582
+ if (!harnessAcceptsImages)
5583
+ return [];
5584
+ return delivered.flatMap((entry) => entry.image ? [{ type: "image", data: entry.image.data, mimeType: entry.image.mimeType }] : []);
5585
+ }
5586
+ function promptWithImages(text2, images) {
5587
+ return images.length ? [{ type: "text", text: text2 }, ...images] : text2;
4830
5588
  }
4831
5589
 
4832
5590
  // apps/body/dist/reply-sanitizer.js
@@ -4900,8 +5658,23 @@ function sanitizeAgentReply(message) {
4900
5658
  return lines.slice(1).join("\n").trim();
4901
5659
  }
4902
5660
 
5661
+ // apps/body/dist/turn-failure-reason.js
5662
+ var TURN_FAILURE_REASON_MAX = 200;
5663
+ function redactToolDetail(value) {
5664
+ return value.replace(/\b(["']?)(api[_-]?key|token|secret|password|passwd|authorization|credential|cookie|private[_-]?key)\1\s*[:=]\s*(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s,}\]]+)/gi, '"$2": "[REDACTED]"').replace(/\b(?:export\s+)?[A-Za-z_][A-Za-z0-9_]*=(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\S+)/g, (assignment) => `${assignment.slice(0, assignment.indexOf("="))}=[REDACTED]`).replace(/\b(?:gh[pousr]_[A-Za-z0-9_]{12,}|github_pat_[A-Za-z0-9_]{12,})\b/gi, "[REDACTED]").replace(/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g, "[REDACTED]").replace(/\b(Bearer\s+)[^\s,]+/gi, "$1[REDACTED]").replace(/\bsk-[A-Za-z0-9_-]{12,}\b/g, "[REDACTED]").replace(/(--?(?:api[_-]?key|token|secret|password|authorization|credential|cookie)\s+)(?:"[^"]*"|'[^']*'|\S+)/gi, "$1[REDACTED]");
5665
+ }
5666
+ function distillTurnFailureReason(error) {
5667
+ const raw = error instanceof Error ? error.message : typeof error === "string" ? error : error && typeof error === "object" && "message" in error ? String(error.message) : error == null ? "" : String(error);
5668
+ const firstLine = raw.split(/\r?\n/).map((line) => line.trim()).find((line) => line && !/^at\s/.test(line)) ?? "";
5669
+ const stripped = firstLine.replace(/^(?:[A-Za-z]*Error|Error):\s*/, "").replace(/\s+/g, " ");
5670
+ const clean4 = redactToolDetail(stripped).trim();
5671
+ if (!clean4)
5672
+ return "turn failed";
5673
+ return clean4.length > TURN_FAILURE_REASON_MAX ? `${clean4.slice(0, TURN_FAILURE_REASON_MAX - 1)}\u2026` : clean4;
5674
+ }
5675
+
4903
5676
  // apps/body/dist/room-session.js
4904
- import { resolve as resolve7 } from "node:path";
5677
+ import { resolve as resolve11 } from "node:path";
4905
5678
 
4906
5679
  // apps/body/dist/read-only-policy.js
4907
5680
  var READ_ONLY_MCP_SERVER_NAME = "beeline-readonly-mcp";
@@ -5033,7 +5806,11 @@ function beelineAgentMcpServer(config, api, context) {
5033
5806
  { name: "BEELINE_DAEMON_ROOM_ID", value: context.roomId },
5034
5807
  { name: "BEELINE_DAEMON_WORKSPACE_ID", value: context.workspaceId },
5035
5808
  ...context.cornerId ? [{ name: "BEELINE_DAEMON_CORNER_ID", value: context.cornerId }] : [],
5036
- ...context.attachRoot ? [{ name: "BEELINE_ATTACH_ROOT", value: context.attachRoot }] : []
5809
+ ...context.attachRoot ? [{ name: "BEELINE_ATTACH_ROOT", value: context.attachRoot }] : [],
5810
+ ...context.grantRunner ? [
5811
+ { name: "BEELINE_GRANT_RUNNER_URL", value: context.grantRunner.url },
5812
+ { name: "BEELINE_GRANT_RUNNER_TOKEN", value: context.grantRunner.token }
5813
+ ] : []
5037
5814
  ]
5038
5815
  };
5039
5816
  }
@@ -5047,14 +5824,14 @@ function readOnlyMcpServer(config, cwd, agentMemoryDir) {
5047
5824
  command: config.readonlyMcpCommand,
5048
5825
  args: [...config.readonlyMcpArgs ?? []],
5049
5826
  env: [
5050
- { name: "BEELINE_READONLY_ROOT", value: resolve7(cwd) },
5827
+ { name: "BEELINE_READONLY_ROOT", value: resolve11(cwd) },
5051
5828
  ...config.agentHomeRoot ? [
5052
5829
  {
5053
5830
  name: "BEELINE_READONLY_AGENT_SKILLS_ROOT",
5054
- value: resolve7(config.agentHomeRoot, skillDir, "skills")
5831
+ value: resolve11(config.agentHomeRoot, skillDir, "skills")
5055
5832
  }
5056
5833
  ] : [],
5057
- ...agentMemoryDir ? [{ name: "BEELINE_READONLY_AGENT_MEMORY_ROOT", value: resolve7(agentMemoryDir) }] : []
5834
+ ...agentMemoryDir ? [{ name: "BEELINE_READONLY_AGENT_MEMORY_ROOT", value: resolve11(agentMemoryDir) }] : []
5058
5835
  ]
5059
5836
  };
5060
5837
  }
@@ -5062,8 +5839,8 @@ function readOnlyMcpServer(config, cwd, agentMemoryDir) {
5062
5839
  // apps/body/dist/bwrap-sandbox.js
5063
5840
  import { spawnSync } from "node:child_process";
5064
5841
  import { lstatSync as lstatSync2 } from "node:fs";
5065
- import { homedir as homedir3 } from "node:os";
5066
- import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve8 } from "node:path";
5842
+ import { homedir as homedir4 } from "node:os";
5843
+ import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve12 } from "node:path";
5067
5844
  var DEFAULT_SANDBOX_POLICY = "bwrap";
5068
5845
  function isSandboxPolicy(value) {
5069
5846
  return value === "bwrap" || value === "off";
@@ -5095,12 +5872,12 @@ var HARNESS_HOME_STATE_DIRS = [
5095
5872
  dirs: [".grok"]
5096
5873
  }
5097
5874
  ];
5098
- function harnessHomeStateDirs(agentCommand, home = homedir3()) {
5875
+ function harnessHomeStateDirs(agentCommand, home = homedir4()) {
5099
5876
  if (!agentCommand)
5100
5877
  return [];
5101
5878
  for (const { match, dirs } of HARNESS_HOME_STATE_DIRS) {
5102
5879
  if (match.test(agentCommand))
5103
- return dirs.map((dir) => resolve8(home, dir));
5880
+ return dirs.map((dir) => resolve12(home, dir));
5104
5881
  }
5105
5882
  return [];
5106
5883
  }
@@ -5112,7 +5889,7 @@ var KNOWN_CREDENTIAL_MASK_PATHS = [
5112
5889
  ".git-credentials",
5113
5890
  ".secrets.env"
5114
5891
  ];
5115
- function credentialMaskPaths(extraPaths, home = homedir3(), stat3 = (path) => {
5892
+ function credentialMaskPaths(extraPaths, home = homedir4(), stat3 = (path) => {
5116
5893
  try {
5117
5894
  const info = lstatSync2(path);
5118
5895
  return { isDirectory: info.isDirectory() };
@@ -5120,10 +5897,10 @@ function credentialMaskPaths(extraPaths, home = homedir3(), stat3 = (path) => {
5120
5897
  return void 0;
5121
5898
  }
5122
5899
  }, requiredPaths = []) {
5123
- const required = new Set(requiredPaths.map((path) => resolve8(path)));
5900
+ const required = new Set(requiredPaths.map((path) => resolve12(path)));
5124
5901
  const candidates = [
5125
- ...KNOWN_CREDENTIAL_MASK_PATHS.map((entry) => resolve8(home, entry)),
5126
- ...(extraPaths ?? []).map((entry) => resolve8(entry))
5902
+ ...KNOWN_CREDENTIAL_MASK_PATHS.map((entry) => resolve12(home, entry)),
5903
+ ...(extraPaths ?? []).map((entry) => resolve12(entry))
5127
5904
  ];
5128
5905
  const seen = /* @__PURE__ */ new Set();
5129
5906
  const masks = [];
@@ -5150,7 +5927,7 @@ function normalize(paths) {
5150
5927
  for (const path of paths) {
5151
5928
  if (!path)
5152
5929
  continue;
5153
- seen.add(resolve8(path));
5930
+ seen.add(resolve12(path));
5154
5931
  }
5155
5932
  return Array.from(seen).sort();
5156
5933
  }
@@ -5188,7 +5965,7 @@ function sandboxMountPlan(spec) {
5188
5965
  writable,
5189
5966
  quotaTmpfs: spec.workbench ? [
5190
5967
  {
5191
- target: resolve8(spec.workbench.dir),
5968
+ target: resolve12(spec.workbench.dir),
5192
5969
  maxBytes: spec.workbench.maxBytes,
5193
5970
  maxInodes: spec.workbench.maxInodes,
5194
5971
  blockGit: true
@@ -5243,7 +6020,7 @@ function buildBwrapArgv(input) {
5243
6020
  for (const binding of quotaTmpfs) {
5244
6021
  args.push("--dir", binding.target, "--size", String(binding.maxBytes), "--tmpfs", binding.target);
5245
6022
  if (binding.blockGit)
5246
- args.push("--ro-bind", "/dev/null", resolve8(binding.target, ".git"));
6023
+ args.push("--ro-bind", "/dev/null", resolve12(binding.target, ".git"));
5247
6024
  }
5248
6025
  args.push("--chdir", input.cwd);
5249
6026
  args.push("--die-with-parent");
@@ -5307,13 +6084,156 @@ function detectBwrapSandbox(options = {}) {
5307
6084
  };
5308
6085
  }
5309
6086
 
6087
+ // apps/body/dist/pi-turn-record.js
6088
+ import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
6089
+ import { resolve as resolve13 } from "node:path";
6090
+ function summarizeProviderError(errorMessage2) {
6091
+ const trimmed = errorMessage2.trim();
6092
+ const statusMatch = /^(\d{3}):\s*([\s\S]*)$/.exec(trimmed);
6093
+ const status = statusMatch ? Number(statusMatch[1]) : void 0;
6094
+ let body = statusMatch ? statusMatch[2].trim() : trimmed;
6095
+ if (body.startsWith("{")) {
6096
+ try {
6097
+ const parsed = JSON.parse(body);
6098
+ const message = typeof parsed.message === "string" ? parsed.message : typeof parsed.error === "string" ? parsed.error : typeof parsed.error?.message === "string" ? parsed.error.message : void 0;
6099
+ if (message)
6100
+ body = message;
6101
+ } catch {
6102
+ }
6103
+ }
6104
+ const firstLine = body.split(/\r?\n/).find((line) => line.trim()) ?? "";
6105
+ const reason = `provider error${status === void 0 ? "" : ` ${status}`}${firstLine ? `: ${firstLine.trim()}` : ""}`;
6106
+ return status === void 0 ? { reason } : { reason, status };
6107
+ }
6108
+ async function sessionFileFromMap(home, sessionId) {
6109
+ try {
6110
+ const raw = await readFile5(resolve13(home, ".pi", "pi-acp", "session-map.json"), "utf8");
6111
+ const map = JSON.parse(raw);
6112
+ const file = map.sessions?.[sessionId]?.sessionFile;
6113
+ return typeof file === "string" && file ? file : void 0;
6114
+ } catch {
6115
+ return void 0;
6116
+ }
6117
+ }
6118
+ async function sessionFileFromLayout(piDir, sessionId) {
6119
+ const sessionsRoot = resolve13(piDir, "sessions");
6120
+ const suffix = `_${sessionId}.jsonl`;
6121
+ let projects;
6122
+ try {
6123
+ projects = await readdir2(sessionsRoot);
6124
+ } catch {
6125
+ return void 0;
6126
+ }
6127
+ for (const project of projects) {
6128
+ const dir = resolve13(sessionsRoot, project);
6129
+ let files;
6130
+ try {
6131
+ files = await readdir2(dir);
6132
+ } catch {
6133
+ continue;
6134
+ }
6135
+ const match = files.find((file) => file.endsWith(suffix));
6136
+ if (match)
6137
+ return resolve13(dir, match);
6138
+ }
6139
+ return void 0;
6140
+ }
6141
+ function messageText(content) {
6142
+ if (!Array.isArray(content))
6143
+ return "";
6144
+ return content.map((block2) => block2 && typeof block2 === "object" && block2.type === "text" ? String(block2.text ?? "") : "").join("").trim();
6145
+ }
6146
+ async function readPiTurnRecord(input) {
6147
+ const piDir = input.agentEnv.PI_CODING_AGENT_DIR;
6148
+ if (!piDir || !input.sessionId)
6149
+ return void 0;
6150
+ const file = (input.agentEnv.HOME ? await sessionFileFromMap(input.agentEnv.HOME, input.sessionId) : void 0) ?? await sessionFileFromLayout(piDir, input.sessionId);
6151
+ if (!file)
6152
+ return void 0;
6153
+ let raw;
6154
+ try {
6155
+ raw = await readFile5(file, "utf8");
6156
+ } catch {
6157
+ return void 0;
6158
+ }
6159
+ let sawUser = false;
6160
+ let last;
6161
+ for (const line of raw.split(/\r?\n/)) {
6162
+ if (!line.trim())
6163
+ continue;
6164
+ let entry;
6165
+ try {
6166
+ entry = JSON.parse(line);
6167
+ } catch {
6168
+ continue;
6169
+ }
6170
+ if (entry.type !== "message" || !entry.message)
6171
+ continue;
6172
+ if (entry.message.role === "user") {
6173
+ sawUser = true;
6174
+ last = void 0;
6175
+ } else if (entry.message.role === "assistant") {
6176
+ last = entry.message;
6177
+ }
6178
+ }
6179
+ if (!sawUser && !last)
6180
+ return void 0;
6181
+ if (!last)
6182
+ return { kind: "missing" };
6183
+ if (last.stopReason === "error") {
6184
+ return {
6185
+ kind: "error",
6186
+ ...summarizeProviderError(typeof last.errorMessage === "string" && last.errorMessage.trim() ? last.errorMessage : "unknown error")
6187
+ };
6188
+ }
6189
+ const text2 = messageText(last.content);
6190
+ if (text2)
6191
+ return { kind: "answer", text: text2 };
6192
+ return {
6193
+ kind: "empty",
6194
+ stopReason: typeof last.stopReason === "string" ? last.stopReason : "unknown"
6195
+ };
6196
+ }
6197
+
6198
+ // apps/body/dist/empty-turn.js
6199
+ async function explainEmptyAgentTurn(input) {
6200
+ const streamFact = describeEmptyTurn(input.result, input.agentLabel);
6201
+ if (!isPiAcpHarness(input.agentLabel))
6202
+ return { reason: streamFact };
6203
+ const record2 = await readPiTurnRecord({ agentEnv: input.agentEnv, sessionId: input.sessionId });
6204
+ if (!record2)
6205
+ return { reason: `pi left no readable turn record; ${streamFact}` };
6206
+ switch (record2.kind) {
6207
+ case "error":
6208
+ return { reason: record2.reason, record: record2 };
6209
+ case "empty":
6210
+ return {
6211
+ reason: `the model ended its turn with no text (stop reason ${record2.stopReason})`,
6212
+ record: record2
6213
+ };
6214
+ case "answer":
6215
+ return {
6216
+ recoveredText: record2.text,
6217
+ reason: "pi recorded the answer but the ACP stream delivered no text",
6218
+ record: record2
6219
+ };
6220
+ case "missing":
6221
+ return { reason: `pi recorded no assistant message for this turn; ${streamFact}`, record: record2 };
6222
+ }
6223
+ }
6224
+ function isAccountOrProviderRefusal(record2) {
6225
+ if (!record2 || record2.kind !== "error" || record2.status === void 0)
6226
+ return false;
6227
+ return [401, 402, 403, 407, 408, 429].includes(record2.status) || record2.status >= 500;
6228
+ }
6229
+
5310
6230
  // apps/body/dist/runtime.js
5311
- import { randomBytes as randomBytes5 } from "node:crypto";
5312
- import { execFile } from "node:child_process";
6231
+ import { randomBytes as randomBytes6 } from "node:crypto";
6232
+ import { execFile as execFile2 } from "node:child_process";
5313
6233
  import { closeSync, openSync } from "node:fs";
5314
- import { mkdir as mkdir2, readFile, readdir as readdir2, rename as rename2, stat, writeFile as writeFile2 } from "node:fs/promises";
5315
- import { homedir as homedir4 } from "node:os";
5316
- import { dirname as dirname3, resolve as resolve9 } from "node:path";
6234
+ import { mkdir as mkdir5, readFile as readFile6, readdir as readdir3, rename as rename2, stat, writeFile as writeFile6 } from "node:fs/promises";
6235
+ import { homedir as homedir5 } from "node:os";
6236
+ import { dirname as dirname5, resolve as resolve14 } from "node:path";
5317
6237
  import { spawn as spawn2 } from "node:child_process";
5318
6238
  import { promisify } from "node:util";
5319
6239
 
@@ -5461,7 +6381,7 @@ function createHasher(hashCons, info = {}) {
5461
6381
  Object.assign(hashC, info);
5462
6382
  return Object.freeze(hashC);
5463
6383
  }
5464
- function randomBytes(bytesLength = 32) {
6384
+ function randomBytes2(bytesLength = 32) {
5465
6385
  anumber(bytesLength, "bytesLength");
5466
6386
  const cr = typeof globalThis === "object" ? globalThis.crypto : null;
5467
6387
  if (typeof cr?.getRandomValues !== "function")
@@ -5779,7 +6699,7 @@ var bytesToHex2 = bytesToHex;
5779
6699
  var concatBytes2 = (...arrays) => concatBytes(...arrays);
5780
6700
  var hexToBytes2 = (hex) => hexToBytes(hex);
5781
6701
  var isBytes2 = isBytes;
5782
- var randomBytes2 = (bytesLength) => randomBytes(bytesLength);
6702
+ var randomBytes3 = (bytesLength) => randomBytes2(bytesLength);
5783
6703
  var _0n = /* @__PURE__ */ BigInt(0);
5784
6704
  var _1n = /* @__PURE__ */ BigInt(1);
5785
6705
  var atitle2 = (title) => title ? `"${title}" ` : "";
@@ -6371,18 +7291,18 @@ function validateTableBytes(numPoints, fpBytes) {
6371
7291
  if (bytes > TABLE_BYTES_MAX)
6372
7292
  throw new Error("invalid window size: table would need ~" + Math.ceil(bytes / 2 ** 20) + " MiB, max " + TABLE_BYTES_MAX / 2 ** 20 + " MiB");
6373
7293
  }
6374
- function probeRandomBytes(randomBytes6, length) {
6375
- if (randomBytes6 === void 0)
7294
+ function probeRandomBytes(randomBytes7, length) {
7295
+ if (randomBytes7 === void 0)
6376
7296
  return void 0;
6377
- afunction(randomBytes6, "randomBytes");
7297
+ afunction(randomBytes7, "randomBytes");
6378
7298
  try {
6379
- const probe = randomBytes6(length);
7299
+ const probe = randomBytes7(length);
6380
7300
  if (!isBytes2(probe) || probe.length !== length)
6381
7301
  return void 0;
6382
7302
  } catch {
6383
7303
  return void 0;
6384
7304
  }
6385
- return randomBytes6;
7305
+ return randomBytes7;
6386
7306
  }
6387
7307
  function validateMSMPoints(points, c2) {
6388
7308
  aarray(points, "points");
@@ -6475,9 +7395,9 @@ var ScalarMultiplier = class {
6475
7395
  baseCanBeBlinded;
6476
7396
  bits;
6477
7397
  // Parametrized with a given Point class (not individual point)
6478
- constructor(Point, randomBytes6) {
7398
+ constructor(Point, randomBytes7) {
6479
7399
  validatePointCons(Point);
6480
- this.randomBytes = probeRandomBytes(randomBytes6, BLIND_BYTES);
7400
+ this.randomBytes = probeRandomBytes(randomBytes7, BLIND_BYTES);
6481
7401
  this.Point = Point;
6482
7402
  this.BASE = Point.BASE;
6483
7403
  this.ZERO = Point.ZERO;
@@ -6765,7 +7685,7 @@ function weierstrass(params, extraOpts = {}) {
6765
7685
  randomBytes: "function"
6766
7686
  });
6767
7687
  const { endo, allowInfinityPoint } = extraOpts;
6768
- const randomBytes6 = extraOpts.randomBytes === void 0 ? randomBytes2 : extraOpts.randomBytes;
7688
+ const randomBytes7 = extraOpts.randomBytes === void 0 ? randomBytes3 : extraOpts.randomBytes;
6769
7689
  if (endo) {
6770
7690
  if (!Fp.is0(CURVE.a) || typeof endo.beta !== "bigint" || !Array.isArray(endo.basises)) {
6771
7691
  throw new Error('invalid endo: expected "beta": bigint and "basises": array');
@@ -7177,7 +8097,7 @@ function weierstrass(params, extraOpts = {}) {
7177
8097
  }
7178
8098
  }
7179
8099
  const normalize2 = (points) => normalizeZ(Point, points);
7180
- const wnaf = new ScalarMultiplier(Point, randomBytes6);
8100
+ const wnaf = new ScalarMultiplier(Point, randomBytes7);
7181
8101
  if (wnaf.bits >= 6)
7182
8102
  Point.BASE.precompute(6);
7183
8103
  Object.freeze(Point.prototype);
@@ -7286,7 +8206,7 @@ function challenge(...args) {
7286
8206
  function schnorrGetPublicKey(secretKey) {
7287
8207
  return schnorrGetExtPubKey(secretKey).bytes;
7288
8208
  }
7289
- function schnorrSign(message, secretKey, auxRand = randomBytes(32)) {
8209
+ function schnorrSign(message, secretKey, auxRand = randomBytes2(32)) {
7290
8210
  const { Fn, BASE } = Pointk1;
7291
8211
  const m2 = abytes2(message, void 0, "message");
7292
8212
  const { bytes: px, scalar: d } = schnorrGetExtPubKey(secretKey);
@@ -7336,7 +8256,7 @@ var schnorr = /* @__PURE__ */ (() => {
7336
8256
  const size = 32;
7337
8257
  const seedLength = 48;
7338
8258
  const randomSecretKey = (seed) => {
7339
- seed = seed === void 0 ? randomBytes(seedLength) : seed;
8259
+ seed = seed === void 0 ? randomBytes2(seedLength) : seed;
7340
8260
  return mapHashToField(abytes2(seed, seedLength, "seed"), secp256k1_CURVE.n);
7341
8261
  };
7342
8262
  return Object.freeze({
@@ -7483,7 +8403,7 @@ function createHasher2(hashCons, info = {}) {
7483
8403
  Object.assign(hashC, info);
7484
8404
  return Object.freeze(hashC);
7485
8405
  }
7486
- function randomBytes3(bytesLength = 32) {
8406
+ function randomBytes4(bytesLength = 32) {
7487
8407
  const cr = typeof globalThis === "object" ? globalThis.crypto : null;
7488
8408
  if (typeof cr?.getRandomValues !== "function")
7489
8409
  throw new Error("crypto.getRandomValues must be defined");
@@ -9179,7 +10099,7 @@ function getWLengths2(Fp, Fn) {
9179
10099
  }
9180
10100
  function ecdh(Point, ecdhOpts = {}) {
9181
10101
  const { Fn } = Point;
9182
- const randomBytes_ = ecdhOpts.randomBytes || randomBytes3;
10102
+ const randomBytes_ = ecdhOpts.randomBytes || randomBytes4;
9183
10103
  const lengths = Object.assign(getWLengths2(Point.Fp, Fn), { seed: getMinHashLength2(Fn.ORDER) });
9184
10104
  function isValidSecretKey(secretKey) {
9185
10105
  try {
@@ -9244,7 +10164,7 @@ function ecdsa2(Point, hash, ecdsaOpts = {}) {
9244
10164
  bits2int_modN: "function"
9245
10165
  });
9246
10166
  ecdsaOpts = Object.assign({}, ecdsaOpts);
9247
- const randomBytes6 = ecdsaOpts.randomBytes || randomBytes3;
10167
+ const randomBytes7 = ecdsaOpts.randomBytes || randomBytes4;
9248
10168
  const hmac2 = ecdsaOpts.hmac || ((key, msg) => hmac(hash, key, msg));
9249
10169
  const { Fp, Fn } = Point;
9250
10170
  const { ORDER: CURVE_ORDER, BITS: fnBits } = Fn;
@@ -9386,7 +10306,7 @@ function ecdsa2(Point, hash, ecdsaOpts = {}) {
9386
10306
  throw new Error("invalid private key");
9387
10307
  const seedArgs = [int2octets(d), int2octets(h1int)];
9388
10308
  if (extraEntropy != null && extraEntropy !== false) {
9389
- const e = extraEntropy === true ? randomBytes6(lengths.secretKey) : extraEntropy;
10309
+ const e = extraEntropy === true ? randomBytes7(lengths.secretKey) : extraEntropy;
9390
10310
  seedArgs.push(abytes3(e, void 0, "extraEntropy"));
9391
10311
  }
9392
10312
  const seed = concatBytes3(...seedArgs);
@@ -9553,7 +10473,7 @@ function challenge2(...args) {
9553
10473
  function schnorrGetPublicKey2(secretKey) {
9554
10474
  return schnorrGetExtPubKey2(secretKey).bytes;
9555
10475
  }
9556
- function schnorrSign2(message, secretKey, auxRand = randomBytes3(32)) {
10476
+ function schnorrSign2(message, secretKey, auxRand = randomBytes4(32)) {
9557
10477
  const { Fn } = Pointk12;
9558
10478
  const m2 = abytes3(message, void 0, "message");
9559
10479
  const { bytes: px, scalar: d } = schnorrGetExtPubKey2(secretKey);
@@ -9595,7 +10515,7 @@ function schnorrVerify2(signature, message, publicKey) {
9595
10515
  var schnorr2 = /* @__PURE__ */ (() => {
9596
10516
  const size = 32;
9597
10517
  const seedLength = 48;
9598
- const randomSecretKey = (seed = randomBytes3(seedLength)) => {
10518
+ const randomSecretKey = (seed = randomBytes4(seedLength)) => {
9599
10519
  return mapHashToField2(seed, secp256k1_CURVE2.n);
9600
10520
  };
9601
10521
  return {
@@ -9701,7 +10621,7 @@ function alphabet(letters) {
9701
10621
  };
9702
10622
  }
9703
10623
  // @__NO_SIDE_EFFECTS__
9704
- function join2(separator = "") {
10624
+ function join3(separator = "") {
9705
10625
  astr("join", separator);
9706
10626
  return {
9707
10627
  encode: (from) => {
@@ -9831,8 +10751,8 @@ var base64 = hasBase64Builtin ? {
9831
10751
  decode(s) {
9832
10752
  return decodeBase64Builtin(s, false);
9833
10753
  }
9834
- } : /* @__PURE__ */ chain(/* @__PURE__ */ radix2(6), /* @__PURE__ */ alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), /* @__PURE__ */ padding(6), /* @__PURE__ */ join2(""));
9835
- var BECH_ALPHABET = /* @__PURE__ */ chain(/* @__PURE__ */ alphabet("qpzry9x8gf2tvdw0s3jn54khce6mua7l"), /* @__PURE__ */ join2(""));
10754
+ } : /* @__PURE__ */ chain(/* @__PURE__ */ radix2(6), /* @__PURE__ */ alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), /* @__PURE__ */ padding(6), /* @__PURE__ */ join3(""));
10755
+ var BECH_ALPHABET = /* @__PURE__ */ chain(/* @__PURE__ */ alphabet("qpzry9x8gf2tvdw0s3jn54khce6mua7l"), /* @__PURE__ */ join3(""));
9836
10756
  var POLYMOD_GENERATORS = [996825010, 642813549, 513874426, 1027748829, 705979059];
9837
10757
  function bech32Polymod(pre) {
9838
10758
  const b = pre >> 25;
@@ -11959,7 +12879,7 @@ function encrypt2(secretKey, pubkey, text2) {
11959
12879
  const privkey = secretKey instanceof Uint8Array ? secretKey : hexToBytes3(secretKey);
11960
12880
  const key = secp256k1.getSharedSecret(privkey, hexToBytes3("02" + pubkey));
11961
12881
  const normalizedKey = getNormalizedX(key);
11962
- let iv = Uint8Array.from(randomBytes3(16));
12882
+ let iv = Uint8Array.from(randomBytes4(16));
11963
12883
  let plaintext = utf8Encoder.encode(text2);
11964
12884
  let ciphertext = cbc(normalizedKey, iv).encrypt(plaintext);
11965
12885
  let ctb64 = base64.encode(new Uint8Array(ciphertext));
@@ -12330,7 +13250,7 @@ function decodePayload(payload) {
12330
13250
  mac: data.subarray(-32)
12331
13251
  };
12332
13252
  }
12333
- function encrypt22(plaintext, conversationKey, nonce = randomBytes3(32)) {
13253
+ function encrypt22(plaintext, conversationKey, nonce = randomBytes4(32)) {
12334
13254
  const { chacha_key, chacha_nonce, hmac_key } = getMessageKeys(conversationKey, nonce);
12335
13255
  const padded = pad(plaintext);
12336
13256
  const ciphertext = chacha20(chacha_key, chacha_nonce, padded);
@@ -13845,23 +14765,23 @@ function decodeNsec(nsec) {
13845
14765
  }
13846
14766
 
13847
14767
  // apps/body/dist/runtime.js
13848
- var execFileAsync = promisify(execFile);
14768
+ var execFileAsync = promisify(execFile2);
13849
14769
  var DEFAULT_AGENT_IDENTITY_NAME = "beeline-agent";
13850
14770
  var DEFAULT_BODY_IDENTITY_NAME = "beeline-body";
13851
14771
  var DEFAULT_DAEMON_MONOLITH_BASE_URL = "https://server.usebeeline.app";
13852
14772
  function defaultSupervisorRoot(env = process.env) {
13853
- return resolve9(env.XDG_STATE_HOME ?? resolve9(homedir4(), ".local", "state"));
14773
+ return resolve14(env.XDG_STATE_HOME ?? resolve14(homedir5(), ".local", "state"));
13854
14774
  }
13855
14775
  function runtimeDirectory(supervisorRoot, publicKey) {
13856
14776
  if (!/^[0-9a-f]{64}$/i.test(publicKey))
13857
14777
  throw new Error("invalid agent public key");
13858
- return resolve9(supervisorRoot, "beeline", "agents", publicKey.toLowerCase());
14778
+ return resolve14(supervisorRoot, "beeline", "agents", publicKey.toLowerCase());
13859
14779
  }
13860
14780
  function runtimeConfigPath(supervisorRoot, publicKey) {
13861
- return resolve9(runtimeDirectory(supervisorRoot, publicKey), "runtime.json");
14781
+ return resolve14(runtimeDirectory(supervisorRoot, publicKey), "runtime.json");
13862
14782
  }
13863
14783
  function identityFromKey(value, name) {
13864
- const secretKey = value ? value.startsWith("nsec1") ? decodeNsec(value) : Uint8Array.from(Buffer.from(value, "hex")) : randomBytes5(32);
14784
+ const secretKey = value ? value.startsWith("nsec1") ? decodeNsec(value) : Uint8Array.from(Buffer.from(value, "hex")) : randomBytes6(32);
13865
14785
  if (secretKey.length !== 32)
13866
14786
  throw new Error("identity secret key must be 32 bytes");
13867
14787
  return { name, secretKey, publicKey: getPublicKey2(secretKey) };
@@ -13888,15 +14808,15 @@ function runtimeAgentCommand(runtime) {
13888
14808
  }
13889
14809
  async function writeRuntimeRecord(runtime) {
13890
14810
  const path = runtimeConfigPath(runtime.supervisorRoot, runtime.agent.publicKey);
13891
- await mkdir2(dirname3(path), { recursive: true, mode: 448 });
14811
+ await mkdir5(dirname5(path), { recursive: true, mode: 448 });
13892
14812
  const staged = `${path}.${process.pid}.tmp`;
13893
- await writeFile2(staged, `${JSON.stringify(runtime, null, 2)}
14813
+ await writeFile6(staged, `${JSON.stringify(runtime, null, 2)}
13894
14814
  `, { mode: 384 });
13895
14815
  await rename2(staged, path);
13896
14816
  return path;
13897
14817
  }
13898
14818
  async function readRuntimeRecord(path) {
13899
- const parsed = JSON.parse(await readFile(path, "utf8"));
14819
+ const parsed = JSON.parse(await readFile6(path, "utf8"));
13900
14820
  if (parsed.version !== 2 || !parsed.agent || !parsed.body || !parsed.communityId) {
13901
14821
  throw new Error(`invalid agent runtime record: ${path}`);
13902
14822
  }
@@ -13914,7 +14834,7 @@ async function migrateRuntimeRecordAccessPolicy(path) {
13914
14834
  return { runtime, migrated: true };
13915
14835
  }
13916
14836
  async function stageMonolithAgentRuntime(input) {
13917
- const supervisorRoot = input.supervisorRoot ? resolve9(input.supervisorRoot) : defaultSupervisorRoot();
14837
+ const supervisorRoot = input.supervisorRoot ? resolve14(input.supervisorRoot) : defaultSupervisorRoot();
13918
14838
  const configPath = runtimeConfigPath(supervisorRoot, input.agentIdentity.publicKey);
13919
14839
  const configuredBaseUrl = input.monolithBaseUrl ?? DEFAULT_DAEMON_MONOLITH_BASE_URL;
13920
14840
  const baseUrl = new URL(configuredBaseUrl).origin;
@@ -13959,9 +14879,9 @@ async function stageMonolithAgentRuntime(input) {
13959
14879
  return { runtime, configPath };
13960
14880
  }
13961
14881
  async function runtimePaths(root) {
13962
- const agents = resolve9(root, "beeline", "agents");
13963
- const entries = await readdir2(agents, { withFileTypes: true }).catch(() => []);
13964
- return entries.filter((entry) => entry.isDirectory()).map((entry) => resolve9(agents, entry.name, "runtime.json"));
14882
+ const agents = resolve14(root, "beeline", "agents");
14883
+ const entries = await readdir3(agents, { withFileTypes: true }).catch(() => []);
14884
+ return entries.filter((entry) => entry.isDirectory()).map((entry) => resolve14(agents, entry.name, "runtime.json"));
13965
14885
  }
13966
14886
  async function findAgentRuntimeConfigPaths(env = process.env, _cwd = process.cwd()) {
13967
14887
  return runtimePaths(defaultSupervisorRoot(env));
@@ -13970,13 +14890,13 @@ async function findRuntimeConfigPaths(cwd = process.cwd(), env = process.env) {
13970
14890
  return findAgentRuntimeConfigPaths(env, cwd);
13971
14891
  }
13972
14892
  async function resolveRuntimeConfigPath(path) {
13973
- return resolve9(path);
14893
+ return resolve14(path);
13974
14894
  }
13975
14895
  async function selectRuntimeConfigPaths(options) {
13976
14896
  const hostScope = true;
13977
14897
  const configs = await options.findHostRuntimes(options.cwd);
13978
14898
  const requestedPubkey = options.requestedPubkey;
13979
- const paths = requestedPubkey ? configs.filter((path) => dirname3(path).endsWith(requestedPubkey)) : [...new Set(configs)];
14899
+ const paths = requestedPubkey ? configs.filter((path) => dirname5(path).endsWith(requestedPubkey)) : [...new Set(configs)];
13980
14900
  if (!paths.length)
13981
14901
  throw new Error(options.noRuntimeMessage(hostScope));
13982
14902
  if (options.requestedPubkey && paths.length > 1)
@@ -13985,7 +14905,7 @@ async function selectRuntimeConfigPaths(options) {
13985
14905
  }
13986
14906
  async function runtimeDaemonPid(configPath) {
13987
14907
  try {
13988
- const pid = Number((await readFile(resolve9(dirname3(configPath), "daemon.pid"), "utf8")).trim());
14908
+ const pid = Number((await readFile6(resolve14(dirname5(configPath), "daemon.pid"), "utf8")).trim());
13989
14909
  if (!Number.isSafeInteger(pid) || pid <= 0)
13990
14910
  return null;
13991
14911
  process.kill(pid, 0);
@@ -13996,13 +14916,13 @@ async function runtimeDaemonPid(configPath) {
13996
14916
  }
13997
14917
  async function daemonIsThisRuntime(pid, configPath) {
13998
14918
  try {
13999
- const argv = (await readFile(`/proc/${pid}/cmdline`, "utf8")).split("\0").filter(Boolean);
14919
+ const argv = (await readFile6(`/proc/${pid}/cmdline`, "utf8")).split("\0").filter(Boolean);
14000
14920
  const flag = argv.lastIndexOf("--config");
14001
- return flag > 0 && argv[flag - 1] === "daemon" && resolve9(argv[flag + 1]) === resolve9(configPath);
14921
+ return flag > 0 && argv[flag - 1] === "daemon" && resolve14(argv[flag + 1]) === resolve14(configPath);
14002
14922
  } catch {
14003
14923
  try {
14004
14924
  const { stdout: stdout6 } = await execFileAsync("ps", ["-p", String(pid), "-o", "command="]);
14005
- return stdout6.includes(" daemon ") && stdout6.includes(resolve9(configPath));
14925
+ return stdout6.includes(" daemon ") && stdout6.includes(resolve14(configPath));
14006
14926
  } catch {
14007
14927
  return false;
14008
14928
  }
@@ -14028,14 +14948,14 @@ async function stopRuntimeDaemon(path, opts = {}) {
14028
14948
  throw new Error(`agent daemon ${pid} did not stop after ${timeout}ms`);
14029
14949
  }
14030
14950
  async function launchRuntimeDaemon(configPath, opts = {}) {
14031
- const directory = dirname3(configPath);
14032
- await mkdir2(directory, { recursive: true, mode: 448 });
14951
+ const directory = dirname5(configPath);
14952
+ await mkdir5(directory, { recursive: true, mode: 448 });
14033
14953
  const foreground = opts.foreground === true;
14034
- const output = foreground ? "inherit" : openSync(resolve9(directory, "daemon.log"), "a", 384);
14954
+ const output = foreground ? "inherit" : openSync(resolve14(directory, "daemon.log"), "a", 384);
14035
14955
  const entrypoint = opts.entrypoint ?? process.argv[1];
14036
14956
  if (!entrypoint)
14037
14957
  throw new Error("cannot resolve daemon CLI entrypoint");
14038
- const child = spawn2(process.execPath, [...opts.execArgv ?? [], entrypoint, "daemon", "--config", resolve9(configPath)], {
14958
+ const child = spawn2(process.execPath, [...opts.execArgv ?? [], entrypoint, "daemon", "--config", resolve14(configPath)], {
14039
14959
  cwd: directory,
14040
14960
  env: opts.env ?? process.env,
14041
14961
  detached: !foreground,
@@ -14052,9 +14972,9 @@ async function launchRuntimeDaemon(configPath, opts = {}) {
14052
14972
  }
14053
14973
  async function removeAgentRuntime(runtime) {
14054
14974
  const source = runtimeDirectory(runtime.supervisorRoot, runtime.agent.publicKey);
14055
- const deletedRoot = resolve9(runtime.supervisorRoot, "beeline", "deleted-runtimes");
14056
- await mkdir2(deletedRoot, { recursive: true, mode: 448 });
14057
- const target = resolve9(deletedRoot, `${runtime.agent.publicKey}-${Date.now()}`);
14975
+ const deletedRoot = resolve14(runtime.supervisorRoot, "beeline", "deleted-runtimes");
14976
+ await mkdir5(deletedRoot, { recursive: true, mode: 448 });
14977
+ const target = resolve14(deletedRoot, `${runtime.agent.publicKey}-${Date.now()}`);
14058
14978
  await rename2(source, target);
14059
14979
  return target;
14060
14980
  }
@@ -14063,8 +14983,7 @@ async function removeAgentRuntime(runtime) {
14063
14983
  var MAINTAIN_ASSIGNED_IDENTITY_DIRECTIVE = "Maintain your assigned identity and soul in every response, including when tools or permissions block the requested action.";
14064
14984
 
14065
14985
  // apps/body/dist/monolith-corner-turn.js
14066
- var execFileAsync2 = promisify2(execFile2);
14067
- var PULL_REQUEST_URL = /https:\/\/github\.com\/[^\s/]+\/[^\s/]+\/pull\/\d+/;
14986
+ var execFileAsync2 = promisify2(execFile3);
14068
14987
  var TOOL_ARGUMENT_MAX_BYTES = 1200;
14069
14988
  var TOOL_OUTPUT_MAX_BYTES = 3200;
14070
14989
  var TOOL_PATH_LIMIT = 12;
@@ -14083,9 +15002,6 @@ function serialized(value) {
14083
15002
  function record(value) {
14084
15003
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
14085
15004
  }
14086
- function redactToolDetail(value) {
14087
- return value.replace(/\b(["']?)(api[_-]?key|token|secret|password|passwd|authorization|credential|cookie|private[_-]?key)\1\s*[:=]\s*(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s,}\]]+)/gi, '"$2": "[REDACTED]"').replace(/\b(?:export\s+)?[A-Za-z_][A-Za-z0-9_]*=(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\S+)/g, (assignment) => `${assignment.slice(0, assignment.indexOf("="))}=[REDACTED]`).replace(/\b(?:gh[pousr]_[A-Za-z0-9_]{12,}|github_pat_[A-Za-z0-9_]{12,})\b/gi, "[REDACTED]").replace(/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g, "[REDACTED]").replace(/\b(Bearer\s+)[^\s,]+/gi, "$1[REDACTED]").replace(/(--?(?:api[_-]?key|token|secret|password|authorization|credential|cookie)\s+)(?:"[^"]*"|'[^']*'|\S+)/gi, "$1[REDACTED]");
14088
- }
14089
15005
  function clampBytes(value, maxBytes) {
14090
15006
  const clean4 = value.trim();
14091
15007
  if (Buffer.byteLength(clean4) <= maxBytes)
@@ -14166,7 +15082,7 @@ function isSuccessfulCommit(call) {
14166
15082
  return false;
14167
15083
  return /\bgit\s+commit\b|\bcommit(?:ted)?\s+(?:changes|files?)\b/i.test(`${call.title ?? ""} ${serialized(call.rawInput)}`);
14168
15084
  }
14169
- async function cornerToolActivity(call, worktreePath) {
15085
+ async function cornerToolActivity(call, worktreePath, requestedBy) {
14170
15086
  const operation = oneLine(call.kind ?? "") || "tool";
14171
15087
  let title = oneLine(redactToolDetail(call.title ?? "")) || `${operation} tool`;
14172
15088
  if (isSuccessfulCommit(call)) {
@@ -14189,6 +15105,7 @@ async function cornerToolActivity(call, worktreePath) {
14189
15105
  status: resultStatus(call),
14190
15106
  ...argumentsSummary,
14191
15107
  ...output ? { output } : {},
15108
+ ...requestedBy ? { requestedBy } : {},
14192
15109
  ...paths.length ? { files: paths.map((path) => ({ path })) } : {}
14193
15110
  };
14194
15111
  }
@@ -14201,14 +15118,26 @@ var MonolithCornerTurnLoop = class {
14201
15118
  agent;
14202
15119
  client;
14203
15120
  sessionId;
15121
+ /** The live session's environment, read back for pi's own turn record. */
15122
+ agentEnv = {};
14204
15123
  turnIdentityInstructions = "";
14205
15124
  busy = false;
14206
15125
  forcedStop = false;
14207
15126
  draftTail = Promise.resolve();
14208
15127
  activityTail = Promise.resolve();
15128
+ /** Session scratch directory attachments are downloaded into (`TMPDIR/beeline-attachments`). */
15129
+ attachmentDir;
15130
+ /** The turn in flight and who asked for it, for ledger rows and the grant runner. */
15131
+ currentTurn;
15132
+ memberNames = /* @__PURE__ */ new Map();
14209
15133
  constructor(options) {
14210
15134
  this.options = options;
14211
15135
  this.agent = runtimeIdentity(options.runtime.agent);
15136
+ options.grantRunner?.register(options.cornerId, {
15137
+ workspaceId: options.workspaceId,
15138
+ cwd: options.worktreePath,
15139
+ turn: () => this.currentTurn
15140
+ });
14212
15141
  }
14213
15142
  isBusy() {
14214
15143
  return this.busy;
@@ -14227,11 +15156,13 @@ var MonolithCornerTurnLoop = class {
14227
15156
  this.client.sessionCancel(this.sessionId);
14228
15157
  await this.options.scheduler.forceSuspend(this.options.cornerId);
14229
15158
  }
14230
- roster() {
14231
- return this.options.api.execute("getWorkspaceRoster", {
15159
+ async roster() {
15160
+ const roster = await this.options.api.execute("getWorkspaceRoster", {
14232
15161
  agentId: this.agent.publicKey,
14233
15162
  workspaceId: this.options.workspaceId
14234
15163
  });
15164
+ this.memberNames = new Map(roster.members.map((member) => [member.identityId, member.name]));
15165
+ return roster;
14235
15166
  }
14236
15167
  async activate() {
14237
15168
  if (this.client?.isAlive && this.sessionId)
@@ -14243,29 +15174,32 @@ var MonolithCornerTurnLoop = class {
14243
15174
  }),
14244
15175
  this.roster()
14245
15176
  ]);
14246
- await mkdir3(this.options.worktreePath, { recursive: true });
15177
+ await mkdir6(this.options.worktreePath, { recursive: true });
15178
+ const selection = configuration.model || configuration.effort ? { model: configuration.model, effort: configuration.effort } : this.options.config.modelSelection;
14247
15179
  const homeOverlay = this.options.config.agentHomeRoot ? await prepareRoomAgentHome({
14248
15180
  root: this.options.config.agentHomeRoot,
14249
15181
  sharedSkills: this.options.config.sharedSkills ?? [],
14250
- ...this.options.config.operatorHome ? { operatorHome: this.options.config.operatorHome } : {}
15182
+ ...this.options.config.operatorHome ? { operatorHome: this.options.config.operatorHome } : {},
15183
+ ...openRouterRoutingInput(this.options.config, selection, this.options.fetchImpl)
14251
15184
  }) : {};
14252
15185
  const command = this.options.config.agentCommand ?? this.options.config.agentBinary;
14253
- const selection = configuration.model || configuration.effort ? { model: configuration.model, effort: configuration.effort } : this.options.config.modelSelection;
14254
15186
  const agentEnv = {
14255
15187
  ...this.options.config.agentEnv,
14256
15188
  ...homeOverlay,
14257
15189
  GH_TOKEN: this.options.githubToken,
14258
15190
  GITHUB_TOKEN: this.options.githubToken
14259
15191
  };
15192
+ this.agentEnv = agentEnv;
14260
15193
  const agentArgs = agentArgsWithModelSelection({
14261
15194
  kind: this.options.config.agentKind,
14262
15195
  command,
14263
15196
  args: this.options.config.agentArgs ?? []
14264
15197
  }, selection);
14265
- const operatorHome = this.options.config.operatorHome ?? homedir5();
15198
+ const operatorHome = this.options.config.operatorHome ?? homedir6();
14266
15199
  const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
15200
+ this.attachmentDir = tmpDir ? join4(tmpDir, "beeline-attachments") : void 0;
14267
15201
  const homeStateDirs = harnessHomeStateDirs(command, agentEnv.HOME ?? operatorHome);
14268
- await Promise.all(homeStateDirs.map((dir) => mkdir3(dir, { recursive: true })));
15202
+ await Promise.all(homeStateDirs.map((dir) => mkdir6(dir, { recursive: true })));
14269
15203
  const spawnCommand = wrapAgentCommand({
14270
15204
  bwrapPath: this.options.config.bwrapPath,
14271
15205
  spec: {
@@ -14311,7 +15245,8 @@ var MonolithCornerTurnLoop = class {
14311
15245
  roomId: this.options.parentRoomId,
14312
15246
  workspaceId: this.options.workspaceId,
14313
15247
  cornerId: this.options.cornerId,
14314
- attachRoot: this.options.worktreePath
15248
+ attachRoot: this.options.worktreePath,
15249
+ ...this.options.grantRunnerEndpoint ? { grantRunner: this.options.grantRunnerEndpoint } : {}
14315
15250
  })
14316
15251
  ];
14317
15252
  const self = roster.members.find((member) => member.identityId === this.agent.publicKey);
@@ -14330,6 +15265,7 @@ var MonolithCornerTurnLoop = class {
14330
15265
  "Work normally with the full coding tools. Commit and push only this feature branch. Use gh to open its pull request.",
14331
15266
  "PR-opening turn rule: as soon as a pull request exists, print its full GitHub URL as your final response and end the turn immediately. Do not call pr_checks_status in that same turn and do not wait for checks inside it. Then stay idle until a later corner fact or human message starts another turn.",
14332
15267
  'Never merge because local tests pass or because gh reports passing checks. On a later turn triggered by a server-posted checks-passed note, call beeline-agent pr_checks_status. Merge only when it returns checks="passed", held=false, and approvalPending=false.',
15268
+ "Merge the PR yourself only after the checks-passed event shows every check green; if any check failed or is still running, say exactly which and stop - never merge red.",
14333
15269
  "If any human in this corner says hold or do not merge, do not merge until a later human explicitly resumes it.",
14334
15270
  "Do not tag the user when a corner turn finishes: the server posts the merge summary card and its push already cover completion. Tag a human only mid-turn, and only when you need a decision or input.",
14335
15271
  "A human approval in the app asks the server to merge. When approval is pending, wait for the server close request instead of racing it with gh. If checks passed, no hold exists, and no approval is pending, merge the pull request yourself with gh.",
@@ -14355,8 +15291,13 @@ var MonolithCornerTurnLoop = class {
14355
15291
  }
14356
15292
  };
14357
15293
  }
14358
- async prompt(requestId, trigger) {
15294
+ async prompt(requestId, trigger, attachments = [], requestedById) {
14359
15295
  const { api, cornerId } = this.options;
15296
+ const requester = requestedById ? {
15297
+ pubkey: requestedById,
15298
+ ...this.memberNames.get(requestedById) ? { name: this.memberNames.get(requestedById) } : {}
15299
+ } : void 0;
15300
+ this.currentTurn = { requestId, ...requester ? { requester } : {} };
14360
15301
  await api.execute("postAgentTurnReceipt", {
14361
15302
  agentId: this.agent.publicKey,
14362
15303
  roomId: cornerId,
@@ -14369,11 +15310,15 @@ var MonolithCornerTurnLoop = class {
14369
15310
  if (this.forcedStop)
14370
15311
  throw new Error("corner turn stopped for daemon handoff");
14371
15312
  this.busy = true;
14372
- const [conversation, roster] = await Promise.all([
15313
+ const [conversation, roster, delivered] = await Promise.all([
14373
15314
  api.execute("getRoomConversation", { roomId: cornerId, limit: 200 }),
14374
- this.roster()
15315
+ this.roster(),
15316
+ this.attachmentDir && attachments.length ? deliverAttachments(attachments, join4(this.attachmentDir, requestId.replace(/[^\w-]/g, "_")), this.options.fetchImpl) : Promise.resolve([])
14375
15317
  ]);
14376
15318
  const names = new Map(roster.members.map((member) => [member.identityId, member.name]));
15319
+ const requestedBy = requester && !requester.name && names.get(requester.pubkey) ? { ...requester, name: names.get(requester.pubkey) } : requester;
15320
+ if (requestedBy)
15321
+ this.currentTurn = { requestId, requester: requestedBy };
14377
15322
  const transcript = conversation.items.slice(-120).map((message) => `${names.get(message.authorId) ?? "Beeline"} [${message.type}]: ${message.body}`).join("\n");
14378
15323
  const prompt = [
14379
15324
  this.turnIdentityInstructions,
@@ -14381,8 +15326,8 @@ var MonolithCornerTurnLoop = class {
14381
15326
  ${this.options.objective}`,
14382
15327
  transcript ? `Corner transcript:
14383
15328
  ${transcript}` : "",
14384
- `Newest trigger:
14385
- ${trigger}`,
15329
+ [`Newest trigger:
15330
+ ${trigger}`, ...attachmentPromptLines(attachments, delivered)].join("\n"),
14386
15331
  "Continue the objective. Obey the PR checks and human hold rules in your session instructions.",
14387
15332
  MAINTAIN_ASSIGNED_IDENTITY_DIRECTIVE
14388
15333
  ].filter(Boolean).join("\n\n");
@@ -14426,7 +15371,7 @@ ${trigger}`,
14426
15371
  return;
14427
15372
  publishedToolCalls.add(key);
14428
15373
  this.activityTail = this.activityTail.catch(() => void 0).then(async () => {
14429
- const activity = await cornerToolActivity(call, this.options.worktreePath);
15374
+ const activity = await cornerToolActivity(call, this.options.worktreePath, requestedBy);
14430
15375
  await api.execute("postAgentActivity", {
14431
15376
  agentId: this.agent.publicKey,
14432
15377
  roomId: cornerId,
@@ -14439,7 +15384,7 @@ ${trigger}`,
14439
15384
  });
14440
15385
  });
14441
15386
  };
14442
- const result = await this.client.sessionPrompt(sessionId, prompt, 12e4, (_delta, full) => {
15387
+ const result = await this.client.sessionPrompt(sessionId, promptWithImages(prompt, attachmentImageBlocks(delivered, this.client.canPromptWithImages())), 12e4, (_delta, full) => {
14443
15388
  postNarrationSegments(full);
14444
15389
  this.draftTail = this.draftTail.catch(() => void 0).then(() => api.execute("postAgentDraft", {
14445
15390
  agentId: this.agent.publicKey,
@@ -14453,9 +15398,19 @@ ${trigger}`,
14453
15398
  await this.activityTail;
14454
15399
  await this.draftTail;
14455
15400
  await this.activityTail;
14456
- const reply = stripAgentReplyPreamble(result.agentText).trim();
14457
- if (!reply)
14458
- throw new Error("ACP corner turn produced no durable reply");
15401
+ let reply = stripAgentReplyPreamble(result.agentText).trim();
15402
+ if (!reply) {
15403
+ const explained = await explainEmptyAgentTurn({
15404
+ agentLabel: this.options.config.agentCommand ?? this.options.config.agentBinary,
15405
+ agentEnv: this.agentEnv,
15406
+ sessionId,
15407
+ result
15408
+ });
15409
+ reply = explained.recoveredText ? stripAgentReplyPreamble(explained.recoveredText).trim() : "";
15410
+ if (!reply)
15411
+ throw new Error(explained.reason);
15412
+ console.warn(`[thin-core] corner ${cornerId} turn ${requestId}: ${explained.reason}`);
15413
+ }
14459
15414
  const durableTail = narrationPostedChars > 0 ? stripAgentReplyPreamble(result.agentText.slice(narrationPostedChars)).trim() : reply;
14460
15415
  if (durableTail) {
14461
15416
  await api.execute("postRoomMessage", {
@@ -14465,17 +15420,6 @@ ${trigger}`,
14465
15420
  presentation: "message"
14466
15421
  });
14467
15422
  }
14468
- const pullRequest = reply.match(PULL_REQUEST_URL)?.[0];
14469
- const alreadyReady = conversation.items.some((item) => /\bPR ready for review\b/i.test(item.body));
14470
- if (pullRequest && !alreadyReady) {
14471
- await api.execute("postRoomMessage", {
14472
- roomId: cornerId,
14473
- requestId,
14474
- text: `PR ready for review
14475
- ${pullRequest}`,
14476
- presentation: "system"
14477
- });
14478
- }
14479
15423
  await api.execute("retractAgentLiveOutput", {
14480
15424
  agentId: this.agent.publicKey,
14481
15425
  roomId: cornerId,
@@ -14496,11 +15440,13 @@ ${pullRequest}`,
14496
15440
  roomId: cornerId,
14497
15441
  requestId,
14498
15442
  status: "failed",
14499
- generationId: `${this.agent.publicKey}:${cornerId}`
15443
+ generationId: `${this.agent.publicKey}:${cornerId}`,
15444
+ reason: distillTurnFailureReason(error)
14500
15445
  });
14501
15446
  throw error;
14502
15447
  } finally {
14503
15448
  this.busy = false;
15449
+ this.currentTurn = void 0;
14504
15450
  }
14505
15451
  }
14506
15452
  async run() {
@@ -14533,7 +15479,14 @@ ${pullRequest}`,
14533
15479
  });
14534
15480
  if (!authority.member || authority.principalKind !== "human")
14535
15481
  continue;
14536
- await this.prompt(item.id, item.body);
15482
+ await this.prompt(item.id, item.body, item.attachments, item.authorId);
15483
+ pollWithoutWait = true;
15484
+ continue;
15485
+ }
15486
+ const grantDecision = item.type === "system" && item.mentionIds.includes(this.agent.publicKey) && parseGrantDecisionLine(item.body) !== void 0;
15487
+ if (grantDecision) {
15488
+ await this.prompt(item.id, `${item.body}
15489
+ This answers your grant request; resume the paused work. If approved and it is a command grant, run it with run_granted_command and the exact argv; if declined, try another way or say what you cannot do.`, [], item.authorId);
14537
15490
  pollWithoutWait = true;
14538
15491
  continue;
14539
15492
  }
@@ -14555,6 +15508,7 @@ ${pullRequest}`,
14555
15508
  }
14556
15509
  }
14557
15510
  } finally {
15511
+ this.options.grantRunner?.unregister(cornerId);
14558
15512
  await this.options.scheduler.suspend(cornerId);
14559
15513
  }
14560
15514
  }
@@ -14574,12 +15528,13 @@ async function wait(ms, signal) {
14574
15528
  }
14575
15529
 
14576
15530
  // apps/body/dist/monolith-room-turn.js
14577
- import { mkdir as mkdir4 } from "node:fs/promises";
14578
- import { homedir as homedir6 } from "node:os";
15531
+ import { mkdir as mkdir7 } from "node:fs/promises";
15532
+ import { homedir as homedir7 } from "node:os";
15533
+ import { join as join5 } from "node:path";
14579
15534
 
14580
15535
  // packages/api-contract/dist/scheduled-prompts.js
14581
15536
  var SCHEDULE_SCHEDULER_NAME = "Beeline Scheduler";
14582
- var SCHEDULED_PROMPT_PREFIX = "Scheduled: ";
15537
+ var SCHEDULE_RAN_VERB = "ran a schedule for";
14583
15538
 
14584
15539
  // apps/body/dist/monolith-room-turn.js
14585
15540
  function isRoomMcpPermissionRequest(request) {
@@ -14591,14 +15546,25 @@ function roomPrincipalMayAddressAgent(authority, humanPermitted) {
14591
15546
  return authority.member && (authority.principalKind === "agent" || authority.principalKind === "human" && humanPermitted);
14592
15547
  }
14593
15548
  function isScheduledPrompt(item, agentId) {
14594
- return item.type === "system" && item.body.startsWith(SCHEDULED_PROMPT_PREFIX) && item.mentionIds.includes(agentId);
15549
+ return item.type === "system" && item.systemEvent?.verb === SCHEDULE_RAN_VERB && item.mentionIds.includes(agentId);
15550
+ }
15551
+ function inboxItemPromptBody(item, agentId) {
15552
+ return isScheduledPrompt(item, agentId) ? item.systemEvent?.consequence ?? item.body : item.body;
15553
+ }
15554
+ function isGrantDecisionLine(item, agentId) {
15555
+ return item.type === "system" && item.mentionIds.includes(agentId) && parseGrantDecisionLine(item.body) !== void 0;
14595
15556
  }
14596
15557
  function inboxItemTriggersTurn(item, agentId) {
14597
15558
  if (item.authorId === agentId)
14598
15559
  return false;
14599
15560
  if (!item.mentionIds.includes(agentId))
14600
15561
  return false;
14601
- return item.type === "message" || isScheduledPrompt(item, agentId);
15562
+ return item.type === "message" || isScheduledPrompt(item, agentId) || isGrantDecisionLine(item, agentId);
15563
+ }
15564
+ function pendingGrantToolCall(call) {
15565
+ if (!/(?:^|[._:/-])request_grant$/i.test(call.title ?? ""))
15566
+ return false;
15567
+ return /pending, card posted/i.test(typeof call.content === "string" ? call.content : JSON.stringify(call.content ?? ""));
14602
15568
  }
14603
15569
  function escapeRegExp(value) {
14604
15570
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -14636,18 +15602,47 @@ var MonolithRoomTurnLoop = class {
14636
15602
  agent;
14637
15603
  client;
14638
15604
  sessionId;
15605
+ /** The live session's environment, read back for pi's own turn record. */
15606
+ agentEnv = {};
14639
15607
  busy = false;
14640
15608
  turnInstructionPrefix = "";
14641
15609
  draftTail = Promise.resolve();
14642
15610
  activeTurn;
14643
15611
  queuedTurns = [];
15612
+ /** Session scratch directory attachments are downloaded into (`TMPDIR/beeline-attachments`). */
15613
+ attachmentDir;
15614
+ /** Local copies already delivered this session, by message id, so transcript renders reuse them. */
15615
+ deliveredAttachments = /* @__PURE__ */ new Map();
15616
+ /** Names from the latest roster read, for ledger bylines the runner writes. */
15617
+ memberNames = /* @__PURE__ */ new Map();
15618
+ /** The request id of the turn that paused on a grant card, until its decision arrives. */
15619
+ pausedOnGrantRequestId;
14644
15620
  constructor(options) {
14645
15621
  this.options = options;
14646
15622
  this.agent = runtimeIdentity(options.runtime.agent);
15623
+ options.grantRunner?.register(options.roomId, {
15624
+ workspaceId: options.workspaceId,
15625
+ cwd: options.cwd,
15626
+ turn: () => this.currentTurnForRunner()
15627
+ });
14647
15628
  }
14648
15629
  isBusy() {
14649
15630
  return this.busy;
14650
15631
  }
15632
+ /** The turn a `request_grant` paused, if any (cleared when its decision resumes it). */
15633
+ pausedGrantRequestId() {
15634
+ return this.pausedOnGrantRequestId;
15635
+ }
15636
+ currentTurnForRunner() {
15637
+ const active = this.activeTurn;
15638
+ if (!active)
15639
+ return void 0;
15640
+ return { requestId: active.item.id, requester: this.requesterOf(active.item.authorId) };
15641
+ }
15642
+ requesterOf(authorId) {
15643
+ const name = this.memberNames.get(authorId);
15644
+ return { pubkey: authorId, ...name ? { name } : {} };
15645
+ }
14651
15646
  currentPrincipalCanDrive(_workspaceId, principalId) {
14652
15647
  return Promise.resolve(isSenderPermitted(this.options.config.accessPolicy ?? LEGACY_ACCESS_POLICY, principalId, this.options.config.accessOwnerPubkey, this.options.config.accessAllowlist));
14653
15648
  }
@@ -14661,11 +15656,24 @@ var MonolithRoomTurnLoop = class {
14661
15656
  this.client.sessionCancel(this.sessionId);
14662
15657
  await this.options.scheduler.forceSuspend(this.options.roomId);
14663
15658
  }
14664
- roster() {
14665
- return this.options.api.execute("getWorkspaceRoster", {
15659
+ async roster() {
15660
+ const roster = await this.options.api.execute("getWorkspaceRoster", {
14666
15661
  agentId: this.agent.publicKey,
14667
15662
  workspaceId: this.options.workspaceId
14668
15663
  });
15664
+ this.memberNames = new Map(roster.members.map((member) => [member.identityId, member.name]));
15665
+ return roster;
15666
+ }
15667
+ /** Download a message's attachments into the session scratch directory once. */
15668
+ async deliver(item) {
15669
+ if (!item.attachments.length || !this.attachmentDir)
15670
+ return [];
15671
+ const cached = this.deliveredAttachments.get(item.id);
15672
+ if (cached)
15673
+ return cached;
15674
+ const delivered = await deliverAttachments(item.attachments, join5(this.attachmentDir, item.id.replace(/[^\w-]/g, "_")), this.options.fetchImpl);
15675
+ this.deliveredAttachments.set(item.id, withoutImageData(delivered));
15676
+ return delivered;
14669
15677
  }
14670
15678
  async activate() {
14671
15679
  if (this.client?.isAlive && this.sessionId)
@@ -14679,24 +15687,27 @@ var MonolithRoomTurnLoop = class {
14679
15687
  this.options.api.execute("getRoomRepositoryState", { roomId: this.options.roomId })
14680
15688
  ]);
14681
15689
  const directMessage = Array.isArray(repositoryState.directParticipants) && repositoryState.directParticipants.length === 2;
14682
- await mkdir4(this.options.cwd, { recursive: true });
15690
+ await mkdir7(this.options.cwd, { recursive: true });
15691
+ const selection = configuration.model || configuration.effort ? { model: configuration.model, effort: configuration.effort } : this.options.config.modelSelection;
14683
15692
  const homeOverlay = this.options.config.agentHomeRoot ? await prepareRoomAgentHome({
14684
15693
  root: this.options.config.agentHomeRoot,
14685
15694
  sharedSkills: this.options.config.sharedSkills ?? [],
14686
- ...this.options.config.operatorHome ? { operatorHome: this.options.config.operatorHome } : {}
15695
+ ...this.options.config.operatorHome ? { operatorHome: this.options.config.operatorHome } : {},
15696
+ ...openRouterRoutingInput(this.options.config, selection, this.options.fetchImpl)
14687
15697
  }) : {};
14688
15698
  const command = this.options.config.agentCommand ?? this.options.config.agentBinary;
14689
- const selection = configuration.model || configuration.effort ? { model: configuration.model, effort: configuration.effort } : this.options.config.modelSelection;
14690
15699
  const agentEnv = { ...this.options.config.agentEnv, ...homeOverlay };
15700
+ this.agentEnv = agentEnv;
14691
15701
  const agentArgs = agentArgsWithModelSelection({
14692
15702
  kind: this.options.config.agentKind,
14693
15703
  command,
14694
15704
  args: this.options.config.agentArgs ?? []
14695
15705
  }, selection);
14696
- const operatorHome = this.options.config.operatorHome ?? homedir6();
15706
+ const operatorHome = this.options.config.operatorHome ?? homedir7();
14697
15707
  const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
15708
+ this.attachmentDir = tmpDir ? join5(tmpDir, "beeline-attachments") : void 0;
14698
15709
  const homeStateDirs = harnessHomeStateDirs(command, agentEnv.HOME ?? operatorHome);
14699
- await Promise.all(homeStateDirs.map((dir) => mkdir4(dir, { recursive: true })));
15710
+ await Promise.all(homeStateDirs.map((dir) => mkdir7(dir, { recursive: true })));
14700
15711
  const spawnCommand = wrapAgentCommand({
14701
15712
  bwrapPath: this.options.config.bwrapPath,
14702
15713
  spec: {
@@ -14716,6 +15727,9 @@ var MonolithRoomTurnLoop = class {
14716
15727
  agentEnv,
14717
15728
  agentCwd: this.options.cwd,
14718
15729
  agentLabel: command,
15730
+ // `bwrapPath` is set only when `detectBwrapSandbox` passed its self-test
15731
+ // (`config.ts`), which is exactly when `wrapAgentCommand` above wraps.
15732
+ osSandbox: Boolean(this.options.config.bwrapPath),
14719
15733
  autoApprovePermissions: false,
14720
15734
  permissionAllowlist: isRoomMcpPermissionRequest
14721
15735
  };
@@ -14727,7 +15741,8 @@ var MonolithRoomTurnLoop = class {
14727
15741
  roomId: this.options.roomId,
14728
15742
  workspaceId: this.options.workspaceId,
14729
15743
  attachRoot: this.options.cwd,
14730
- directMessage
15744
+ directMessage,
15745
+ ...this.options.grantRunnerEndpoint ? { grantRunner: this.options.grantRunnerEndpoint } : {}
14731
15746
  })
14732
15747
  ];
14733
15748
  const self = roster.members.find((member) => member.identityId === this.agent.publicKey);
@@ -14796,11 +15811,11 @@ var MonolithRoomTurnLoop = class {
14796
15811
  active.steers.push(item);
14797
15812
  active.steerTail = active.steerTail.catch(() => void 0).then(async () => {
14798
15813
  try {
14799
- const roster = await this.roster();
15814
+ const [roster, delivered] = await Promise.all([this.roster(), this.deliver(item)]);
14800
15815
  const author = roster.members.find((member) => member.identityId === item.authorId)?.name ?? item.authorId.slice(0, 12);
14801
15816
  await this.client.sessionSteer(this.sessionId, [
14802
15817
  `Human steer received while the current turn is running from ${author}:`,
14803
- roomMessagePrompt("", item.body, item.attachments),
15818
+ roomMessagePrompt("", item.body, item.attachments, delivered),
14804
15819
  "Adjust the current work now. Keep the original request and earlier messages as context."
14805
15820
  ].join("\n\n"));
14806
15821
  } catch (error) {
@@ -14815,6 +15830,8 @@ var MonolithRoomTurnLoop = class {
14815
15830
  const api = this.options.api;
14816
15831
  this.busy = true;
14817
15832
  try {
15833
+ if (!this.memberNames.has(item.authorId))
15834
+ await this.roster().catch(() => void 0);
14818
15835
  await api.execute("postAgentTurnReceipt", {
14819
15836
  agentId: this.agent.publicKey,
14820
15837
  roomId: this.options.roomId,
@@ -14826,21 +15843,38 @@ var MonolithRoomTurnLoop = class {
14826
15843
  agentId: this.agent.publicKey,
14827
15844
  roomId: this.options.roomId,
14828
15845
  requestId: item.id,
14829
- activity: [{ kind: "thinking", title: "Working", status: "in_progress" }]
15846
+ activity: [
15847
+ {
15848
+ kind: "thinking",
15849
+ title: "Working",
15850
+ status: "in_progress",
15851
+ requestedBy: this.requesterOf(item.authorId)
15852
+ }
15853
+ ]
14830
15854
  });
14831
15855
  await this.options.scheduler.run(this.options.roomId, this.lifecycle(), async () => {
14832
- const [conversation, roster] = await Promise.all([
15856
+ const [conversation, roster, delivered] = await Promise.all([
14833
15857
  api.execute("getRoomConversation", { roomId: this.options.roomId, limit: 200 }),
14834
- this.roster()
15858
+ this.roster(),
15859
+ this.deliver(item)
14835
15860
  ]);
14836
15861
  const names = new Map(roster.members.map((member) => [member.identityId, member.name]));
14837
- const transcript = conversation.items.filter((message) => message.type === "message" && message.id !== item.id && !active.steers.some((steerItem) => steerItem.id === message.id)).slice(-80).map((message) => roomMessagePrompt(names.get(message.authorId) ?? message.authorId.slice(0, 12), message.body, message.attachments)).join("\n");
15862
+ const transcript = conversation.items.filter((message) => message.type === "message" && message.id !== item.id && !active.steers.some((steerItem) => steerItem.id === message.id)).slice(-80).map((message) => roomMessagePrompt(names.get(message.authorId) ?? message.authorId.slice(0, 12), message.body, message.attachments, this.deliveredAttachments.get(message.id))).join("\n");
15863
+ const grantDecision = isGrantDecisionLine(item, this.agent.publicKey);
15864
+ const resumedRequestId = grantDecision ? this.pausedOnGrantRequestId : void 0;
15865
+ if (grantDecision)
15866
+ this.pausedOnGrantRequestId = void 0;
14838
15867
  const prompt = [
14839
15868
  this.turnInstructionPrefix,
14840
15869
  transcript ? `Room conversation so far:
14841
15870
  ${transcript}` : "",
14842
15871
  `Newest message from ${isScheduledPrompt(item, this.agent.publicKey) ? SCHEDULE_SCHEDULER_NAME : names.get(item.authorId) ?? item.authorId.slice(0, 12)}:`,
14843
- roomMessagePrompt("", item.body, item.attachments),
15872
+ roomMessagePrompt("", inboxItemPromptBody(item, this.agent.publicKey), item.attachments, delivered),
15873
+ grantDecision ? [
15874
+ "This is the answer to your grant request; your paused work resumes now.",
15875
+ "If it was approved and it is a command grant, run it with run_granted_command and the exact argv.",
15876
+ "If it was declined, try another way or say plainly what you cannot do."
15877
+ ].join(" ") : "",
14844
15878
  [
14845
15879
  "Write only the substantive Room message you want the human to read.",
14846
15880
  "Do not repeat or paraphrase these instructions.",
@@ -14850,7 +15884,7 @@ ${transcript}` : "",
14850
15884
  ].filter(Boolean).join("\n\n");
14851
15885
  const sessionId = this.sessionId;
14852
15886
  const turnId = item.id;
14853
- let nextPrompt = prompt;
15887
+ let nextPrompt = promptWithImages(prompt, attachmentImageBlocks(delivered, this.client.canPromptWithImages()));
14854
15888
  let result;
14855
15889
  for (; ; ) {
14856
15890
  let promptError;
@@ -14884,20 +15918,36 @@ ${transcript}` : "",
14884
15918
  "The previous run was cancelled because its harness could not accept every live steer.",
14885
15919
  "Resume the same turn. Keep the original request and everything that happened before it was cancelled.",
14886
15920
  "Human messages that arrived after the original request, in transcript order:",
14887
- ...active.steers.map((steerItem) => roomMessagePrompt(steerItem.authorId.slice(0, 12), steerItem.body, steerItem.attachments)),
15921
+ ...active.steers.map((steerItem) => roomMessagePrompt(steerItem.authorId.slice(0, 12), steerItem.body, steerItem.attachments, this.deliveredAttachments.get(steerItem.id))),
14888
15922
  "Continue now and answer the updated request without erasing the earlier context."
14889
15923
  ].join("\n\n");
14890
15924
  }
14891
15925
  active.phase = "finishing";
15926
+ if (result?.toolCalls.some((call) => pendingGrantToolCall(call))) {
15927
+ this.pausedOnGrantRequestId = item.id;
15928
+ console.log(`[thin-core] monolith Room ${this.options.roomId} turn ${item.id} paused on a grant card`);
15929
+ } else if (resumedRequestId) {
15930
+ console.log(`[thin-core] monolith Room ${this.options.roomId} turn ${resumedRequestId} resumed by grant decision ${item.id}`);
15931
+ }
14892
15932
  const openCornerCall = result?.toolCalls.find((call) => /(?:^|[._:/-])open_corner$/i.test(call.title ?? ""));
14893
15933
  if (openCornerCall) {
14894
15934
  console.log(`[thin-core] monolith Room ${this.options.roomId} tool call: ${openCornerCall.title}`);
14895
15935
  this.options.onCornerOpened?.();
14896
15936
  }
14897
15937
  await this.draftTail;
14898
- const reply = sanitizeAgentReply(result.agentText);
14899
- if (!reply)
14900
- throw new Error("ACP turn produced no durable Room reply");
15938
+ let reply = sanitizeAgentReply(result.agentText);
15939
+ if (!reply) {
15940
+ const explained = await explainEmptyAgentTurn({
15941
+ agentLabel: this.options.config.agentCommand ?? this.options.config.agentBinary,
15942
+ agentEnv: this.agentEnv,
15943
+ sessionId,
15944
+ result
15945
+ });
15946
+ reply = explained.recoveredText ? sanitizeAgentReply(explained.recoveredText) : "";
15947
+ if (!reply)
15948
+ throw new Error(explained.reason);
15949
+ console.warn(`[thin-core] monolith Room ${this.options.roomId} turn ${item.id}: ${explained.reason}`);
15950
+ }
14901
15951
  await api.execute("postRoomMessage", {
14902
15952
  roomId: this.options.roomId,
14903
15953
  requestId: item.id,
@@ -14926,7 +15976,8 @@ ${transcript}` : "",
14926
15976
  roomId: this.options.roomId,
14927
15977
  requestId: item.id,
14928
15978
  status: "failed",
14929
- generationId: `${this.agent.publicKey}:${this.options.roomId}`
15979
+ generationId: `${this.agent.publicKey}:${this.options.roomId}`,
15980
+ reason: distillTurnFailureReason(error)
14930
15981
  });
14931
15982
  throw error;
14932
15983
  } finally {
@@ -14965,7 +16016,7 @@ ${transcript}` : "",
14965
16016
  for (const item of inbox.items) {
14966
16017
  if (!inboxItemTriggersTurn(item, this.agent.publicKey))
14967
16018
  continue;
14968
- if (!isScheduledPrompt(item, this.agent.publicKey)) {
16019
+ if (!isScheduledPrompt(item, this.agent.publicKey) && !isGrantDecisionLine(item, this.agent.publicKey)) {
14969
16020
  const authority = await api.execute("getRoomAuthority", {
14970
16021
  roomId,
14971
16022
  principalId: item.authorId
@@ -14995,6 +16046,7 @@ ${transcript}` : "",
14995
16046
  }
14996
16047
  } finally {
14997
16048
  clearInterval(heartbeat);
16049
+ this.options.grantRunner?.unregister(roomId);
14998
16050
  if (this.activeTurn?.phase === "prompting" && this.client && this.sessionId) {
14999
16051
  this.client.sessionCancel(this.sessionId);
15000
16052
  }
@@ -15004,20 +16056,10 @@ ${transcript}` : "",
15004
16056
  }
15005
16057
  }
15006
16058
  };
15007
- function roomMessagePrompt(author, body, attachments) {
16059
+ function roomMessagePrompt(author, body, attachments, delivered) {
15008
16060
  const message = body.trim() || "(shared attachments)";
15009
16061
  const rendered = author ? `${author}: ${message}` : message;
15010
- if (!attachments.length)
15011
- return rendered;
15012
- return [
15013
- rendered,
15014
- "Attachments available to this turn (use the capability URL when the task requires the file):",
15015
- ...attachments.map((attachment) => {
15016
- const kind = attachment.mimeType?.startsWith("image/") ? "image" : "file";
15017
- const metadata = [attachment.mimeType, attachment.size ? `${attachment.size} bytes` : ""].filter(Boolean).join(", ");
15018
- return `- ${kind}: ${attachment.name ?? "attachment"}${metadata ? ` (${metadata})` : ""}: ${attachment.url}`;
15019
- })
15020
- ].join("\n");
16062
+ return [rendered, ...attachmentPromptLines(attachments, delivered)].join("\n");
15021
16063
  }
15022
16064
  async function wait2(ms, signal) {
15023
16065
  if (signal?.aborted)
@@ -15384,7 +16426,7 @@ async function mapWithConcurrency(values, limit, visit) {
15384
16426
  }
15385
16427
  }));
15386
16428
  }
15387
- var execFileAsync3 = promisify3(execFile3);
16429
+ var execFileAsync3 = promisify3(execFile4);
15388
16430
  var RoomRuntimeCoordinator = class {
15389
16431
  configPath;
15390
16432
  baseConfig;
@@ -15404,6 +16446,9 @@ var RoomRuntimeCoordinator = class {
15404
16446
  workspaceRemovalConfirmations = 0;
15405
16447
  roomRemovalConfirmations = /* @__PURE__ */ new Map();
15406
16448
  confirmationPending = false;
16449
+ /** One command-grant runner per daemon; Rooms and corners register their checkouts on it. */
16450
+ grantRunner;
16451
+ grantRunnerServer;
15407
16452
  constructor(runtime, configPath, baseConfig, options) {
15408
16453
  this.configPath = configPath;
15409
16454
  this.baseConfig = baseConfig;
@@ -15413,6 +16458,11 @@ var RoomRuntimeCoordinator = class {
15413
16458
  this.runtime = runtime;
15414
16459
  this.agent = runtimeIdentity(runtime.agent);
15415
16460
  this.now = options.now ?? Date.now;
16461
+ this.grantRunner = new GrantCommandRunner({
16462
+ api: options.daemonApi,
16463
+ agentId: this.agent.publicKey
16464
+ });
16465
+ this.grantRunnerServer = new GrantRunnerServer(this.grantRunner);
15416
16466
  this.watchdogStaleMs = options.watchdogStaleMs ?? DEFAULT_ROOM_WATCHDOG_STALE_MS;
15417
16467
  this.reconcileHeartbeatMs = options.reconcileHeartbeatMs ?? DEFAULT_RECONCILE_HEARTBEAT_MS;
15418
16468
  this.drainDeadlineMs = options.drainDeadlineMs ?? DEFAULT_DRAIN_DEADLINE_MS;
@@ -15439,7 +16489,15 @@ var RoomRuntimeCoordinator = class {
15439
16489
  return this.reconcileHeartbeatMs;
15440
16490
  }
15441
16491
  isWorkspaceIdle() {
15442
- return [...this.running.values()].every((room) => !room.body.isBusy());
16492
+ return this.activeTurnCount() === 0;
16493
+ }
16494
+ /** Turns executing right now. Serving a Room or corner with no turn in flight is idle. */
16495
+ activeTurnCount() {
16496
+ let count = 0;
16497
+ for (const room of this.running.values())
16498
+ if (room.body.isBusy())
16499
+ count += 1;
16500
+ return count;
15443
16501
  }
15444
16502
  quiesceForUpdateIfIdle() {
15445
16503
  if (!this.isWorkspaceIdle())
@@ -15535,13 +16593,13 @@ var RoomRuntimeCoordinator = class {
15535
16593
  return this.runtime.rooms.find((room) => room.channelId === roomId);
15536
16594
  }
15537
16595
  roomRoot(roomId) {
15538
- return this.roomRecord(roomId)?.root ?? resolve10(dirname4(this.configPath), "rooms", roomId);
16596
+ return this.roomRecord(roomId)?.root ?? resolve15(dirname6(this.configPath), "rooms", roomId);
15539
16597
  }
15540
16598
  roomAgentHomeRoot(workspaceRoot, required = false) {
15541
16599
  const flag = process.env.BUZZY_BODY_ROOM_HOME;
15542
16600
  if (!required && flag === "0")
15543
16601
  return void 0;
15544
- const home = resolve10(workspaceRoot, "agent-home");
16602
+ const home = resolve15(workspaceRoot, "agent-home");
15545
16603
  if (!required && flag !== "1" && !existsSync4(home) && existsSync4(workspaceRoot))
15546
16604
  return void 0;
15547
16605
  try {
@@ -15558,19 +16616,30 @@ var RoomRuntimeCoordinator = class {
15558
16616
  return {
15559
16617
  ...this.baseConfig,
15560
16618
  workspaceRoot,
15561
- agentPrivateRoot: resolve10(workspaceRoot, "agent-private"),
15562
- agentMemoryRoot: resolve10(dirname4(this.configPath), "memory"),
16619
+ agentPrivateRoot: resolve15(workspaceRoot, "agent-private"),
16620
+ agentMemoryRoot: resolve15(dirname6(this.configPath), "memory"),
16621
+ openRouterRoutingCacheDir: openRouterRoutingCacheDir(dirname6(this.configPath)),
15563
16622
  ...agentHomeRoot ? { agentHomeRoot } : {}
15564
16623
  };
15565
16624
  }
16625
+ /** The loopback door for run_granted_command; started once, on first use. */
16626
+ grantRunnerEndpoint() {
16627
+ return this.grantRunnerServer.start().catch((error) => {
16628
+ console.error("[thin-core] grant runner unavailable; run_granted_command is off:", error);
16629
+ return void 0;
16630
+ });
16631
+ }
15566
16632
  async startRoom(roomId) {
15567
16633
  const controller = new AbortController();
15568
16634
  const cwd = await this.materializeRoomCheckout(roomId);
16635
+ const grantRunnerEndpoint = await this.grantRunnerEndpoint();
15569
16636
  const startedAt = this.now();
15570
16637
  const loop = new MonolithRoomTurnLoop({
15571
16638
  roomId,
15572
16639
  workspaceId: this.runtime.communityId,
15573
16640
  cwd,
16641
+ grantRunner: this.grantRunner,
16642
+ ...grantRunnerEndpoint ? { grantRunnerEndpoint } : {},
15574
16643
  runtime: this.runtime,
15575
16644
  config: this.roomConfig(roomId),
15576
16645
  api: this.options.daemonApi,
@@ -15615,12 +16684,12 @@ var RoomRuntimeCoordinator = class {
15615
16684
  return this.roomRoot(roomId);
15616
16685
  const remote = roomCheckoutRemote(repository.remote);
15617
16686
  const targetBranch = repository.targetBranch || "main";
15618
- const checkoutId = createHash("sha256").update(`${remote}\0${targetBranch}`).digest("hex").slice(0, 24);
15619
- const path = resolve10(this.runtime.supervisorRoot, "beeline", "room-checkouts", checkoutId);
15620
- await mkdir5(dirname4(path), { recursive: true, mode: 448 });
16687
+ const checkoutId = createHash2("sha256").update(`${remote}\0${targetBranch}`).digest("hex").slice(0, 24);
16688
+ const path = resolve15(this.runtime.supervisorRoot, "beeline", "room-checkouts", checkoutId);
16689
+ await mkdir8(dirname6(path), { recursive: true, mode: 448 });
15621
16690
  const token = remote.startsWith("https://github.com/") ? await this.options.daemonApi.execute("getRoomGitHubToken", { roomId }) : void 0;
15622
16691
  const env = token ? githubGitEnv(token.token) : process.env;
15623
- if (!existsSync4(resolve10(path, ".git"))) {
16692
+ if (!existsSync4(resolve15(path, ".git"))) {
15624
16693
  await execFileAsync3("git", ["clone", "--no-checkout", remote, path], {
15625
16694
  env,
15626
16695
  maxBuffer: 4 * 1024 * 1024
@@ -15682,9 +16751,12 @@ var RoomRuntimeCoordinator = class {
15682
16751
  });
15683
16752
  }
15684
16753
  const controller = new AbortController();
16754
+ const grantRunnerEndpoint = await this.grantRunnerEndpoint();
15685
16755
  const startedAt = this.now();
15686
16756
  const loop = new MonolithCornerTurnLoop({
15687
16757
  cornerId: corner.cornerId,
16758
+ grantRunner: this.grantRunner,
16759
+ ...grantRunnerEndpoint ? { grantRunnerEndpoint } : {},
15688
16760
  parentRoomId: corner.parentRoomId,
15689
16761
  workspaceId: this.runtime.communityId,
15690
16762
  objective,
@@ -15737,13 +16809,13 @@ var RoomRuntimeCoordinator = class {
15737
16809
  }
15738
16810
  async materializeCornerWorktree(input) {
15739
16811
  const remote = githubHttpsRemote(input.remote);
15740
- const repositoryHash = createHash("sha256").update(remote).digest("hex").slice(0, 24);
15741
- const gitCommonDir = resolve10(this.runtime.supervisorRoot, "beeline", "repositories", `${repositoryHash}.git`);
15742
- const path = resolve10(this.runtime.supervisorRoot, "beeline", "corners", input.cornerId);
15743
- await mkdir5(dirname4(gitCommonDir), { recursive: true, mode: 448 });
15744
- await mkdir5(dirname4(path), { recursive: true, mode: 448 });
16812
+ const repositoryHash = createHash2("sha256").update(remote).digest("hex").slice(0, 24);
16813
+ const gitCommonDir = resolve15(this.runtime.supervisorRoot, "beeline", "repositories", `${repositoryHash}.git`);
16814
+ const path = resolve15(this.runtime.supervisorRoot, "beeline", "corners", input.cornerId);
16815
+ await mkdir8(dirname6(gitCommonDir), { recursive: true, mode: 448 });
16816
+ await mkdir8(dirname6(path), { recursive: true, mode: 448 });
15745
16817
  const authEnv = githubGitEnv(input.token);
15746
- if (!existsSync4(resolve10(gitCommonDir, "HEAD"))) {
16818
+ if (!existsSync4(resolve15(gitCommonDir, "HEAD"))) {
15747
16819
  await execFileAsync3("git", ["clone", "--bare", remote, gitCommonDir], {
15748
16820
  env: authEnv,
15749
16821
  maxBuffer: 4 * 1024 * 1024
@@ -15756,7 +16828,7 @@ var RoomRuntimeCoordinator = class {
15756
16828
  "origin",
15757
16829
  `+refs/heads/${input.targetBranch}:refs/remotes/origin/${input.targetBranch}`
15758
16830
  ], { env: authEnv, maxBuffer: 4 * 1024 * 1024 });
15759
- if (!existsSync4(resolve10(path, ".git"))) {
16831
+ if (!existsSync4(resolve15(path, ".git"))) {
15760
16832
  await rm3(path, { recursive: true, force: true });
15761
16833
  await execFileAsync3("git", [
15762
16834
  `--git-dir=${gitCommonDir}`,
@@ -15793,7 +16865,7 @@ var RoomRuntimeCoordinator = class {
15793
16865
  `${this.agent.publicKey.slice(0, 16)}@users.noreply.github.com`
15794
16866
  ]);
15795
16867
  const top = await execFileAsync3("git", ["-C", path, "rev-parse", "--show-toplevel"]);
15796
- if (resolve10(top.stdout.trim()) !== resolve10(path)) {
16868
+ if (resolve15(top.stdout.trim()) !== resolve15(path)) {
15797
16869
  throw new Error(`corner worktree escaped its isolated root: ${top.stdout.trim()}`);
15798
16870
  }
15799
16871
  return { path, gitCommonDir };
@@ -15860,6 +16932,7 @@ var RoomRuntimeCoordinator = class {
15860
16932
  await Promise.allSettled(rooms.map((room) => room.body.forceRecoverRoom()));
15861
16933
  await drained;
15862
16934
  }
16935
+ await this.grantRunnerServer.close();
15863
16936
  await this.scheduler.dispose();
15864
16937
  }
15865
16938
  };
@@ -15918,6 +16991,9 @@ var ThinDaemonCore = class {
15918
16991
  isWorkspaceIdle() {
15919
16992
  return this.roomRuntime.isWorkspaceIdle();
15920
16993
  }
16994
+ activeTurnCount() {
16995
+ return this.roomRuntime.activeTurnCount();
16996
+ }
15921
16997
  quiesceForUpdateIfIdle() {
15922
16998
  return this.roomRuntime.quiesceForUpdateIfIdle();
15923
16999
  }
@@ -15964,7 +17040,7 @@ var ThinDaemonCore = class {
15964
17040
  };
15965
17041
 
15966
17042
  // apps/body/dist/daemon-api-client.js
15967
- import { resolve as resolve11 } from "node:path";
17043
+ import { resolve as resolve16 } from "node:path";
15968
17044
  var DaemonApiError = class extends Error {
15969
17045
  status;
15970
17046
  retryable;
@@ -16023,8 +17099,8 @@ var DaemonApiClient = class {
16023
17099
  };
16024
17100
  async function activateDaemonTransport(path, fetchImpl = fetch) {
16025
17101
  const runtime = await readRuntimeRecord(path);
16026
- const expectedPath = resolve11(runtimeDirectory(runtime.supervisorRoot, runtime.agent.publicKey), "runtime.json");
16027
- if (resolve11(path) !== expectedPath) {
17102
+ const expectedPath = resolve16(runtimeDirectory(runtime.supervisorRoot, runtime.agent.publicKey), "runtime.json");
17103
+ if (resolve16(path) !== expectedPath) {
16028
17104
  throw new Error(`refusing daemon token exchange outside canonical runtime path: ${path}`);
16029
17105
  }
16030
17106
  const transport = runtime.transport;
@@ -16063,17 +17139,17 @@ async function activateDaemonTransport(path, fetchImpl = fetch) {
16063
17139
  }
16064
17140
 
16065
17141
  // apps/body/dist/start-command.js
16066
- import { dirname as dirname6 } from "node:path";
17142
+ import { dirname as dirname8 } from "node:path";
16067
17143
  var import_picocolors = __toESM(require_picocolors(), 1);
16068
17144
 
16069
17145
  // apps/body/dist/systemd.js
16070
- import { execFile as execFile4 } from "node:child_process";
16071
- import { mkdir as mkdir6, readFile as readFile2, writeFile as writeFile3 } from "node:fs/promises";
16072
- import { homedir as homedir7 } from "node:os";
16073
- import { dirname as dirname5, resolve as resolve12 } from "node:path";
17146
+ import { execFile as execFile5 } from "node:child_process";
17147
+ import { mkdir as mkdir9, readFile as readFile7, writeFile as writeFile7 } from "node:fs/promises";
17148
+ import { homedir as homedir8 } from "node:os";
17149
+ import { dirname as dirname7, resolve as resolve17 } from "node:path";
16074
17150
  import { setTimeout as sleep } from "node:timers/promises";
16075
17151
  import { promisify as promisify4 } from "node:util";
16076
- var execFileAsync4 = promisify4(execFile4);
17152
+ var execFileAsync4 = promisify4(execFile5);
16077
17153
  var DELIBERATE_REMOVAL_EXIT_STATUS = 78;
16078
17154
  var DAEMON_DISTRESS_EXIT_STATUS = 77;
16079
17155
  var UNKNOWN_AGENT_EXIT_STATUS = 79;
@@ -16111,10 +17187,10 @@ WantedBy=default.target
16111
17187
  `;
16112
17188
  }
16113
17189
  function isCanonicalInstalledLauncher(env = process.env, invocationPath = process.argv[1]) {
16114
- const home = env.HOME?.trim() || homedir7();
16115
- const expectedLibDir = resolve12(home, ".local", "lib", "beeline");
17190
+ const home = env.HOME?.trim() || homedir8();
17191
+ const expectedLibDir = resolve17(home, ".local", "lib", "beeline");
16116
17192
  const expectedPrefix = `${expectedLibDir}/`;
16117
- return resolve12(env.BEELINE_LIB_DIR?.trim() || "/") === expectedLibDir && Boolean(invocationPath) && resolve12(invocationPath).startsWith(expectedPrefix);
17193
+ return resolve17(env.BEELINE_LIB_DIR?.trim() || "/") === expectedLibDir && Boolean(invocationPath) && resolve17(invocationPath).startsWith(expectedPrefix);
16118
17194
  }
16119
17195
  function assertCanonicalInstalledLauncher(env, invocationPath) {
16120
17196
  if (isCanonicalInstalledLauncher(env, invocationPath))
@@ -16122,8 +17198,8 @@ function assertCanonicalInstalledLauncher(env, invocationPath) {
16122
17198
  throw new Error("refusing to modify the shared Beeline systemd unit outside the canonical ~/.local/bin/beeline launcher");
16123
17199
  }
16124
17200
  function systemdUserUnitPath(env = process.env) {
16125
- const configRoot = env.XDG_CONFIG_HOME?.trim() || resolve12(homedir7(), ".config");
16126
- return resolve12(configRoot, "systemd", "user", SYSTEMD_UNIT_NAME);
17201
+ const configRoot = env.XDG_CONFIG_HOME?.trim() || resolve17(homedir8(), ".config");
17202
+ return resolve17(configRoot, "systemd", "user", SYSTEMD_UNIT_NAME);
16127
17203
  }
16128
17204
  var runSystemctl = async (args) => {
16129
17205
  const result = await execFileAsync4("systemctl", ["--user", ...args], {
@@ -16139,10 +17215,10 @@ async function installAgentService(publicKey, options = {}) {
16139
17215
  assertCanonicalInstalledLauncher(env, options.invocationPath);
16140
17216
  const path = systemdUserUnitPath(env);
16141
17217
  const content = agentServiceUnit();
16142
- const existing = await readFile2(path, "utf8").catch(() => "");
17218
+ const existing = await readFile7(path, "utf8").catch(() => "");
16143
17219
  if (existing !== content) {
16144
- await mkdir6(dirname5(path), { recursive: true, mode: 448 });
16145
- await writeFile3(path, content, { mode: 384 });
17220
+ await mkdir9(dirname7(path), { recursive: true, mode: 448 });
17221
+ await writeFile7(path, content, { mode: 384 });
16146
17222
  }
16147
17223
  const run2 = options.run ?? runSystemctl;
16148
17224
  await run2(["daemon-reload"]);
@@ -16280,7 +17356,7 @@ async function runStartCommand(args, interactiveUi) {
16280
17356
  continue;
16281
17357
  }
16282
17358
  const spinnerHandle = spinner();
16283
- spinnerHandle.start(`Starting ${dirname6(path)}\u2026`);
17359
+ spinnerHandle.start(`Starting ${dirname8(path)}\u2026`);
16284
17360
  try {
16285
17361
  await startRuntime(path, spinnerHandle);
16286
17362
  spinnerHandle.stop(import_picocolors.default.green("Started."));
@@ -16296,9 +17372,9 @@ async function runStartCommand(args, interactiveUi) {
16296
17372
 
16297
17373
  // apps/body/dist/connect-command.js
16298
17374
  import { spawn as spawn5 } from "node:child_process";
16299
- import { createHash as createHash3 } from "node:crypto";
16300
- import { chmod as chmod4, mkdir as mkdir9, readFile as readFile5, unlink as unlink2, writeFile as writeFile6 } from "node:fs/promises";
16301
- import { dirname as dirname9, resolve as resolve15 } from "node:path";
17375
+ import { createHash as createHash4 } from "node:crypto";
17376
+ import { chmod as chmod4, mkdir as mkdir11, readFile as readFile9, unlink as unlink2, writeFile as writeFile9 } from "node:fs/promises";
17377
+ import { dirname as dirname10, resolve as resolve19 } from "node:path";
16302
17378
  import { stdin as stdin3, stdout as stdout4 } from "node:process";
16303
17379
 
16304
17380
  // packages/api-contract/dist/agent-pairing-code.js
@@ -16608,63 +17684,6 @@ async function pairDevice(grant, options = {}) {
16608
17684
  return { runtime: activated.runtime, configPath: staged.configPath, pid };
16609
17685
  }
16610
17686
 
16611
- // apps/body/dist/provider-key-store.js
16612
- import { chmod as chmod2, mkdir as mkdir7, readFile as readFile3, writeFile as writeFile4 } from "node:fs/promises";
16613
- import { homedir as homedir8 } from "node:os";
16614
- import { dirname as dirname7, resolve as resolve13 } from "node:path";
16615
- var PROVIDER_KEY_ENV_VARS = {
16616
- openrouter: "OPENROUTER_API_KEY",
16617
- openai: "OPENAI_API_KEY",
16618
- anthropic: "ANTHROPIC_API_KEY",
16619
- google: "GOOGLE_API_KEY",
16620
- xai: "XAI_API_KEY"
16621
- };
16622
- var GOOGLE_ENV_ALIAS = "GEMINI_API_KEY";
16623
- function providerKeyStorePath(env = process.env) {
16624
- const configRoot = env.XDG_CONFIG_HOME?.trim() || resolve13(homedir8(), ".config");
16625
- return resolve13(configRoot, "beeline", "providers.json");
16626
- }
16627
- async function readProviderKeyStore(env = process.env) {
16628
- const path = providerKeyStorePath(env);
16629
- const raw = await readFile3(path, "utf8").catch(() => void 0);
16630
- if (!raw)
16631
- return {};
16632
- try {
16633
- const parsed = JSON.parse(raw);
16634
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
16635
- return {};
16636
- const entries = Object.entries(parsed).filter((entry) => entry[0] in PROVIDER_KEY_ENV_VARS && typeof entry[1] === "string" && entry[1].length > 0);
16637
- return Object.fromEntries(entries);
16638
- } catch {
16639
- return {};
16640
- }
16641
- }
16642
- async function readSavedProviderKey(provider, env = process.env) {
16643
- return (await readProviderKeyStore(env))[provider];
16644
- }
16645
- async function saveProviderKey(provider, key, env = process.env) {
16646
- const path = providerKeyStorePath(env);
16647
- const store = { ...await readProviderKeyStore(env), [provider]: key };
16648
- await mkdir7(dirname7(path), { recursive: true, mode: 448 });
16649
- await writeFile4(path, `${JSON.stringify(store, null, 2)}
16650
- `, { mode: 384 });
16651
- await chmod2(path, 384);
16652
- }
16653
- function providerKeyFromEnvironment(provider, env = process.env) {
16654
- const primary = env[PROVIDER_KEY_ENV_VARS[provider]]?.trim();
16655
- if (primary)
16656
- return primary;
16657
- if (provider === "google")
16658
- return env[GOOGLE_ENV_ALIAS]?.trim() || void 0;
16659
- return void 0;
16660
- }
16661
- function maskProviderKey(key) {
16662
- const trimmed = key.trim();
16663
- if (trimmed.length <= 9)
16664
- return "\u2026";
16665
- return `${trimmed.slice(0, 6)}\u2026${trimmed.slice(-3)}`;
16666
- }
16667
-
16668
17687
  // apps/body/dist/connect-command.js
16669
17688
  init_self_update();
16670
17689
  init_self_update_manifest();
@@ -16881,7 +17900,7 @@ function requestConnectGrant(baseUrl, pairingCode, selection, fetchImpl) {
16881
17900
  const normalizedPairingCode = normalizeAgentPairingCode(pairingCode);
16882
17901
  if (!normalizedPairingCode)
16883
17902
  throw new Error("invalid pairing code");
16884
- const avatarSeed = createHash3("sha256").update(normalizedPairingCode.toUpperCase()).digest("hex").slice(0, 32);
17903
+ const avatarSeed = createHash4("sha256").update(normalizedPairingCode.toUpperCase()).digest("hex").slice(0, 32);
16885
17904
  return jsonRequest(`${baseUrl}/auth/agent/connect`, {
16886
17905
  pairing_code: normalizedPairingCode,
16887
17906
  harness: selection.harness,
@@ -16906,7 +17925,7 @@ async function installCurrentRelease(fetchImpl) {
16906
17925
  });
16907
17926
  await activateRelease(layout, releaseId);
16908
17927
  return {
16909
- binary: resolve15(layout.binDir, "beeline"),
17928
+ binary: resolve19(layout.binDir, "beeline"),
16910
17929
  version: published.version ?? releaseId
16911
17930
  };
16912
17931
  }
@@ -16929,8 +17948,8 @@ function providerEnvironment(selection) {
16929
17948
  };
16930
17949
  }
16931
17950
  async function writePrivateJson(path, value) {
16932
- await mkdir9(dirname9(path), { recursive: true, mode: 448 });
16933
- await writeFile6(path, `${JSON.stringify(value, null, 2)}
17951
+ await mkdir11(dirname10(path), { recursive: true, mode: 448 });
17952
+ await writeFile9(path, `${JSON.stringify(value, null, 2)}
16934
17953
  `, { mode: 384 });
16935
17954
  await chmod4(path, 384);
16936
17955
  }
@@ -16938,10 +17957,10 @@ async function writeProviderEnv(selection, agentPubkey) {
16938
17957
  const values = providerEnvironment(selection);
16939
17958
  if (Object.keys(values).length === 0)
16940
17959
  return void 0;
16941
- const path = resolve15(defaultSupervisorRoot(process.env), "beeline", "connect", `${agentPubkey}.env`);
16942
- await mkdir9(dirname9(path), { recursive: true, mode: 448 });
17960
+ const path = resolve19(defaultSupervisorRoot(process.env), "beeline", "connect", `${agentPubkey}.env`);
17961
+ await mkdir11(dirname10(path), { recursive: true, mode: 448 });
16943
17962
  const contents = Object.entries(values).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join("\n");
16944
- await writeFile6(path, `${contents}
17963
+ await writeFile9(path, `${contents}
16945
17964
  `, { mode: 384 });
16946
17965
  await chmod4(path, 384);
16947
17966
  return path;
@@ -16995,7 +18014,7 @@ async function runConnectCommand(code, options = {}) {
16995
18014
  const grant = await brassSpinner("Connecting to your Beeline Workspace\u2026", () => requestConnectGrant(baseUrl, pairingCode, selection, fetchImpl), (connectedGrant) => `Connected to ${connectedGrant.workspace_name}`);
16996
18015
  const installedRelease = await brassSpinner("Installing the Beeline daemon\u2026", () => installCurrentRelease(fetchImpl), (release) => `Installed Beeline helper ${release.version}`);
16997
18016
  const llmEnvFile = await writeProviderEnv(selection, grant.agent_pubkey);
16998
- const grantPath = resolve15(defaultSupervisorRoot(process.env), "beeline", "connect", `grant-${process.pid}-${Date.now()}.json`);
18017
+ const grantPath = resolve19(defaultSupervisorRoot(process.env), "beeline", "connect", `grant-${process.pid}-${Date.now()}.json`);
16999
18018
  await writePrivateJson(grantPath, {
17000
18019
  agentSecretKey: grant.agent_secret_key,
17001
18020
  bodySecretKey: grant.body_secret_key,
@@ -17046,11 +18065,22 @@ async function runConnectFinishCommand(path) {
17046
18065
  throw new Error("connect-finish may run only from the canonical installed Beeline launcher");
17047
18066
  }
17048
18067
  try {
17049
- const grant = JSON.parse(await readFile5(resolve15(path), "utf8"));
18068
+ const grant = JSON.parse(await readFile9(resolve19(path), "utf8"));
17050
18069
  if (!isDevicePairingGrant(grant))
17051
18070
  throw new Error("device connection grant is invalid");
17052
- await completeDevicePairing(grant);
17053
- await unlink2(resolve15(path));
18071
+ const connected = await completeDevicePairing(grant);
18072
+ await unlink2(resolve19(path));
18073
+ const providerEnv = grant.llmEnvFile ? await readFile9(grant.llmEnvFile, "utf8").catch(() => "") : "";
18074
+ const model = openRouterModelId(grant.model, {
18075
+ OPENROUTER_API_KEY: /^OPENROUTER_API_KEY=\S/m.test(providerEnv) ? "set" : ""
18076
+ });
18077
+ if (model) {
18078
+ const decision2 = await resolveOpenRouterRouting({
18079
+ model,
18080
+ cacheDir: openRouterRoutingCacheDir(dirname10(connected.configPath))
18081
+ });
18082
+ console.log(decision2.line);
18083
+ }
17054
18084
  } catch (error) {
17055
18085
  throw new ConnectFailureError(connectPlainFailure(error));
17056
18086
  }
@@ -17064,20 +18094,20 @@ init_self_update_manifest();
17064
18094
  // apps/body/dist/managed-update.js
17065
18095
  init_self_update();
17066
18096
  import { spawn as spawn6 } from "node:child_process";
17067
- import { mkdir as mkdir11, rm as rm5, stat as stat2, writeFile as writeFile8 } from "node:fs/promises";
17068
- import { dirname as dirname11, resolve as resolve17 } from "node:path";
18097
+ import { mkdir as mkdir13, rm as rm5, stat as stat2, writeFile as writeFile11 } from "node:fs/promises";
18098
+ import { dirname as dirname12, resolve as resolve21 } from "node:path";
17069
18099
 
17070
18100
  // apps/body/dist/update-rollback-alert.js
17071
- import { mkdir as mkdir10, readFile as readFile6, rename as rename4, writeFile as writeFile7 } from "node:fs/promises";
17072
- import { dirname as dirname10, resolve as resolve16 } from "node:path";
18101
+ import { mkdir as mkdir12, readFile as readFile10, rename as rename4, writeFile as writeFile10 } from "node:fs/promises";
18102
+ import { dirname as dirname11, resolve as resolve20 } from "node:path";
17073
18103
  function updateRollbackAlertPath(runtimeDir) {
17074
- return resolve16(runtimeDir, "update-rollback-alert.json");
18104
+ return resolve20(runtimeDir, "update-rollback-alert.json");
17075
18105
  }
17076
18106
  async function writeAlert(runtimeDir, alert) {
17077
18107
  const path = updateRollbackAlertPath(runtimeDir);
17078
18108
  const staged = `${path}.${process.pid}.tmp`;
17079
- await mkdir10(dirname10(path), { recursive: true });
17080
- await writeFile7(staged, `${JSON.stringify(alert, null, 2)}
18109
+ await mkdir12(dirname11(path), { recursive: true });
18110
+ await writeFile10(staged, `${JSON.stringify(alert, null, 2)}
17081
18111
  `, { mode: 384 });
17082
18112
  await rename4(staged, path);
17083
18113
  }
@@ -17089,7 +18119,7 @@ async function queueUpdateRollbackAlert(runtimeDir, releaseId, now2 = Date.now()
17089
18119
  }
17090
18120
  async function readUpdateRollbackAlert(runtimeDir) {
17091
18121
  try {
17092
- const value = JSON.parse(await readFile6(updateRollbackAlertPath(runtimeDir), "utf8"));
18122
+ const value = JSON.parse(await readFile10(updateRollbackAlertPath(runtimeDir), "utf8"));
17093
18123
  if (value.version !== 1 || typeof value.releaseId !== "string")
17094
18124
  return void 0;
17095
18125
  return value;
@@ -17114,13 +18144,13 @@ var LOCK_STALE_MS = UPDATE_WORKER_DEADLINE_MS + 5 * 6e4;
17114
18144
  var DEFAULT_UPDATE_INITIAL_DELAY_MS = 0;
17115
18145
  async function withInstallLock(layout, work, options = {}) {
17116
18146
  const now2 = options.now ?? Date.now;
17117
- const lock = resolve17(layout.releasesRoot, ".state", "install.lock");
18147
+ const lock = resolve21(layout.releasesRoot, ".state", "install.lock");
17118
18148
  const deadline = now2() + (options.waitMs ?? 1e4);
17119
- await mkdir11(dirname11(lock), { recursive: true });
18149
+ await mkdir13(dirname12(lock), { recursive: true });
17120
18150
  for (; ; ) {
17121
18151
  try {
17122
- await mkdir11(lock);
17123
- await writeFile8(resolve17(lock, "owner"), `${process.pid}
18152
+ await mkdir13(lock);
18153
+ await writeFile11(resolve21(lock, "owner"), `${process.pid}
17124
18154
  ${now2()}
17125
18155
  `, "utf8");
17126
18156
  break;
@@ -17157,7 +18187,6 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
17157
18187
  #requested = false;
17158
18188
  #restartRequest;
17159
18189
  #stagedReleaseId;
17160
- #drainDeadlineLogged = false;
17161
18190
  constructor(options) {
17162
18191
  this.#layout = options.layout;
17163
18192
  this.#loadedRelease = options.loadedRelease;
@@ -17244,7 +18273,7 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
17244
18273
  if (!attempt || attempt.releaseId !== desiredRelease || attempt.status !== "pending") {
17245
18274
  const from = await readInstalledBundleIdentity({
17246
18275
  ...this.#layout,
17247
- libDir: resolve17(this.#layout.releasesRoot, this.#loadedRelease)
18276
+ libDir: resolve21(this.#layout.releasesRoot, this.#loadedRelease)
17248
18277
  }).catch(() => void 0) ?? {};
17249
18278
  const to = await readInstalledBundleIdentity(this.#layout).catch(() => void 0) ?? {};
17250
18279
  const record2 = {
@@ -17281,11 +18310,13 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
17281
18310
  }
17282
18311
  /**
17283
18312
  * Resolve one handoff tick against the daemon's authoritative turn registry.
17284
- * A staged release stays inert while accepted work is running.
17285
- * `quiesceIfIdle` closes intake in the same synchronous transition that
17286
- * proves idle, so a new turn cannot race activation. Expiring the bounded
17287
- * drain window records delayed convergence; it never turns a routine update
17288
- * into an ACP cancellation or a failed Room receipt.
18313
+ * Busy means a turn is executing right now; serving Rooms and corners with
18314
+ * no turn in flight is idle. `quiesceIfIdle` closes intake in the same
18315
+ * synchronous transition that proves idle, so a new turn cannot race
18316
+ * activation. A staged release stays inert while a turn runs — until the
18317
+ * absolute drain deadline, when the restart is forced (`forced: true`) and
18318
+ * the caller cancels the running turn; the convergence contract outranks a
18319
+ * stuck turn. `ManagedUpdateDrain` enforces that deadline with a timer.
17289
18320
  */
17290
18321
  async restartRequest(quiesceIfIdle) {
17291
18322
  const activeDrift = await this.check();
@@ -17294,12 +18325,11 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
17294
18325
  const request = this.#restartRequest;
17295
18326
  if (!request)
17296
18327
  throw new Error("update drift was detected without an in-memory restart request");
18328
+ let forced = false;
17297
18329
  if (!quiesceIfIdle()) {
17298
- if (this.#now() >= request.drainDeadlineAt && !this.#drainDeadlineLogged) {
17299
- console.log(`[thin-core] update drain window elapsed with active work; keeping the staged release ${request.desiredRelease} inert instead of interrupting the Room turn`);
17300
- this.#drainDeadlineLogged = true;
17301
- }
17302
- return { kind: "waiting", request };
18330
+ if (this.#now() < request.drainDeadlineAt)
18331
+ return { kind: "waiting", request };
18332
+ forced = true;
17303
18333
  }
17304
18334
  if (this.#stagedReleaseId) {
17305
18335
  const stagedReleaseId = this.#stagedReleaseId;
@@ -17319,7 +18349,7 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
17319
18349
  this.#restartRequest = void 0;
17320
18350
  await this.#journalDrift(stagedReleaseId);
17321
18351
  }
17322
- return { kind: "restart", request: this.#restartRequest ?? request };
18352
+ return { kind: "restart", request: this.#restartRequest ?? request, forced };
17323
18353
  }
17324
18354
  };
17325
18355
  async function coordinateManagedUpdateHandoff(update, quiesceIfIdle, restart, waiting = async () => void 0) {
@@ -17330,9 +18360,88 @@ async function coordinateManagedUpdateHandoff(update, quiesceIfIdle, restart, wa
17330
18360
  await waiting(next.request);
17331
18361
  return "waiting-for-idle";
17332
18362
  }
17333
- await restart(next.request);
18363
+ await restart(next.request, next.forced ? "forced" : "drained");
17334
18364
  return "restarting";
17335
18365
  }
18366
+ var UPDATE_DRAIN_WAIT_LOG_INTERVAL_MS = 6e4;
18367
+ var ManagedUpdateDrain = class {
18368
+ #options;
18369
+ #now;
18370
+ #log;
18371
+ #setTimer;
18372
+ #clearTimer;
18373
+ #deadlineTimer;
18374
+ #waitLogTimer;
18375
+ #resolving;
18376
+ #restarting = false;
18377
+ constructor(options) {
18378
+ this.#options = options;
18379
+ this.#now = options.now ?? Date.now;
18380
+ this.#log = options.log ?? ((line) => console.log(line));
18381
+ this.#setTimer = options.setTimer ?? ((callback, ms) => setTimeout(callback, ms));
18382
+ this.#clearTimer = options.clearTimer ?? ((handle) => clearTimeout(handle));
18383
+ }
18384
+ /** Called from every completed core progress tick. */
18385
+ tick() {
18386
+ return this.#resolve();
18387
+ }
18388
+ #resolve() {
18389
+ if (this.#restarting)
18390
+ return Promise.resolve("restarting");
18391
+ if (this.#resolving)
18392
+ return this.#resolving;
18393
+ this.#resolving = this.#step().finally(() => {
18394
+ this.#resolving = void 0;
18395
+ });
18396
+ return this.#resolving;
18397
+ }
18398
+ async #step() {
18399
+ const { update, quiesceIfIdle, activeTurnCount } = this.#options;
18400
+ let armed;
18401
+ const progress = await coordinateManagedUpdateHandoff(update, quiesceIfIdle, async (request, mode) => {
18402
+ this.#disarm();
18403
+ if (mode === "forced") {
18404
+ this.#log(`[thin-core] update restart forced: drain deadline reached with ${activeTurnCount()} active turn(s); cancelling them and restarting onto ${request.desiredRelease}`);
18405
+ }
18406
+ await this.#options.restart(request, mode);
18407
+ this.#restarting = true;
18408
+ }, async (request) => {
18409
+ armed = request;
18410
+ await this.#options.waiting?.(request);
18411
+ });
18412
+ if (armed && !this.#deadlineTimer)
18413
+ this.#arm(armed);
18414
+ return progress;
18415
+ }
18416
+ #arm(request) {
18417
+ this.#logWaiting(request);
18418
+ this.#scheduleWaitLog(request);
18419
+ this.#deadlineTimer = this.#setTimer(() => void this.#resolve().catch((error) => {
18420
+ this.#log(`[thin-core] update restart at the drain deadline failed; retrying on the next tick: ${error instanceof Error ? error.message : String(error)}`);
18421
+ }), Math.max(0, request.drainDeadlineAt - this.#now()));
18422
+ }
18423
+ #scheduleWaitLog(request) {
18424
+ this.#waitLogTimer = this.#setTimer(() => {
18425
+ this.#waitLogTimer = void 0;
18426
+ if (this.#restarting)
18427
+ return;
18428
+ this.#logWaiting(request);
18429
+ this.#scheduleWaitLog(request);
18430
+ }, UPDATE_DRAIN_WAIT_LOG_INTERVAL_MS);
18431
+ }
18432
+ #logWaiting(request) {
18433
+ const minutesLeft = Math.max(0, Math.ceil((request.drainDeadlineAt - this.#now()) / 6e4));
18434
+ this.#log(`[thin-core] update restart waiting: ${this.#options.activeTurnCount()} active turn(s); deadline in ${minutesLeft}m`);
18435
+ }
18436
+ #disarm() {
18437
+ if (this.#deadlineTimer !== void 0)
18438
+ this.#clearTimer(this.#deadlineTimer);
18439
+ if (this.#waitLogTimer !== void 0)
18440
+ this.#clearTimer(this.#waitLogTimer);
18441
+ this.#deadlineTimer = void 0;
18442
+ this.#waitLogTimer = void 0;
18443
+ }
18444
+ };
17336
18445
  function numberEnv(env, name, fallback) {
17337
18446
  const value = Number(env[name]);
17338
18447
  return Number.isFinite(value) && value >= 0 ? value : fallback;
@@ -17451,7 +18560,7 @@ async function proveLoadedReleaseReady(layout, runtimeDir, loadedRelease, option
17451
18560
  });
17452
18561
  if (!accepted)
17453
18562
  return false;
17454
- await writeFile8(resolve17(runtimeDir, "daemon-ready.json"), `${JSON.stringify({
18563
+ await writeFile11(resolve21(runtimeDir, "daemon-ready.json"), `${JSON.stringify({
17455
18564
  readyAt: (options.now ?? Date.now)(),
17456
18565
  loadedRelease,
17457
18566
  functionalProof: options.functionalProof
@@ -17656,16 +18765,16 @@ async function runUpdateCommand(args) {
17656
18765
  init_self_update();
17657
18766
 
17658
18767
  // apps/body/dist/daemon-failure.js
17659
- import { mkdir as mkdir12, readFile as readFile7, rename as rename5, rm as rm6, writeFile as writeFile9 } from "node:fs/promises";
17660
- import { dirname as dirname12, resolve as resolve18 } from "node:path";
18768
+ import { mkdir as mkdir14, readFile as readFile11, rename as rename5, rm as rm6, writeFile as writeFile12 } from "node:fs/promises";
18769
+ import { dirname as dirname13, resolve as resolve22 } from "node:path";
17661
18770
  var DAEMON_FAILURE_LIMIT = 3;
17662
18771
  var DAEMON_FAILURE_WINDOW_MS = 5 * 6e4;
17663
18772
  function daemonFailurePath(runtimeDir) {
17664
- return resolve18(runtimeDir, "daemon-distress.json");
18773
+ return resolve22(runtimeDir, "daemon-distress.json");
17665
18774
  }
17666
18775
  async function readFailureRecord(runtimeDir) {
17667
18776
  try {
17668
- const value = JSON.parse(await readFile7(daemonFailurePath(runtimeDir), "utf8"));
18777
+ const value = JSON.parse(await readFile11(daemonFailurePath(runtimeDir), "utf8"));
17669
18778
  if (value.version !== 1 || !Array.isArray(value.failures) || value.failures.some((failure) => typeof failure !== "number") || typeof value.lastError !== "string") {
17670
18779
  return void 0;
17671
18780
  }
@@ -17677,8 +18786,8 @@ async function readFailureRecord(runtimeDir) {
17677
18786
  async function writeFailureRecord(runtimeDir, record2) {
17678
18787
  const path = daemonFailurePath(runtimeDir);
17679
18788
  const staged = `${path}.${process.pid}.tmp`;
17680
- await mkdir12(dirname12(path), { recursive: true, mode: 448 });
17681
- await writeFile9(staged, `${JSON.stringify(record2, null, 2)}
18789
+ await mkdir14(dirname13(path), { recursive: true, mode: 448 });
18790
+ await writeFile12(staged, `${JSON.stringify(record2, null, 2)}
17682
18791
  `, { mode: 384 });
17683
18792
  await rename5(staged, path);
17684
18793
  }
@@ -17700,9 +18809,9 @@ async function clearDaemonStartFailures(runtimeDir) {
17700
18809
  }
17701
18810
 
17702
18811
  // apps/body/dist/update-functional-probe.js
17703
- import { mkdir as mkdir13, rm as rm7 } from "node:fs/promises";
18812
+ import { mkdir as mkdir15, rm as rm7 } from "node:fs/promises";
17704
18813
  import { homedir as homedir10 } from "node:os";
17705
- import { resolve as resolve19 } from "node:path";
18814
+ import { resolve as resolve23 } from "node:path";
17706
18815
  var UPDATE_PROBE_SESSION_TIMEOUT_MS = 1e4;
17707
18816
  var UPDATE_PROBE_SESSION_OPEN_TIMEOUT_MS = 2e4;
17708
18817
  var UPDATE_PROBE_TURN_TIMEOUT_MS = 45e3;
@@ -17724,11 +18833,11 @@ async function runUpdateFunctionalProbe(input) {
17724
18833
  if (input.sandboxRequired && !input.config.bwrapPath) {
17725
18834
  throw new UpdateFunctionalProbeError("sandbox-unavailable", "the configured bubblewrap boundary did not pass its startup self-test");
17726
18835
  }
17727
- const root = resolve19(input.runtimeDir, "update-functional-probe");
17728
- const cwd = resolve19(root, "checkout");
17729
- const homeRoot = resolve19(root, "agent-home");
18836
+ const root = resolve23(input.runtimeDir, "update-functional-probe");
18837
+ const cwd = resolve23(root, "checkout");
18838
+ const homeRoot = resolve23(root, "agent-home");
17730
18839
  await rm7(root, { recursive: true, force: true });
17731
- await mkdir13(cwd, { recursive: true, mode: 448 });
18840
+ await mkdir15(cwd, { recursive: true, mode: 448 });
17732
18841
  let client;
17733
18842
  try {
17734
18843
  const agentEnv = {
@@ -17738,7 +18847,8 @@ async function runUpdateFunctionalProbe(input) {
17738
18847
  operatorHome: input.config.operatorHome ?? homedir10(),
17739
18848
  sharedSkills: input.config.sharedSkills ?? [],
17740
18849
  skillReleaseId: input.releaseId,
17741
- failClosed: true
18850
+ failClosed: true,
18851
+ ...openRouterRoutingInput({ ...input.config, openRouterRoutingCacheDir: openRouterRoutingCacheDir(input.runtimeDir) }, input.config.modelSelection)
17742
18852
  })
17743
18853
  };
17744
18854
  const selectedAgent = {
@@ -17750,11 +18860,12 @@ async function runUpdateFunctionalProbe(input) {
17750
18860
  command,
17751
18861
  args: agentArgsWithModelSelection(selectedAgent, input.config.modelSelection)
17752
18862
  };
18863
+ let modelAnswer = {};
17753
18864
  if (input.config.bwrapPath) {
17754
18865
  const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
17755
18866
  const operatorHome = input.config.operatorHome ?? homedir10();
17756
18867
  const homeStateDirs = harnessHomeStateDirs(command, agentEnv.HOME ?? operatorHome);
17757
- await Promise.all(homeStateDirs.map((dir) => mkdir13(dir, { recursive: true })));
18868
+ await Promise.all(homeStateDirs.map((dir) => mkdir15(dir, { recursive: true })));
17758
18869
  spawnCommand = wrapAgentCommand({
17759
18870
  bwrapPath: input.config.bwrapPath,
17760
18871
  spec: {
@@ -17793,8 +18904,21 @@ async function runUpdateFunctionalProbe(input) {
17793
18904
  }
17794
18905
  try {
17795
18906
  const served = await client.sessionPrompt(opened.sessionId, "Reply READY.", input.turnTimeoutMs ?? UPDATE_PROBE_TURN_TIMEOUT_MS);
17796
- if (!served.agentText.trim()) {
17797
- throw new UpdateFunctionalProbeError("turn-failed", "the harness completed a session/prompt without an agent answer");
18907
+ if (served.agentText.trim()) {
18908
+ modelAnswer = { modelAnswer: "served" };
18909
+ } else {
18910
+ const explained = await explainEmptyAgentTurn({
18911
+ agentLabel: command,
18912
+ agentEnv,
18913
+ sessionId: opened.sessionId,
18914
+ result: served
18915
+ });
18916
+ const modelSide = isAccountOrProviderRefusal(explained.record) || explained.record?.kind === "empty";
18917
+ if (!modelSide) {
18918
+ throw new UpdateFunctionalProbeError("turn-failed", `the harness completed a session/prompt without an agent answer: ${explained.reason}`);
18919
+ }
18920
+ console.warn(`[body] update probe: the bundle reached the model boundary but the model answered nothing (${explained.reason}); that is the model's own answer on any release, so the probe passes without one`);
18921
+ modelAnswer = { modelAnswer: "unavailable", modelAnswerReason: explained.reason };
17798
18922
  }
17799
18923
  } catch (error) {
17800
18924
  throw new UpdateFunctionalProbeError("turn-failed", error instanceof Error ? error.message : String(error), { cause: error });
@@ -17809,7 +18933,8 @@ async function runUpdateFunctionalProbe(input) {
17809
18933
  sandboxed: Boolean(input.config.bwrapPath),
17810
18934
  sessionStarted: true,
17811
18935
  turnCompleted: true,
17812
- nativeTools: []
18936
+ nativeTools: [],
18937
+ ...modelAnswer
17813
18938
  };
17814
18939
  } finally {
17815
18940
  await client?.stop().catch(() => void 0);
@@ -17818,8 +18943,8 @@ async function runUpdateFunctionalProbe(input) {
17818
18943
  }
17819
18944
 
17820
18945
  // apps/body/dist/release-status.js
17821
- import { readFile as readFile8, readdir as readdir3, rename as rename6, writeFile as writeFile10 } from "node:fs/promises";
17822
- import { resolve as resolve20 } from "node:path";
18946
+ import { readFile as readFile12, readdir as readdir4, rename as rename6, writeFile as writeFile13 } from "node:fs/promises";
18947
+ import { resolve as resolve24 } from "node:path";
17823
18948
  var DAEMON_RELEASE_STATUS_FILE = "release-status.json";
17824
18949
  var RELEASE_VERSION = /^v\d+\.\d+\.\d+$/;
17825
18950
  var SOURCE_SHA = /^[0-9a-f]{7,64}$/;
@@ -17836,9 +18961,9 @@ async function writeDaemonReleaseStatus(runtimeDir, agentPubkey, identity, optio
17836
18961
  pid: options.pid ?? process.pid,
17837
18962
  readyAt: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
17838
18963
  };
17839
- const target = resolve20(runtimeDir, DAEMON_RELEASE_STATUS_FILE);
18964
+ const target = resolve24(runtimeDir, DAEMON_RELEASE_STATUS_FILE);
17840
18965
  const temporary = `${target}.${status.pid}.tmp`;
17841
- await writeFile10(temporary, `${JSON.stringify(status, null, 2)}
18966
+ await writeFile13(temporary, `${JSON.stringify(status, null, 2)}
17842
18967
  `, { mode: 384 });
17843
18968
  await rename6(temporary, target);
17844
18969
  return status;
@@ -17879,7 +19004,7 @@ var DaemonExitError = class extends Error {
17879
19004
  };
17880
19005
  async function runStoredDaemon(pathOrPointer) {
17881
19006
  const configPath = await resolveRuntimeConfigPath(pathOrPointer);
17882
- daemonFailureRuntimeDir = dirname13(configPath);
19007
+ daemonFailureRuntimeDir = dirname14(configPath);
17883
19008
  const accessMigration = await migrateRuntimeRecordAccessPolicy(configPath);
17884
19009
  let runtime = accessMigration.runtime;
17885
19010
  if (!runtime.transport) {
@@ -17891,7 +19016,7 @@ async function runStoredDaemon(pathOrPointer) {
17891
19016
  runtime = activated.runtime;
17892
19017
  const daemonApi = activated.client;
17893
19018
  const agent = runtimeAgentCommand(runtime);
17894
- await writeFile11(resolve21(dirname13(configPath), "daemon.pid"), `${process.pid}
19019
+ await writeFile14(resolve25(dirname14(configPath), "daemon.pid"), `${process.pid}
17895
19020
  `, { mode: 384 });
17896
19021
  const env = {
17897
19022
  ...process.env,
@@ -17899,7 +19024,7 @@ async function runStoredDaemon(pathOrPointer) {
17899
19024
  BUZZ_DEV_MCP_BIN: runtime.mcpBinary
17900
19025
  };
17901
19026
  const config = loadBodyConfig({
17902
- workspaceRoot: resolve21(dirname13(configPath), "workspace"),
19027
+ workspaceRoot: resolve25(dirname14(configPath), "workspace"),
17903
19028
  llmEnvFile: runtime.llmEnvFile,
17904
19029
  env,
17905
19030
  agent
@@ -17934,7 +19059,7 @@ async function runStoredDaemon(pathOrPointer) {
17934
19059
  const stop = () => controller.abort();
17935
19060
  process.once("SIGINT", stop);
17936
19061
  process.once("SIGTERM", stop);
17937
- const runtimeDir = dirname13(configPath);
19062
+ const runtimeDir = dirname14(configPath);
17938
19063
  const layout = beelineInstallLayout(process.env);
17939
19064
  const notifier = new SystemdNotifier();
17940
19065
  let rollbackAlertDrain;
@@ -17980,6 +19105,22 @@ async function runStoredDaemon(pathOrPointer) {
17980
19105
  let stoppingStatus = "daemon stopped";
17981
19106
  try {
17982
19107
  const core = new ThinDaemonCore(runtime, configPath, config, { daemonApi });
19108
+ const updateDrain = update ? new ManagedUpdateDrain({
19109
+ update,
19110
+ quiesceIfIdle: () => core.quiesceForUpdateIfIdle(),
19111
+ activeTurnCount: () => core.activeTurnCount(),
19112
+ restart: async ({ desiredRelease, drainDeadlineAt }, mode) => {
19113
+ if (mode === "forced")
19114
+ await core.prepareForForcedUpdateRestart();
19115
+ core.setDrainDeadlineAt(drainDeadlineAt);
19116
+ stoppingStatus = `update pending, converging; loaded_release=${loadedRelease ?? "unknown"}; desired_release=${desiredRelease}; ${mode === "forced" ? "active work cancelled at the drain deadline" : "active work drained"}; intake quiesced; exit_deadline=${new Date(drainDeadlineAt).toISOString()}`;
19117
+ await notifier.stopping(stoppingStatus);
19118
+ controller.abort();
19119
+ },
19120
+ waiting: async ({ desiredRelease, drainDeadlineAt }) => {
19121
+ await notifier.progress(`loaded_release=${loadedRelease ?? "unknown"}; update ready; active agent work is still running; handoff deferred; desired_release=${desiredRelease}; exit_deadline=${new Date(drainDeadlineAt).toISOString()}`);
19122
+ }
19123
+ }) : void 0;
17983
19124
  const result = await core.run({
17984
19125
  signal: controller.signal,
17985
19126
  onEstablished: async () => {
@@ -18003,28 +19144,26 @@ async function runStoredDaemon(pathOrPointer) {
18003
19144
  }
18004
19145
  functionalProof = gate.proof;
18005
19146
  pendingSuccessor = false;
18006
- console.log(`[thin-core] successor functional probe passed on exact release ${loadedRelease}: ${functionalProof?.harness ?? "unknown"} session/new + turn`);
19147
+ console.log(`[thin-core] successor functional probe passed on exact release ${loadedRelease}: ${functionalProof?.harness ?? "unknown"} session/new + turn` + (functionalProof?.modelAnswer === "unavailable" ? ` (model answer unavailable: ${functionalProof.modelAnswerReason})` : ""));
18007
19148
  }
18008
19149
  await clearDaemonStartFailures(runtimeDir);
18009
19150
  await writeDaemonReleaseStatus(runtimeDir, runtime.agent.publicKey, loadedReleaseIdentity);
18010
19151
  await notifier.ready(`ready; loaded_release=${loadedRelease ?? "development"}`);
18011
19152
  ready = true;
19153
+ void syncAgentModelCatalog({
19154
+ api: daemonApi,
19155
+ agent,
19156
+ agentEnv: config.agentEnv,
19157
+ agentId: runtime.agent.publicKey,
19158
+ workspaceId: runtime.communityId,
19159
+ runtimeDir,
19160
+ ...runtime.modelSelection ? { runtimeSelection: runtime.modelSelection } : {}
19161
+ });
18012
19162
  },
18013
19163
  onProgress: async (status) => {
18014
19164
  void drainRollbackAlert(core.activeRoomIds()[0] ?? runtime.rooms[0]?.channelId);
18015
19165
  await notifier.progress(`loaded_release=${loadedRelease ?? "development"}; ${status}`);
18016
- if (!update)
18017
- return;
18018
- await coordinateManagedUpdateHandoff(update, () => core.quiesceForUpdateIfIdle(), async ({ desiredRelease, drainDeadlineAt }) => {
18019
- if (Date.now() >= drainDeadlineAt)
18020
- await core.prepareForForcedUpdateRestart();
18021
- core.setDrainDeadlineAt(drainDeadlineAt);
18022
- stoppingStatus = `update pending, converging; loaded_release=${loadedRelease ?? "unknown"}; desired_release=${desiredRelease}; active work drained; intake quiesced; exit_deadline=${new Date(drainDeadlineAt).toISOString()}`;
18023
- await notifier.stopping(stoppingStatus);
18024
- controller.abort();
18025
- }, async ({ desiredRelease, drainDeadlineAt }) => {
18026
- await notifier.progress(`loaded_release=${loadedRelease ?? "unknown"}; update ready; active agent work is still running; handoff deferred; desired_release=${desiredRelease}; exit_deadline=${new Date(drainDeadlineAt).toISOString()}`);
18027
- });
19166
+ await updateDrain?.tick();
18028
19167
  }
18029
19168
  });
18030
19169
  if (result === "agent-removed") {
@@ -18046,8 +19185,8 @@ async function runStoredDaemon(pathOrPointer) {
18046
19185
  throw error;
18047
19186
  } finally {
18048
19187
  await notifier.stopping(stoppingStatus).catch(() => void 0);
18049
- const pidPath = resolve21(dirname13(configPath), "daemon.pid");
18050
- const recorded = Number((await readFile9(pidPath, "utf8").catch(() => "")).trim());
19188
+ const pidPath = resolve25(dirname14(configPath), "daemon.pid");
19189
+ const recorded = Number((await readFile13(pidPath, "utf8").catch(() => "")).trim());
18051
19190
  if (recorded === process.pid) {
18052
19191
  await unlink3(pidPath).catch(() => void 0);
18053
19192
  }
@@ -18103,14 +19242,14 @@ async function main() {
18103
19242
  const agentPubkey = agentFlag >= 0 ? args[agentFlag + 1] : void 0;
18104
19243
  if (!configPath && agentPubkey) {
18105
19244
  const configs = await findAgentRuntimeConfigPaths(process.env, process.cwd());
18106
- configPath = configs.find((candidate) => dirname13(candidate).endsWith(agentPubkey));
19245
+ configPath = configs.find((candidate) => dirname14(candidate).endsWith(agentPubkey));
18107
19246
  }
18108
19247
  if (!configPath && agentPubkey) {
18109
19248
  throw new DaemonExitError(`unknown agent ${agentPubkey}: no durable runtime exists; refusing systemd restart loop`, UNKNOWN_AGENT_EXIT_STATUS);
18110
19249
  }
18111
19250
  if (!configPath)
18112
19251
  throw new Error("daemon requires --config <runtime.json> or --agent <pubkey>");
18113
- await runStoredDaemon(resolve21(configPath));
19252
+ await runStoredDaemon(resolve25(configPath));
18114
19253
  return;
18115
19254
  }
18116
19255
  if (command === "update") {
@@ -18127,7 +19266,7 @@ async function main() {
18127
19266
  if (!agentPubkey)
18128
19267
  throw new Error("stop requires --agent <pubkey>");
18129
19268
  const configs = await findAgentRuntimeConfigPaths(process.env, process.cwd());
18130
- const configPath = configs.find((candidate) => dirname13(candidate).endsWith(agentPubkey));
19269
+ const configPath = configs.find((candidate) => dirname14(candidate).endsWith(agentPubkey));
18131
19270
  if (!configPath)
18132
19271
  throw new Error(`no stored runtime found for agent ${agentPubkey}`);
18133
19272
  const runtime = await readRuntimeRecord(configPath);