usebeeline 0.0.45 → 0.0.47

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 +1246 -441
  2. package/package.json +1 -1
@@ -196,11 +196,11 @@ function parseUpdateManifest(raw, platform) {
196
196
  }
197
197
  const bundle = entry;
198
198
  const file = typeof bundle.file === "string" ? bundle.file : "";
199
- const sha2563 = typeof bundle.sha256 === "string" ? bundle.sha256.toLowerCase() : "";
199
+ const sha2564 = typeof bundle.sha256 === "string" ? bundle.sha256.toLowerCase() : "";
200
200
  if (!file || !/^[a-z0-9][a-z0-9._-]*$/i.test(file)) {
201
201
  throw new Error(`update manifest names an unusable bundle file: ${JSON.stringify(bundle.file)}`);
202
202
  }
203
- if (!/^[0-9a-f]{64}$/.test(sha2563)) {
203
+ if (!/^[0-9a-f]{64}$/.test(sha2564)) {
204
204
  throw new Error(`update manifest carries no usable sha256 for ${platform}`);
205
205
  }
206
206
  const sourceCommit = typeof bundle.commit === "string" && bundle.commit ? bundle.commit : typeof manifest.sourceCommit === "string" ? manifest.sourceCommit : void 0;
@@ -212,7 +212,7 @@ function parseUpdateManifest(raw, platform) {
212
212
  bundles,
213
213
  bundle: {
214
214
  file,
215
- sha256: sha2563,
215
+ sha256: sha2564,
216
216
  ...typeof bundle.bytes === "number" ? { bytes: bundle.bytes } : {},
217
217
  ...sourceCommit ? { commit: sourceCommit } : {},
218
218
  ...version ? { version } : {},
@@ -307,14 +307,14 @@ __export(self_update_exports, {
307
307
  writeUpdateAttemptFixture: () => writeUpdateAttemptFixture,
308
308
  writeUpdateState: () => writeUpdateState
309
309
  });
310
- import { createHash as createHash4 } from "node:crypto";
310
+ import { createHash as createHash5 } from "node:crypto";
311
311
  import { constants as fsConstants } from "node:fs";
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";
312
+ import { access, chmod as chmod3, lstat as lstat2, mkdir as mkdir11, open, readFile as readFile9, rename as rename3, rm as rm5, 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 dirname9, join as join6, resolve as resolve18 } from "node:path";
315
+ import { dirname as dirname9, join as join7, resolve as resolve19 } from "node:path";
316
316
  function anchorLayout(rawLibDir) {
317
- const libDir = resolve18(rawLibDir);
317
+ const libDir = resolve19(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: resolve18(libDir, "../../bin"),
333
+ binDir: resolve19(libDir, "../../bin"),
334
334
  libDir,
335
- releasesRoot: resolve18(libDir, `../${RELEASES_SEGMENT}`)
335
+ releasesRoot: resolve19(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(resolve18(home, ".local", "lib", "beeline"));
346
+ return anchorLayout(resolve19(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 [join6(bundleDir, "lib", "beeline", "bundle.json"), join6(bundleDir, "bundle.json")];
356
+ return [join7(bundleDir, "lib", "beeline", "bundle.json"), join7(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 readFile8(candidate, "utf8");
362
+ raw = await readFile9(candidate, "utf8");
363
363
  break;
364
364
  } catch {
365
365
  }
@@ -377,17 +377,17 @@ async function readBundleJson(bundleDir) {
377
377
  }
378
378
  }
379
379
  function updateStatePath(layout) {
380
- return join6(layout.releasesRoot, ".state", "update-state.json");
380
+ return join7(layout.releasesRoot, ".state", "update-state.json");
381
381
  }
382
382
  async function readUpdateState(layout) {
383
383
  try {
384
- return JSON.parse(await readFile8(updateStatePath(layout), "utf8"));
384
+ return JSON.parse(await readFile9(updateStatePath(layout), "utf8"));
385
385
  } catch {
386
386
  return {};
387
387
  }
388
388
  }
389
389
  async function writeUpdateState(layout, state) {
390
- await mkdir10(join6(layout.releasesRoot, ".state"), { recursive: true });
390
+ await mkdir11(join7(layout.releasesRoot, ".state"), { recursive: true });
391
391
  await writeFile8(updateStatePath(layout), `${JSON.stringify(state, null, 2)}
392
392
  `, "utf8");
393
393
  }
@@ -469,7 +469,7 @@ function run(command, args, timeoutMs) {
469
469
  });
470
470
  }
471
471
  function entrypointCandidates(bundleDir) {
472
- return [join6(bundleDir, BUNDLE_ENTRYPOINT), join6(bundleDir, "beeline-cli.mjs")];
472
+ return [join7(bundleDir, BUNDLE_ENTRYPOINT), join7(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 log2 = opts.logger ?? ((line) => console.log(`[body] self-update: ${line}`));
493
493
  const releaseId = sanitizeReleaseId(published.commit ?? published.version ?? `release-${Date.now()}`);
494
- const releaseDir = join6(layout.releasesRoot, releaseId);
495
- const okMarker = join6(releaseDir, ".stage-ok");
494
+ const releaseDir = join7(layout.releasesRoot, releaseId);
495
+ const okMarker = join7(releaseDir, ".stage-ok");
496
496
  let previouslyVerified = false;
497
497
  try {
498
- const recorded = await readFile8(okMarker, "utf8");
498
+ const recorded = await readFile9(okMarker, "utf8");
499
499
  if (recorded.trim() === published.sha256)
500
500
  return releaseId;
501
501
  previouslyVerified = true;
502
502
  } catch {
503
503
  }
504
- await mkdir10(releaseDir, { recursive: true });
505
- const tempArchive = join6(layout.releasesRoot, `.download-${releaseId}-${process.pid}.tar.gz`);
504
+ await mkdir11(releaseDir, { recursive: true });
505
+ const tempArchive = join7(layout.releasesRoot, `.download-${releaseId}-${process.pid}.tar.gz`);
506
506
  try {
507
507
  log2(`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 = createHash4("sha256");
514
+ const hash = createHash5("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);
@@ -542,13 +542,13 @@ 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(join6(releaseDir, relative3), fsConstants.F_OK);
545
+ await access(join7(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, [join6(releaseDir, BUNDLE_ENTRYPOINT), "--version"], 6e4);
551
+ const probe = await run(process.execPath, [join7(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
  }
@@ -559,10 +559,10 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
559
559
  return releaseId;
560
560
  } catch (error) {
561
561
  if (!previouslyVerified)
562
- await rm4(releaseDir, { recursive: true, force: true });
562
+ await rm5(releaseDir, { recursive: true, force: true });
563
563
  throw error;
564
564
  } finally {
565
- await rm4(tempArchive, { force: true });
565
+ await rm5(tempArchive, { force: true });
566
566
  }
567
567
  }
568
568
  function forwarderScript(tool) {
@@ -586,21 +586,21 @@ async function replaceFile(path, contents, mode) {
586
586
  await rename3(temp, path);
587
587
  }
588
588
  async function activateRelease(layout, releaseId) {
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 });
589
+ const releaseDir = join7(layout.releasesRoot, releaseId);
590
+ await access(join7(releaseDir, BUNDLE_ENTRYPOINT), fsConstants.F_OK);
591
+ await mkdir11(layout.releasesRoot, { recursive: true });
592
+ await mkdir11(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 = join6(layout.releasesRoot, legacyId);
598
+ const legacyDir = join7(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, join6(layout.releasesRoot, previousReleaseId));
603
- await normalizeLegacyBundleShape(join6(layout.releasesRoot, previousReleaseId));
602
+ await rename3(layout.libDir, join7(layout.releasesRoot, previousReleaseId));
603
+ await normalizeLegacyBundleShape(join7(layout.releasesRoot, previousReleaseId));
604
604
  } catch {
605
605
  await rename3(layout.libDir, legacyDir);
606
606
  await normalizeLegacyBundleShape(legacyDir);
@@ -608,19 +608,19 @@ async function activateRelease(layout, releaseId) {
608
608
  }
609
609
  }
610
610
  const tempLink = `${layout.libDir}.new-${process.pid}`;
611
- await rm4(tempLink, { force: true });
612
- await symlink2(join6("beeline-releases", releaseId), tempLink);
611
+ await rm5(tempLink, { force: true });
612
+ await symlink2(join7("beeline-releases", releaseId), tempLink);
613
613
  await rename3(tempLink, layout.libDir);
614
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 = join6(bundleDir, "lib", "beeline");
619
+ const innerLib = join7(bundleDir, "lib", "beeline");
620
620
  let anyFlat = false;
621
621
  for (const name of LEGACY_FLAT_BUNDLE_FILES) {
622
622
  try {
623
- await access(join6(bundleDir, name), fsConstants.F_OK);
623
+ await access(join7(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 mkdir10(innerLib, { recursive: true });
631
+ await mkdir11(innerLib, { recursive: true });
632
632
  for (const name of LEGACY_FLAT_BUNDLE_FILES) {
633
633
  try {
634
- await access(join6(innerLib, name), fsConstants.F_OK);
634
+ await access(join7(innerLib, name), fsConstants.F_OK);
635
635
  continue;
636
636
  } catch {
637
637
  }
638
- await rename3(join6(bundleDir, name), join6(innerLib, name)).catch(() => void 0);
638
+ await rename3(join7(bundleDir, name), join7(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 = join6(activeBundleRoot, "bin", tool);
643
+ const target = join7(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(join6(layout.binDir, tool), forwarderScript(tool), 493);
649
+ await replaceFile(join7(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 = join6(layout.binDir, "beeline");
655
+ const forwarderPath = join7(layout.binDir, "beeline");
656
656
  let current;
657
657
  try {
658
- current = await readFile8(forwarderPath, "utf8");
658
+ current = await readFile9(forwarderPath, "utf8");
659
659
  } catch {
660
660
  current = void 0;
661
661
  }
662
662
  if (current === forwarderScript("beeline"))
663
663
  return false;
664
- await mkdir10(layout.binDir, { recursive: true });
664
+ await mkdir11(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 = join6(layout.releasesRoot, previousReleaseId);
670
+ const releaseDir = join7(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
- await rm4(tempLink, { force: true });
677
- await symlink2(join6("beeline-releases", previousReleaseId), tempLink);
676
+ await rm5(tempLink, { force: true });
677
+ await symlink2(join7("beeline-releases", previousReleaseId), tempLink);
678
678
  await rename3(tempLink, layout.libDir);
679
679
  await fsyncDir(dirname9(layout.libDir));
680
680
  }
681
681
  function updateAttemptPath(layout) {
682
- return join6(layout.releasesRoot, ".state", "update-attempt.json");
682
+ return join7(layout.releasesRoot, ".state", "update-attempt.json");
683
683
  }
684
684
  async function readUpdateAttempt(layout) {
685
685
  try {
686
- const raw = JSON.parse(await readFile8(updateAttemptPath(layout), "utf8"));
686
+ const raw = JSON.parse(await readFile9(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,7 +693,7 @@ async function readUpdateAttempt(layout) {
693
693
  }
694
694
  }
695
695
  async function writeUpdateAttempt(layout, record2) {
696
- await mkdir10(join6(layout.releasesRoot, ".state"), { recursive: true });
696
+ await mkdir11(join7(layout.releasesRoot, ".state"), { recursive: true });
697
697
  const path = updateAttemptPath(layout);
698
698
  const staged = `${path}.${process.pid}.tmp`;
699
699
  await writeFile8(staged, `${JSON.stringify(record2, null, 2)}
@@ -955,8 +955,8 @@ var init_self_update = __esm({
955
955
  });
956
956
 
957
957
  // apps/body/dist/cli.js
958
- import { dirname as dirname15, resolve as resolve25 } from "node:path";
959
- import { readFile as readFile13, unlink as unlink3, writeFile as writeFile14 } from "node:fs/promises";
958
+ import { dirname as dirname15, resolve as resolve26 } from "node:path";
959
+ import { readFile as readFile14, 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
@@ -3828,7 +3828,7 @@ var AcpClient = class extends EventEmitter {
3828
3828
  const current = this.activeRunIds.get(sessionId);
3829
3829
  if (current)
3830
3830
  return Promise.resolve(current);
3831
- return new Promise((resolve26, reject) => {
3831
+ return new Promise((resolve27, reject) => {
3832
3832
  const onUpdate = (update) => {
3833
3833
  if (update.sessionId !== sessionId)
3834
3834
  return;
@@ -3836,7 +3836,7 @@ var AcpClient = class extends EventEmitter {
3836
3836
  if (!runId)
3837
3837
  return;
3838
3838
  cleanup();
3839
- resolve26(runId);
3839
+ resolve27(runId);
3840
3840
  };
3841
3841
  const timer = setTimeout(() => {
3842
3842
  cleanup();
@@ -3920,13 +3920,13 @@ var AcpClient = class extends EventEmitter {
3920
3920
  }
3921
3921
  const id = this.nextId++;
3922
3922
  const payload = { jsonrpc: "2.0", id, method, params };
3923
- return new Promise((resolve26, reject) => {
3923
+ return new Promise((resolve27, reject) => {
3924
3924
  const timer = setTimeout(() => {
3925
3925
  this.pending.delete(id);
3926
3926
  reject(new AcpRequestTimeoutError(method, timeoutMs, this.stderrTail, Boolean(onStart), detail));
3927
3927
  }, timeoutMs);
3928
3928
  this.pending.set(id, {
3929
- resolve: resolve26,
3929
+ resolve: resolve27,
3930
3930
  reject,
3931
3931
  timer,
3932
3932
  method,
@@ -13277,10 +13277,10 @@ async function activateDaemonTransport(path, fetchImpl = fetch) {
13277
13277
 
13278
13278
  // apps/body/dist/room-runtime.js
13279
13279
  import { execFile as execFile4 } from "node:child_process";
13280
- import { createHash as createHash3 } from "node:crypto";
13280
+ import { createHash as createHash4 } from "node:crypto";
13281
13281
  import { existsSync as existsSync4, mkdirSync } from "node:fs";
13282
- import { mkdir as mkdir8, rm as rm3 } from "node:fs/promises";
13283
- import { dirname as dirname6, resolve as resolve16 } from "node:path";
13282
+ import { mkdir as mkdir9, rm as rm4 } from "node:fs/promises";
13283
+ import { dirname as dirname6, resolve as resolve17 } from "node:path";
13284
13284
  import { promisify as promisify3 } from "node:util";
13285
13285
 
13286
13286
  // apps/body/dist/grant-runner.js
@@ -14122,17 +14122,17 @@ var GrantRunnerServer = class {
14122
14122
 
14123
14123
  // apps/body/dist/monolith-corner-turn.js
14124
14124
  import { execFile as execFile3 } from "node:child_process";
14125
- import { mkdir as mkdir6 } from "node:fs/promises";
14125
+ import { mkdir as mkdir7 } from "node:fs/promises";
14126
14126
  import { homedir as homedir6 } from "node:os";
14127
- import { join as join4 } from "node:path";
14127
+ import { join as join5 } from "node:path";
14128
14128
  import { promisify as promisify2 } from "node:util";
14129
14129
 
14130
14130
  // apps/body/dist/agent-home.js
14131
14131
  import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
14132
- import { randomUUID } from "node:crypto";
14133
- import { chmod as chmod2, copyFile, lstat, mkdir as mkdir4, readdir as readdir2, realpath, rename as rename2, rm as rm2, symlink, unlink, writeFile as writeFile5 } from "node:fs/promises";
14132
+ import { createHash as createHash3, randomUUID } from "node:crypto";
14133
+ import { chmod as chmod2, copyFile, lstat, mkdir as mkdir4, readFile as readFile6, readdir as readdir2, realpath, rename as rename2, rm as rm2, symlink, unlink, writeFile as writeFile5 } from "node:fs/promises";
14134
14134
  import { homedir as homedir5 } from "node:os";
14135
- import { basename as basename3, dirname as dirname5, relative as relative2, resolve as resolve13, sep } from "node:path";
14135
+ import { basename as basename3, dirname as dirname5, join as join3, relative as relative2, resolve as resolve13, sep } from "node:path";
14136
14136
 
14137
14137
  // apps/body/dist/beeline-skill.js
14138
14138
  import { readFileSync as readFileSync3 } from "node:fs";
@@ -14145,7 +14145,7 @@ var BEELINE_ROOM_CAPABILITIES = [
14145
14145
  "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.",
14146
14146
  "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.",
14147
14147
  "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.",
14148
- "To send a file, call beeline-agent attach_file with a path inside your checkout or your writable scratch directory (where files you generate live); it is attached to your reply.",
14148
+ "To create a file (this Room has no other way to write one), call beeline-agent write_scratch_file with a relative path and content - text by default, or base64 for bytes you computed; it returns a path in your writable session home. To send a file, call beeline-agent attach_file with a path inside your checkout or anywhere in your writable session home (wherever a file you or your harness generated actually landed, including one you just wrote); it is attached to your reply. write_scratch_file produces the file, not a picture - turning it into a raster image needs a converter, which needs shell, which this Room does not have.",
14149
14149
  "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.",
14150
14150
  "When repository work is needed, you MUST call beeline-agent open_corner with a name of at most three words - it titles the corner everywhere - and a complete objective of no more than 24 words. The host-governed call is the only way to start write work.",
14151
14151
  "When open_corner succeeds, the server posts the corner card: do not announce or restate the opening. End the turn with nothing more unless the person asked something else.",
@@ -14157,7 +14157,7 @@ var BEELINE_DM_CAPABILITIES = [
14157
14157
  "The repository filesystem is read-only in this session.",
14158
14158
  "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.",
14159
14159
  "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.",
14160
- "To send a file, call beeline-agent attach_file with a path inside your checkout or your writable scratch directory (where files you generate live); it is attached to your reply.",
14160
+ "To create a file (this Room has no other way to write one), call beeline-agent write_scratch_file with a relative path and content - text by default, or base64 for bytes you computed; it returns a path in your writable session home. To send a file, call beeline-agent attach_file with a path inside your checkout or anywhere in your writable session home (wherever a file you or your harness generated actually landed, including one you just wrote); it is attached to your reply. write_scratch_file produces the file, not a picture - turning it into a raster image needs a converter, which needs shell, which this Room does not have.",
14161
14161
  "Tag the person only when you need a decision or input, or when the task they asked for is finished.",
14162
14162
  "Never claim an action or reply happened unless the prompt or a tool result proves it."
14163
14163
  ].join(" ");
@@ -14805,6 +14805,9 @@ var OPERATOR_SKILL_SOURCE_DIRS = [
14805
14805
  ".pi/agent/skills"
14806
14806
  ];
14807
14807
  var AGENT_SKILL_DIRS = ["claude", "codex", "grok", "pi"];
14808
+ function agentSkillDir(kind) {
14809
+ return AGENT_SKILL_DIRS.includes(kind ?? "") ? kind : "codex";
14810
+ }
14808
14811
  var SHARED_SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
14809
14812
  function isSharedSkillName(value) {
14810
14813
  return typeof value === "string" && SHARED_SKILL_NAME_PATTERN.test(value) && !BEELINE_DEFAULT_SKILL_NAMES.includes(value);
@@ -14861,7 +14864,7 @@ async function prepareRoomAgentHome(input) {
14861
14864
  await symlink(source, target).catch(() => void 0);
14862
14865
  }
14863
14866
  const prior = agentHomeProvisionQueues.get(root) ?? Promise.resolve();
14864
- const provision = prior.catch(() => void 0).then(() => provisionAgentSkillsAndMcp(root, operatorHome, input.skillReleaseId ?? runningBeelineReleaseId(), input.failClosed ?? false, input.sharedSkills ?? [], input.openRouterRouting));
14867
+ const provision = prior.catch(() => void 0).then(() => provisionAgentSkillsAndMcp(root, operatorHome, input.skillReleaseId ?? runningBeelineReleaseId(), input.failClosed ?? false, input.sharedSkills ?? [], agentSkillDir(input.agentKind), input.openRouterRouting));
14865
14868
  agentHomeProvisionQueues.set(root, provision);
14866
14869
  try {
14867
14870
  await provision;
@@ -14871,15 +14874,12 @@ async function prepareRoomAgentHome(input) {
14871
14874
  }
14872
14875
  return roomAgentHomeEnv(root);
14873
14876
  }
14874
- async function provisionAgentSkillsAndMcp(root, operatorHome, skillReleaseId, failClosed, sharedSkills, openRouterRouting) {
14877
+ async function provisionAgentSkillsAndMcp(root, operatorHome, skillReleaseId, failClosed, sharedSkills, skillDir, openRouterRouting) {
14875
14878
  const managedSkills = [
14876
14879
  { name: USING_BEELINE_SKILL_NAME, content: usingBeelineSkillMarkdown(skillReleaseId) }
14877
14880
  ];
14878
14881
  const shared = await resolveSharedSkillSources(operatorHome, sharedSkills);
14879
- for (const dir of AGENT_SKILL_DIRS) {
14880
- const target = resolve13(root, dir, "skills");
14881
- await provisionManagedSkillsDir(target, managedSkills, shared, sharedSkills.length === 0);
14882
- }
14882
+ await provisionManagedSkillsDir(resolve13(root, skillDir, "skills"), managedSkills, shared, sharedSkills.length === 0);
14883
14883
  for (const config of HARNESS_MCP_CONFIGS) {
14884
14884
  try {
14885
14885
  const source = resolve13(operatorHome, config.toml);
@@ -15013,26 +15013,19 @@ function filteredHarnessMcpToml(source) {
15013
15013
  async function provisionManagedSkillsDir(target, managedSkills, sharedSkills, optionalShares) {
15014
15014
  const parent = dirname5(target);
15015
15015
  await assertRealContainedDirectory(parent, dirname5(parent));
15016
+ const plan = await planManagedSkills(managedSkills, sharedSkills, optionalShares);
15017
+ if (await materializedSkillManifest(target) === plan.manifest)
15018
+ return;
15016
15019
  const staged = resolve13(parent, `.skills.${process.pid}.${randomUUID()}.tmp`);
15017
15020
  await mkdir4(staged, { mode: 448 });
15018
- const names = new Set(managedSkills.map((skill) => skill.name));
15019
15021
  try {
15020
- for (const skill of managedSkills) {
15021
- const skillDir = resolve13(staged, skill.name);
15022
- await mkdir4(skillDir, { recursive: true });
15023
- await writeIsolatedHarnessFile(resolve13(skillDir, "SKILL.md"), skill.content);
15024
- }
15025
- for (const shared of sharedSkills) {
15026
- if (names.has(shared.name)) {
15027
- throw new Error(`shared skill collides with Beeline-owned skill: ${shared.name}`);
15028
- }
15029
- names.add(shared.name);
15030
- try {
15031
- await copySafeSkillTree(shared.source, resolve13(staged, shared.name), shared.source);
15032
- } catch (error) {
15033
- if (!optionalShares)
15034
- throw error;
15035
- console.warn(`[body] skipping shared skill ${shared.name}:`, error);
15022
+ for (const entry of plan.entries) {
15023
+ if (entry.kind === "managed") {
15024
+ const skillDir = resolve13(staged, entry.name);
15025
+ await mkdir4(skillDir, { recursive: true });
15026
+ await writeIsolatedHarnessFile(resolve13(skillDir, "SKILL.md"), entry.content);
15027
+ } else {
15028
+ await copySafeSkillTree(entry.source, resolve13(staged, entry.name), entry.source);
15036
15029
  }
15037
15030
  }
15038
15031
  const existing = await lstat(target).catch(() => void 0);
@@ -15043,6 +15036,70 @@ async function provisionManagedSkillsDir(target, managedSkills, sharedSkills, op
15043
15036
  await rm2(staged, { recursive: true, force: true });
15044
15037
  }
15045
15038
  }
15039
+ async function planManagedSkills(managedSkills, sharedSkills, optionalShares) {
15040
+ const entries = [];
15041
+ const lines = [];
15042
+ const names = new Set(managedSkills.map((skill) => skill.name));
15043
+ for (const skill of managedSkills) {
15044
+ entries.push({ kind: "managed", name: skill.name, content: skill.content });
15045
+ lines.push(`d ${skill.name}`, `f ${skill.name}/SKILL.md ${sha2563(skill.content)}`);
15046
+ }
15047
+ for (const shared of sharedSkills) {
15048
+ if (names.has(shared.name)) {
15049
+ throw new Error(`shared skill collides with Beeline-owned skill: ${shared.name}`);
15050
+ }
15051
+ names.add(shared.name);
15052
+ try {
15053
+ const tree = [];
15054
+ await walkSafeSkillTree(shared.source, shared.source, {
15055
+ directory: async (rel) => void tree.push(`d ${join3(shared.name, rel)}`),
15056
+ file: async (rel, realPath) => void tree.push(`f ${join3(shared.name, rel)} ${sha2563(await readFile6(realPath))}`)
15057
+ });
15058
+ entries.push({ kind: "shared", name: shared.name, source: shared.source });
15059
+ lines.push(...tree);
15060
+ } catch (error) {
15061
+ if (!optionalShares)
15062
+ throw error;
15063
+ console.warn(`[body] skipping shared skill ${shared.name}:`, error);
15064
+ }
15065
+ }
15066
+ return { entries, manifest: lines.sort().join("\n") };
15067
+ }
15068
+ async function materializedSkillManifest(target) {
15069
+ const stats = await lstat(target).catch(() => void 0);
15070
+ if (!stats?.isDirectory() || stats.isSymbolicLink())
15071
+ return void 0;
15072
+ const lines = [];
15073
+ const visit = async (directory, prefix) => {
15074
+ for (const entry of await readdir2(directory)) {
15075
+ const path = resolve13(directory, entry);
15076
+ const rel = prefix ? join3(prefix, entry) : entry;
15077
+ const entryStats = await lstat(path);
15078
+ if (entryStats.isSymbolicLink())
15079
+ return false;
15080
+ if (entryStats.isDirectory()) {
15081
+ lines.push(`d ${rel}`);
15082
+ if (!await visit(path, rel))
15083
+ return false;
15084
+ continue;
15085
+ }
15086
+ if (!entryStats.isFile() || entryStats.nlink !== 1)
15087
+ return false;
15088
+ lines.push(`f ${rel} ${sha2563(await readFile6(path))}`);
15089
+ }
15090
+ return true;
15091
+ };
15092
+ try {
15093
+ if (!await visit(target, ""))
15094
+ return void 0;
15095
+ } catch {
15096
+ return void 0;
15097
+ }
15098
+ return lines.sort().join("\n");
15099
+ }
15100
+ function sha2563(value) {
15101
+ return createHash3("sha256").update(value).digest("hex");
15102
+ }
15046
15103
  async function resolveSharedSkillSources(operatorHome, names) {
15047
15104
  if (names.length > 0)
15048
15105
  return resolveExplicitSkillSources(operatorHome, names);
@@ -15127,7 +15184,7 @@ async function assertRealContainedDirectory(path, root) {
15127
15184
  }
15128
15185
  }
15129
15186
  var BLOCKED_SHARED_FILENAMES = /^(?:\.env(?:\..*)?|auth\.json|\.credentials\.json|config\.toml|settings(?:\.local)?\.json|\.mcp\.json|credentials?|secrets?|plugins?|\.codex-plugin|\.claude-plugin|memory(?:\.md)?|\.netrc|\.git-credentials|.*\.(?:pem|key))$/i;
15130
- async function copySafeSkillTree(source, target, sourceRoot) {
15187
+ async function walkSafeSkillTree(source, sourceRoot, visitor, rel = "") {
15131
15188
  assertContained(sourceRoot, source);
15132
15189
  const resolvedSource = await realpath(source);
15133
15190
  if (resolvedSource !== resolve13(source)) {
@@ -15141,19 +15198,30 @@ async function copySafeSkillTree(source, target, sourceRoot) {
15141
15198
  throw new Error(`shared skill contains credential or configuration material: ${source}`);
15142
15199
  }
15143
15200
  if (stats.isDirectory()) {
15144
- await mkdir4(target, { mode: 448 });
15201
+ await visitor.directory(rel);
15145
15202
  for (const entry of await readdir2(resolvedSource)) {
15146
15203
  if (entry === "." || entry === "..")
15147
15204
  throw new Error("invalid shared skill entry");
15148
- await copySafeSkillTree(resolve13(source, entry), resolve13(target, entry), sourceRoot);
15205
+ await walkSafeSkillTree(resolve13(source, entry), sourceRoot, visitor, rel ? join3(rel, entry) : entry);
15149
15206
  }
15150
15207
  return;
15151
15208
  }
15152
15209
  if (!stats.isFile() || stats.nlink !== 1) {
15153
15210
  throw new Error(`shared skill contains a nonordinary file: ${source}`);
15154
15211
  }
15155
- await copyFile(resolvedSource, target);
15156
- await chmod2(target, 384);
15212
+ await visitor.file(rel, resolvedSource);
15213
+ }
15214
+ async function copySafeSkillTree(source, target, sourceRoot) {
15215
+ await walkSafeSkillTree(source, sourceRoot, {
15216
+ directory: async (rel) => {
15217
+ await mkdir4(resolve13(target, rel), { mode: 448 });
15218
+ },
15219
+ file: async (rel, realPath) => {
15220
+ const destination = resolve13(target, rel);
15221
+ await copyFile(realPath, destination);
15222
+ await chmod2(destination, 384);
15223
+ }
15224
+ });
15157
15225
  }
15158
15226
  async function writeIsolatedHarnessFile(path, content) {
15159
15227
  const parent = dirname5(path);
@@ -15207,7 +15275,7 @@ function harnessStateDirsFromEnv(env) {
15207
15275
 
15208
15276
  // apps/body/dist/attachment-delivery.js
15209
15277
  import { mkdir as mkdir5, writeFile as writeFile6 } from "node:fs/promises";
15210
- import { basename as basename4, extname, join as join3 } from "node:path";
15278
+ import { basename as basename4, extname, join as join4 } from "node:path";
15211
15279
  var MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
15212
15280
  var FETCH_TIMEOUT_MS = 3e4;
15213
15281
  var MAX_INLINE_IMAGE_BYTES = 5 * 1024 * 1024;
@@ -15245,7 +15313,7 @@ async function deliverAttachments(attachments, dir, fetchImpl = fetch) {
15245
15313
  const bytes = Buffer.from(await response.arrayBuffer());
15246
15314
  if (bytes.length > MAX_ATTACHMENT_BYTES)
15247
15315
  return tooLarge(bytes.length);
15248
- const path = join3(dir, safeFileName(attachment, index, taken));
15316
+ const path = join4(dir, safeFileName(attachment, index, taken));
15249
15317
  await writeFile6(path, bytes);
15250
15318
  const mimeType = attachment.mimeType ?? response.headers.get("content-type") ?? "";
15251
15319
  if (!mimeType.startsWith("image/"))
@@ -15428,6 +15496,109 @@ function isCornerStatusRestatement(reply, systemLines) {
15428
15496
  return statusWords(text2).every((word) => !word || admitted.has(word));
15429
15497
  }
15430
15498
 
15499
+ // apps/body/dist/turn-stream.js
15500
+ function durableReplyText(agentText) {
15501
+ return sanitizeAgentReply(agentText);
15502
+ }
15503
+ var AgentTurnStream = class {
15504
+ options;
15505
+ latest = "";
15506
+ /**
15507
+ * The newest snapshot not yet handed to a write. A draft is a picture of the
15508
+ * whole answer so far, so an older snapshot that never reached the wire is
15509
+ * not a lost message — it is a frame nobody needed. Keeping only the newest
15510
+ * one bounds the lane at ONE write in flight plus ONE waiting.
15511
+ */
15512
+ pending;
15513
+ /** The draft write on the wire, if any. Never rejects; failures are logged. */
15514
+ inFlight;
15515
+ /** Closed lanes publish nothing more, so the answer never queues behind a draft. */
15516
+ closed = false;
15517
+ constructor(options) {
15518
+ this.options = options;
15519
+ }
15520
+ /**
15521
+ * The ACP delta hook: hand it straight to `sessionPrompt`. `full` is every
15522
+ * assistant run so far joined — not the final answer — so it is only ever
15523
+ * shown provisionally.
15524
+ */
15525
+ onChunk = (_delta, full) => {
15526
+ this.latest = full;
15527
+ const text2 = sanitizeAgentReply(full);
15528
+ if (!text2 || this.closed)
15529
+ return;
15530
+ this.pending = text2;
15531
+ this.publishPending();
15532
+ };
15533
+ /**
15534
+ * Hand the newest snapshot to the wire, one write at a time.
15535
+ *
15536
+ * Serialized, never parallel: two drafts in flight can land out of order and
15537
+ * a reader would watch the answer go backwards. Serialized used to mean an
15538
+ * unbounded chain — every delta got its own write, and the durable reply
15539
+ * awaited the whole tail, so a finished answer sat behind writes showing text
15540
+ * nobody would ever read. It waits for at most one write now, and only to
15541
+ * keep the retract last.
15542
+ */
15543
+ publishPending() {
15544
+ if (this.inFlight || this.pending === void 0)
15545
+ return;
15546
+ const text2 = this.pending;
15547
+ this.pending = void 0;
15548
+ const { api, agentId, roomId, requestId, label } = this.options;
15549
+ this.inFlight = api.execute("postAgentDraft", { agentId, roomId, turnId: requestId, text: text2 }).then(() => void 0).catch((error) => console.error(`[thin-core] ${label} draft publish failed:`, error)).then(() => {
15550
+ this.inFlight = void 0;
15551
+ this.publishPending();
15552
+ });
15553
+ }
15554
+ /**
15555
+ * Everything the delta hook has seen this turn: every assistant run joined,
15556
+ * which is a LONGER string than `PromptResult.agentText` whenever the turn
15557
+ * spoke before a tool call. Nothing durable is derived from it.
15558
+ */
15559
+ get streamedText() {
15560
+ return this.latest;
15561
+ }
15562
+ /** Forget the previous run's stream text; a re-pinned retry starts clean. */
15563
+ beginRun() {
15564
+ this.latest = "";
15565
+ this.pending = void 0;
15566
+ }
15567
+ /**
15568
+ * Stop drafting. The answer is known from here on, so anything still waiting
15569
+ * is obsolete and is dropped rather than published ahead of the final.
15570
+ */
15571
+ close() {
15572
+ this.closed = true;
15573
+ this.pending = void 0;
15574
+ }
15575
+ /**
15576
+ * Post the durable reply under the turn's request id and dissolve the draft.
15577
+ * An empty reply settles through the turn receipt instead, and the lane is
15578
+ * retracted either way.
15579
+ */
15580
+ async settle(reply, fields = {}) {
15581
+ this.close();
15582
+ const { api, agentId, roomId, requestId } = this.options;
15583
+ if (reply) {
15584
+ await api.execute("postRoomMessage", {
15585
+ roomId,
15586
+ requestId,
15587
+ text: reply,
15588
+ presentation: "message",
15589
+ ...fields
15590
+ });
15591
+ }
15592
+ await this.inFlight;
15593
+ await api.execute("retractAgentLiveOutput", {
15594
+ agentId,
15595
+ roomId,
15596
+ turnId: requestId,
15597
+ kind: "draft"
15598
+ });
15599
+ }
15600
+ };
15601
+
15431
15602
  // apps/body/dist/turn-failure-reason.js
15432
15603
  var TURN_FAILURE_REASON_MAX = 200;
15433
15604
  function redactToolDetail(value) {
@@ -15482,6 +15653,17 @@ function toolCallFailureLine(call) {
15482
15653
  return detail ? `${title} refused: ${detail}` : `${title} refused with no reason given`;
15483
15654
  }
15484
15655
 
15656
+ // apps/body/dist/session-config-fingerprint.js
15657
+ function sessionConfigFingerprint(input) {
15658
+ return JSON.stringify([
15659
+ input.model ?? "",
15660
+ input.effort ?? "",
15661
+ input.soul?.name ?? "",
15662
+ input.soul?.instructions ?? "",
15663
+ input.agentName ?? ""
15664
+ ]);
15665
+ }
15666
+
15485
15667
  // apps/body/dist/room-session.js
15486
15668
  import { resolve as resolve14 } from "node:path";
15487
15669
 
@@ -15524,6 +15706,62 @@ function shellPayload(toolCall) {
15524
15706
  const record2 = rawInput;
15525
15707
  return typeof record2.command === "string" || typeof record2.cmd === "string";
15526
15708
  }
15709
+ var ROOM_MOUNTED_MCP_SERVERS = [
15710
+ READ_ONLY_MCP_SERVER_NAME,
15711
+ BEELINE_AGENT_MCP_SERVER_NAME
15712
+ ];
15713
+ var DISPATCHER_TOOL_NAME_KEYS = ["tool_name", "toolName"];
15714
+ var DISPATCHER_TOOL_INPUT_KEYS = ["tool_input", "toolInput"];
15715
+ var TOOL_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
15716
+ function permissionRecord(rawInput) {
15717
+ return rawInput && typeof rawInput === "object" && !Array.isArray(rawInput) ? rawInput : void 0;
15718
+ }
15719
+ function dispatchedPayloadIsShell(record2) {
15720
+ if (!record2)
15721
+ return false;
15722
+ return DISPATCHER_TOOL_INPUT_KEYS.some((key) => key in record2 && shellPayload({ rawInput: record2[key] }));
15723
+ }
15724
+ function toolIdentityCandidates(toolCall) {
15725
+ const candidates = [];
15726
+ const add = (value) => {
15727
+ if (typeof value === "string" && value.trim())
15728
+ candidates.push(value.trim());
15729
+ };
15730
+ add(toolCall?.title);
15731
+ const record2 = permissionRecord(toolCall?.rawInput);
15732
+ for (const key of DISPATCHER_TOOL_NAME_KEYS)
15733
+ add(record2?.[key]);
15734
+ return candidates;
15735
+ }
15736
+ function normalizedServerName(name) {
15737
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "_");
15738
+ }
15739
+ function resolveMountedMcpToolCall(request, mountedServers) {
15740
+ const toolCall = request.toolCall;
15741
+ if (shellPayload(toolCall))
15742
+ return void 0;
15743
+ const record2 = permissionRecord(toolCall?.rawInput);
15744
+ if (dispatchedPayloadIsShell(record2))
15745
+ return void 0;
15746
+ const candidates = toolIdentityCandidates(toolCall);
15747
+ for (const server of mountedServers) {
15748
+ const spellings = /* @__PURE__ */ new Set([server.toLowerCase(), normalizedServerName(server)]);
15749
+ for (const candidate of candidates) {
15750
+ const lowered = candidate.toLowerCase();
15751
+ for (const spelling of spellings) {
15752
+ for (const separator of TOOL_NAME_SEPARATORS) {
15753
+ const prefix = `${spelling}${separator}`;
15754
+ if (!lowered.startsWith(prefix))
15755
+ continue;
15756
+ const tool = candidate.slice(prefix.length).trim();
15757
+ if (TOOL_NAME_PATTERN.test(tool))
15758
+ return { server, tool };
15759
+ }
15760
+ }
15761
+ }
15762
+ }
15763
+ return void 0;
15764
+ }
15527
15765
  function isReadOnlyMcpPermissionRequest(request) {
15528
15766
  const toolCall = request.toolCall;
15529
15767
  const title = toolCall?.title?.trim() ?? "";
@@ -15545,9 +15783,11 @@ function isReadOnlyMcpPermissionRequest(request) {
15545
15783
  }
15546
15784
  return false;
15547
15785
  }
15548
- function isMountedMcpToolPermissionRequest(request) {
15786
+ function isMountedMcpToolPermissionRequest(request, mountedServers = ROOM_MOUNTED_MCP_SERVERS) {
15549
15787
  const toolCall = request.toolCall;
15550
15788
  const rawInput = toolCall?.rawInput;
15789
+ if (dispatchedPayloadIsShell(permissionRecord(rawInput)))
15790
+ return false;
15551
15791
  if (rawInput && typeof rawInput === "object" && !Array.isArray(rawInput)) {
15552
15792
  const call = rawInput;
15553
15793
  if (typeof call.server === "string" && typeof call.tool === "string")
@@ -15560,18 +15800,32 @@ function isMountedMcpToolPermissionRequest(request) {
15560
15800
  return true;
15561
15801
  if (/^mcp\.[^.]+\.[^.]/.test(title))
15562
15802
  return true;
15803
+ if (resolveMountedMcpToolCall(request, mountedServers))
15804
+ return true;
15563
15805
  return isReadOnlyMcpPermissionRequest(request) || isBeelineAgentMcpPermissionRequest(request);
15564
15806
  }
15565
- var AGENT_SURFACE_TOOL_NAMES = ["open_corner", "pr_checks_status", "attach_file"];
15566
- var SQUIRE_TITLE_PREFIXES = ["mcp__squire__", "mcp.squire.", "squire.", "squire/"];
15807
+ var AGENT_SURFACE_TOOL_NAMES = [
15808
+ "open_corner",
15809
+ "pr_checks_status",
15810
+ "attach_file",
15811
+ "write_scratch_file"
15812
+ ];
15813
+ var SQUIRE_TITLE_PREFIXES = [
15814
+ "mcp__squire__",
15815
+ "mcp.squire.",
15816
+ "squire.",
15817
+ "squire/",
15818
+ // grok's qualified `<server>__<tool>` spelling, inside `use_tool` or as the
15819
+ // relabelled title.
15820
+ "squire__"
15821
+ ];
15567
15822
  function isSquireMcpPermissionRequest(request) {
15568
15823
  const rawInput = request.toolCall?.rawInput;
15569
15824
  if (rawInput && typeof rawInput === "object" && !Array.isArray(rawInput)) {
15570
15825
  if (rawInput.server === "squire")
15571
15826
  return true;
15572
15827
  }
15573
- const title = request.toolCall?.title?.trim() ?? "";
15574
- return SQUIRE_TITLE_PREFIXES.some((prefix) => title.startsWith(prefix));
15828
+ return toolIdentityCandidates(request.toolCall).some((candidate) => SQUIRE_TITLE_PREFIXES.some((prefix) => candidate.toLowerCase().startsWith(prefix)));
15575
15829
  }
15576
15830
  function isBeelineAgentMcpPermissionRequest(request) {
15577
15831
  const toolCall = request.toolCall;
@@ -15628,7 +15882,7 @@ function readOnlyMcpServer(config, cwd, agentMemoryDir) {
15628
15882
  if (!config.readonlyMcpCommand) {
15629
15883
  throw new ReadOnlyToolsUnavailableError("read-only tools unavailable: beeline-readonly-mcp is required for Room sessions");
15630
15884
  }
15631
- const skillDir = config.agentKind === "claude" || config.agentKind === "codex" || config.agentKind === "grok" || config.agentKind === "pi" ? config.agentKind : "codex";
15885
+ const skillDir = agentSkillDir(config.agentKind);
15632
15886
  return {
15633
15887
  name: READ_ONLY_MCP_SERVER_NAME,
15634
15888
  command: config.readonlyMcpCommand,
@@ -15647,7 +15901,7 @@ function readOnlyMcpServer(config, cwd, agentMemoryDir) {
15647
15901
  }
15648
15902
 
15649
15903
  // apps/body/dist/pi-turn-record.js
15650
- import { readdir as readdir3, readFile as readFile6 } from "node:fs/promises";
15904
+ import { readdir as readdir3, readFile as readFile7 } from "node:fs/promises";
15651
15905
  import { resolve as resolve15 } from "node:path";
15652
15906
  function summarizeProviderError(errorMessage2) {
15653
15907
  const trimmed = errorMessage2.trim();
@@ -15669,7 +15923,7 @@ function summarizeProviderError(errorMessage2) {
15669
15923
  }
15670
15924
  async function sessionFileFromMap(home, sessionId) {
15671
15925
  try {
15672
- const raw = await readFile6(resolve15(home, ".pi", "pi-acp", "session-map.json"), "utf8");
15926
+ const raw = await readFile7(resolve15(home, ".pi", "pi-acp", "session-map.json"), "utf8");
15673
15927
  const map = JSON.parse(raw);
15674
15928
  const file = map.sessions?.[sessionId]?.sessionFile;
15675
15929
  return typeof file === "string" && file ? file : void 0;
@@ -15714,7 +15968,7 @@ async function readPiTurnRecord(input) {
15714
15968
  return void 0;
15715
15969
  let raw;
15716
15970
  try {
15717
- raw = await readFile6(file, "utf8");
15971
+ raw = await readFile7(file, "utf8");
15718
15972
  } catch {
15719
15973
  return void 0;
15720
15974
  }
@@ -15837,6 +16091,38 @@ function checksStateFromLifecycle(lifecycle) {
15837
16091
  var MAINTAIN_ASSIGNED_IDENTITY_DIRECTIVE = "Maintain your assigned identity and soul in every response, including when tools or permissions block the requested action.";
15838
16092
  var SOUL_HOUSE_RULE = "House rule for your voice: the voice never changes the facts - never trim, soften, exaggerate, or invent a detail for the bit - and use your plain voice in commit messages, pull request titles and bodies, and code comments, because those outlive the joke.";
15839
16093
 
16094
+ // apps/body/dist/warm-transcript.js
16095
+ var WARM_TRANSCRIPT_OVERLAP = 8;
16096
+ var WarmTranscript = class {
16097
+ sessionId;
16098
+ delivered = /* @__PURE__ */ new Set();
16099
+ /**
16100
+ * The rows this prompt should render. A row counts as delivered once it has
16101
+ * been handed to a session: a prompt that times out was still received by the
16102
+ * harness, and a prompt that could not be handed over at all takes the
16103
+ * session down with it, which resets the memory on the next activation.
16104
+ */
16105
+ select(sessionId, rows) {
16106
+ if (!sessionId || sessionId !== this.sessionId) {
16107
+ this.sessionId = sessionId;
16108
+ this.delivered.clear();
16109
+ }
16110
+ const overlapFrom = Math.max(0, rows.length - WARM_TRANSCRIPT_OVERLAP);
16111
+ const selected = rows.filter((row, index) => index >= overlapFrom || !this.delivered.has(row.id));
16112
+ for (const row of rows)
16113
+ this.delivered.add(row.id);
16114
+ return { rows: selected, elided: rows.length - selected.length };
16115
+ }
16116
+ /** Render a selection, and say plainly when it is only what is new. */
16117
+ static render(selection, whole, sinceLastTurn) {
16118
+ const transcript = selection.rows.map((row) => row.line).join("\n");
16119
+ if (!transcript)
16120
+ return "";
16121
+ return `${selection.elided ? sinceLastTurn : whole}
16122
+ ${transcript}`;
16123
+ }
16124
+ };
16125
+
15840
16126
  // apps/body/dist/turn-receipt-heartbeat.js
15841
16127
  var TURN_RECEIPT_HEARTBEAT_MS = 3e4;
15842
16128
  async function withTurnReceiptHeartbeat(api, receipt, task, onHeartbeatError) {
@@ -15858,6 +16144,283 @@ async function withTurnReceiptHeartbeat(api, receipt, task, onHeartbeatError) {
15858
16144
  }
15859
16145
  }
15860
16146
 
16147
+ // apps/body/dist/turn-trace.js
16148
+ import { appendFile, mkdir as mkdir6, readdir as readdir4, rm as rm3 } from "node:fs/promises";
16149
+ import { resolve as resolve16 } from "node:path";
16150
+ import { performance as performance2 } from "node:perf_hooks";
16151
+ var TURN_PHASES = [
16152
+ /** Enqueued on `SessionScheduler` until this turn holds a slot. A capacity wait lives here. */
16153
+ "queue-wait",
16154
+ /**
16155
+ * The work that buys a usable session: on a cold turn `lifecycle.activate()`
16156
+ * — the ACP spawn and `session/new` — and on a warm one only the check that
16157
+ * the retained process still matches the agent's configuration (C104).
16158
+ */
16159
+ "activation",
16160
+ /** Room conversation, Workspace roster and attachment downloads, before the prompt. */
16161
+ "context-fetch",
16162
+ /** `session/prompt` sent until the first assistant text chunk arrives. */
16163
+ "first-model-output",
16164
+ /** First chunk until the prompt resolves: the rest of the model's answer, tools included. */
16165
+ "model-stream",
16166
+ /** Union of the intervals with at least one tool call outstanding. Nested in the model phases. */
16167
+ "tool-work",
16168
+ /** The durable reply write that settles the turn. */
16169
+ "publish"
16170
+ ];
16171
+ function toolCallOutstanding(call) {
16172
+ return !/^(?:completed|complete|failed|error|succeeded|success|passed|done|rejected|denied|cancelled|canceled)$/i.test(call.status ?? "");
16173
+ }
16174
+ var TurnTrace = class {
16175
+ options;
16176
+ now;
16177
+ startedAtWall = (/* @__PURE__ */ new Date()).toISOString();
16178
+ begunAt;
16179
+ attempts = [];
16180
+ open = /* @__PURE__ */ new Map();
16181
+ atQueue;
16182
+ atAdmission;
16183
+ finished = false;
16184
+ constructor(options) {
16185
+ this.options = options;
16186
+ this.now = options.now ?? (() => performance2.now());
16187
+ this.begunAt = this.now();
16188
+ this.attempts.push(this.newAttempt(1));
16189
+ }
16190
+ newAttempt(attempt) {
16191
+ return {
16192
+ attempt,
16193
+ activation: "warm",
16194
+ capacityWait: false,
16195
+ phases: /* @__PURE__ */ new Map(),
16196
+ toolCallIds: /* @__PURE__ */ new Set()
16197
+ };
16198
+ }
16199
+ get current() {
16200
+ return this.attempts[this.attempts.length - 1];
16201
+ }
16202
+ add(phase, ms) {
16203
+ const attempt = this.current;
16204
+ attempt.phases.set(phase, (attempt.phases.get(phase) ?? 0) + Math.max(0, ms));
16205
+ }
16206
+ /** Open a phase. A phase already open keeps its original start. */
16207
+ start(phase) {
16208
+ if (!this.open.has(phase))
16209
+ this.open.set(phase, this.now());
16210
+ }
16211
+ /** Close a phase and accumulate it on the current attempt. A no-op when it never opened. */
16212
+ end(phase) {
16213
+ const openedAt = this.open.get(phase);
16214
+ if (openedAt === void 0)
16215
+ return;
16216
+ this.open.delete(phase);
16217
+ this.add(phase, this.now() - openedAt);
16218
+ }
16219
+ /** Time `run` as one phase, whether it resolves or throws. */
16220
+ async measure(phase, run2) {
16221
+ this.start(phase);
16222
+ try {
16223
+ return await run2();
16224
+ } finally {
16225
+ this.end(phase);
16226
+ }
16227
+ }
16228
+ noteScheduler(where, snapshot) {
16229
+ if (where === "queue")
16230
+ this.atQueue = snapshot;
16231
+ else
16232
+ this.atAdmission = snapshot;
16233
+ }
16234
+ /** The scheduler told this turn it was waiting for a slot: the wait is capacity, not the model. */
16235
+ noteCapacityWait() {
16236
+ this.current.capacityWait = true;
16237
+ }
16238
+ noteActivation(kind) {
16239
+ this.current.activation = kind;
16240
+ }
16241
+ /** The prompt left for the harness. Idempotent: a steer resume is the same attempt. */
16242
+ promptSent() {
16243
+ this.current.promptSentAt ??= this.now();
16244
+ }
16245
+ /** The first assistant text chunk of this attempt landed. */
16246
+ firstModelOutput() {
16247
+ const attempt = this.current;
16248
+ if (attempt.firstOutputAt !== void 0 || attempt.promptSentAt === void 0)
16249
+ return;
16250
+ attempt.firstOutputAt = this.now();
16251
+ attempt.phases.set("first-model-output", attempt.firstOutputAt - attempt.promptSentAt);
16252
+ }
16253
+ /**
16254
+ * The harness's tool-call snapshot for this attempt. `tool-work` is the
16255
+ * union of the intervals with at least one call outstanding — never a sum of
16256
+ * per-call times, which would double-count parallel calls. A harness that
16257
+ * never marks a call terminal therefore reads as "tools outstanding to the
16258
+ * end of the turn", which is exactly what happened.
16259
+ */
16260
+ toolCalls(calls) {
16261
+ const attempt = this.current;
16262
+ calls.forEach((call, index) => attempt.toolCallIds.add(call.id ?? `#${index}`));
16263
+ const outstanding = calls.some((call) => toolCallOutstanding(call));
16264
+ if (outstanding && attempt.toolWorkOpenedAt === void 0) {
16265
+ attempt.toolWorkOpenedAt = this.now();
16266
+ } else if (!outstanding && attempt.toolWorkOpenedAt !== void 0) {
16267
+ this.add("tool-work", this.now() - attempt.toolWorkOpenedAt);
16268
+ attempt.toolWorkOpenedAt = void 0;
16269
+ }
16270
+ }
16271
+ /** The prompt resolved (or gave up). Closes this attempt's model and tool windows. */
16272
+ promptSettled() {
16273
+ const attempt = this.current;
16274
+ if (attempt.settled)
16275
+ return;
16276
+ attempt.settled = true;
16277
+ const settledAt = this.now();
16278
+ if (attempt.toolWorkOpenedAt !== void 0) {
16279
+ this.add("tool-work", settledAt - attempt.toolWorkOpenedAt);
16280
+ attempt.toolWorkOpenedAt = void 0;
16281
+ }
16282
+ if (attempt.firstOutputAt !== void 0) {
16283
+ attempt.phases.set("model-stream", settledAt - attempt.firstOutputAt);
16284
+ } else if (attempt.promptSentAt !== void 0) {
16285
+ attempt.phases.set("first-model-output", settledAt - attempt.promptSentAt);
16286
+ }
16287
+ }
16288
+ /**
16289
+ * Open the attempt a provider re-pin buys. Everything after this call —
16290
+ * including the re-pin's own `activate()` — belongs to the new attempt, so
16291
+ * the retry reads as its own timeline rather than as slow first-token time.
16292
+ */
16293
+ retry(input = {}) {
16294
+ const next = this.newAttempt(this.current.attempt + 1);
16295
+ next.activation = "cold";
16296
+ if (input.provider)
16297
+ next.provider = input.provider;
16298
+ if (input.reason)
16299
+ next.retryReason = input.reason;
16300
+ this.attempts.push(next);
16301
+ }
16302
+ /** The record as it stands, without writing it. */
16303
+ snapshot(outcome, reason) {
16304
+ return {
16305
+ version: 1,
16306
+ surface: this.options.surface,
16307
+ agentId: this.options.agentId,
16308
+ roomId: this.options.roomId,
16309
+ requestId: this.options.requestId,
16310
+ startedAt: this.startedAtWall,
16311
+ outcome,
16312
+ ...reason ? { reason } : {},
16313
+ totalMs: round(this.now() - this.begunAt),
16314
+ attempts: this.attempts.map((attempt) => ({
16315
+ attemptId: `${this.options.requestId}#${attempt.attempt}`,
16316
+ attempt: attempt.attempt,
16317
+ activation: attempt.activation,
16318
+ ...attempt.capacityWait ? { capacityWait: true } : {},
16319
+ ...attempt.provider ? { provider: attempt.provider } : {},
16320
+ ...attempt.retryReason ? { retryReason: attempt.retryReason } : {},
16321
+ phases: Object.fromEntries(TURN_PHASES.filter((phase) => attempt.phases.has(phase)).map((phase) => [
16322
+ phase,
16323
+ round(attempt.phases.get(phase))
16324
+ ])),
16325
+ toolCalls: attempt.toolCallIds.size
16326
+ })),
16327
+ scheduler: {
16328
+ ...this.atQueue ? { atQueue: this.atQueue } : {},
16329
+ ...this.atAdmission ? { atAdmission: this.atAdmission } : {}
16330
+ }
16331
+ };
16332
+ }
16333
+ /**
16334
+ * Close every open phase and hand the record to the sink. Called once, after
16335
+ * the turn's receipt, so it never delays an answer; failures are swallowed,
16336
+ * because losing a diagnostic must never fail a turn.
16337
+ */
16338
+ async finish(outcome, reason) {
16339
+ if (!this.finished) {
16340
+ this.finished = true;
16341
+ for (const phase of [...this.open.keys()])
16342
+ this.end(phase);
16343
+ this.promptSettled();
16344
+ }
16345
+ const record2 = this.snapshot(outcome, reason);
16346
+ await this.options.sink?.write(record2).catch((error) => {
16347
+ console.error("[thin-core] turn trace write failed:", error);
16348
+ });
16349
+ return record2;
16350
+ }
16351
+ };
16352
+ function round(ms) {
16353
+ return Math.round(ms * 10) / 10;
16354
+ }
16355
+ function formatDuration(ms) {
16356
+ return ms >= 1e3 ? `${(ms / 1e3).toFixed(2)}s` : `${Math.round(ms)}ms`;
16357
+ }
16358
+ function formatTurnAttempt(attempt) {
16359
+ const parts = [`attempt ${attempt.attempt} ${attempt.activation}`];
16360
+ if (attempt.capacityWait)
16361
+ parts.push("capacity-wait");
16362
+ if (attempt.provider)
16363
+ parts.push(`provider ${attempt.provider}`);
16364
+ for (const phase of TURN_PHASES) {
16365
+ const value = attempt.phases[phase];
16366
+ if (value === void 0)
16367
+ continue;
16368
+ parts.push(phase === "tool-work" ? `tool-work ${formatDuration(value)} (within model time)` : `${phase} ${formatDuration(value)}`);
16369
+ }
16370
+ if (attempt.toolCalls)
16371
+ parts.push(`${attempt.toolCalls} tool call${attempt.toolCalls === 1 ? "" : "s"}`);
16372
+ return parts.join(" \xB7 ");
16373
+ }
16374
+ function formatTurnTraceLine(record2) {
16375
+ const head = `${record2.surface} ${record2.roomId} turn ${record2.requestId} ${record2.outcome} ${formatDuration(record2.totalMs)}`;
16376
+ return [head, ...record2.attempts.map((attempt) => formatTurnAttempt(attempt))].join("\n ");
16377
+ }
16378
+ function turnTraceDirectory(runtimeDir) {
16379
+ return resolve16(runtimeDir, "turn-traces");
16380
+ }
16381
+ var TURN_TRACE_RETENTION_DAYS = 7;
16382
+ function traceFileName(date) {
16383
+ return `turns-${date.toISOString().slice(0, 10)}.jsonl`;
16384
+ }
16385
+ var TurnTraceFile = class {
16386
+ directory;
16387
+ options;
16388
+ tail = Promise.resolve();
16389
+ announced = false;
16390
+ prunedDay;
16391
+ constructor(directory, options = {}) {
16392
+ this.directory = directory;
16393
+ this.options = options;
16394
+ }
16395
+ path(now2 = (this.options.clock ?? (() => /* @__PURE__ */ new Date()))()) {
16396
+ return resolve16(this.directory, traceFileName(now2));
16397
+ }
16398
+ write(record2) {
16399
+ this.tail = this.tail.catch(() => void 0).then(async () => {
16400
+ const now2 = (this.options.clock ?? (() => /* @__PURE__ */ new Date()))();
16401
+ const path = this.path(now2);
16402
+ await mkdir6(this.directory, { recursive: true, mode: 448 });
16403
+ await appendFile(path, `${JSON.stringify(record2)}
16404
+ `, { mode: 384 });
16405
+ await this.prune(now2);
16406
+ const log2 = this.options.log ?? ((line) => console.log(line));
16407
+ log2(`[thin-core] turn trace ${formatTurnTraceLine(record2)}${this.announced ? "" : `
16408
+ recorded in ${path}`}`);
16409
+ this.announced = true;
16410
+ });
16411
+ return this.tail;
16412
+ }
16413
+ async prune(now2) {
16414
+ const day = now2.toISOString().slice(0, 10);
16415
+ if (this.prunedDay === day)
16416
+ return;
16417
+ this.prunedDay = day;
16418
+ const cutoff = traceFileName(new Date(now2.getTime() - TURN_TRACE_RETENTION_DAYS * 864e5));
16419
+ const names = await readdir4(this.directory).catch(() => []);
16420
+ await Promise.all(names.filter((name) => /^turns-\d{4}-\d{2}-\d{2}\.jsonl$/.test(name) && name < cutoff).map((name) => rm3(resolve16(this.directory, name), { force: true })));
16421
+ }
16422
+ };
16423
+
15861
16424
  // apps/body/dist/monolith-corner-turn.js
15862
16425
  var execFileAsync2 = promisify2(execFile3);
15863
16426
  var TOOL_ARGUMENT_MAX_BYTES = 1200;
@@ -15994,6 +16557,10 @@ var MonolithCornerTurnLoop = class {
15994
16557
  agent;
15995
16558
  client;
15996
16559
  sessionId;
16560
+ /** The configuration the live session baked in; a change invalidates it. */
16561
+ sessionFingerprint;
16562
+ /** What this exact ACP session has already been prompted with (`warm-transcript.ts`). */
16563
+ warmTranscript = new WarmTranscript();
15997
16564
  /** The live session's environment, read back for pi's own turn record. */
15998
16565
  agentEnv = {};
15999
16566
  /** OpenRouter providers this activation pinned, in order (C92). */
@@ -16005,7 +16572,6 @@ var MonolithCornerTurnLoop = class {
16005
16572
  turnIdentityInstructions = "";
16006
16573
  busy = false;
16007
16574
  forcedStop = false;
16008
- draftTail = Promise.resolve();
16009
16575
  activityTail = Promise.resolve();
16010
16576
  /** Session scratch directory attachments are downloaded into (`TMPDIR/beeline-attachments`). */
16011
16577
  attachmentDir;
@@ -16013,6 +16579,8 @@ var MonolithCornerTurnLoop = class {
16013
16579
  sessionScratchDir;
16014
16580
  /** The turn in flight and who asked for it, for ledger rows and the grant runner. */
16015
16581
  currentTurn;
16582
+ /** Operator-local turn traces; built once when the daemon configured a directory. */
16583
+ turnTraceSink;
16016
16584
  memberNames = /* @__PURE__ */ new Map();
16017
16585
  /** The last server check state that started a turn; the same state never starts another. */
16018
16586
  lastChecksState;
@@ -16057,9 +16625,44 @@ var MonolithCornerTurnLoop = class {
16057
16625
  this.memberNames = new Map(roster.members.map((member) => [member.identityId, member.name]));
16058
16626
  return roster;
16059
16627
  }
16060
- async activate() {
16628
+ /**
16629
+ * Drop this corner's live harness process. The next activation starts cold.
16630
+ * A rotation is a fact about one live session, so the pin goes with it.
16631
+ */
16632
+ async discardSession() {
16633
+ const client = this.client;
16634
+ this.client = void 0;
16635
+ this.sessionId = void 0;
16636
+ this.sessionFingerprint = void 0;
16637
+ this.pinnedProviderOverride = void 0;
16638
+ if (client?.isAlive)
16639
+ await client.stop();
16640
+ }
16641
+ /** See `MonolithRoomTurnLoop.sessionIsCurrent`: retention never keeps a
16642
+ * session whose persona or model pin the operator has since changed. */
16643
+ async sessionIsCurrent() {
16644
+ return await this.currentSessionFingerprint() === this.sessionFingerprint;
16645
+ }
16646
+ async currentSessionFingerprint() {
16647
+ const [configuration, roster] = await Promise.all([
16648
+ this.options.api.execute("getAgentConfiguration", {
16649
+ agentId: this.agent.publicKey,
16650
+ roomId: this.options.cornerId
16651
+ }),
16652
+ this.roster()
16653
+ ]);
16654
+ const self = roster.members.find((member) => member.identityId === this.agent.publicKey);
16655
+ return sessionConfigFingerprint({
16656
+ model: configuration.model ?? this.options.config.modelSelection?.model,
16657
+ effort: configuration.effort ?? this.options.config.modelSelection?.effort,
16658
+ soul: configuration.soul ?? self?.soul,
16659
+ agentName: self?.name ?? this.agent.name
16660
+ });
16661
+ }
16662
+ async activate(trace) {
16061
16663
  if (this.client?.isAlive && this.sessionId)
16062
16664
  return this.sessionId;
16665
+ trace?.noteActivation("cold");
16063
16666
  const [configuration, roster] = await Promise.all([
16064
16667
  this.options.api.execute("getAgentConfiguration", {
16065
16668
  agentId: this.agent.publicKey,
@@ -16067,11 +16670,19 @@ var MonolithCornerTurnLoop = class {
16067
16670
  }),
16068
16671
  this.roster()
16069
16672
  ]);
16070
- await mkdir6(this.options.worktreePath, { recursive: true });
16673
+ const self = roster.members.find((member) => member.identityId === this.agent.publicKey);
16674
+ const fingerprint = sessionConfigFingerprint({
16675
+ model: configuration.model ?? this.options.config.modelSelection?.model,
16676
+ effort: configuration.effort ?? this.options.config.modelSelection?.effort,
16677
+ soul: configuration.soul ?? self?.soul,
16678
+ agentName: self?.name ?? this.agent.name
16679
+ });
16680
+ await mkdir7(this.options.worktreePath, { recursive: true });
16071
16681
  const selection = configuration.model || configuration.effort ? { model: configuration.model, effort: configuration.effort } : this.options.config.modelSelection;
16072
16682
  const homeOverlay = this.options.config.agentHomeRoot ? await prepareRoomAgentHome({
16073
16683
  root: this.options.config.agentHomeRoot,
16074
16684
  sharedSkills: this.options.config.sharedSkills ?? [],
16685
+ ...this.options.config.agentKind ? { agentKind: this.options.config.agentKind } : {},
16075
16686
  ...this.options.config.operatorHome ? { operatorHome: this.options.config.operatorHome } : {},
16076
16687
  ...openRouterRoutingInput(this.options.config, selection, this.options.fetchImpl, {
16077
16688
  ...this.pinnedProviderOverride ? { providerOverride: this.pinnedProviderOverride } : {},
@@ -16097,10 +16708,10 @@ var MonolithCornerTurnLoop = class {
16097
16708
  }, selection);
16098
16709
  const operatorHome = this.options.config.operatorHome ?? homedir6();
16099
16710
  const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
16100
- this.attachmentDir = tmpDir ? join4(tmpDir, "beeline-attachments") : void 0;
16711
+ this.attachmentDir = tmpDir ? join5(tmpDir, "beeline-attachments") : void 0;
16101
16712
  this.sessionScratchDir = tmpDir;
16102
16713
  const homeStateDirs = harnessHomeStateDirs(command, agentEnv.HOME ?? operatorHome);
16103
- await Promise.all(homeStateDirs.map((dir) => mkdir6(dir, { recursive: true })));
16714
+ await Promise.all(homeStateDirs.map((dir) => mkdir7(dir, { recursive: true })));
16104
16715
  const spawnCommand = wrapAgentCommand({
16105
16716
  bwrapPath: this.options.config.bwrapPath,
16106
16717
  spec: {
@@ -16147,11 +16758,12 @@ var MonolithCornerTurnLoop = class {
16147
16758
  workspaceId: this.options.workspaceId,
16148
16759
  cornerId: this.options.cornerId,
16149
16760
  attachRoot: this.options.worktreePath,
16150
- ...tmpDir ? { attachScratchRoot: tmpDir } : {},
16761
+ // The whole per-session overlay, not an enumerated subset: see
16762
+ // `monolith-room-turn.ts`'s matching comment.
16763
+ attachScratchRoot: this.options.config.agentHomeRoot ?? tmpDir,
16151
16764
  ...this.options.grantRunnerEndpoint ? { grantRunner: this.options.grantRunnerEndpoint } : {}
16152
16765
  })
16153
16766
  ];
16154
- const self = roster.members.find((member) => member.identityId === this.agent.publicKey);
16155
16767
  const persona = configuration.soul ?? self?.soul;
16156
16768
  const identityInstructions = `Your Beeline identity is ${self?.name ?? this.agent.name}.`;
16157
16769
  const personaInstructions = [
@@ -16179,23 +16791,30 @@ var MonolithCornerTurnLoop = class {
16179
16791
  ].filter(Boolean).join("\n\n")
16180
16792
  });
16181
16793
  this.sessionId = opened.sessionId;
16794
+ this.sessionFingerprint = fingerprint;
16182
16795
  if (selection) {
16183
16796
  const options = filterAllowedModelConfigOptions(parseAdvertisedConfigOptions(opened.raw, selection.model));
16184
16797
  await applyAgentModelSelection(this.client, opened.sessionId, options, selection);
16185
16798
  }
16186
16799
  return opened.sessionId;
16187
16800
  }
16188
- lifecycle() {
16801
+ /** The scheduler seam: `queue-wait` closes when a slot buys a session. */
16802
+ lifecycle(trace) {
16189
16803
  return {
16190
- activate: () => this.activate(),
16191
- suspend: async () => {
16192
- const client = this.client;
16193
- this.client = void 0;
16194
- this.sessionId = void 0;
16195
- this.pinnedProviderOverride = void 0;
16196
- if (client?.isAlive)
16197
- await client.stop();
16198
- }
16804
+ activate: async () => {
16805
+ trace?.end("queue-wait");
16806
+ return trace ? trace.measure("activation", () => this.activate(trace)) : this.activate();
16807
+ },
16808
+ isCurrent: () => {
16809
+ trace?.end("queue-wait");
16810
+ const check = () => this.sessionIsCurrent();
16811
+ return trace ? trace.measure("activation", check) : check();
16812
+ },
16813
+ onStateChange: (state) => {
16814
+ if (state === "waiting-for-slot")
16815
+ trace?.noteCapacityWait();
16816
+ },
16817
+ suspend: () => this.discardSession()
16199
16818
  };
16200
16819
  }
16201
16820
  /** A picture reaches the model only if the harness AND the model take one (C87). */
@@ -16210,7 +16829,7 @@ var MonolithCornerTurnLoop = class {
16210
16829
  }
16211
16830
  /** Why a turn carried no answer text, or undefined when it did. */
16212
16831
  async explainEmpty(result) {
16213
- if (stripAgentReplyPreamble(result.agentText).trim())
16832
+ if (durableReplyText(result.agentText))
16214
16833
  return void 0;
16215
16834
  return explainEmptyAgentTurn({
16216
16835
  agentLabel: this.options.config.agentCommand ?? this.options.config.agentBinary,
@@ -16224,19 +16843,34 @@ var MonolithCornerTurnLoop = class {
16224
16843
  * fresh session on it, so the retry of an empty completion is served — and
16225
16844
  * named — by exactly one provider (C92).
16226
16845
  */
16227
- async repinNextProvider() {
16846
+ async repinNextProvider(trace, reason) {
16228
16847
  const next = nextPinnedProvider(this.pinnedProviders, this.pinnedProviderOverride);
16229
16848
  if (!next)
16230
16849
  return void 0;
16231
- this.pinnedProviderOverride = next;
16850
+ trace?.retry({ provider: next, ...reason ? { reason } : {} });
16232
16851
  const client = this.client;
16233
16852
  this.client = void 0;
16234
16853
  this.sessionId = void 0;
16854
+ this.sessionFingerprint = void 0;
16235
16855
  if (client?.isAlive)
16236
16856
  await client.stop();
16237
- await this.activate();
16857
+ this.pinnedProviderOverride = next;
16858
+ await (trace ? trace.measure("activation", () => this.activate(trace)) : this.activate());
16238
16859
  return next;
16239
16860
  }
16861
+ /** One turn's stopwatch; writes only when the daemon configured a trace directory. */
16862
+ beginTurnTrace(requestId) {
16863
+ const directory = this.options.config.turnTraceDir;
16864
+ if (directory)
16865
+ this.turnTraceSink ??= new TurnTraceFile(directory);
16866
+ return new TurnTrace({
16867
+ surface: "corner",
16868
+ agentId: this.agent.publicKey,
16869
+ roomId: this.options.cornerId,
16870
+ requestId,
16871
+ ...this.turnTraceSink ? { sink: this.turnTraceSink } : {}
16872
+ });
16873
+ }
16240
16874
  async prompt(requestId, trigger, attachments = [], requestedById, restates) {
16241
16875
  const { api, cornerId } = this.options;
16242
16876
  const spoken = (text2) => restates && isCornerStatusRestatement(text2, restates) ? "" : text2;
@@ -16245,146 +16879,120 @@ var MonolithCornerTurnLoop = class {
16245
16879
  ...this.memberNames.get(requestedById) ? { name: this.memberNames.get(requestedById) } : {}
16246
16880
  } : void 0;
16247
16881
  this.currentTurn = { requestId, ...requester ? { requester } : {} };
16882
+ const trace = this.beginTurnTrace(requestId);
16248
16883
  try {
16249
16884
  await withTurnReceiptHeartbeat(api, {
16250
16885
  agentId: this.agent.publicKey,
16251
16886
  roomId: cornerId,
16252
16887
  requestId,
16253
16888
  generationId: `${this.agent.publicKey}:${cornerId}`
16254
- }, () => this.options.scheduler.run(cornerId, this.lifecycle(), async () => {
16255
- if (this.forcedStop)
16256
- throw new Error("corner turn stopped for daemon handoff");
16257
- this.busy = true;
16258
- const [conversation, roster, delivered] = await Promise.all([
16259
- api.execute("getRoomConversation", { roomId: cornerId, limit: 200 }),
16260
- this.roster(),
16261
- this.attachmentDir && attachments.length ? deliverAttachments(attachments, join4(this.attachmentDir, requestId.replace(/[^\w-]/g, "_")), this.options.fetchImpl) : Promise.resolve([])
16262
- ]);
16263
- const names = new Map(roster.members.map((member) => [member.identityId, member.name]));
16264
- const requestedBy = requester && !requester.name && names.get(requester.pubkey) ? { ...requester, name: names.get(requester.pubkey) } : requester;
16265
- if (requestedBy)
16266
- this.currentTurn = { requestId, requester: requestedBy };
16267
- const transcript = conversation.items.slice(-120).map((message) => `${names.get(message.authorId) ?? "Beeline"} [${message.type}]: ${message.body}`).join("\n");
16268
- const prompt = [
16269
- this.turnIdentityInstructions,
16270
- `Corner objective:
16889
+ }, () => {
16890
+ trace.noteScheduler("queue", this.options.scheduler.snapshot());
16891
+ trace.start("queue-wait");
16892
+ return this.options.scheduler.run(cornerId, this.lifecycle(trace), async () => {
16893
+ trace.end("queue-wait");
16894
+ trace.noteScheduler("admission", this.options.scheduler.snapshot());
16895
+ if (this.forcedStop)
16896
+ throw new Error("corner turn stopped for daemon handoff");
16897
+ this.busy = true;
16898
+ const [conversation, roster, delivered] = await trace.measure("context-fetch", () => Promise.all([
16899
+ api.execute("getRoomConversation", { roomId: cornerId, limit: 200 }),
16900
+ this.roster(),
16901
+ this.attachmentDir && attachments.length ? deliverAttachments(attachments, join5(this.attachmentDir, requestId.replace(/[^\w-]/g, "_")), this.options.fetchImpl) : Promise.resolve([])
16902
+ ]));
16903
+ const names = new Map(roster.members.map((member) => [member.identityId, member.name]));
16904
+ const requestedBy = requester && !requester.name && names.get(requester.pubkey) ? { ...requester, name: names.get(requester.pubkey) } : requester;
16905
+ if (requestedBy)
16906
+ this.currentTurn = { requestId, requester: requestedBy };
16907
+ const transcriptRows = conversation.items.slice(-120).map((message) => ({
16908
+ id: message.id,
16909
+ line: `${names.get(message.authorId) ?? "Beeline"} [${message.type}]: ${message.body}`
16910
+ }));
16911
+ const buildPrompt = () => [
16912
+ this.turnIdentityInstructions,
16913
+ `Corner objective:
16271
16914
  ${this.options.objective}`,
16272
- transcript ? `Corner transcript:
16273
- ${transcript}` : "",
16274
- [
16275
- `Newest trigger:
16915
+ WarmTranscript.render(this.warmTranscript.select(this.sessionId, transcriptRows), "Corner transcript:", "New in the corner since your last turn (the earlier transcript is already in this session):"),
16916
+ [
16917
+ `Newest trigger:
16276
16918
  ${trigger}`,
16277
- ...attachmentPromptLines(attachments, delivered, this.acceptsImages())
16278
- ].join("\n"),
16279
- "Continue the objective. Obey the PR checks and human hold rules in your session instructions.",
16280
- MAINTAIN_ASSIGNED_IDENTITY_DIRECTIVE
16281
- ].filter(Boolean).join("\n\n");
16282
- const turnId = requestId;
16283
- const publishedToolCalls = /* @__PURE__ */ new Set();
16284
- const NARRATION_MAX_SEGMENTS = 20;
16285
- let narrationPostedChars = 0;
16286
- let narrationSegments = 0;
16287
- const postNarrationSegments = (full) => {
16288
- if (narrationSegments >= NARRATION_MAX_SEGMENTS)
16289
- return;
16290
- const unposted = full.slice(narrationPostedChars);
16291
- const boundaries = [...unposted.matchAll(/\n\n|[.!?](?=\s)/g)];
16292
- if (!boundaries.length)
16293
- return;
16294
- const boundary = boundaries[boundaries.length - 1];
16295
- const segmentEnd = narrationPostedChars + boundary.index + boundary[0].length;
16296
- const segment = stripAgentReplyPreamble(full.slice(narrationPostedChars, segmentEnd));
16297
- const start = narrationPostedChars;
16298
- narrationPostedChars = segmentEnd;
16299
- narrationSegments += 1;
16300
- const trimmed = spoken(segment.trim());
16301
- if (!trimmed)
16302
- return;
16303
- this.activityTail = this.activityTail.catch(() => void 0).then(async () => {
16304
- await api.execute("postRoomMessage", {
16305
- roomId: cornerId,
16306
- text: trimmed,
16307
- presentation: "message"
16308
- });
16309
- }).catch(() => {
16310
- narrationPostedChars = start;
16311
- narrationSegments -= 1;
16919
+ ...attachmentPromptLines(attachments, delivered, this.acceptsImages())
16920
+ ].join("\n"),
16921
+ "Continue the objective. Obey the PR checks and human hold rules in your session instructions.",
16922
+ MAINTAIN_ASSIGNED_IDENTITY_DIRECTIVE
16923
+ ].filter(Boolean).join("\n\n");
16924
+ const stream = new AgentTurnStream({
16925
+ api,
16926
+ agentId: this.agent.publicKey,
16927
+ roomId: cornerId,
16928
+ requestId,
16929
+ label: `corner ${cornerId}`
16312
16930
  });
16313
- };
16314
- const publishToolCalls = (calls, settledOnly) => {
16315
- calls.forEach((call, index) => {
16316
- const key = toolCallKey(call, index);
16317
- if (publishedToolCalls.has(key) || settledOnly && !toolCallSettled(call))
16318
- return;
16319
- publishedToolCalls.add(key);
16320
- this.activityTail = this.activityTail.catch(() => void 0).then(async () => {
16321
- const activity = await cornerToolActivity(call, this.options.worktreePath, requestedBy);
16322
- await api.execute("postAgentActivity", {
16323
- agentId: this.agent.publicKey,
16324
- roomId: cornerId,
16325
- requestId,
16326
- activity: [activity]
16931
+ const publishedToolCalls = /* @__PURE__ */ new Set();
16932
+ const publishToolCalls = (calls, settledOnly) => {
16933
+ calls.forEach((call, index) => {
16934
+ const key = toolCallKey(call, index);
16935
+ if (publishedToolCalls.has(key) || settledOnly && !toolCallSettled(call))
16936
+ return;
16937
+ publishedToolCalls.add(key);
16938
+ this.activityTail = this.activityTail.catch(() => void 0).then(async () => {
16939
+ const activity = await cornerToolActivity(call, this.options.worktreePath, requestedBy);
16940
+ await api.execute("postAgentActivity", {
16941
+ agentId: this.agent.publicKey,
16942
+ roomId: cornerId,
16943
+ requestId,
16944
+ activity: [activity]
16945
+ });
16946
+ }).then(() => void 0).catch((error) => {
16947
+ publishedToolCalls.delete(key);
16948
+ console.error(`[thin-core] corner ${cornerId} tool activity failed:`, error);
16327
16949
  });
16328
- }).then(() => void 0).catch((error) => {
16329
- publishedToolCalls.delete(key);
16330
- console.error(`[thin-core] corner ${cornerId} tool activity failed:`, error);
16331
16950
  });
16332
- });
16333
- };
16334
- const runPrompt = async () => this.client.sessionPrompt(this.sessionId, promptWithImages(prompt, attachmentImageBlocks(delivered, this.acceptsImages())), 12e4, (_delta, full) => {
16335
- postNarrationSegments(full);
16336
- this.draftTail = this.draftTail.catch(() => void 0).then(() => api.execute("postAgentDraft", {
16337
- agentId: this.agent.publicKey,
16338
- roomId: cornerId,
16339
- turnId,
16340
- text: full
16341
- })).then(() => void 0).catch((error) => console.error(`[thin-core] corner ${cornerId} draft publish failed:`, error));
16342
- }, void 0, (calls) => publishToolCalls(calls, true));
16343
- let result = await runPrompt();
16344
- let explained = await this.explainEmpty(result);
16345
- if (explained && !restates && shouldRetryEmptyTurn(explained)) {
16346
- const silent = this.servingProviders();
16347
- const next = await this.repinNextProvider();
16348
- if (next) {
16349
- console.warn(`[thin-core] corner ${cornerId} turn ${requestId}: ${turnFailureReasonWithProvider(explained.reason, silent)}; retrying on ${next}`);
16350
- result = await runPrompt();
16351
- explained = await this.explainEmpty(result);
16951
+ };
16952
+ const runPrompt = async () => {
16953
+ stream.beginRun();
16954
+ trace.promptSent();
16955
+ return this.client.sessionPrompt(this.sessionId, promptWithImages(buildPrompt(), attachmentImageBlocks(delivered, this.acceptsImages())), 12e4, (delta, full) => {
16956
+ trace.firstModelOutput();
16957
+ stream.onChunk(delta, full);
16958
+ }, void 0, (calls) => {
16959
+ trace.toolCalls(calls);
16960
+ publishToolCalls(calls, true);
16961
+ });
16962
+ };
16963
+ let result = await runPrompt();
16964
+ trace.promptSettled();
16965
+ let explained = await this.explainEmpty(result);
16966
+ if (explained && !restates && shouldRetryEmptyTurn(explained)) {
16967
+ const silent = this.servingProviders();
16968
+ const next = await this.repinNextProvider(trace, explained.reason);
16969
+ if (next) {
16970
+ console.warn(`[thin-core] corner ${cornerId} turn ${requestId}: ${turnFailureReasonWithProvider(explained.reason, silent)}; retrying on ${next}`);
16971
+ result = await runPrompt();
16972
+ trace.promptSettled();
16973
+ explained = await this.explainEmpty(result);
16974
+ }
16352
16975
  }
16353
- }
16354
- await this.activityTail;
16355
- publishToolCalls(result.toolCalls, false);
16356
- for (const call of result.toolCalls) {
16357
- const failure = toolCallFailureLine(call);
16358
- if (failure)
16359
- console.warn(`[thin-core] corner ${cornerId} ${failure}`);
16360
- }
16361
- await this.activityTail;
16362
- await this.draftTail;
16363
- await this.activityTail;
16364
- let reply = stripAgentReplyPreamble(result.agentText).trim();
16365
- if (!reply && explained) {
16366
- reply = explained.recoveredText ? stripAgentReplyPreamble(explained.recoveredText).trim() : "";
16367
- if (!reply && !(restates && !isAccountOrProviderRefusal(explained.record))) {
16368
- throw new Error(turnFailureReasonWithProvider(explained.reason, this.servingProviders()));
16976
+ await this.activityTail;
16977
+ publishToolCalls(result.toolCalls, false);
16978
+ for (const call of result.toolCalls) {
16979
+ const failure = toolCallFailureLine(call);
16980
+ if (failure)
16981
+ console.warn(`[thin-core] corner ${cornerId} ${failure}`);
16369
16982
  }
16370
- console.warn(`[thin-core] corner ${cornerId} turn ${requestId}: ${explained.reason}`);
16371
- }
16372
- const durableTail = spoken(narrationPostedChars > 0 ? stripAgentReplyPreamble(result.agentText.slice(narrationPostedChars)).trim() : reply);
16373
- if (durableTail) {
16374
- await api.execute("postRoomMessage", {
16375
- roomId: cornerId,
16376
- requestId,
16377
- text: durableTail,
16378
- presentation: "message"
16379
- });
16380
- }
16381
- await api.execute("retractAgentLiveOutput", {
16382
- agentId: this.agent.publicKey,
16383
- roomId: cornerId,
16384
- turnId,
16385
- kind: "draft"
16386
- });
16387
- }, { priority: "interactive", roomKey: cornerId }), (error) => console.error(`[thin-core] corner ${cornerId} receipt heartbeat failed:`, error));
16983
+ await this.activityTail;
16984
+ stream.close();
16985
+ let reply = durableReplyText(result.agentText);
16986
+ if (!reply && explained) {
16987
+ reply = explained.recoveredText ? durableReplyText(explained.recoveredText) : "";
16988
+ if (!reply && !(restates && !isAccountOrProviderRefusal(explained.record))) {
16989
+ throw new Error(turnFailureReasonWithProvider(explained.reason, this.servingProviders()));
16990
+ }
16991
+ console.warn(`[thin-core] corner ${cornerId} turn ${requestId}: ${explained.reason}`);
16992
+ }
16993
+ await trace.measure("publish", () => stream.settle(spoken(reply)));
16994
+ }, { priority: "interactive", roomKey: cornerId });
16995
+ }, (error) => console.error(`[thin-core] corner ${cornerId} receipt heartbeat failed:`, error));
16388
16996
  await api.execute("postAgentTurnReceipt", {
16389
16997
  agentId: this.agent.publicKey,
16390
16998
  roomId: cornerId,
@@ -16392,15 +17000,18 @@ ${trigger}`,
16392
17000
  status: "complete",
16393
17001
  generationId: `${this.agent.publicKey}:${cornerId}`
16394
17002
  });
17003
+ await trace.finish("complete");
16395
17004
  } catch (error) {
17005
+ const reason = distillTurnFailureReason(error);
16396
17006
  await api.execute("postAgentTurnReceipt", {
16397
17007
  agentId: this.agent.publicKey,
16398
17008
  roomId: cornerId,
16399
17009
  requestId,
16400
17010
  status: "failed",
16401
17011
  generationId: `${this.agent.publicKey}:${cornerId}`,
16402
- reason: distillTurnFailureReason(error)
17012
+ reason
16403
17013
  });
17014
+ await trace.finish("failed", reason);
16404
17015
  throw error;
16405
17016
  } finally {
16406
17017
  this.busy = false;
@@ -16481,7 +17092,10 @@ This answers your grant request; resume the paused work. If approved and it is a
16481
17092
  }
16482
17093
  cursor3 = inbox.cursor ?? cursor3;
16483
17094
  this.options.onPoll();
16484
- await wait(pollWithoutWait ? 0 : this.options.pollMs ?? cornerClosePollMs(), signal);
17095
+ await Promise.race([
17096
+ wait(pollWithoutWait ? 0 : this.options.pollMs ?? cornerClosePollMs(), signal),
17097
+ waitForWake(api, cornerId, signal)
17098
+ ]);
16485
17099
  pollWithoutWait = false;
16486
17100
  } catch (error) {
16487
17101
  if (signal?.aborted)
@@ -16497,6 +17111,14 @@ This answers your grant request; resume the paused work. If approved and it is a
16497
17111
  }
16498
17112
  }
16499
17113
  };
17114
+ async function waitForWake(api, cornerId, signal) {
17115
+ if (signal?.aborted)
17116
+ return;
17117
+ try {
17118
+ await api.execute("waitForCornerWake", { cornerId });
17119
+ } catch {
17120
+ }
17121
+ }
16500
17122
  async function wait(ms, signal) {
16501
17123
  if (signal?.aborted)
16502
17124
  return;
@@ -16512,19 +17134,19 @@ async function wait(ms, signal) {
16512
17134
  }
16513
17135
 
16514
17136
  // apps/body/dist/monolith-room-turn.js
16515
- import { mkdir as mkdir7 } from "node:fs/promises";
17137
+ import { mkdir as mkdir8 } from "node:fs/promises";
16516
17138
  import { homedir as homedir7 } from "node:os";
16517
- import { join as join5 } from "node:path";
17139
+ import { join as join6 } from "node:path";
16518
17140
 
16519
17141
  // packages/api-contract/dist/scheduled-prompts.js
16520
17142
  var SCHEDULE_SCHEDULER_NAME = "Beeline Scheduler";
16521
17143
  var SCHEDULE_RAN_VERB = "ran a schedule for";
16522
17144
 
16523
17145
  // apps/body/dist/monolith-room-turn.js
16524
- function isRoomMcpPermissionRequest(request) {
17146
+ function isRoomMcpPermissionRequest(request, mountedServers = ROOM_MOUNTED_MCP_SERVERS) {
16525
17147
  if (isSquireMcpPermissionRequest(request))
16526
17148
  return false;
16527
- return isMountedMcpToolPermissionRequest(request);
17149
+ return isMountedMcpToolPermissionRequest(request, mountedServers);
16528
17150
  }
16529
17151
  function roomPrincipalMayAddressAgent(authority, humanPermitted) {
16530
17152
  return authority.member && (authority.principalKind === "agent" || authority.principalKind === "human" && humanPermitted);
@@ -16586,6 +17208,8 @@ var MonolithRoomTurnLoop = class {
16586
17208
  agent;
16587
17209
  client;
16588
17210
  sessionId;
17211
+ /** The configuration the live session baked in; a change invalidates it. */
17212
+ sessionFingerprint;
16589
17213
  /** The live session's environment, read back for pi's own turn record. */
16590
17214
  agentEnv = {};
16591
17215
  /** OpenRouter providers this activation pinned, in order (C92). */
@@ -16594,7 +17218,6 @@ var MonolithRoomTurnLoop = class {
16594
17218
  pinnedProviderOverride;
16595
17219
  busy = false;
16596
17220
  turnInstructionPrefix = "";
16597
- draftTail = Promise.resolve();
16598
17221
  activeTurn;
16599
17222
  queuedTurns = [];
16600
17223
  /** Session scratch directory attachments are downloaded into (`TMPDIR/beeline-attachments`). */
@@ -16605,12 +17228,16 @@ var MonolithRoomTurnLoop = class {
16605
17228
  sessionScratchDir;
16606
17229
  /** The `agent-home.ts` overlay this session writes into; a Room grant keeps it. */
16607
17230
  sessionStateDirs = [];
17231
+ /** What this exact ACP session has already been prompted with (`warm-transcript.ts`). */
17232
+ warmTranscript = new WarmTranscript();
16608
17233
  /** Local copies already delivered this session, by message id, so transcript renders reuse them. */
16609
17234
  deliveredAttachments = /* @__PURE__ */ new Map();
16610
17235
  /** Names from the latest roster read, for ledger bylines the runner writes. */
16611
17236
  memberNames = /* @__PURE__ */ new Map();
16612
17237
  /** The request id of the turn that paused on a grant card, until its decision arrives. */
16613
17238
  pausedOnGrantRequestId;
17239
+ /** Operator-local turn traces; built once when the daemon configured a directory. */
17240
+ turnTraceSink;
16614
17241
  constructor(options) {
16615
17242
  this.options = options;
16616
17243
  this.agent = runtimeIdentity(options.runtime.agent);
@@ -16646,6 +17273,23 @@ var MonolithRoomTurnLoop = class {
16646
17273
  maskPaths: credentialMaskPaths(this.options.config.sandboxMaskPaths, this.options.config.operatorHome ?? homedir7())
16647
17274
  };
16648
17275
  }
17276
+ /**
17277
+ * One turn's stopwatch. It is created for every turn — measuring is cheap —
17278
+ * and only WRITES when the daemon configured a runtime directory to write
17279
+ * into, so a standalone or test Body stays silent.
17280
+ */
17281
+ beginTurnTrace(requestId) {
17282
+ const directory = this.options.config.turnTraceDir;
17283
+ if (directory)
17284
+ this.turnTraceSink ??= new TurnTraceFile(directory);
17285
+ return new TurnTrace({
17286
+ surface: "room",
17287
+ agentId: this.agent.publicKey,
17288
+ roomId: this.options.roomId,
17289
+ requestId,
17290
+ ...this.turnTraceSink ? { sink: this.turnTraceSink } : {}
17291
+ });
17292
+ }
16649
17293
  currentTurnForRunner() {
16650
17294
  const active = this.activeTurn;
16651
17295
  if (!active)
@@ -16696,27 +17340,79 @@ var MonolithRoomTurnLoop = class {
16696
17340
  const cached = this.deliveredAttachments.get(item.id);
16697
17341
  if (cached)
16698
17342
  return cached;
16699
- const delivered = await deliverAttachments(item.attachments, join5(this.attachmentDir, item.id.replace(/[^\w-]/g, "_")), this.options.fetchImpl);
17343
+ const delivered = await deliverAttachments(item.attachments, join6(this.attachmentDir, item.id.replace(/[^\w-]/g, "_")), this.options.fetchImpl);
16700
17344
  this.deliveredAttachments.set(item.id, withoutImageData(delivered));
16701
17345
  return delivered;
16702
17346
  }
16703
- async activate() {
17347
+ repositoryState() {
17348
+ return this.options.api.execute("getRoomRepositoryState", { roomId: this.options.roomId });
17349
+ }
17350
+ /**
17351
+ * Drop this Room's live harness process. The next activation starts cold.
17352
+ * A rotation is a fact about one live session, so the pin goes with it.
17353
+ */
17354
+ async discardSession() {
17355
+ const client = this.client;
17356
+ this.client = void 0;
17357
+ this.sessionId = void 0;
17358
+ this.sessionFingerprint = void 0;
17359
+ this.pinnedProviderOverride = void 0;
17360
+ if (client?.isAlive)
17361
+ await client.stop();
17362
+ }
17363
+ /**
17364
+ * Whether the retained session still matches the agent's server-side
17365
+ * configuration. Retention (C104) is a saving only while what it keeps is
17366
+ * still current, and a session's persona and model pin are fixed when it
17367
+ * opens: they cannot be corrected in place, so a changed one has to cost a
17368
+ * respawn. The check is one round trip — the roster half of which the turn
17369
+ * was going to fetch anyway — against a cold spawn measured in seconds.
17370
+ */
17371
+ async sessionIsCurrent() {
17372
+ return await this.currentSessionFingerprint() === this.sessionFingerprint;
17373
+ }
17374
+ async currentSessionFingerprint() {
17375
+ const [configuration, roster] = await Promise.all([
17376
+ this.options.api.execute("getAgentConfiguration", {
17377
+ agentId: this.agent.publicKey,
17378
+ roomId: this.options.roomId
17379
+ }),
17380
+ this.roster()
17381
+ ]);
17382
+ const self = roster.members.find((member) => member.identityId === this.agent.publicKey);
17383
+ return sessionConfigFingerprint({
17384
+ model: configuration.model ?? this.options.config.modelSelection?.model,
17385
+ effort: configuration.effort ?? this.options.config.modelSelection?.effort,
17386
+ soul: configuration.soul ?? self?.soul,
17387
+ agentName: self?.name ?? this.agent.name
17388
+ });
17389
+ }
17390
+ async activate(trace) {
16704
17391
  if (this.client?.isAlive && this.sessionId)
16705
17392
  return this.sessionId;
17393
+ trace?.noteActivation("cold");
16706
17394
  const [configuration, roster, repositoryState] = await Promise.all([
16707
17395
  this.options.api.execute("getAgentConfiguration", {
16708
17396
  agentId: this.agent.publicKey,
16709
17397
  roomId: this.options.roomId
16710
17398
  }),
16711
17399
  this.roster(),
16712
- this.options.api.execute("getRoomRepositoryState", { roomId: this.options.roomId })
17400
+ this.repositoryState()
16713
17401
  ]);
17402
+ const self = roster.members.find((member) => member.identityId === this.agent.publicKey);
17403
+ const fingerprint = sessionConfigFingerprint({
17404
+ model: configuration.model ?? this.options.config.modelSelection?.model,
17405
+ effort: configuration.effort ?? this.options.config.modelSelection?.effort,
17406
+ soul: configuration.soul ?? self?.soul,
17407
+ agentName: self?.name ?? this.agent.name
17408
+ });
16714
17409
  const directMessage = Array.isArray(repositoryState.directParticipants) && repositoryState.directParticipants.length === 2;
16715
- await mkdir7(this.options.cwd, { recursive: true });
17410
+ await mkdir8(this.options.cwd, { recursive: true });
16716
17411
  const selection = configuration.model || configuration.effort ? { model: configuration.model, effort: configuration.effort } : this.options.config.modelSelection;
16717
17412
  const homeOverlay = this.options.config.agentHomeRoot ? await prepareRoomAgentHome({
16718
17413
  root: this.options.config.agentHomeRoot,
16719
17414
  sharedSkills: this.options.config.sharedSkills ?? [],
17415
+ ...this.options.config.agentKind ? { agentKind: this.options.config.agentKind } : {},
16720
17416
  ...this.options.config.operatorHome ? { operatorHome: this.options.config.operatorHome } : {},
16721
17417
  ...openRouterRoutingInput(this.options.config, selection, this.options.fetchImpl, {
16722
17418
  ...this.pinnedProviderOverride ? { providerOverride: this.pinnedProviderOverride } : {},
@@ -16737,11 +17433,11 @@ var MonolithRoomTurnLoop = class {
16737
17433
  }, selection);
16738
17434
  const operatorHome = this.options.config.operatorHome ?? homedir7();
16739
17435
  const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
16740
- this.attachmentDir = tmpDir ? join5(tmpDir, "beeline-attachments") : void 0;
17436
+ this.attachmentDir = tmpDir ? join6(tmpDir, "beeline-attachments") : void 0;
16741
17437
  this.sessionScratchDir = tmpDir;
16742
17438
  this.sessionStateDirs = stateDirs;
16743
17439
  const homeStateDirs = harnessHomeStateDirs(command, agentEnv.HOME ?? operatorHome);
16744
- await Promise.all(homeStateDirs.map((dir) => mkdir7(dir, { recursive: true })));
17440
+ await Promise.all(homeStateDirs.map((dir) => mkdir8(dir, { recursive: true })));
16745
17441
  const spawnCommand = wrapAgentCommand({
16746
17442
  bwrapPath: this.options.config.bwrapPath,
16747
17443
  spec: {
@@ -16755,6 +17451,22 @@ var MonolithRoomTurnLoop = class {
16755
17451
  command,
16756
17452
  args: agentArgs
16757
17453
  });
17454
+ const servers = [
17455
+ readOnlyMcpServer(this.options.config, this.options.cwd),
17456
+ beelineAgentMcpServer(this.options.config, this.options.api, {
17457
+ roomId: this.options.roomId,
17458
+ workspaceId: this.options.workspaceId,
17459
+ attachRoot: this.options.cwd,
17460
+ // The whole per-session overlay, not an enumerated subset: the agent
17461
+ // never picks where a harness writes a file it generates (grok's own
17462
+ // images dir, say), so anything inside the overlay it could possibly
17463
+ // have written must be attachable, whatever subdirectory that is.
17464
+ attachScratchRoot: this.options.config.agentHomeRoot ?? tmpDir,
17465
+ directMessage,
17466
+ ...this.options.grantRunnerEndpoint ? { grantRunner: this.options.grantRunnerEndpoint } : {}
17467
+ })
17468
+ ];
17469
+ const mountedServers = servers.map((server) => server.name);
16758
17470
  const clientOptions = {
16759
17471
  agentCommand: spawnCommand.command,
16760
17472
  agentArgs: spawnCommand.args,
@@ -16765,22 +17477,10 @@ var MonolithRoomTurnLoop = class {
16765
17477
  // (`config.ts`), which is exactly when `wrapAgentCommand` above wraps.
16766
17478
  osSandbox: Boolean(this.options.config.bwrapPath),
16767
17479
  autoApprovePermissions: false,
16768
- permissionAllowlist: isRoomMcpPermissionRequest
17480
+ permissionAllowlist: (request) => isRoomMcpPermissionRequest(request, mountedServers)
16769
17481
  };
16770
17482
  this.client = (this.options.createAcpClient ?? ((value) => new AcpClient(value)))(clientOptions);
16771
17483
  await this.client.start();
16772
- const servers = [
16773
- readOnlyMcpServer(this.options.config, this.options.cwd),
16774
- beelineAgentMcpServer(this.options.config, this.options.api, {
16775
- roomId: this.options.roomId,
16776
- workspaceId: this.options.workspaceId,
16777
- attachRoot: this.options.cwd,
16778
- ...tmpDir ? { attachScratchRoot: tmpDir } : {},
16779
- directMessage,
16780
- ...this.options.grantRunnerEndpoint ? { grantRunner: this.options.grantRunnerEndpoint } : {}
16781
- })
16782
- ];
16783
- const self = roster.members.find((member) => member.identityId === this.agent.publicKey);
16784
17484
  const persona = configuration.soul ?? self?.soul;
16785
17485
  const identityInstructions = `Your Beeline Room identity is ${self?.name ?? this.agent.name}.`;
16786
17486
  const personaInstructions = [
@@ -16805,23 +17505,35 @@ var MonolithRoomTurnLoop = class {
16805
17505
  systemPrompt: [identityInstructions, personaInstructions, capabilityContext.sessionPrompt].filter(Boolean).join("\n\n")
16806
17506
  });
16807
17507
  this.sessionId = opened.sessionId;
17508
+ this.sessionFingerprint = fingerprint;
16808
17509
  if (selection) {
16809
17510
  const options = filterAllowedModelConfigOptions(parseAdvertisedConfigOptions(opened.raw, selection.model));
16810
17511
  await applyAgentModelSelection(this.client, opened.sessionId, options, selection);
16811
17512
  }
16812
17513
  return opened.sessionId;
16813
17514
  }
16814
- lifecycle() {
17515
+ /**
17516
+ * The scheduler seam, and the only place that can see the boundary between
17517
+ * waiting for a slot and spawning a harness: `queue-wait` closes the instant
17518
+ * `activate()` is called, and `cold` vs `warm` is decided by whether this
17519
+ * Room already holds a live ACP client.
17520
+ */
17521
+ lifecycle(trace) {
16815
17522
  return {
16816
- activate: () => this.activate(),
16817
- suspend: async () => {
16818
- const client = this.client;
16819
- this.client = void 0;
16820
- this.sessionId = void 0;
16821
- this.pinnedProviderOverride = void 0;
16822
- if (client?.isAlive)
16823
- await client.stop();
16824
- }
17523
+ activate: async () => {
17524
+ trace?.end("queue-wait");
17525
+ return trace ? trace.measure("activation", () => this.activate(trace)) : this.activate();
17526
+ },
17527
+ isCurrent: () => {
17528
+ trace?.end("queue-wait");
17529
+ const check = () => this.sessionIsCurrent();
17530
+ return trace ? trace.measure("activation", check) : check();
17531
+ },
17532
+ onStateChange: (state) => {
17533
+ if (state === "waiting-for-slot")
17534
+ trace?.noteCapacityWait();
17535
+ },
17536
+ suspend: () => this.discardSession()
16825
17537
  };
16826
17538
  }
16827
17539
  /** The pinned providers a failure reason should name for this session. */
@@ -16830,7 +17542,7 @@ var MonolithRoomTurnLoop = class {
16830
17542
  }
16831
17543
  /** Why a turn carried no answer text, or undefined when it did. */
16832
17544
  async explainEmpty(result) {
16833
- if (sanitizeAgentReply(result.agentText))
17545
+ if (durableReplyText(result.agentText))
16834
17546
  return void 0;
16835
17547
  return explainEmptyAgentTurn({
16836
17548
  agentLabel: this.options.config.agentCommand ?? this.options.config.agentBinary,
@@ -16845,17 +17557,19 @@ var MonolithRoomTurnLoop = class {
16845
17557
  * named — by exactly one provider. Undefined when the pin has nowhere left
16846
17558
  * to go.
16847
17559
  */
16848
- async repinNextProvider() {
17560
+ async repinNextProvider(trace, reason) {
16849
17561
  const next = nextPinnedProvider(this.pinnedProviders, this.pinnedProviderOverride);
16850
17562
  if (!next)
16851
17563
  return void 0;
16852
- this.pinnedProviderOverride = next;
17564
+ trace?.retry({ provider: next, ...reason ? { reason } : {} });
16853
17565
  const client = this.client;
16854
17566
  this.client = void 0;
16855
17567
  this.sessionId = void 0;
17568
+ this.sessionFingerprint = void 0;
16856
17569
  if (client?.isAlive)
16857
17570
  await client.stop();
16858
- await this.activate();
17571
+ this.pinnedProviderOverride = next;
17572
+ await (trace ? trace.measure("activation", () => this.activate(trace)) : this.activate());
16859
17573
  return next;
16860
17574
  }
16861
17575
  startPrompt(item) {
@@ -16898,6 +17612,7 @@ var MonolithRoomTurnLoop = class {
16898
17612
  const { item } = active;
16899
17613
  const api = this.options.api;
16900
17614
  this.busy = true;
17615
+ const trace = this.beginTurnTrace(item.id);
16901
17616
  try {
16902
17617
  if (!this.memberNames.has(item.authorId))
16903
17618
  await this.roster().catch(() => void 0);
@@ -16920,22 +17635,28 @@ var MonolithRoomTurnLoop = class {
16920
17635
  }
16921
17636
  ]
16922
17637
  });
16923
- await this.options.scheduler.run(this.options.roomId, this.lifecycle(), async () => {
16924
- const [conversation, roster, delivered] = await Promise.all([
17638
+ trace.noteScheduler("queue", this.options.scheduler.snapshot());
17639
+ trace.start("queue-wait");
17640
+ await this.options.scheduler.run(this.options.roomId, this.lifecycle(trace), async () => {
17641
+ trace.end("queue-wait");
17642
+ trace.noteScheduler("admission", this.options.scheduler.snapshot());
17643
+ const [conversation, roster, delivered] = await trace.measure("context-fetch", () => Promise.all([
16925
17644
  api.execute("getRoomConversation", { roomId: this.options.roomId, limit: 200 }),
16926
17645
  this.roster(),
16927
17646
  this.deliver(item)
16928
- ]);
17647
+ ]));
16929
17648
  const names = new Map(roster.members.map((member) => [member.identityId, member.name]));
16930
- 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), this.acceptsImages())).join("\n");
17649
+ const transcriptRows = conversation.items.filter((message) => message.type === "message" && message.id !== item.id && !active.steers.some((steerItem) => steerItem.id === message.id)).slice(-80).map((message) => ({
17650
+ id: message.id,
17651
+ line: roomMessagePrompt(names.get(message.authorId) ?? message.authorId.slice(0, 12), message.body, message.attachments, this.deliveredAttachments.get(message.id), this.acceptsImages())
17652
+ }));
16931
17653
  const grantDecision = isGrantDecisionLine(item, this.agent.publicKey);
16932
17654
  const resumedRequestId = grantDecision ? this.pausedOnGrantRequestId : void 0;
16933
17655
  if (grantDecision)
16934
17656
  this.pausedOnGrantRequestId = void 0;
16935
- const prompt = [
17657
+ const buildPrompt = () => [
16936
17658
  this.turnInstructionPrefix,
16937
- transcript ? `Room conversation so far:
16938
- ${transcript}` : "",
17659
+ WarmTranscript.render(this.warmTranscript.select(this.sessionId, transcriptRows), "Room conversation so far:", "New in the Room since your last turn (the earlier conversation is already in this session):"),
16939
17660
  `Newest message from ${isScheduledPrompt(item, this.agent.publicKey) ? SCHEDULE_SCHEDULER_NAME : names.get(item.authorId) ?? item.authorId.slice(0, 12)}:`,
16940
17661
  roomMessagePrompt("", inboxItemPromptBody(item, this.agent.publicKey), item.attachments, delivered, this.acceptsImages()),
16941
17662
  grantDecision ? [
@@ -16950,25 +17671,25 @@ ${transcript}` : "",
16950
17671
  MAINTAIN_ASSIGNED_IDENTITY_DIRECTIVE
16951
17672
  ].join(" ")
16952
17673
  ].filter(Boolean).join("\n\n");
16953
- const turnId = item.id;
17674
+ const stream = new AgentTurnStream({
17675
+ api,
17676
+ agentId: this.agent.publicKey,
17677
+ roomId: this.options.roomId,
17678
+ requestId: item.id,
17679
+ label: `monolith Room ${this.options.roomId}`
17680
+ });
16954
17681
  const runPrompt = async () => {
16955
- let nextPrompt = promptWithImages(prompt, attachmentImageBlocks(delivered, this.acceptsImages()));
17682
+ let nextPrompt = promptWithImages(buildPrompt(), attachmentImageBlocks(delivered, this.acceptsImages()));
16956
17683
  let result2;
16957
17684
  for (; ; ) {
16958
17685
  let promptError;
16959
17686
  try {
16960
- let latestDraft = "";
16961
- result2 = await this.client.sessionPrompt(this.sessionId, nextPrompt, 12e4, (_delta, full) => {
16962
- latestDraft = sanitizeAgentReply(full);
16963
- if (!latestDraft)
16964
- return;
16965
- this.draftTail = this.draftTail.catch(() => void 0).then(() => api.execute("postAgentDraft", {
16966
- agentId: this.agent.publicKey,
16967
- roomId: this.options.roomId,
16968
- turnId,
16969
- text: latestDraft
16970
- })).then(() => void 0).catch((error) => console.error(`[thin-core] monolith Room ${this.options.roomId} draft publish failed:`, error));
16971
- });
17687
+ stream.beginRun();
17688
+ trace.promptSent();
17689
+ result2 = await this.client.sessionPrompt(this.sessionId, nextPrompt, 12e4, (delta, full) => {
17690
+ trace.firstModelOutput();
17691
+ stream.onChunk(delta, full);
17692
+ }, void 0, (calls) => trace.toolCalls(calls));
16972
17693
  } catch (error) {
16973
17694
  promptError = error;
16974
17695
  }
@@ -16993,13 +17714,15 @@ ${transcript}` : "",
16993
17714
  return result2;
16994
17715
  };
16995
17716
  let result = await runPrompt();
17717
+ trace.promptSettled();
16996
17718
  let explained = await this.explainEmpty(result);
16997
17719
  if (explained && shouldRetryEmptyTurn(explained)) {
16998
17720
  const silent = this.servingProviders();
16999
- const next = await this.repinNextProvider();
17721
+ const next = await this.repinNextProvider(trace, explained.reason);
17000
17722
  if (next) {
17001
17723
  console.warn(`[thin-core] monolith Room ${this.options.roomId} turn ${item.id}: ${turnFailureReasonWithProvider(explained.reason, silent)}; retrying on ${next}`);
17002
17724
  result = await runPrompt();
17725
+ trace.promptSettled();
17003
17726
  explained = await this.explainEmpty(result);
17004
17727
  }
17005
17728
  }
@@ -17022,10 +17745,10 @@ ${transcript}` : "",
17022
17745
  console.warn(`[thin-core] monolith Room ${this.options.roomId} ${failure}`);
17023
17746
  }
17024
17747
  }
17025
- await this.draftTail;
17026
- let reply = sanitizeAgentReply(result.agentText);
17748
+ stream.close();
17749
+ let reply = durableReplyText(result.agentText);
17027
17750
  if (!reply && explained) {
17028
- reply = explained.recoveredText ? sanitizeAgentReply(explained.recoveredText) : "";
17751
+ reply = explained.recoveredText ? durableReplyText(explained.recoveredText) : "";
17029
17752
  if (!reply) {
17030
17753
  throw new Error(turnFailureReasonWithProvider(explained.reason, this.servingProviders()));
17031
17754
  }
@@ -17034,22 +17757,10 @@ ${transcript}` : "",
17034
17757
  if (openCornerCall && !isFailedToolCall(openCornerCall)) {
17035
17758
  reply = stripCornerOpenEcho(reply);
17036
17759
  }
17037
- if (reply) {
17038
- await api.execute("postRoomMessage", {
17039
- roomId: this.options.roomId,
17040
- requestId: item.id,
17041
- triggerMessageId: item.id,
17042
- text: reply,
17043
- presentation: "message",
17044
- mentionIds: agentReplyMentionIds(reply, roster, this.agent.publicKey)
17045
- });
17046
- }
17047
- await api.execute("retractAgentLiveOutput", {
17048
- agentId: this.agent.publicKey,
17049
- roomId: this.options.roomId,
17050
- turnId,
17051
- kind: "draft"
17052
- });
17760
+ await trace.measure("publish", () => stream.settle(reply, reply ? {
17761
+ triggerMessageId: item.id,
17762
+ mentionIds: agentReplyMentionIds(reply, roster, this.agent.publicKey)
17763
+ } : {}));
17053
17764
  }, { priority: "interactive", roomKey: this.options.roomId });
17054
17765
  }, (error) => console.error(`[thin-core] monolith Room ${this.options.roomId} receipt heartbeat failed:`, error));
17055
17766
  await api.execute("postAgentTurnReceipt", {
@@ -17059,15 +17770,18 @@ ${transcript}` : "",
17059
17770
  status: "complete",
17060
17771
  generationId: `${this.agent.publicKey}:${this.options.roomId}`
17061
17772
  });
17773
+ await trace.finish("complete");
17062
17774
  } catch (error) {
17775
+ const reason = distillTurnFailureReason(error);
17063
17776
  await api.execute("postAgentTurnReceipt", {
17064
17777
  agentId: this.agent.publicKey,
17065
17778
  roomId: this.options.roomId,
17066
17779
  requestId: item.id,
17067
17780
  status: "failed",
17068
17781
  generationId: `${this.agent.publicKey}:${this.options.roomId}`,
17069
- reason: distillTurnFailureReason(error)
17782
+ reason
17070
17783
  });
17784
+ await trace.finish("failed", reason);
17071
17785
  throw error;
17072
17786
  } finally {
17073
17787
  this.busy = false;
@@ -17173,6 +17887,22 @@ function resolvePerRoomLiveSessions(env = process.env) {
17173
17887
  const value = Number(raw);
17174
17888
  return Number.isSafeInteger(value) && value > 0 ? value : DEFAULT_PER_ROOM_LIVE_SESSIONS;
17175
17889
  }
17890
+ var DEFAULT_SESSION_IDLE_MS = 30 * 6e4;
17891
+ var DEFAULT_MAX_WARM_SESSIONS = 4;
17892
+ function resolveMaxWarmSessions(env = process.env) {
17893
+ const raw = env.BUZZY_BODY_MAX_WARM_SESSIONS?.trim();
17894
+ if (!raw)
17895
+ return DEFAULT_MAX_WARM_SESSIONS;
17896
+ const value = Number(raw);
17897
+ return Number.isSafeInteger(value) && value > 0 ? value : DEFAULT_MAX_WARM_SESSIONS;
17898
+ }
17899
+ function resolveSessionIdleMs(env = process.env) {
17900
+ const raw = env.BUZZY_BODY_SESSION_IDLE_MS?.trim();
17901
+ if (!raw)
17902
+ return DEFAULT_SESSION_IDLE_MS;
17903
+ const value = Number(raw);
17904
+ return Number.isSafeInteger(value) && value > 0 ? value : DEFAULT_SESSION_IDLE_MS;
17905
+ }
17176
17906
  var DEFAULT_WORKSPACE_LIVE_SESSIONS_FLOOR = 4;
17177
17907
  var SessionScheduler = class {
17178
17908
  /** Fixed ceiling when explicitly configured; undefined selects the dynamic one. */
@@ -17182,6 +17912,7 @@ var SessionScheduler = class {
17182
17912
  activeRoomCount;
17183
17913
  reserveInteractiveSlot;
17184
17914
  idleMs;
17915
+ maxWarmSessions;
17185
17916
  live = /* @__PURE__ */ new Map();
17186
17917
  busy = /* @__PURE__ */ new Set();
17187
17918
  /** Per-logical-session queue. Interactive turns sort ahead of waiting background work. */
@@ -17199,10 +17930,14 @@ var SessionScheduler = class {
17199
17930
  capacityTail = Promise.resolve();
17200
17931
  sweepTimer;
17201
17932
  constructor(options = {}) {
17202
- this.idleMs = options.idleMs ?? 5 * 6e4;
17933
+ this.idleMs = options.idleMs ?? DEFAULT_SESSION_IDLE_MS;
17203
17934
  if (!Number.isSafeInteger(this.idleMs) || this.idleMs < 1) {
17204
17935
  throw new Error("idleMs must be a positive integer");
17205
17936
  }
17937
+ this.maxWarmSessions = options.maxWarmSessions ?? DEFAULT_MAX_WARM_SESSIONS;
17938
+ if (!Number.isSafeInteger(this.maxWarmSessions) || this.maxWarmSessions < 1) {
17939
+ throw new Error("maxWarmSessions must be a positive integer");
17940
+ }
17206
17941
  this.reserveInteractiveSlot = options.reserveInteractiveSlot ?? false;
17207
17942
  if (options.maxLiveSessions !== void 0) {
17208
17943
  if (!Number.isSafeInteger(options.maxLiveSessions) || options.maxLiveSessions < 1) {
@@ -17307,6 +18042,11 @@ var SessionScheduler = class {
17307
18042
  generations(key) {
17308
18043
  return this.physicalHistory.get(key) ?? [];
17309
18044
  }
18045
+ /**
18046
+ * Read-only capacity diagnostic, exported beside the turn trace
18047
+ * (`turn-trace.ts`) so a stalled turn can be attributed to a capacity wait
18048
+ * rather than to a slow model. It reads state and changes none.
18049
+ */
17310
18050
  snapshot() {
17311
18051
  let pending = 0;
17312
18052
  for (const session of this.live.values())
@@ -17318,9 +18058,18 @@ var SessionScheduler = class {
17318
18058
  busy: this.busy.size,
17319
18059
  queuedChannels: (/* @__PURE__ */ new Set([...this.queues.keys(), ...this.drainingKeys])).size,
17320
18060
  maxLive: this.workspaceCapacity(),
17321
- perRoom: this.perRoomLiveSessions
18061
+ perRoom: this.perRoomLiveSessions,
18062
+ warm: this.warmKeys().length,
18063
+ maxWarm: this.maxWarmSessions
17322
18064
  };
17323
18065
  }
18066
+ /**
18067
+ * Live keys with no turn running and nothing queued behind them: exactly the
18068
+ * processes retention is keeping resident, LRU-ordered oldest first.
18069
+ */
18070
+ warmKeys() {
18071
+ return [...this.live.entries()].filter(([key, session]) => !session.pending && !this.busy.has(key) && !this.drainingKeys.has(key) && (this.queues.get(key)?.length ?? 0) === 0).sort((a2, b) => a2[1].lastUsedAt - b[1].lastUsedAt).map(([key]) => key);
18072
+ }
17324
18073
  async suspend(key) {
17325
18074
  if (this.busy.has(key) || this.drainingKeys.has(key) || (this.queues.get(key)?.length ?? 0) > 0)
17326
18075
  return;
@@ -17400,36 +18149,47 @@ var SessionScheduler = class {
17400
18149
  let waitForCapacity;
17401
18150
  let evicted;
17402
18151
  let reservation;
18152
+ let retained;
17403
18153
  try {
17404
18154
  if (priority === "background" && interactiveQueued())
17405
18155
  return false;
17406
- if (this.live.has(key)) {
18156
+ retained = this.live.get(key);
18157
+ if (retained) {
17407
18158
  this.busy.add(key);
17408
- return true;
17409
- }
17410
- const reserved = priority === "background" && this.reserveInteractiveSlot ? 1 : 0;
17411
- const roomLimit = Math.max(1, this.perRoomLiveSessions - reserved);
17412
- const workspaceLimit = Math.max(1, this.workspaceCapacity() - reserved);
17413
- const roomLive = [...this.live.values()].filter((session) => session.roomKey === roomKey).length;
17414
- if (roomLive >= roomLimit) {
17415
- evicted = this.claimIdleVictim((session) => session.roomKey === roomKey);
17416
- } else if (this.occupied() >= workspaceLimit) {
17417
- evicted = this.claimIdleVictim(() => true);
17418
- }
17419
- if (!evicted && (roomLive >= roomLimit || this.occupied() >= workspaceLimit)) {
17420
- waitForCapacity = new Promise((resolveWaiter) => this.waiters.push(resolveWaiter));
17421
18159
  } else {
17422
- const idleMs = lifecycle.idleMs ?? this.idleMs;
17423
- if (!Number.isSafeInteger(idleMs) || idleMs < 1) {
17424
- throw new Error("session lifecycle idleMs must be a positive integer");
18160
+ const reserved = priority === "background" && this.reserveInteractiveSlot ? 1 : 0;
18161
+ const roomLimit = Math.max(1, this.perRoomLiveSessions - reserved);
18162
+ const workspaceLimit = Math.max(1, this.workspaceCapacity() - reserved);
18163
+ const roomLive = [...this.live.values()].filter((session) => session.roomKey === roomKey).length;
18164
+ if (roomLive >= roomLimit) {
18165
+ evicted = this.claimIdleVictim((session) => session.roomKey === roomKey);
18166
+ } else if (this.occupied() >= workspaceLimit) {
18167
+ evicted = this.claimIdleVictim(() => true);
18168
+ }
18169
+ if (!evicted && (roomLive >= roomLimit || this.occupied() >= workspaceLimit)) {
18170
+ waitForCapacity = new Promise((resolveWaiter) => this.waiters.push(resolveWaiter));
18171
+ } else {
18172
+ const idleMs = lifecycle.idleMs ?? this.idleMs;
18173
+ if (!Number.isSafeInteger(idleMs) || idleMs < 1) {
18174
+ throw new Error("session lifecycle idleMs must be a positive integer");
18175
+ }
18176
+ reservation = { lifecycle, lastUsedAt: Date.now(), roomKey, pending: true, idleMs };
18177
+ this.live.set(key, reservation);
18178
+ this.busy.add(key);
17425
18179
  }
17426
- reservation = { lifecycle, lastUsedAt: Date.now(), roomKey, pending: true, idleMs };
17427
- this.live.set(key, reservation);
17428
- this.busy.add(key);
17429
18180
  }
17430
18181
  } finally {
17431
18182
  releaseCapacity();
17432
18183
  }
18184
+ if (retained) {
18185
+ if (await this.stillCurrent(lifecycle))
18186
+ return true;
18187
+ if (this.live.get(key) === retained)
18188
+ this.live.delete(key);
18189
+ this.busy.delete(key);
18190
+ await this.retire(retained);
18191
+ continue;
18192
+ }
17433
18193
  if (!reservation) {
17434
18194
  await this.notifyState(lifecycle, "waiting-for-slot");
17435
18195
  await waitForCapacity;
@@ -17475,6 +18235,15 @@ var SessionScheduler = class {
17475
18235
  this.suspending.add(victim[1]);
17476
18236
  return victim[1];
17477
18237
  }
18238
+ /**
18239
+ * Retire what retention should no longer be holding: first anything past its
18240
+ * idle window, then — LRU first — whatever still exceeds the resident
18241
+ * ceiling. The second pass is the real bound on retention: without it a
18242
+ * longer idle window would let a Workspace with many Rooms grow its resident
18243
+ * process count with the capacity ceiling. It runs on the sweep tick (at
18244
+ * most every 30s), so the ceiling is enforced within one tick of a turn
18245
+ * ending rather than instantly.
18246
+ */
17478
18247
  async sweepIdle() {
17479
18248
  const now2 = Date.now();
17480
18249
  for (const [key, session] of [...this.live.entries()]) {
@@ -17482,6 +18251,26 @@ var SessionScheduler = class {
17482
18251
  await this.suspend(key);
17483
18252
  }
17484
18253
  }
18254
+ const warm = this.warmKeys();
18255
+ for (const key of warm.slice(0, Math.max(0, warm.length - this.maxWarmSessions))) {
18256
+ await this.suspend(key);
18257
+ }
18258
+ }
18259
+ /**
18260
+ * Ask a lifecycle whether its retained process is still usable. A lifecycle
18261
+ * that cannot answer keeps its session: a server hiccup is not a
18262
+ * configuration change, and throwing away a live process over one would
18263
+ * spend a cold start to learn nothing.
18264
+ */
18265
+ async stillCurrent(lifecycle) {
18266
+ if (!lifecycle.isCurrent)
18267
+ return true;
18268
+ try {
18269
+ return await lifecycle.isCurrent();
18270
+ } catch (error) {
18271
+ console.warn("[body] retained session currency check failed; keeping it:", error);
18272
+ return true;
18273
+ }
17485
18274
  }
17486
18275
  wakeCapacityWaiters() {
17487
18276
  const waiters = this.waiters.splice(0);
@@ -17561,7 +18350,8 @@ var RoomRuntimeCoordinator = class {
17561
18350
  perRoomLiveSessions: resolvePerRoomLiveSessions(process.env),
17562
18351
  workspaceFloor: Number(process.env.BUZZY_BODY_MAX_SESSIONS_FLOOR ?? String(DEFAULT_WORKSPACE_LIVE_SESSIONS_FLOOR)),
17563
18352
  activeRoomCount: () => this.running.size,
17564
- idleMs: Number(process.env.BUZZY_BODY_SESSION_IDLE_MS ?? String(5 * 6e4)),
18353
+ idleMs: resolveSessionIdleMs(process.env),
18354
+ maxWarmSessions: resolveMaxWarmSessions(process.env),
17565
18355
  reserveInteractiveSlot: true
17566
18356
  });
17567
18357
  }
@@ -17571,6 +18361,15 @@ var RoomRuntimeCoordinator = class {
17571
18361
  activeRoomCount() {
17572
18362
  return this.running.size;
17573
18363
  }
18364
+ /**
18365
+ * The session scheduler's capacity, read-only, beside the turn traces
18366
+ * (`turn-trace.ts` embeds the same snapshot in every record). A turn that
18367
+ * sat in `queue-wait` while this was at its ceiling waited on capacity, not
18368
+ * on a model.
18369
+ */
18370
+ schedulerSnapshot() {
18371
+ return this.scheduler.snapshot();
18372
+ }
17574
18373
  needsFastReconcile() {
17575
18374
  return this.confirmationPending;
17576
18375
  }
@@ -17682,13 +18481,13 @@ var RoomRuntimeCoordinator = class {
17682
18481
  return this.runtime.rooms.find((room) => room.channelId === roomId);
17683
18482
  }
17684
18483
  roomRoot(roomId) {
17685
- return this.roomRecord(roomId)?.root ?? resolve16(dirname6(this.configPath), "rooms", roomId);
18484
+ return this.roomRecord(roomId)?.root ?? resolve17(dirname6(this.configPath), "rooms", roomId);
17686
18485
  }
17687
18486
  roomAgentHomeRoot(workspaceRoot, required = false) {
17688
18487
  const flag = process.env.BUZZY_BODY_ROOM_HOME;
17689
18488
  if (!required && flag === "0")
17690
18489
  return void 0;
17691
- const home = resolve16(workspaceRoot, "agent-home");
18490
+ const home = resolve17(workspaceRoot, "agent-home");
17692
18491
  if (!required && flag !== "1" && !existsSync4(home) && existsSync4(workspaceRoot))
17693
18492
  return void 0;
17694
18493
  try {
@@ -17705,9 +18504,10 @@ var RoomRuntimeCoordinator = class {
17705
18504
  return {
17706
18505
  ...this.baseConfig,
17707
18506
  workspaceRoot,
17708
- agentPrivateRoot: resolve16(workspaceRoot, "agent-private"),
17709
- agentMemoryRoot: resolve16(dirname6(this.configPath), "memory"),
18507
+ agentPrivateRoot: resolve17(workspaceRoot, "agent-private"),
18508
+ agentMemoryRoot: resolve17(dirname6(this.configPath), "memory"),
17710
18509
  openRouterRoutingCacheDir: openRouterRoutingCacheDir(dirname6(this.configPath)),
18510
+ turnTraceDir: turnTraceDirectory(dirname6(this.configPath)),
17711
18511
  ...agentHomeRoot ? { agentHomeRoot } : {}
17712
18512
  };
17713
18513
  }
@@ -17773,12 +18573,12 @@ var RoomRuntimeCoordinator = class {
17773
18573
  return this.roomRoot(roomId);
17774
18574
  const remote = roomCheckoutRemote(repository.remote);
17775
18575
  const targetBranch = repository.targetBranch || "main";
17776
- const checkoutId = createHash3("sha256").update(`${remote}\0${targetBranch}`).digest("hex").slice(0, 24);
17777
- const path = resolve16(this.runtime.supervisorRoot, "beeline", "room-checkouts", checkoutId);
17778
- await mkdir8(dirname6(path), { recursive: true, mode: 448 });
18576
+ const checkoutId = createHash4("sha256").update(`${remote}\0${targetBranch}`).digest("hex").slice(0, 24);
18577
+ const path = resolve17(this.runtime.supervisorRoot, "beeline", "room-checkouts", checkoutId);
18578
+ await mkdir9(dirname6(path), { recursive: true, mode: 448 });
17779
18579
  const token = remote.startsWith("https://github.com/") ? await this.options.daemonApi.execute("getRoomGitHubToken", { roomId }) : void 0;
17780
18580
  const env = token ? githubGitEnv(token.token) : process.env;
17781
- if (!existsSync4(resolve16(path, ".git"))) {
18581
+ if (!existsSync4(resolve17(path, ".git"))) {
17782
18582
  await execFileAsync3("git", ["clone", "--no-checkout", remote, path], {
17783
18583
  env,
17784
18584
  maxBuffer: 4 * 1024 * 1024
@@ -17808,9 +18608,13 @@ var RoomRuntimeCoordinator = class {
17808
18608
  this.options.daemonApi.execute("getRoomRepositoryState", {
17809
18609
  roomId: corner.parentRoomId
17810
18610
  }),
18611
+ // Startup recovers the objective from the corner's FIRST durable
18612
+ // message, so this is the one conversation read that wants the oldest
18613
+ // end of the Room. Every other read defaults to the newest page.
17811
18614
  this.options.daemonApi.execute("getRoomConversation", {
17812
18615
  roomId: corner.cornerId,
17813
- limit: 200
18616
+ limit: 200,
18617
+ window: "earliest"
17814
18618
  }),
17815
18619
  this.options.daemonApi.execute("getRoomGitHubToken", {
17816
18620
  roomId: corner.parentRoomId
@@ -17898,13 +18702,13 @@ var RoomRuntimeCoordinator = class {
17898
18702
  }
17899
18703
  async materializeCornerWorktree(input) {
17900
18704
  const remote = githubHttpsRemote(input.remote);
17901
- const repositoryHash = createHash3("sha256").update(remote).digest("hex").slice(0, 24);
17902
- const gitCommonDir = resolve16(this.runtime.supervisorRoot, "beeline", "repositories", `${repositoryHash}.git`);
17903
- const path = resolve16(this.runtime.supervisorRoot, "beeline", "corners", input.cornerId);
17904
- await mkdir8(dirname6(gitCommonDir), { recursive: true, mode: 448 });
17905
- await mkdir8(dirname6(path), { recursive: true, mode: 448 });
18705
+ const repositoryHash = createHash4("sha256").update(remote).digest("hex").slice(0, 24);
18706
+ const gitCommonDir = resolve17(this.runtime.supervisorRoot, "beeline", "repositories", `${repositoryHash}.git`);
18707
+ const path = resolve17(this.runtime.supervisorRoot, "beeline", "corners", input.cornerId);
18708
+ await mkdir9(dirname6(gitCommonDir), { recursive: true, mode: 448 });
18709
+ await mkdir9(dirname6(path), { recursive: true, mode: 448 });
17906
18710
  const authEnv = githubGitEnv(input.token);
17907
- if (!existsSync4(resolve16(gitCommonDir, "HEAD"))) {
18711
+ if (!existsSync4(resolve17(gitCommonDir, "HEAD"))) {
17908
18712
  await execFileAsync3("git", ["clone", "--bare", remote, gitCommonDir], {
17909
18713
  env: authEnv,
17910
18714
  maxBuffer: 4 * 1024 * 1024
@@ -17917,8 +18721,8 @@ var RoomRuntimeCoordinator = class {
17917
18721
  "origin",
17918
18722
  `+refs/heads/${input.targetBranch}:refs/remotes/origin/${input.targetBranch}`
17919
18723
  ], { env: authEnv, maxBuffer: 4 * 1024 * 1024 });
17920
- if (!existsSync4(resolve16(path, ".git"))) {
17921
- await rm3(path, { recursive: true, force: true });
18724
+ if (!existsSync4(resolve17(path, ".git"))) {
18725
+ await rm4(path, { recursive: true, force: true });
17922
18726
  await execFileAsync3("git", [
17923
18727
  `--git-dir=${gitCommonDir}`,
17924
18728
  "worktree",
@@ -17954,7 +18758,7 @@ var RoomRuntimeCoordinator = class {
17954
18758
  `${this.agent.publicKey.slice(0, 16)}@users.noreply.github.com`
17955
18759
  ]);
17956
18760
  const top = await execFileAsync3("git", ["-C", path, "rev-parse", "--show-toplevel"]);
17957
- if (resolve16(top.stdout.trim()) !== resolve16(path)) {
18761
+ if (resolve17(top.stdout.trim()) !== resolve17(path)) {
17958
18762
  throw new Error(`corner worktree escaped its isolated root: ${top.stdout.trim()}`);
17959
18763
  }
17960
18764
  return { path, gitCommonDir };
@@ -18132,9 +18936,9 @@ var ThinDaemonCore = class {
18132
18936
 
18133
18937
  // apps/body/dist/systemd.js
18134
18938
  import { execFile as execFile5 } from "node:child_process";
18135
- import { mkdir as mkdir9, readFile as readFile7, writeFile as writeFile7 } from "node:fs/promises";
18939
+ import { mkdir as mkdir10, readFile as readFile8, writeFile as writeFile7 } from "node:fs/promises";
18136
18940
  import { homedir as homedir8 } from "node:os";
18137
- import { dirname as dirname7, resolve as resolve17 } from "node:path";
18941
+ import { dirname as dirname7, resolve as resolve18 } from "node:path";
18138
18942
  import { setTimeout as sleep } from "node:timers/promises";
18139
18943
  import { promisify as promisify4 } from "node:util";
18140
18944
  var execFileAsync4 = promisify4(execFile5);
@@ -18176,9 +18980,9 @@ WantedBy=default.target
18176
18980
  }
18177
18981
  function isCanonicalInstalledLauncher(env = process.env, invocationPath = process.argv[1]) {
18178
18982
  const home = env.HOME?.trim() || homedir8();
18179
- const expectedLibDir = resolve17(home, ".local", "lib", "beeline");
18983
+ const expectedLibDir = resolve18(home, ".local", "lib", "beeline");
18180
18984
  const expectedPrefix = `${expectedLibDir}/`;
18181
- return resolve17(env.BEELINE_LIB_DIR?.trim() || "/") === expectedLibDir && Boolean(invocationPath) && resolve17(invocationPath).startsWith(expectedPrefix);
18985
+ return resolve18(env.BEELINE_LIB_DIR?.trim() || "/") === expectedLibDir && Boolean(invocationPath) && resolve18(invocationPath).startsWith(expectedPrefix);
18182
18986
  }
18183
18987
  function assertCanonicalInstalledLauncher(env, invocationPath) {
18184
18988
  if (isCanonicalInstalledLauncher(env, invocationPath))
@@ -18186,8 +18990,8 @@ function assertCanonicalInstalledLauncher(env, invocationPath) {
18186
18990
  throw new Error("refusing to modify the shared Beeline systemd unit outside the canonical ~/.local/bin/beeline launcher");
18187
18991
  }
18188
18992
  function systemdUserUnitPath(env = process.env) {
18189
- const configRoot = env.XDG_CONFIG_HOME?.trim() || resolve17(homedir8(), ".config");
18190
- return resolve17(configRoot, "systemd", "user", SYSTEMD_UNIT_NAME);
18993
+ const configRoot = env.XDG_CONFIG_HOME?.trim() || resolve18(homedir8(), ".config");
18994
+ return resolve18(configRoot, "systemd", "user", SYSTEMD_UNIT_NAME);
18191
18995
  }
18192
18996
  var runSystemctl = async (args) => {
18193
18997
  const result = await execFileAsync4("systemctl", ["--user", ...args], {
@@ -18203,9 +19007,9 @@ async function installAgentService(publicKey, options = {}) {
18203
19007
  assertCanonicalInstalledLauncher(env, options.invocationPath);
18204
19008
  const path = systemdUserUnitPath(env);
18205
19009
  const content = agentServiceUnit();
18206
- const existing = await readFile7(path, "utf8").catch(() => "");
19010
+ const existing = await readFile8(path, "utf8").catch(() => "");
18207
19011
  if (existing !== content) {
18208
- await mkdir9(dirname7(path), { recursive: true, mode: 448 });
19012
+ await mkdir10(dirname7(path), { recursive: true, mode: 448 });
18209
19013
  await writeFile7(path, content, { mode: 384 });
18210
19014
  }
18211
19015
  const run2 = options.run ?? runSystemctl;
@@ -18377,9 +19181,9 @@ async function runStartCommand(args, interactiveUi) {
18377
19181
 
18378
19182
  // apps/body/dist/connect-command.js
18379
19183
  import { spawn as spawn5 } from "node:child_process";
18380
- import { createHash as createHash5 } from "node:crypto";
18381
- import { chmod as chmod4, mkdir as mkdir11, readFile as readFile9, unlink as unlink2, writeFile as writeFile9 } from "node:fs/promises";
18382
- import { dirname as dirname10, resolve as resolve19 } from "node:path";
19184
+ import { createHash as createHash6 } from "node:crypto";
19185
+ import { chmod as chmod4, mkdir as mkdir12, readFile as readFile10, unlink as unlink2, writeFile as writeFile9 } from "node:fs/promises";
19186
+ import { dirname as dirname10, resolve as resolve20 } from "node:path";
18383
19187
  import { stdin as stdin3, stdout as stdout4 } from "node:process";
18384
19188
 
18385
19189
  // packages/api-contract/dist/agent-pairing-code.js
@@ -18960,7 +19764,7 @@ function requestConnectGrant(baseUrl, pairingCode, selection, fetchImpl) {
18960
19764
  const normalizedPairingCode = normalizeAgentPairingCode(pairingCode);
18961
19765
  if (!normalizedPairingCode)
18962
19766
  throw new Error("invalid pairing code");
18963
- const avatarSeed = createHash5("sha256").update(normalizedPairingCode.toUpperCase()).digest("hex").slice(0, 32);
19767
+ const avatarSeed = createHash6("sha256").update(normalizedPairingCode.toUpperCase()).digest("hex").slice(0, 32);
18964
19768
  return jsonRequest(`${baseUrl}/auth/agent/connect`, {
18965
19769
  pairing_code: normalizedPairingCode,
18966
19770
  harness: selection.harness,
@@ -18993,7 +19797,7 @@ async function installCurrentRelease(fetchImpl) {
18993
19797
  });
18994
19798
  await activateRelease(layout, releaseId);
18995
19799
  return {
18996
- binary: resolve19(layout.binDir, "beeline"),
19800
+ binary: resolve20(layout.binDir, "beeline"),
18997
19801
  version: published.version ?? releaseId
18998
19802
  };
18999
19803
  }
@@ -19016,7 +19820,7 @@ function providerEnvironment(selection) {
19016
19820
  };
19017
19821
  }
19018
19822
  async function writePrivateJson(path, value) {
19019
- await mkdir11(dirname10(path), { recursive: true, mode: 448 });
19823
+ await mkdir12(dirname10(path), { recursive: true, mode: 448 });
19020
19824
  await writeFile9(path, `${JSON.stringify(value, null, 2)}
19021
19825
  `, { mode: 384 });
19022
19826
  await chmod4(path, 384);
@@ -19025,8 +19829,8 @@ async function writeProviderEnv(selection, agentPubkey) {
19025
19829
  const values = providerEnvironment(selection);
19026
19830
  if (Object.keys(values).length === 0)
19027
19831
  return void 0;
19028
- const path = resolve19(defaultSupervisorRoot(process.env), "beeline", "connect", `${agentPubkey}.env`);
19029
- await mkdir11(dirname10(path), { recursive: true, mode: 448 });
19832
+ const path = resolve20(defaultSupervisorRoot(process.env), "beeline", "connect", `${agentPubkey}.env`);
19833
+ await mkdir12(dirname10(path), { recursive: true, mode: 448 });
19030
19834
  const contents = Object.entries(values).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join("\n");
19031
19835
  await writeFile9(path, `${contents}
19032
19836
  `, { mode: 384 });
@@ -19090,7 +19894,7 @@ async function runConnectWizard(code, fetchImpl) {
19090
19894
  const grant = { ...claimed, agent_name: await confirmSeededName(baseUrl, pairingCode, claimed, fetchImpl) };
19091
19895
  const installedRelease = await brassSpinner("Installing the Beeline daemon\u2026", () => installCurrentRelease(fetchImpl), (release) => `Installed Beeline helper ${release.version}`);
19092
19896
  const llmEnvFile = await writeProviderEnv(selection, grant.agent_pubkey);
19093
- const grantPath = resolve19(defaultSupervisorRoot(process.env), "beeline", "connect", `grant-${process.pid}-${Date.now()}.json`);
19897
+ const grantPath = resolve20(defaultSupervisorRoot(process.env), "beeline", "connect", `grant-${process.pid}-${Date.now()}.json`);
19094
19898
  await writePrivateJson(grantPath, {
19095
19899
  agentSecretKey: grant.agent_secret_key,
19096
19900
  bodySecretKey: grant.body_secret_key,
@@ -19169,12 +19973,12 @@ async function runConnectFinishCommand(path) {
19169
19973
  throw new Error("connect-finish may run only from the canonical installed Beeline launcher");
19170
19974
  }
19171
19975
  try {
19172
- const grant = JSON.parse(await readFile9(resolve19(path), "utf8"));
19976
+ const grant = JSON.parse(await readFile10(resolve20(path), "utf8"));
19173
19977
  if (!isDevicePairingGrant(grant))
19174
19978
  throw new Error("device connection grant is invalid");
19175
19979
  const connected = await completeDevicePairing(grant);
19176
- await unlink2(resolve19(path));
19177
- const providerEnv = grant.llmEnvFile ? await readFile9(grant.llmEnvFile, "utf8").catch(() => "") : "";
19980
+ await unlink2(resolve20(path));
19981
+ const providerEnv = grant.llmEnvFile ? await readFile10(grant.llmEnvFile, "utf8").catch(() => "") : "";
19178
19982
  const apiKey = (/^OPENROUTER_API_KEY=(\S+)/m.exec(providerEnv)?.[1] ?? "").replace(/^["']|["']$/g, "");
19179
19983
  const model = openRouterModelId(grant.model, { OPENROUTER_API_KEY: apiKey });
19180
19984
  if (model) {
@@ -19202,19 +20006,19 @@ init_self_update_manifest();
19202
20006
  // apps/body/dist/managed-update.js
19203
20007
  init_self_update();
19204
20008
  import { spawn as spawn6 } from "node:child_process";
19205
- import { mkdir as mkdir13, rm as rm5, stat as stat2, writeFile as writeFile11 } from "node:fs/promises";
19206
- import { dirname as dirname12, resolve as resolve21 } from "node:path";
20009
+ import { mkdir as mkdir14, rm as rm6, stat as stat2, writeFile as writeFile11 } from "node:fs/promises";
20010
+ import { dirname as dirname12, resolve as resolve22 } from "node:path";
19207
20011
 
19208
20012
  // apps/body/dist/update-rollback-alert.js
19209
- import { mkdir as mkdir12, readFile as readFile10, rename as rename4, writeFile as writeFile10 } from "node:fs/promises";
19210
- import { dirname as dirname11, resolve as resolve20 } from "node:path";
20013
+ import { mkdir as mkdir13, readFile as readFile11, rename as rename4, writeFile as writeFile10 } from "node:fs/promises";
20014
+ import { dirname as dirname11, resolve as resolve21 } from "node:path";
19211
20015
  function updateRollbackAlertPath(runtimeDir) {
19212
- return resolve20(runtimeDir, "update-rollback-alert.json");
20016
+ return resolve21(runtimeDir, "update-rollback-alert.json");
19213
20017
  }
19214
20018
  async function writeAlert(runtimeDir, alert) {
19215
20019
  const path = updateRollbackAlertPath(runtimeDir);
19216
20020
  const staged = `${path}.${process.pid}.tmp`;
19217
- await mkdir12(dirname11(path), { recursive: true });
20021
+ await mkdir13(dirname11(path), { recursive: true });
19218
20022
  await writeFile10(staged, `${JSON.stringify(alert, null, 2)}
19219
20023
  `, { mode: 384 });
19220
20024
  await rename4(staged, path);
@@ -19227,7 +20031,7 @@ async function queueUpdateRollbackAlert(runtimeDir, releaseId, now2 = Date.now()
19227
20031
  }
19228
20032
  async function readUpdateRollbackAlert(runtimeDir) {
19229
20033
  try {
19230
- const value = JSON.parse(await readFile10(updateRollbackAlertPath(runtimeDir), "utf8"));
20034
+ const value = JSON.parse(await readFile11(updateRollbackAlertPath(runtimeDir), "utf8"));
19231
20035
  if (value.version !== 1 || typeof value.releaseId !== "string")
19232
20036
  return void 0;
19233
20037
  return value;
@@ -19252,13 +20056,13 @@ var LOCK_STALE_MS = UPDATE_WORKER_DEADLINE_MS + 5 * 6e4;
19252
20056
  var DEFAULT_UPDATE_INITIAL_DELAY_MS = 0;
19253
20057
  async function withInstallLock(layout, work, options = {}) {
19254
20058
  const now2 = options.now ?? Date.now;
19255
- const lock = resolve21(layout.releasesRoot, ".state", "install.lock");
20059
+ const lock = resolve22(layout.releasesRoot, ".state", "install.lock");
19256
20060
  const deadline = now2() + (options.waitMs ?? 1e4);
19257
- await mkdir13(dirname12(lock), { recursive: true });
20061
+ await mkdir14(dirname12(lock), { recursive: true });
19258
20062
  for (; ; ) {
19259
20063
  try {
19260
- await mkdir13(lock);
19261
- await writeFile11(resolve21(lock, "owner"), `${process.pid}
20064
+ await mkdir14(lock);
20065
+ await writeFile11(resolve22(lock, "owner"), `${process.pid}
19262
20066
  ${now2()}
19263
20067
  `, "utf8");
19264
20068
  break;
@@ -19267,7 +20071,7 @@ ${now2()}
19267
20071
  throw error;
19268
20072
  const age = now2() - await stat2(lock).then((value) => value.mtimeMs).catch(() => now2());
19269
20073
  if (age > LOCK_STALE_MS) {
19270
- await rm5(lock, { recursive: true, force: true });
20074
+ await rm6(lock, { recursive: true, force: true });
19271
20075
  continue;
19272
20076
  }
19273
20077
  if (now2() >= deadline)
@@ -19278,7 +20082,7 @@ ${now2()}
19278
20082
  try {
19279
20083
  return await work();
19280
20084
  } finally {
19281
- await rm5(lock, { recursive: true, force: true });
20085
+ await rm6(lock, { recursive: true, force: true });
19282
20086
  }
19283
20087
  }
19284
20088
  var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
@@ -19381,7 +20185,7 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
19381
20185
  if (!attempt || attempt.releaseId !== desiredRelease || attempt.status !== "pending") {
19382
20186
  const from = await readInstalledBundleIdentity({
19383
20187
  ...this.#layout,
19384
- libDir: resolve21(this.#layout.releasesRoot, this.#loadedRelease)
20188
+ libDir: resolve22(this.#layout.releasesRoot, this.#loadedRelease)
19385
20189
  }).catch(() => void 0) ?? {};
19386
20190
  const to = await readInstalledBundleIdentity(this.#layout).catch(() => void 0) ?? {};
19387
20191
  const record2 = {
@@ -19668,7 +20472,7 @@ async function proveLoadedReleaseReady(layout, runtimeDir, loadedRelease, option
19668
20472
  });
19669
20473
  if (!accepted)
19670
20474
  return false;
19671
- await writeFile11(resolve21(runtimeDir, "daemon-ready.json"), `${JSON.stringify({
20475
+ await writeFile11(resolve22(runtimeDir, "daemon-ready.json"), `${JSON.stringify({
19672
20476
  readyAt: (options.now ?? Date.now)(),
19673
20477
  loadedRelease,
19674
20478
  functionalProof: options.functionalProof
@@ -19873,16 +20677,16 @@ async function runUpdateCommand(args) {
19873
20677
  init_self_update();
19874
20678
 
19875
20679
  // apps/body/dist/daemon-failure.js
19876
- import { mkdir as mkdir14, readFile as readFile11, rename as rename5, rm as rm6, writeFile as writeFile12 } from "node:fs/promises";
19877
- import { dirname as dirname13, resolve as resolve22 } from "node:path";
20680
+ import { mkdir as mkdir15, readFile as readFile12, rename as rename5, rm as rm7, writeFile as writeFile12 } from "node:fs/promises";
20681
+ import { dirname as dirname13, resolve as resolve23 } from "node:path";
19878
20682
  var DAEMON_FAILURE_LIMIT = 3;
19879
20683
  var DAEMON_FAILURE_WINDOW_MS = 5 * 6e4;
19880
20684
  function daemonFailurePath(runtimeDir) {
19881
- return resolve22(runtimeDir, "daemon-distress.json");
20685
+ return resolve23(runtimeDir, "daemon-distress.json");
19882
20686
  }
19883
20687
  async function readFailureRecord(runtimeDir) {
19884
20688
  try {
19885
- const value = JSON.parse(await readFile11(daemonFailurePath(runtimeDir), "utf8"));
20689
+ const value = JSON.parse(await readFile12(daemonFailurePath(runtimeDir), "utf8"));
19886
20690
  if (value.version !== 1 || !Array.isArray(value.failures) || value.failures.some((failure) => typeof failure !== "number") || typeof value.lastError !== "string") {
19887
20691
  return void 0;
19888
20692
  }
@@ -19894,7 +20698,7 @@ async function readFailureRecord(runtimeDir) {
19894
20698
  async function writeFailureRecord(runtimeDir, record2) {
19895
20699
  const path = daemonFailurePath(runtimeDir);
19896
20700
  const staged = `${path}.${process.pid}.tmp`;
19897
- await mkdir14(dirname13(path), { recursive: true, mode: 448 });
20701
+ await mkdir15(dirname13(path), { recursive: true, mode: 448 });
19898
20702
  await writeFile12(staged, `${JSON.stringify(record2, null, 2)}
19899
20703
  `, { mode: 384 });
19900
20704
  await rename5(staged, path);
@@ -19913,13 +20717,13 @@ async function recordDaemonStartFailure(runtimeDir, error, now2 = Date.now()) {
19913
20717
  return { distressed, count: failures.length, path: daemonFailurePath(runtimeDir) };
19914
20718
  }
19915
20719
  async function clearDaemonStartFailures(runtimeDir) {
19916
- await rm6(daemonFailurePath(runtimeDir), { force: true });
20720
+ await rm7(daemonFailurePath(runtimeDir), { force: true });
19917
20721
  }
19918
20722
 
19919
20723
  // apps/body/dist/update-functional-probe.js
19920
- import { mkdir as mkdir15, rm as rm7 } from "node:fs/promises";
20724
+ import { mkdir as mkdir16, rm as rm8 } from "node:fs/promises";
19921
20725
  import { homedir as homedir10 } from "node:os";
19922
- import { resolve as resolve23 } from "node:path";
20726
+ import { resolve as resolve24 } from "node:path";
19923
20727
  var UPDATE_PROBE_SESSION_TIMEOUT_MS = 1e4;
19924
20728
  var UPDATE_PROBE_SESSION_OPEN_TIMEOUT_MS = 2e4;
19925
20729
  var UPDATE_PROBE_TURN_TIMEOUT_MS = 45e3;
@@ -19965,11 +20769,11 @@ async function runUpdateFunctionalProbe(input) {
19965
20769
  if (input.sandboxRequired && !input.config.bwrapPath) {
19966
20770
  throw new UpdateFunctionalProbeError("sandbox-unavailable", "the configured bubblewrap boundary did not pass its startup self-test");
19967
20771
  }
19968
- const root = input.probeRoot ?? resolve23(input.runtimeDir, "update-functional-probe");
19969
- const cwd = resolve23(root, "checkout");
19970
- const homeRoot = resolve23(root, "agent-home");
19971
- await rm7(root, { recursive: true, force: true });
19972
- await mkdir15(cwd, { recursive: true, mode: 448 });
20772
+ const root = input.probeRoot ?? resolve24(input.runtimeDir, "update-functional-probe");
20773
+ const cwd = resolve24(root, "checkout");
20774
+ const homeRoot = resolve24(root, "agent-home");
20775
+ await rm8(root, { recursive: true, force: true });
20776
+ await mkdir16(cwd, { recursive: true, mode: 448 });
19973
20777
  let client;
19974
20778
  try {
19975
20779
  const agentEnv = {
@@ -19978,6 +20782,7 @@ async function runUpdateFunctionalProbe(input) {
19978
20782
  root: homeRoot,
19979
20783
  operatorHome: input.config.operatorHome ?? homedir10(),
19980
20784
  sharedSkills: input.config.sharedSkills ?? [],
20785
+ ...input.config.agentKind ? { agentKind: input.config.agentKind } : {},
19981
20786
  skillReleaseId: input.releaseId,
19982
20787
  failClosed: true,
19983
20788
  ...openRouterRoutingInput({ ...input.config, openRouterRoutingCacheDir: openRouterRoutingCacheDir(input.runtimeDir) }, input.config.modelSelection)
@@ -19997,7 +20802,7 @@ async function runUpdateFunctionalProbe(input) {
19997
20802
  const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
19998
20803
  const operatorHome = input.config.operatorHome ?? homedir10();
19999
20804
  const homeStateDirs = harnessHomeStateDirs(command, agentEnv.HOME ?? operatorHome);
20000
- await Promise.all(homeStateDirs.map((dir) => mkdir15(dir, { recursive: true })));
20805
+ await Promise.all(homeStateDirs.map((dir) => mkdir16(dir, { recursive: true })));
20001
20806
  spawnCommand = wrapAgentCommand({
20002
20807
  bwrapPath: input.config.bwrapPath,
20003
20808
  spec: {
@@ -20086,13 +20891,13 @@ async function runUpdateFunctionalProbe(input) {
20086
20891
  };
20087
20892
  } finally {
20088
20893
  await client?.stop().catch(() => void 0);
20089
- await rm7(root, { recursive: true, force: true }).catch(() => void 0);
20894
+ await rm8(root, { recursive: true, force: true }).catch(() => void 0);
20090
20895
  }
20091
20896
  }
20092
20897
 
20093
20898
  // apps/body/dist/current-release-probe.js
20094
20899
  import { spawn as spawn7 } from "node:child_process";
20095
- import { dirname as dirname14, join as join7 } from "node:path";
20900
+ import { dirname as dirname14, join as join8 } from "node:path";
20096
20901
  init_self_update();
20097
20902
  var CURRENT_RELEASE_PROBE_TIMEOUT_MS = 8e4;
20098
20903
  var UPDATE_PROBE_COMMAND = "update-probe";
@@ -20127,13 +20932,13 @@ function outcomeFromReport(report) {
20127
20932
  }
20128
20933
  }
20129
20934
  async function probeReleaseInSubprocess(input) {
20130
- const bundleDir = join7(input.layout.releasesRoot, input.releaseId);
20935
+ const bundleDir = join8(input.layout.releasesRoot, input.releaseId);
20131
20936
  const entrypoint = await resolveBundleEntrypoint(bundleDir);
20132
20937
  if (!entrypoint) {
20133
20938
  return { kind: "unavailable", reason: `release ${input.releaseId} has no runnable CLI entrypoint` };
20134
20939
  }
20135
20940
  const timeoutMs = input.timeoutMs ?? CURRENT_RELEASE_PROBE_TIMEOUT_MS;
20136
- return new Promise((resolve26) => {
20941
+ return new Promise((resolve27) => {
20137
20942
  const child = spawn7(input.execPath ?? process.execPath, [entrypoint, UPDATE_PROBE_COMMAND, "--config", input.runtimeConfigPath], { env: input.env ?? process.env, stdio: ["ignore", "pipe", "pipe"] });
20138
20943
  let stdout6 = "";
20139
20944
  let stderr = "";
@@ -20143,7 +20948,7 @@ async function probeReleaseInSubprocess(input) {
20143
20948
  return;
20144
20949
  settled = true;
20145
20950
  clearTimeout(timer);
20146
- resolve26(outcome);
20951
+ resolve27(outcome);
20147
20952
  };
20148
20953
  const timer = setTimeout(() => {
20149
20954
  child.kill("SIGKILL");
@@ -20190,7 +20995,7 @@ async function runUpdateProbeCommand(args, options = {}) {
20190
20995
  const runtime = await readRuntimeRecord(configPath);
20191
20996
  const agent = runtimeAgentCommand(runtime);
20192
20997
  const config = loadBodyConfig({
20193
- workspaceRoot: join7(dirname14(configPath), "workspace"),
20998
+ workspaceRoot: join8(dirname14(configPath), "workspace"),
20194
20999
  llmEnvFile: runtime.llmEnvFile,
20195
21000
  env: { ...env, BUZZ_AGENT_BIN: agent.command, BUZZ_DEV_MCP_BIN: runtime.mcpBinary },
20196
21001
  agent
@@ -20215,15 +21020,15 @@ async function runUpdateProbeCommand(args, options = {}) {
20215
21020
  releaseId,
20216
21021
  sandboxRequired: runtime.sandbox !== "off",
20217
21022
  // The successor's probe still holds `<runtimeDir>/update-functional-probe`.
20218
- probeRoot: join7(runtimeDir, "current-release-probe")
21023
+ probeRoot: join8(runtimeDir, "current-release-probe")
20219
21024
  }));
20220
21025
  const report = outcome.kind === "served" ? { probe: "served" } : outcome.kind === "refused" ? { probe: "refused", status: outcome.status, reason: outcome.reason } : { probe: "failed", reason: outcome.reason };
20221
21026
  write(JSON.stringify(report));
20222
21027
  }
20223
21028
 
20224
21029
  // apps/body/dist/release-status.js
20225
- import { readFile as readFile12, readdir as readdir4, rename as rename6, writeFile as writeFile13 } from "node:fs/promises";
20226
- import { resolve as resolve24 } from "node:path";
21030
+ import { readFile as readFile13, readdir as readdir5, rename as rename6, writeFile as writeFile13 } from "node:fs/promises";
21031
+ import { resolve as resolve25 } from "node:path";
20227
21032
  var DAEMON_RELEASE_STATUS_FILE = "release-status.json";
20228
21033
  var RELEASE_VERSION = /^v\d+\.\d+\.\d+$/;
20229
21034
  var SOURCE_SHA = /^[0-9a-f]{7,64}$/;
@@ -20240,7 +21045,7 @@ async function writeDaemonReleaseStatus(runtimeDir, agentPubkey, identity, optio
20240
21045
  pid: options.pid ?? process.pid,
20241
21046
  readyAt: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
20242
21047
  };
20243
- const target = resolve24(runtimeDir, DAEMON_RELEASE_STATUS_FILE);
21048
+ const target = resolve25(runtimeDir, DAEMON_RELEASE_STATUS_FILE);
20244
21049
  const temporary = `${target}.${status.pid}.tmp`;
20245
21050
  await writeFile13(temporary, `${JSON.stringify(status, null, 2)}
20246
21051
  `, { mode: 384 });
@@ -20295,7 +21100,7 @@ async function runStoredDaemon(pathOrPointer) {
20295
21100
  runtime = activated.runtime;
20296
21101
  const daemonApi = activated.client;
20297
21102
  const agent = runtimeAgentCommand(runtime);
20298
- await writeFile14(resolve25(dirname15(configPath), "daemon.pid"), `${process.pid}
21103
+ await writeFile14(resolve26(dirname15(configPath), "daemon.pid"), `${process.pid}
20299
21104
  `, { mode: 384 });
20300
21105
  const env = {
20301
21106
  ...process.env,
@@ -20303,7 +21108,7 @@ async function runStoredDaemon(pathOrPointer) {
20303
21108
  BUZZ_DEV_MCP_BIN: runtime.mcpBinary
20304
21109
  };
20305
21110
  const config = loadBodyConfig({
20306
- workspaceRoot: resolve25(dirname15(configPath), "workspace"),
21111
+ workspaceRoot: resolve26(dirname15(configPath), "workspace"),
20307
21112
  llmEnvFile: runtime.llmEnvFile,
20308
21113
  env,
20309
21114
  agent
@@ -20473,8 +21278,8 @@ async function runStoredDaemon(pathOrPointer) {
20473
21278
  throw error;
20474
21279
  } finally {
20475
21280
  await notifier.stopping(stoppingStatus).catch(() => void 0);
20476
- const pidPath = resolve25(dirname15(configPath), "daemon.pid");
20477
- const recorded = Number((await readFile13(pidPath, "utf8").catch(() => "")).trim());
21281
+ const pidPath = resolve26(dirname15(configPath), "daemon.pid");
21282
+ const recorded = Number((await readFile14(pidPath, "utf8").catch(() => "")).trim());
20478
21283
  if (recorded === process.pid) {
20479
21284
  await unlink3(pidPath).catch(() => void 0);
20480
21285
  }
@@ -20537,7 +21342,7 @@ async function main() {
20537
21342
  }
20538
21343
  if (!configPath)
20539
21344
  throw new Error("daemon requires --config <runtime.json> or --agent <pubkey>");
20540
- await runStoredDaemon(resolve25(configPath));
21345
+ await runStoredDaemon(resolve26(configPath));
20541
21346
  return;
20542
21347
  }
20543
21348
  if (command === "update") {