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.
- package/dist/usebeeline.mjs +1246 -441
- package/package.json +1 -1
package/dist/usebeeline.mjs
CHANGED
|
@@ -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
|
|
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(
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
315
|
+
import { dirname as dirname9, join as join7, resolve as resolve19 } from "node:path";
|
|
316
316
|
function anchorLayout(rawLibDir) {
|
|
317
|
-
const libDir =
|
|
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:
|
|
333
|
+
binDir: resolve19(libDir, "../../bin"),
|
|
334
334
|
libDir,
|
|
335
|
-
releasesRoot:
|
|
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(
|
|
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 [
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 [
|
|
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 =
|
|
495
|
-
const okMarker =
|
|
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
|
|
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
|
|
505
|
-
const tempArchive =
|
|
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 =
|
|
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(
|
|
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, [
|
|
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
|
|
562
|
+
await rm5(releaseDir, { recursive: true, force: true });
|
|
563
563
|
throw error;
|
|
564
564
|
} finally {
|
|
565
|
-
await
|
|
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 =
|
|
590
|
-
await access(
|
|
591
|
-
await
|
|
592
|
-
await
|
|
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 =
|
|
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,
|
|
603
|
-
await normalizeLegacyBundleShape(
|
|
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
|
|
612
|
-
await symlink2(
|
|
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 =
|
|
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(
|
|
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
|
|
631
|
+
await mkdir11(innerLib, { recursive: true });
|
|
632
632
|
for (const name of LEGACY_FLAT_BUNDLE_FILES) {
|
|
633
633
|
try {
|
|
634
|
-
await access(
|
|
634
|
+
await access(join7(innerLib, name), fsConstants.F_OK);
|
|
635
635
|
continue;
|
|
636
636
|
} catch {
|
|
637
637
|
}
|
|
638
|
-
await rename3(
|
|
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 =
|
|
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(
|
|
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 =
|
|
655
|
+
const forwarderPath = join7(layout.binDir, "beeline");
|
|
656
656
|
let current;
|
|
657
657
|
try {
|
|
658
|
-
current = await
|
|
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
|
|
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 =
|
|
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
|
|
677
|
-
await symlink2(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
959
|
-
import { readFile as
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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:
|
|
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
|
|
13280
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
13281
13281
|
import { existsSync as existsSync4, mkdirSync } from "node:fs";
|
|
13282
|
-
import { mkdir as
|
|
13283
|
-
import { dirname as dirname6, resolve as
|
|
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
|
|
14125
|
+
import { mkdir as mkdir7 } from "node:fs/promises";
|
|
14126
14126
|
import { homedir as homedir6 } from "node:os";
|
|
14127
|
-
import { join as
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
15021
|
-
|
|
15022
|
-
|
|
15023
|
-
|
|
15024
|
-
|
|
15025
|
-
|
|
15026
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
15156
|
-
|
|
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
|
|
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 =
|
|
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 = [
|
|
15566
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 ?
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
16801
|
+
/** The scheduler seam: `queue-wait` closes when a slot buys a session. */
|
|
16802
|
+
lifecycle(trace) {
|
|
16189
16803
|
return {
|
|
16190
|
-
activate: () =>
|
|
16191
|
-
|
|
16192
|
-
|
|
16193
|
-
|
|
16194
|
-
|
|
16195
|
-
|
|
16196
|
-
|
|
16197
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}, () =>
|
|
16255
|
-
|
|
16256
|
-
|
|
16257
|
-
this.
|
|
16258
|
-
|
|
16259
|
-
|
|
16260
|
-
this.
|
|
16261
|
-
|
|
16262
|
-
|
|
16263
|
-
|
|
16264
|
-
|
|
16265
|
-
|
|
16266
|
-
|
|
16267
|
-
|
|
16268
|
-
|
|
16269
|
-
|
|
16270
|
-
|
|
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
|
-
|
|
16273
|
-
|
|
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
|
-
|
|
16278
|
-
|
|
16279
|
-
|
|
16280
|
-
|
|
16281
|
-
|
|
16282
|
-
|
|
16283
|
-
|
|
16284
|
-
|
|
16285
|
-
|
|
16286
|
-
|
|
16287
|
-
|
|
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
|
-
|
|
16315
|
-
|
|
16316
|
-
|
|
16317
|
-
|
|
16318
|
-
|
|
16319
|
-
|
|
16320
|
-
|
|
16321
|
-
|
|
16322
|
-
|
|
16323
|
-
|
|
16324
|
-
|
|
16325
|
-
|
|
16326
|
-
|
|
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
|
-
|
|
16335
|
-
|
|
16336
|
-
|
|
16337
|
-
|
|
16338
|
-
|
|
16339
|
-
|
|
16340
|
-
|
|
16341
|
-
|
|
16342
|
-
|
|
16343
|
-
|
|
16344
|
-
|
|
16345
|
-
|
|
16346
|
-
|
|
16347
|
-
|
|
16348
|
-
|
|
16349
|
-
|
|
16350
|
-
|
|
16351
|
-
|
|
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
|
-
|
|
16355
|
-
|
|
16356
|
-
|
|
16357
|
-
|
|
16358
|
-
|
|
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
|
-
|
|
16371
|
-
|
|
16372
|
-
|
|
16373
|
-
|
|
16374
|
-
|
|
16375
|
-
|
|
16376
|
-
|
|
16377
|
-
|
|
16378
|
-
|
|
16379
|
-
}
|
|
16380
|
-
|
|
16381
|
-
|
|
16382
|
-
|
|
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
|
|
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
|
|
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
|
|
17137
|
+
import { mkdir as mkdir8 } from "node:fs/promises";
|
|
16516
17138
|
import { homedir as homedir7 } from "node:os";
|
|
16517
|
-
import { join as
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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
|
|
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 ?
|
|
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) =>
|
|
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
|
-
|
|
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: () =>
|
|
16817
|
-
|
|
16818
|
-
|
|
16819
|
-
|
|
16820
|
-
|
|
16821
|
-
|
|
16822
|
-
|
|
16823
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16924
|
-
|
|
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
|
|
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
|
|
17657
|
+
const buildPrompt = () => [
|
|
16936
17658
|
this.turnInstructionPrefix,
|
|
16937
|
-
|
|
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
|
|
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(
|
|
17682
|
+
let nextPrompt = promptWithImages(buildPrompt(), attachmentImageBlocks(delivered, this.acceptsImages()));
|
|
16956
17683
|
let result2;
|
|
16957
17684
|
for (; ; ) {
|
|
16958
17685
|
let promptError;
|
|
16959
17686
|
try {
|
|
16960
|
-
|
|
16961
|
-
|
|
16962
|
-
|
|
16963
|
-
|
|
16964
|
-
|
|
16965
|
-
|
|
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
|
-
|
|
17026
|
-
let reply =
|
|
17748
|
+
stream.close();
|
|
17749
|
+
let reply = durableReplyText(result.agentText);
|
|
17027
17750
|
if (!reply && explained) {
|
|
17028
|
-
reply = 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
|
-
|
|
17038
|
-
|
|
17039
|
-
|
|
17040
|
-
|
|
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
|
|
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 ??
|
|
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
|
-
|
|
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
|
|
17423
|
-
|
|
17424
|
-
|
|
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:
|
|
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 ??
|
|
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 =
|
|
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:
|
|
17709
|
-
agentMemoryRoot:
|
|
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 =
|
|
17777
|
-
const path =
|
|
17778
|
-
await
|
|
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(
|
|
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 =
|
|
17902
|
-
const gitCommonDir =
|
|
17903
|
-
const path =
|
|
17904
|
-
await
|
|
17905
|
-
await
|
|
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(
|
|
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(
|
|
17921
|
-
await
|
|
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 (
|
|
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
|
|
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
|
|
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 =
|
|
18983
|
+
const expectedLibDir = resolve18(home, ".local", "lib", "beeline");
|
|
18180
18984
|
const expectedPrefix = `${expectedLibDir}/`;
|
|
18181
|
-
return
|
|
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() ||
|
|
18190
|
-
return
|
|
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
|
|
19010
|
+
const existing = await readFile8(path, "utf8").catch(() => "");
|
|
18207
19011
|
if (existing !== content) {
|
|
18208
|
-
await
|
|
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
|
|
18381
|
-
import { chmod as chmod4, mkdir as
|
|
18382
|
-
import { dirname as dirname10, resolve as
|
|
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 =
|
|
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:
|
|
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
|
|
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 =
|
|
19029
|
-
await
|
|
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 =
|
|
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
|
|
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(
|
|
19177
|
-
const providerEnv = grant.llmEnvFile ? await
|
|
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
|
|
19206
|
-
import { dirname as dirname12, resolve as
|
|
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
|
|
19210
|
-
import { dirname as dirname11, resolve as
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
20059
|
+
const lock = resolve22(layout.releasesRoot, ".state", "install.lock");
|
|
19256
20060
|
const deadline = now2() + (options.waitMs ?? 1e4);
|
|
19257
|
-
await
|
|
20061
|
+
await mkdir14(dirname12(lock), { recursive: true });
|
|
19258
20062
|
for (; ; ) {
|
|
19259
20063
|
try {
|
|
19260
|
-
await
|
|
19261
|
-
await writeFile11(
|
|
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
|
|
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
|
|
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:
|
|
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(
|
|
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
|
|
19877
|
-
import { dirname as dirname13, resolve as
|
|
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
|
|
20685
|
+
return resolve23(runtimeDir, "daemon-distress.json");
|
|
19882
20686
|
}
|
|
19883
20687
|
async function readFailureRecord(runtimeDir) {
|
|
19884
20688
|
try {
|
|
19885
|
-
const value = JSON.parse(await
|
|
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
|
|
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
|
|
20720
|
+
await rm7(daemonFailurePath(runtimeDir), { force: true });
|
|
19917
20721
|
}
|
|
19918
20722
|
|
|
19919
20723
|
// apps/body/dist/update-functional-probe.js
|
|
19920
|
-
import { mkdir as
|
|
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
|
|
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 ??
|
|
19969
|
-
const cwd =
|
|
19970
|
-
const homeRoot =
|
|
19971
|
-
await
|
|
19972
|
-
await
|
|
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) =>
|
|
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
|
|
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
|
|
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 =
|
|
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((
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
|
20226
|
-
import { resolve as
|
|
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 =
|
|
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(
|
|
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:
|
|
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 =
|
|
20477
|
-
const recorded = Number((await
|
|
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(
|
|
21345
|
+
await runStoredDaemon(resolve26(configPath));
|
|
20541
21346
|
return;
|
|
20542
21347
|
}
|
|
20543
21348
|
if (command === "update") {
|