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