usebeeline 0.0.37 → 0.0.44
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 +1880 -500
- 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" : "";
|
|
@@ -359,7 +359,7 @@ 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
|
}
|
|
@@ -381,14 +381,14 @@ function updateStatePath(layout) {
|
|
|
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 = {}) {
|
|
@@ -489,29 +489,29 @@ function requiredBundlePaths() {
|
|
|
489
489
|
}
|
|
490
490
|
async function stageRelease(layout, manifestUrl, published, opts = {}) {
|
|
491
491
|
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
492
|
-
const
|
|
492
|
+
const log2 = opts.logger ?? ((line) => console.log(`[body] self-update: ${line}`));
|
|
493
493
|
const releaseId = sanitizeReleaseId(published.commit ?? published.version ?? `release-${Date.now()}`);
|
|
494
494
|
const releaseDir = join6(layout.releasesRoot, releaseId);
|
|
495
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
|
|
504
|
+
await mkdir10(releaseDir, { recursive: true });
|
|
505
505
|
const tempArchive = join6(layout.releasesRoot, `.download-${releaseId}-${process.pid}.tar.gz`);
|
|
506
506
|
try {
|
|
507
|
-
|
|
507
|
+
log2(`downloading ${published.file}`);
|
|
508
508
|
const response = await fetchImpl(archiveUrlFor(manifestUrl, published.file), {
|
|
509
509
|
signal: AbortSignal.timeout(10 * 6e4)
|
|
510
510
|
});
|
|
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 = "";
|
|
@@ -553,9 +553,9 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
|
|
|
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
|
+
log2(`staged release ${releaseId} (sha256 verified)`);
|
|
559
559
|
return releaseId;
|
|
560
560
|
} catch (error) {
|
|
561
561
|
if (!previouslyVerified)
|
|
@@ -581,15 +581,15 @@ 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
589
|
const releaseDir = join6(layout.releasesRoot, releaseId);
|
|
590
590
|
await access(join6(releaseDir, BUNDLE_ENTRYPOINT), fsConstants.F_OK);
|
|
591
|
-
await
|
|
592
|
-
await
|
|
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") {
|
|
@@ -611,7 +611,7 @@ async function activateRelease(layout, releaseId) {
|
|
|
611
611
|
await rm4(tempLink, { force: true });
|
|
612
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
|
}
|
|
@@ -628,7 +628,7 @@ 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
634
|
await access(join6(innerLib, name), fsConstants.F_OK);
|
|
@@ -655,13 +655,13 @@ async function repairInstallForwarders(layout, opts = {}) {
|
|
|
655
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;
|
|
@@ -676,14 +676,14 @@ async function rollbackToPreviousRelease(layout, previousReleaseId) {
|
|
|
676
676
|
await rm4(tempLink, { force: true });
|
|
677
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
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 dirname15, 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
|
|
@@ -2057,6 +2057,49 @@ var MULTISELECT_INSTRUCTIONS = [
|
|
|
2057
2057
|
`${styleText2("dim", "Space:")} select`,
|
|
2058
2058
|
`${styleText2("dim", "Enter:")} confirm`
|
|
2059
2059
|
];
|
|
2060
|
+
var log = {
|
|
2061
|
+
message: (s = [], {
|
|
2062
|
+
symbol: e = styleText2("gray", S_BAR),
|
|
2063
|
+
secondarySymbol: r2 = styleText2("gray", S_BAR),
|
|
2064
|
+
output: m2 = process.stdout,
|
|
2065
|
+
spacing: l2 = 1,
|
|
2066
|
+
withGuide: c2
|
|
2067
|
+
} = {}) => {
|
|
2068
|
+
const t2 = [], o = c2 ?? settings.withGuide, f = o ? r2 : "", O = o ? `${e} ` : "", u4 = o ? `${r2} ` : "";
|
|
2069
|
+
for (let i3 = 0; i3 < l2; i3++)
|
|
2070
|
+
t2.push(f);
|
|
2071
|
+
const g2 = Array.isArray(s) ? s : s.split(`
|
|
2072
|
+
`);
|
|
2073
|
+
if (g2.length > 0) {
|
|
2074
|
+
const [i3, ...y] = g2;
|
|
2075
|
+
i3.length > 0 ? t2.push(`${O}${i3}`) : t2.push(o ? e : "");
|
|
2076
|
+
for (const p of y)
|
|
2077
|
+
p.length > 0 ? t2.push(`${u4}${p}`) : t2.push(o ? r2 : "");
|
|
2078
|
+
}
|
|
2079
|
+
m2.write(`${t2.join(`
|
|
2080
|
+
`)}
|
|
2081
|
+
`);
|
|
2082
|
+
},
|
|
2083
|
+
info: (s, e) => {
|
|
2084
|
+
log.message(s, { ...e, symbol: styleText2("blue", S_INFO) });
|
|
2085
|
+
},
|
|
2086
|
+
success: (s, e) => {
|
|
2087
|
+
log.message(s, { ...e, symbol: styleText2("green", S_SUCCESS) });
|
|
2088
|
+
},
|
|
2089
|
+
step: (s, e) => {
|
|
2090
|
+
log.message(s, { ...e, symbol: styleText2("green", S_STEP_SUBMIT) });
|
|
2091
|
+
},
|
|
2092
|
+
warn: (s, e) => {
|
|
2093
|
+
log.message(s, { ...e, symbol: styleText2("yellow", S_WARN) });
|
|
2094
|
+
},
|
|
2095
|
+
/** alias for `log.warn()`. */
|
|
2096
|
+
warning: (s, e) => {
|
|
2097
|
+
log.warn(s, e);
|
|
2098
|
+
},
|
|
2099
|
+
error: (s, e) => {
|
|
2100
|
+
log.message(s, { ...e, symbol: styleText2("red", S_ERROR) });
|
|
2101
|
+
}
|
|
2102
|
+
};
|
|
2060
2103
|
var cancel = (o = "", t2) => {
|
|
2061
2104
|
const i3 = t2?.output ?? process.stdout, e = t2?.withGuide ?? settings.withGuide ? `${styleText2("gray", S_BAR_END)} ` : "";
|
|
2062
2105
|
i3.write(`${e}${styleText2("red", o)}
|
|
@@ -3099,6 +3142,9 @@ function agentMessageChunkText(update) {
|
|
|
3099
3142
|
}
|
|
3100
3143
|
var CHUNK_CONTINUES_PREVIOUS_WORD = /^[\s'\u2018\u2019\u02bc.,!?;:%)\]}-]/;
|
|
3101
3144
|
var PI_ACP_HARNESS = /(^|[/\\])pi-acp(?:\.[a-z]+)?$/i;
|
|
3145
|
+
function isPiAcpHarness(agentLabel) {
|
|
3146
|
+
return Boolean(agentLabel && PI_ACP_HARNESS.test(agentLabel));
|
|
3147
|
+
}
|
|
3102
3148
|
function withoutOneTrailingLineEnding(text2) {
|
|
3103
3149
|
if (/\r?\n\r?\n$/.test(text2))
|
|
3104
3150
|
return text2;
|
|
@@ -3166,6 +3212,19 @@ function finalAgentMessageText(updates, agentLabel) {
|
|
|
3166
3212
|
return "";
|
|
3167
3213
|
return last;
|
|
3168
3214
|
}
|
|
3215
|
+
function describeEmptyTurn(result, agentLabel) {
|
|
3216
|
+
const counts = /* @__PURE__ */ new Map();
|
|
3217
|
+
for (const { update } of result.updates) {
|
|
3218
|
+
const kind = typeof update.sessionUpdate === "string" ? update.sessionUpdate : "unknown";
|
|
3219
|
+
if (kind === "session_info_update" || kind === "available_commands_update")
|
|
3220
|
+
continue;
|
|
3221
|
+
counts.set(kind, (counts.get(kind) ?? 0) + 1);
|
|
3222
|
+
}
|
|
3223
|
+
const stream = counts.size ? `the stream carried only ${[...counts].map(([kind, count]) => `${kind}\xD7${count}`).join(", ")}` : "the stream carried no content updates";
|
|
3224
|
+
const lastRun = agentMessageRuns(result.updates, agentLabel).at(-1);
|
|
3225
|
+
const narration = lastRun && isPureRetryNarration(lastRun) ? `; the last message was retry narration "${lastRun.trim().slice(0, 80)}"` : "";
|
|
3226
|
+
return `harness ended the turn (${result.stopReason}) with no answer text; ${stream}${narration}`;
|
|
3227
|
+
}
|
|
3169
3228
|
function updateText(update) {
|
|
3170
3229
|
const content = update.content;
|
|
3171
3230
|
if (typeof content === "string")
|
|
@@ -3186,7 +3245,7 @@ var THOUGHT_UPDATE_TYPES = /* @__PURE__ */ new Set([
|
|
|
3186
3245
|
]);
|
|
3187
3246
|
function agentStreamSnapshot(updates, agentLabel) {
|
|
3188
3247
|
const completedMessages = [];
|
|
3189
|
-
let
|
|
3248
|
+
let messageText2 = "";
|
|
3190
3249
|
let lastWasMessage = false;
|
|
3191
3250
|
let thoughtText = "";
|
|
3192
3251
|
let thoughtRunOpen = false;
|
|
@@ -3196,20 +3255,20 @@ function agentStreamSnapshot(updates, agentLabel) {
|
|
|
3196
3255
|
const delta = normalizeStreamDelta(agentMessageChunkText(update), agentLabel);
|
|
3197
3256
|
if (!delta)
|
|
3198
3257
|
continue;
|
|
3199
|
-
if (!lastWasMessage &&
|
|
3200
|
-
if (!completedMessages.includes(
|
|
3201
|
-
completedMessages.push(
|
|
3202
|
-
|
|
3258
|
+
if (!lastWasMessage && messageText2 && !/\s$/.test(messageText2) && !CHUNK_CONTINUES_PREVIOUS_WORD.test(delta)) {
|
|
3259
|
+
if (!completedMessages.includes(messageText2))
|
|
3260
|
+
completedMessages.push(messageText2);
|
|
3261
|
+
messageText2 = "";
|
|
3203
3262
|
}
|
|
3204
|
-
|
|
3263
|
+
messageText2 += delta;
|
|
3205
3264
|
lastWasMessage = true;
|
|
3206
3265
|
thoughtRunOpen = false;
|
|
3207
3266
|
continue;
|
|
3208
3267
|
}
|
|
3209
|
-
if (lastWasMessage &&
|
|
3210
|
-
if (!completedMessages.includes(
|
|
3211
|
-
completedMessages.push(
|
|
3212
|
-
|
|
3268
|
+
if (lastWasMessage && messageText2) {
|
|
3269
|
+
if (!completedMessages.includes(messageText2))
|
|
3270
|
+
completedMessages.push(messageText2);
|
|
3271
|
+
messageText2 = "";
|
|
3213
3272
|
}
|
|
3214
3273
|
lastWasMessage = false;
|
|
3215
3274
|
if (THOUGHT_UPDATE_TYPES.has(kind)) {
|
|
@@ -3225,7 +3284,7 @@ function agentStreamSnapshot(updates, agentLabel) {
|
|
|
3225
3284
|
}
|
|
3226
3285
|
}
|
|
3227
3286
|
return {
|
|
3228
|
-
messageText,
|
|
3287
|
+
messageText: messageText2,
|
|
3229
3288
|
...thoughtText || completedMessages.length ? { thoughtText: thoughtText || completedMessages.at(-1) } : {}
|
|
3230
3289
|
};
|
|
3231
3290
|
}
|
|
@@ -3720,7 +3779,7 @@ var AcpClient = class extends EventEmitter {
|
|
|
3720
3779
|
const current = this.activeRunIds.get(sessionId);
|
|
3721
3780
|
if (current)
|
|
3722
3781
|
return Promise.resolve(current);
|
|
3723
|
-
return new Promise((
|
|
3782
|
+
return new Promise((resolve26, reject) => {
|
|
3724
3783
|
const onUpdate = (update) => {
|
|
3725
3784
|
if (update.sessionId !== sessionId)
|
|
3726
3785
|
return;
|
|
@@ -3728,7 +3787,7 @@ var AcpClient = class extends EventEmitter {
|
|
|
3728
3787
|
if (!runId)
|
|
3729
3788
|
return;
|
|
3730
3789
|
cleanup();
|
|
3731
|
-
|
|
3790
|
+
resolve26(runId);
|
|
3732
3791
|
};
|
|
3733
3792
|
const timer = setTimeout(() => {
|
|
3734
3793
|
cleanup();
|
|
@@ -3748,23 +3807,23 @@ var AcpClient = class extends EventEmitter {
|
|
|
3748
3807
|
const tracked = metadataKey ? this.toolCallMetadata.get(metadataKey) : void 0;
|
|
3749
3808
|
if (tracked)
|
|
3750
3809
|
p.toolCall = { ...tracked, ...p.toolCall };
|
|
3751
|
-
let
|
|
3810
|
+
let decision2 = this.autoApprove ? "allow" : "reject";
|
|
3752
3811
|
if (!this.autoApprove && this.permissionAllowlist) {
|
|
3753
3812
|
try {
|
|
3754
|
-
|
|
3813
|
+
decision2 = this.permissionAllowlist(p) ? "allow" : "reject";
|
|
3755
3814
|
} catch (error) {
|
|
3756
3815
|
this.emit("permission/error", error);
|
|
3757
|
-
|
|
3816
|
+
decision2 = "reject";
|
|
3758
3817
|
}
|
|
3759
3818
|
} else if (this.permissionHandler) {
|
|
3760
3819
|
try {
|
|
3761
|
-
|
|
3820
|
+
decision2 = await this.permissionHandler(p);
|
|
3762
3821
|
} catch (error) {
|
|
3763
3822
|
this.emit("permission/error", error);
|
|
3764
|
-
|
|
3823
|
+
decision2 = "reject";
|
|
3765
3824
|
}
|
|
3766
3825
|
}
|
|
3767
|
-
if (
|
|
3826
|
+
if (decision2 === "allow") {
|
|
3768
3827
|
const allow = p?.options?.find((o) => o.kind === "allow_once" || o.kind === "allow_always") ?? p?.options?.[0];
|
|
3769
3828
|
if (allow?.optionId) {
|
|
3770
3829
|
if (metadataKey)
|
|
@@ -3812,13 +3871,13 @@ var AcpClient = class extends EventEmitter {
|
|
|
3812
3871
|
}
|
|
3813
3872
|
const id = this.nextId++;
|
|
3814
3873
|
const payload = { jsonrpc: "2.0", id, method, params };
|
|
3815
|
-
return new Promise((
|
|
3874
|
+
return new Promise((resolve26, reject) => {
|
|
3816
3875
|
const timer = setTimeout(() => {
|
|
3817
3876
|
this.pending.delete(id);
|
|
3818
3877
|
reject(new AcpRequestTimeoutError(method, timeoutMs, this.stderrTail, Boolean(onStart)));
|
|
3819
3878
|
}, timeoutMs);
|
|
3820
3879
|
this.pending.set(id, {
|
|
3821
|
-
resolve:
|
|
3880
|
+
resolve: resolve26,
|
|
3822
3881
|
reject,
|
|
3823
3882
|
timer,
|
|
3824
3883
|
method,
|
|
@@ -4157,12 +4216,25 @@ async function applyAgentModelSelection(client, sessionId, advertisedOptions, se
|
|
|
4157
4216
|
}
|
|
4158
4217
|
|
|
4159
4218
|
// apps/body/dist/model-catalog.js
|
|
4219
|
+
function modelCatalogProbeEnvironment(agent, agentEnv, scratchCwd) {
|
|
4220
|
+
if (agent.kind !== "goose")
|
|
4221
|
+
return agentEnv;
|
|
4222
|
+
return {
|
|
4223
|
+
...agentEnv,
|
|
4224
|
+
GOOSE_PATH_ROOT: resolve4(scratchCwd, "goose")
|
|
4225
|
+
};
|
|
4226
|
+
}
|
|
4227
|
+
function filterAgentModelCatalog(agent, raw, agentEnv) {
|
|
4228
|
+
const allowed = filterAllowedModelConfigOptions(raw);
|
|
4229
|
+
return agent.kind === "goose" ? allowed : filterModelOptionsByCredentials(allowed, agentEnv);
|
|
4230
|
+
}
|
|
4160
4231
|
async function withAgentModelCatalog(agent, agentEnv, selection, inspect) {
|
|
4161
4232
|
const scratchCwd = await mkdtemp(resolve4(tmpdir(), "beeline-pair-model-check-"));
|
|
4233
|
+
const probeEnv = modelCatalogProbeEnvironment(agent, agentEnv, scratchCwd);
|
|
4162
4234
|
const client = new AcpClient({
|
|
4163
4235
|
agentCommand: agent.command,
|
|
4164
4236
|
agentArgs: agentArgsWithModelSelection(agent, selection),
|
|
4165
|
-
agentEnv,
|
|
4237
|
+
agentEnv: probeEnv,
|
|
4166
4238
|
agentCwd: scratchCwd,
|
|
4167
4239
|
// The wizard probe runs on the human's own machine for a few seconds —
|
|
4168
4240
|
// inherit the caller's environment so harness launchers resolve (`pi`,
|
|
@@ -4175,7 +4247,7 @@ async function withAgentModelCatalog(agent, agentEnv, selection, inspect) {
|
|
|
4175
4247
|
await client.start();
|
|
4176
4248
|
const { sessionId, raw: sessionRaw } = await client.sessionNew({ cwd: scratchCwd });
|
|
4177
4249
|
const raw = parseAdvertisedConfigOptions(sessionRaw, selection?.model, isGrokAgentCommand(agent));
|
|
4178
|
-
const catalog =
|
|
4250
|
+
const catalog = filterAgentModelCatalog(agent, raw, probeEnv);
|
|
4179
4251
|
return await inspect({ client, sessionId, raw, catalog });
|
|
4180
4252
|
} finally {
|
|
4181
4253
|
await client.stop().catch(() => void 0);
|
|
@@ -4236,31 +4308,433 @@ async function applyRuntimeModelPreflight(config, agent, selection, validate = v
|
|
|
4236
4308
|
config.modelUnavailable = await revalidateRuntimeModelSelection(agent, config.agentEnv, selection, validate);
|
|
4237
4309
|
}
|
|
4238
4310
|
|
|
4239
|
-
// apps/body/dist/
|
|
4240
|
-
import { execFile as execFile3 } from "node:child_process";
|
|
4311
|
+
// apps/body/dist/model-catalog-sync.js
|
|
4241
4312
|
import { createHash } from "node:crypto";
|
|
4313
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
4314
|
+
import { resolve as resolve5 } from "node:path";
|
|
4315
|
+
var MODEL_CATALOG_PROBE_TIMEOUT_MS = 3e4;
|
|
4316
|
+
var MODEL_CATALOG_HASH_FILE = "model-catalog.sha256";
|
|
4317
|
+
function modelCatalogHash(options, selection) {
|
|
4318
|
+
return createHash("sha256").update(JSON.stringify({ options, selection: selection ?? null })).digest("hex");
|
|
4319
|
+
}
|
|
4320
|
+
async function withTimeout(work, timeoutMs, what) {
|
|
4321
|
+
let timer;
|
|
4322
|
+
const expiry = new Promise((_, reject) => {
|
|
4323
|
+
timer = setTimeout(() => reject(new Error(`${what} timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
4324
|
+
});
|
|
4325
|
+
try {
|
|
4326
|
+
return await Promise.race([work, expiry]);
|
|
4327
|
+
} finally {
|
|
4328
|
+
clearTimeout(timer);
|
|
4329
|
+
work.catch(() => void 0);
|
|
4330
|
+
}
|
|
4331
|
+
}
|
|
4332
|
+
async function syncAgentModelCatalog(input) {
|
|
4333
|
+
const log2 = input.log ?? ((line) => console.log(line));
|
|
4334
|
+
const fetchCatalog = input.fetchCatalog ?? fetchAgentModelCatalog;
|
|
4335
|
+
const hashPath = resolve5(input.runtimeDir, MODEL_CATALOG_HASH_FILE);
|
|
4336
|
+
try {
|
|
4337
|
+
const configuration = await input.api.execute("getAgentConfiguration", {
|
|
4338
|
+
agentId: input.agentId
|
|
4339
|
+
});
|
|
4340
|
+
const selection = configuration.model || configuration.effort ? {
|
|
4341
|
+
...configuration.model ? { model: configuration.model } : {},
|
|
4342
|
+
...configuration.effort ? { effort: configuration.effort } : {}
|
|
4343
|
+
} : input.runtimeSelection;
|
|
4344
|
+
const { catalog } = await withTimeout(fetchCatalog(input.agent, input.agentEnv, selection), input.timeoutMs ?? MODEL_CATALOG_PROBE_TIMEOUT_MS, "model catalog probe");
|
|
4345
|
+
const hash = modelCatalogHash(catalog, selection);
|
|
4346
|
+
const previous = await readFile(hashPath, "utf8").catch(() => "");
|
|
4347
|
+
if (previous.trim() === hash)
|
|
4348
|
+
return "unchanged";
|
|
4349
|
+
await input.api.execute("postAgentModelCatalog", {
|
|
4350
|
+
agentId: input.agentId,
|
|
4351
|
+
workspaceId: input.workspaceId,
|
|
4352
|
+
// `fetchAgentModelCatalog` already applied the category allow-list.
|
|
4353
|
+
options: catalog,
|
|
4354
|
+
...selection ? { selection } : {}
|
|
4355
|
+
});
|
|
4356
|
+
await writeFile(hashPath, `${hash}
|
|
4357
|
+
`, { mode: 384 });
|
|
4358
|
+
log2(`[body] model catalog posted: ${catalog.length} axis(es)` + (selection?.model ? `, model ${selection.model}` : "") + (selection?.effort ? `, effort ${selection.effort}` : ""));
|
|
4359
|
+
return "posted";
|
|
4360
|
+
} catch (error) {
|
|
4361
|
+
log2(`[body] model catalog not posted this activation: ${error instanceof Error ? error.message : String(error)}`);
|
|
4362
|
+
return "failed";
|
|
4363
|
+
}
|
|
4364
|
+
}
|
|
4365
|
+
|
|
4366
|
+
// apps/body/dist/room-runtime.js
|
|
4367
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
4368
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
4242
4369
|
import { existsSync as existsSync4, mkdirSync } from "node:fs";
|
|
4243
|
-
import { mkdir as
|
|
4244
|
-
import { dirname as
|
|
4370
|
+
import { mkdir as mkdir8, rm as rm3 } from "node:fs/promises";
|
|
4371
|
+
import { dirname as dirname6, resolve as resolve15 } from "node:path";
|
|
4245
4372
|
import { promisify as promisify3 } from "node:util";
|
|
4246
4373
|
|
|
4374
|
+
// apps/body/dist/grant-runner.js
|
|
4375
|
+
import { execFile } from "node:child_process";
|
|
4376
|
+
import { randomBytes } from "node:crypto";
|
|
4377
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
4378
|
+
import { createServer } from "node:http";
|
|
4379
|
+
import { dirname as dirname3, resolve as resolve7 } from "node:path";
|
|
4380
|
+
|
|
4381
|
+
// packages/api-contract/dist/agent-grants.js
|
|
4382
|
+
var AGENT_GRANT_KINDS = ["path", "host", "secret", "device", "budget", "command"];
|
|
4383
|
+
var SHELL_METACHARACTERS = /[;&|<>$`\\'"(){}\n\r\t*?[\]~#!]/;
|
|
4384
|
+
var SECRET_NAME = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
4385
|
+
function parseCommandGrantTarget(target) {
|
|
4386
|
+
if (typeof target !== "string" || !target.trim()) {
|
|
4387
|
+
throw new Error("command target is required");
|
|
4388
|
+
}
|
|
4389
|
+
if (target !== target.trim() || /\s{2,}/.test(target)) {
|
|
4390
|
+
throw new Error("command target must be one line with single spaces");
|
|
4391
|
+
}
|
|
4392
|
+
if (SHELL_METACHARACTERS.test(target)) {
|
|
4393
|
+
throw new Error("command target must not contain shell metacharacters");
|
|
4394
|
+
}
|
|
4395
|
+
const words = target.split(" ");
|
|
4396
|
+
const argv = [];
|
|
4397
|
+
const secrets = [];
|
|
4398
|
+
for (let index = 0; index < words.length; index += 1) {
|
|
4399
|
+
const word = words[index];
|
|
4400
|
+
if (word === "--with") {
|
|
4401
|
+
const name = words[index + 1];
|
|
4402
|
+
if (!name || !SECRET_NAME.test(name)) {
|
|
4403
|
+
throw new Error("--with must name one UPPER_CASE secret");
|
|
4404
|
+
}
|
|
4405
|
+
if (!secrets.includes(name))
|
|
4406
|
+
secrets.push(name);
|
|
4407
|
+
index += 1;
|
|
4408
|
+
continue;
|
|
4409
|
+
}
|
|
4410
|
+
if (secrets.length)
|
|
4411
|
+
throw new Error("--with suffixes must come after the command words");
|
|
4412
|
+
argv.push(word);
|
|
4413
|
+
}
|
|
4414
|
+
if (!argv.length)
|
|
4415
|
+
throw new Error("command target must name a command");
|
|
4416
|
+
return { argv, secrets };
|
|
4417
|
+
}
|
|
4418
|
+
function commandGrantMatches(rule, requested) {
|
|
4419
|
+
if (requested.length < rule.argv.length)
|
|
4420
|
+
return false;
|
|
4421
|
+
return rule.argv.every((word, index) => requested[index] === word);
|
|
4422
|
+
}
|
|
4423
|
+
var DECISION_LINE = new RegExp(`^(.+?) (approved once|approved|declined) (${AGENT_GRANT_KINDS.join("|")}) (.+)$`, "s");
|
|
4424
|
+
function parseGrantDecisionLine(body) {
|
|
4425
|
+
const match = DECISION_LINE.exec(body);
|
|
4426
|
+
if (!match)
|
|
4427
|
+
return void 0;
|
|
4428
|
+
const decision2 = match[2] === "approved once" ? "once" : match[2] === "approved" ? "always" : "deny";
|
|
4429
|
+
return {
|
|
4430
|
+
deciderName: match[1],
|
|
4431
|
+
decision: decision2,
|
|
4432
|
+
kind: match[3],
|
|
4433
|
+
target: match[4]
|
|
4434
|
+
};
|
|
4435
|
+
}
|
|
4436
|
+
|
|
4437
|
+
// apps/body/dist/provider-key-store.js
|
|
4438
|
+
import { chmod, mkdir, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
4439
|
+
import { homedir as homedir2 } from "node:os";
|
|
4440
|
+
import { dirname as dirname2, resolve as resolve6 } from "node:path";
|
|
4441
|
+
var PROVIDER_KEY_ENV_VARS = {
|
|
4442
|
+
openrouter: "OPENROUTER_API_KEY",
|
|
4443
|
+
openai: "OPENAI_API_KEY",
|
|
4444
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
4445
|
+
google: "GOOGLE_API_KEY",
|
|
4446
|
+
xai: "XAI_API_KEY"
|
|
4447
|
+
};
|
|
4448
|
+
var GOOGLE_ENV_ALIAS = "GEMINI_API_KEY";
|
|
4449
|
+
function providerKeyStorePath(env = process.env) {
|
|
4450
|
+
const configRoot = env.XDG_CONFIG_HOME?.trim() || resolve6(homedir2(), ".config");
|
|
4451
|
+
return resolve6(configRoot, "beeline", "providers.json");
|
|
4452
|
+
}
|
|
4453
|
+
async function readProviderKeyStore(env = process.env) {
|
|
4454
|
+
const path = providerKeyStorePath(env);
|
|
4455
|
+
const raw = await readFile2(path, "utf8").catch(() => void 0);
|
|
4456
|
+
if (!raw)
|
|
4457
|
+
return {};
|
|
4458
|
+
try {
|
|
4459
|
+
const parsed = JSON.parse(raw);
|
|
4460
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
4461
|
+
return {};
|
|
4462
|
+
const entries = Object.entries(parsed).filter((entry) => entry[0] in PROVIDER_KEY_ENV_VARS && typeof entry[1] === "string" && entry[1].length > 0);
|
|
4463
|
+
return Object.fromEntries(entries);
|
|
4464
|
+
} catch {
|
|
4465
|
+
return {};
|
|
4466
|
+
}
|
|
4467
|
+
}
|
|
4468
|
+
async function readSavedProviderKey(provider, env = process.env) {
|
|
4469
|
+
return (await readProviderKeyStore(env))[provider];
|
|
4470
|
+
}
|
|
4471
|
+
async function saveProviderKey(provider, key, env = process.env) {
|
|
4472
|
+
const path = providerKeyStorePath(env);
|
|
4473
|
+
const store = { ...await readProviderKeyStore(env), [provider]: key };
|
|
4474
|
+
await mkdir(dirname2(path), { recursive: true, mode: 448 });
|
|
4475
|
+
await writeFile2(path, `${JSON.stringify(store, null, 2)}
|
|
4476
|
+
`, { mode: 384 });
|
|
4477
|
+
await chmod(path, 384);
|
|
4478
|
+
}
|
|
4479
|
+
function providerKeyFromEnvironment(provider, env = process.env) {
|
|
4480
|
+
const primary = env[PROVIDER_KEY_ENV_VARS[provider]]?.trim();
|
|
4481
|
+
if (primary)
|
|
4482
|
+
return primary;
|
|
4483
|
+
if (provider === "google")
|
|
4484
|
+
return env[GOOGLE_ENV_ALIAS]?.trim() || void 0;
|
|
4485
|
+
return void 0;
|
|
4486
|
+
}
|
|
4487
|
+
function maskProviderKey(key) {
|
|
4488
|
+
const trimmed = key.trim();
|
|
4489
|
+
if (trimmed.length <= 9)
|
|
4490
|
+
return "\u2026";
|
|
4491
|
+
return `${trimmed.slice(0, 6)}\u2026${trimmed.slice(-3)}`;
|
|
4492
|
+
}
|
|
4493
|
+
|
|
4494
|
+
// apps/body/dist/grant-runner.js
|
|
4495
|
+
var GRANT_COMMAND_TIMEOUT_MS = 10 * 6e4;
|
|
4496
|
+
var GRANT_COMMAND_OUTPUT_CAP_BYTES = 64 * 1024;
|
|
4497
|
+
var ARGV_MAX_WORDS = 256;
|
|
4498
|
+
var ARGV_WORD_MAX_LENGTH = 4096;
|
|
4499
|
+
function operatorSecretResolver(env = process.env) {
|
|
4500
|
+
const providerByEnvVar = new Map(Object.entries(PROVIDER_KEY_ENV_VARS).map(([provider, envVar]) => [envVar, provider]));
|
|
4501
|
+
return async (name) => {
|
|
4502
|
+
const provider = providerByEnvVar.get(name);
|
|
4503
|
+
if (provider) {
|
|
4504
|
+
const saved = (await readProviderKeyStore(env))[provider];
|
|
4505
|
+
if (saved)
|
|
4506
|
+
return saved;
|
|
4507
|
+
}
|
|
4508
|
+
const raw = await readFile3(resolve7(dirname3(providerKeyStorePath(env)), "secrets.json"), "utf8").catch(() => void 0);
|
|
4509
|
+
if (!raw)
|
|
4510
|
+
return void 0;
|
|
4511
|
+
try {
|
|
4512
|
+
const parsed = JSON.parse(raw);
|
|
4513
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
4514
|
+
return void 0;
|
|
4515
|
+
const value = parsed[name];
|
|
4516
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
4517
|
+
} catch {
|
|
4518
|
+
return void 0;
|
|
4519
|
+
}
|
|
4520
|
+
};
|
|
4521
|
+
}
|
|
4522
|
+
function validateGrantArgv(value) {
|
|
4523
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
4524
|
+
throw new Error("argv must be a non-empty array");
|
|
4525
|
+
if (value.length > ARGV_MAX_WORDS)
|
|
4526
|
+
throw new Error(`argv exceeds ${ARGV_MAX_WORDS} words`);
|
|
4527
|
+
return value.map((word) => {
|
|
4528
|
+
if (typeof word !== "string" || !word)
|
|
4529
|
+
throw new Error("argv words must be non-empty strings");
|
|
4530
|
+
if (word.length > ARGV_WORD_MAX_LENGTH || /[\0\r\n]/.test(word)) {
|
|
4531
|
+
throw new Error("argv words must be single-line and bounded");
|
|
4532
|
+
}
|
|
4533
|
+
return word;
|
|
4534
|
+
});
|
|
4535
|
+
}
|
|
4536
|
+
function matchCommandGrant(grants, workspaceId, argv) {
|
|
4537
|
+
for (const grant of grants) {
|
|
4538
|
+
if (grant.kind !== "command" || grant.workspaceId !== workspaceId)
|
|
4539
|
+
continue;
|
|
4540
|
+
let rule;
|
|
4541
|
+
try {
|
|
4542
|
+
rule = parseCommandGrantTarget(grant.target);
|
|
4543
|
+
} catch {
|
|
4544
|
+
continue;
|
|
4545
|
+
}
|
|
4546
|
+
if (commandGrantMatches(rule, argv))
|
|
4547
|
+
return { grant, rule };
|
|
4548
|
+
}
|
|
4549
|
+
return void 0;
|
|
4550
|
+
}
|
|
4551
|
+
function capOutput(value, cap) {
|
|
4552
|
+
if (Buffer.byteLength(value) <= cap)
|
|
4553
|
+
return value;
|
|
4554
|
+
const half = Math.floor(cap / 2);
|
|
4555
|
+
const bytes = Buffer.from(value);
|
|
4556
|
+
return `${bytes.subarray(0, half).toString("utf8")}
|
|
4557
|
+
\u2026[${bytes.length - cap} bytes omitted]\u2026
|
|
4558
|
+
${bytes.subarray(bytes.length - half).toString("utf8")}`;
|
|
4559
|
+
}
|
|
4560
|
+
function scrubSecrets(value, secrets) {
|
|
4561
|
+
let scrubbed = value;
|
|
4562
|
+
for (const [name, secret] of secrets) {
|
|
4563
|
+
if (secret.length >= 4)
|
|
4564
|
+
scrubbed = scrubbed.split(secret).join(`[${name}]`);
|
|
4565
|
+
}
|
|
4566
|
+
return scrubbed;
|
|
4567
|
+
}
|
|
4568
|
+
var GrantCommandRunner = class {
|
|
4569
|
+
options;
|
|
4570
|
+
rooms = /* @__PURE__ */ new Map();
|
|
4571
|
+
resolveSecret;
|
|
4572
|
+
constructor(options) {
|
|
4573
|
+
this.options = options;
|
|
4574
|
+
this.resolveSecret = options.resolveSecret ?? operatorSecretResolver(options.env ?? process.env);
|
|
4575
|
+
}
|
|
4576
|
+
register(roomId, room) {
|
|
4577
|
+
this.rooms.set(roomId, room);
|
|
4578
|
+
}
|
|
4579
|
+
unregister(roomId) {
|
|
4580
|
+
this.rooms.delete(roomId);
|
|
4581
|
+
}
|
|
4582
|
+
async run(input) {
|
|
4583
|
+
if (typeof input.roomId !== "string" || !input.roomId)
|
|
4584
|
+
throw new Error("roomId is required");
|
|
4585
|
+
const room = this.rooms.get(input.roomId);
|
|
4586
|
+
if (!room)
|
|
4587
|
+
throw new Error("this daemon is not serving that Room");
|
|
4588
|
+
const argv = validateGrantArgv(input.argv);
|
|
4589
|
+
const live = await this.options.api.execute("listAgentGrants", { agentId: this.options.agentId });
|
|
4590
|
+
const match = matchCommandGrant(live.grants, room.workspaceId, argv);
|
|
4591
|
+
if (!match) {
|
|
4592
|
+
throw new Error(`no approved command grant matches: ${argv.join(" ")}. Ask with request_grant kind=command first.`);
|
|
4593
|
+
}
|
|
4594
|
+
const { grant, rule } = match;
|
|
4595
|
+
const secrets = /* @__PURE__ */ new Map();
|
|
4596
|
+
for (const name of rule.secrets) {
|
|
4597
|
+
const value = await this.resolveSecret(name);
|
|
4598
|
+
if (!value)
|
|
4599
|
+
throw new Error(`secret ${name} is not in the operator key store`);
|
|
4600
|
+
secrets.set(name, value);
|
|
4601
|
+
}
|
|
4602
|
+
if (grant.status === "once") {
|
|
4603
|
+
await this.options.api.execute("consumeAgentGrant", { grantId: grant.grantId });
|
|
4604
|
+
}
|
|
4605
|
+
const source = this.options.env ?? process.env;
|
|
4606
|
+
const env = {
|
|
4607
|
+
...source.PATH ? { PATH: source.PATH } : {},
|
|
4608
|
+
...source.HOME ? { HOME: source.HOME } : {},
|
|
4609
|
+
...Object.fromEntries(secrets)
|
|
4610
|
+
};
|
|
4611
|
+
const cap = this.options.outputCapBytes ?? GRANT_COMMAND_OUTPUT_CAP_BYTES;
|
|
4612
|
+
const outcome = await new Promise((resolveRun) => {
|
|
4613
|
+
const child = execFile(argv[0], argv.slice(1), {
|
|
4614
|
+
cwd: room.cwd,
|
|
4615
|
+
env,
|
|
4616
|
+
timeout: this.options.timeoutMs ?? GRANT_COMMAND_TIMEOUT_MS,
|
|
4617
|
+
killSignal: "SIGKILL",
|
|
4618
|
+
maxBuffer: cap * 4,
|
|
4619
|
+
encoding: "utf8"
|
|
4620
|
+
}, (error, stdout6, stderr) => {
|
|
4621
|
+
const combined = [stdout6, stderr].filter(Boolean).join(stderr && stdout6 ? "\n" : "");
|
|
4622
|
+
const failure = error;
|
|
4623
|
+
const spawnFailure = Boolean(failure && typeof failure.code === "string");
|
|
4624
|
+
resolveRun({
|
|
4625
|
+
exitCode: spawnFailure ? null : child.exitCode ?? (typeof failure?.code === "number" ? failure.code : 0),
|
|
4626
|
+
...failure?.signal ? { signal: failure.signal } : {},
|
|
4627
|
+
timedOut: Boolean(failure?.killed && failure?.signal === "SIGKILL"),
|
|
4628
|
+
output: spawnFailure ? `${combined}${combined ? "\n" : ""}${failure.message}` : combined
|
|
4629
|
+
});
|
|
4630
|
+
});
|
|
4631
|
+
});
|
|
4632
|
+
const output = capOutput(scrubSecrets(outcome.output, secrets), cap);
|
|
4633
|
+
const turn = room.turn();
|
|
4634
|
+
const requester = turn?.requester ?? {
|
|
4635
|
+
pubkey: grant.requestedBy,
|
|
4636
|
+
...grant.requestedByName ? { name: grant.requestedByName } : {}
|
|
4637
|
+
};
|
|
4638
|
+
const status = outcome.timedOut ? "timed out" : outcome.exitCode === null ? "error" : `exit ${outcome.exitCode}`;
|
|
4639
|
+
await this.options.api.execute("postAgentActivity", {
|
|
4640
|
+
agentId: this.options.agentId,
|
|
4641
|
+
roomId: input.roomId,
|
|
4642
|
+
requestId: turn?.requestId ?? `grant:${grant.grantId}`,
|
|
4643
|
+
activity: [
|
|
4644
|
+
{
|
|
4645
|
+
kind: "tool",
|
|
4646
|
+
title: `ran ${argv.join(" ")} under grant ${grant.grantId} \xB7 asked by ${requester.name ?? requester.pubkey.slice(0, 12)}`,
|
|
4647
|
+
operation: "execute",
|
|
4648
|
+
status,
|
|
4649
|
+
command: argv.join(" "),
|
|
4650
|
+
...output ? { output } : {},
|
|
4651
|
+
requestedBy: requester
|
|
4652
|
+
}
|
|
4653
|
+
]
|
|
4654
|
+
});
|
|
4655
|
+
return {
|
|
4656
|
+
grantId: grant.grantId,
|
|
4657
|
+
exitCode: outcome.exitCode,
|
|
4658
|
+
...outcome.signal ? { signal: outcome.signal } : {},
|
|
4659
|
+
timedOut: outcome.timedOut,
|
|
4660
|
+
output
|
|
4661
|
+
};
|
|
4662
|
+
}
|
|
4663
|
+
};
|
|
4664
|
+
var GrantRunnerServer = class {
|
|
4665
|
+
runner;
|
|
4666
|
+
server;
|
|
4667
|
+
endpoint;
|
|
4668
|
+
constructor(runner) {
|
|
4669
|
+
this.runner = runner;
|
|
4670
|
+
}
|
|
4671
|
+
async start() {
|
|
4672
|
+
if (this.endpoint)
|
|
4673
|
+
return this.endpoint;
|
|
4674
|
+
const token = randomBytes(32).toString("base64url");
|
|
4675
|
+
const server = createServer((request, response) => {
|
|
4676
|
+
void this.handle(request, response, token);
|
|
4677
|
+
});
|
|
4678
|
+
await new Promise((resolveListen, reject) => {
|
|
4679
|
+
server.once("error", reject);
|
|
4680
|
+
server.listen(0, "127.0.0.1", () => resolveListen());
|
|
4681
|
+
});
|
|
4682
|
+
server.unref?.();
|
|
4683
|
+
const address = server.address();
|
|
4684
|
+
this.server = server;
|
|
4685
|
+
this.endpoint = { url: `http://127.0.0.1:${address.port}`, token };
|
|
4686
|
+
return this.endpoint;
|
|
4687
|
+
}
|
|
4688
|
+
async close() {
|
|
4689
|
+
const server = this.server;
|
|
4690
|
+
this.server = void 0;
|
|
4691
|
+
this.endpoint = void 0;
|
|
4692
|
+
if (!server)
|
|
4693
|
+
return;
|
|
4694
|
+
await new Promise((resolveClose) => server.close(() => resolveClose()));
|
|
4695
|
+
}
|
|
4696
|
+
async handle(request, response, token) {
|
|
4697
|
+
const send = (status, body) => {
|
|
4698
|
+
response.writeHead(status, { "content-type": "application/json" });
|
|
4699
|
+
response.end(JSON.stringify(body));
|
|
4700
|
+
};
|
|
4701
|
+
if (request.headers.authorization !== `Bearer ${token}`) {
|
|
4702
|
+
send(401, { error: "grant runner token required" });
|
|
4703
|
+
return;
|
|
4704
|
+
}
|
|
4705
|
+
if (request.method !== "POST" || request.url !== "/run") {
|
|
4706
|
+
send(404, { error: "not found" });
|
|
4707
|
+
return;
|
|
4708
|
+
}
|
|
4709
|
+
const chunks = [];
|
|
4710
|
+
for await (const chunk of request)
|
|
4711
|
+
chunks.push(chunk);
|
|
4712
|
+
try {
|
|
4713
|
+
const body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
4714
|
+
send(200, await this.runner.run({ roomId: body.roomId, argv: body.argv }));
|
|
4715
|
+
} catch (error) {
|
|
4716
|
+
send(400, { error: error instanceof Error ? error.message : String(error) });
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
};
|
|
4720
|
+
|
|
4247
4721
|
// apps/body/dist/monolith-corner-turn.js
|
|
4248
|
-
import { execFile as
|
|
4249
|
-
import { mkdir as
|
|
4250
|
-
import { homedir as
|
|
4722
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
4723
|
+
import { mkdir as mkdir6 } from "node:fs/promises";
|
|
4724
|
+
import { homedir as homedir6 } from "node:os";
|
|
4251
4725
|
import { join as join4 } from "node:path";
|
|
4252
4726
|
import { promisify as promisify2 } from "node:util";
|
|
4253
4727
|
|
|
4254
4728
|
// apps/body/dist/agent-home.js
|
|
4255
4729
|
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
|
|
4256
4730
|
import { randomUUID } from "node:crypto";
|
|
4257
|
-
import { chmod, copyFile, lstat, mkdir, readdir, realpath, rename, rm as rm2, symlink, unlink, writeFile } from "node:fs/promises";
|
|
4258
|
-
import { homedir as
|
|
4259
|
-
import { basename as basename3, dirname as
|
|
4731
|
+
import { chmod as chmod2, copyFile, lstat, mkdir as mkdir3, readdir, realpath, rename, rm as rm2, symlink, unlink, writeFile as writeFile4 } from "node:fs/promises";
|
|
4732
|
+
import { homedir as homedir3 } from "node:os";
|
|
4733
|
+
import { basename as basename3, dirname as dirname4, relative, resolve as resolve10, sep } from "node:path";
|
|
4260
4734
|
|
|
4261
4735
|
// apps/body/dist/beeline-skill.js
|
|
4262
4736
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
4263
|
-
import { resolve as
|
|
4737
|
+
import { resolve as resolve8 } from "node:path";
|
|
4264
4738
|
var USING_BEELINE_SKILL_NAME = "using-beeline";
|
|
4265
4739
|
var BEELINE_ROOM_CAPABILITIES = [
|
|
4266
4740
|
"The repository filesystem is read-only in this Room session.",
|
|
@@ -4272,6 +4746,7 @@ var BEELINE_ROOM_CAPABILITIES = [
|
|
|
4272
4746
|
"To send a file, call beeline-agent attach_file with a path inside your checkout; it is attached to your reply.",
|
|
4273
4747
|
"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.",
|
|
4274
4748
|
"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.",
|
|
4749
|
+
"When open_corner succeeds, the server posts the corner card: do not announce or restate the opening. End the turn with nothing more unless the person asked something else.",
|
|
4275
4750
|
"Never claim an action or reply happened unless the prompt or a tool result proves it."
|
|
4276
4751
|
].join(" ");
|
|
4277
4752
|
var BEELINE_DM_CAPABILITIES = [
|
|
@@ -4304,7 +4779,7 @@ function runningBeelineReleaseId(env = process.env, read = (path) => readFileSyn
|
|
|
4304
4779
|
const lib = env.BEELINE_LIB_DIR;
|
|
4305
4780
|
if (!lib)
|
|
4306
4781
|
return "source";
|
|
4307
|
-
const manifest = JSON.parse(read(
|
|
4782
|
+
const manifest = JSON.parse(read(resolve8(lib, "bundle.json")));
|
|
4308
4783
|
return [manifest.version, manifest.commit].filter(Boolean).join("-") || "source";
|
|
4309
4784
|
} catch {
|
|
4310
4785
|
return "source";
|
|
@@ -4340,6 +4815,226 @@ var SQUIRE_GOVERNED_TOOLS = [
|
|
|
4340
4815
|
];
|
|
4341
4816
|
var SQUIRE_GOVERNED_TOOL_SET = new Set(SQUIRE_GOVERNED_TOOLS);
|
|
4342
4817
|
|
|
4818
|
+
// apps/body/dist/openrouter-routing.js
|
|
4819
|
+
import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
|
|
4820
|
+
import { resolve as resolve9 } from "node:path";
|
|
4821
|
+
var OPENROUTER_ENDPOINTS_BASE_URL = "https://openrouter.ai/api/v1/models";
|
|
4822
|
+
var OPENROUTER_ROUTING_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
4823
|
+
var OPENROUTER_ROUTING_FETCH_TIMEOUT_MS = 1e4;
|
|
4824
|
+
var OPENROUTER_UPTIME_BAR = 98;
|
|
4825
|
+
var OPENROUTER_UPTIME_BAR_RELAXED = 95;
|
|
4826
|
+
var OPENROUTER_MIN_PROVIDERS = 2;
|
|
4827
|
+
var OPENROUTER_FALLBACK_PROVIDERS = ["deepinfra", "novita"];
|
|
4828
|
+
function openRouterModelId(model, env = {}) {
|
|
4829
|
+
const trimmed = model?.trim();
|
|
4830
|
+
if (!trimmed)
|
|
4831
|
+
return void 0;
|
|
4832
|
+
if (trimmed.startsWith("openrouter/")) {
|
|
4833
|
+
const id = trimmed.slice("openrouter/".length);
|
|
4834
|
+
return id.includes("/") ? id : void 0;
|
|
4835
|
+
}
|
|
4836
|
+
if (env.OPENROUTER_API_KEY?.trim() && /^[^/\s]+\/[^/\s][^\s]*$/.test(trimmed))
|
|
4837
|
+
return trimmed;
|
|
4838
|
+
return void 0;
|
|
4839
|
+
}
|
|
4840
|
+
function parseOpenRouterEndpoints(payload) {
|
|
4841
|
+
const data = payload?.data;
|
|
4842
|
+
const record2 = data && typeof data === "object" ? data : {};
|
|
4843
|
+
const raw = Array.isArray(record2.endpoints) ? record2.endpoints : [];
|
|
4844
|
+
const endpoints = [];
|
|
4845
|
+
for (const entry of raw) {
|
|
4846
|
+
if (!entry || typeof entry !== "object")
|
|
4847
|
+
continue;
|
|
4848
|
+
const endpoint2 = entry;
|
|
4849
|
+
const tag = typeof endpoint2.tag === "string" ? endpoint2.tag : "";
|
|
4850
|
+
const provider = tag.split("/")[0]?.trim() ?? "";
|
|
4851
|
+
if (!provider)
|
|
4852
|
+
continue;
|
|
4853
|
+
const uptime = typeof endpoint2.uptime_last_30m === "number" ? endpoint2.uptime_last_30m : 0;
|
|
4854
|
+
const contextLength = typeof endpoint2.context_length === "number" ? endpoint2.context_length : 0;
|
|
4855
|
+
const supported = Array.isArray(endpoint2.supported_parameters) ? endpoint2.supported_parameters : [];
|
|
4856
|
+
endpoints.push({ provider, uptime, contextLength, tools: supported.includes("tools") });
|
|
4857
|
+
}
|
|
4858
|
+
return {
|
|
4859
|
+
endpoints,
|
|
4860
|
+
contextLength: typeof record2.context_length === "number" ? record2.context_length : advertisedContextLength(endpoints)
|
|
4861
|
+
};
|
|
4862
|
+
}
|
|
4863
|
+
function advertisedContextLength(endpoints) {
|
|
4864
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4865
|
+
for (const endpoint2 of endpoints) {
|
|
4866
|
+
if (!endpoint2.tools || endpoint2.contextLength <= 0)
|
|
4867
|
+
continue;
|
|
4868
|
+
counts.set(endpoint2.contextLength, (counts.get(endpoint2.contextLength) ?? 0) + 1);
|
|
4869
|
+
}
|
|
4870
|
+
let best;
|
|
4871
|
+
for (const [length, count] of counts) {
|
|
4872
|
+
if (best === void 0) {
|
|
4873
|
+
best = length;
|
|
4874
|
+
continue;
|
|
4875
|
+
}
|
|
4876
|
+
const bestCount = counts.get(best) ?? 0;
|
|
4877
|
+
if (count > bestCount || count === bestCount && length > best)
|
|
4878
|
+
best = length;
|
|
4879
|
+
}
|
|
4880
|
+
return best;
|
|
4881
|
+
}
|
|
4882
|
+
function selectReliableOpenRouterProviders(endpoints, contextLength) {
|
|
4883
|
+
const eligible = endpoints.filter((endpoint2) => endpoint2.tools && (contextLength === void 0 || endpoint2.contextLength >= contextLength));
|
|
4884
|
+
const atBar = (bar) => {
|
|
4885
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4886
|
+
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));
|
|
4887
|
+
};
|
|
4888
|
+
const strict = atBar(OPENROUTER_UPTIME_BAR);
|
|
4889
|
+
if (strict.length >= OPENROUTER_MIN_PROVIDERS) {
|
|
4890
|
+
return { providers: strict, bar: OPENROUTER_UPTIME_BAR, contextLength };
|
|
4891
|
+
}
|
|
4892
|
+
const relaxed = atBar(OPENROUTER_UPTIME_BAR_RELAXED);
|
|
4893
|
+
if (relaxed.length > 0) {
|
|
4894
|
+
return { providers: relaxed, bar: OPENROUTER_UPTIME_BAR_RELAXED, contextLength };
|
|
4895
|
+
}
|
|
4896
|
+
return { providers: [], bar: null, contextLength };
|
|
4897
|
+
}
|
|
4898
|
+
function openRouterRoutingFor(providers) {
|
|
4899
|
+
return {
|
|
4900
|
+
only: [...providers],
|
|
4901
|
+
order: [...providers],
|
|
4902
|
+
allow_fallbacks: true,
|
|
4903
|
+
require_parameters: false
|
|
4904
|
+
};
|
|
4905
|
+
}
|
|
4906
|
+
function openRouterRoutingCacheDir(runtimeDir) {
|
|
4907
|
+
return resolve9(runtimeDir, "openrouter-routing");
|
|
4908
|
+
}
|
|
4909
|
+
function cachePath(cacheDir, model) {
|
|
4910
|
+
return resolve9(cacheDir, `${model.replace(/[^A-Za-z0-9._-]+/g, "_")}.json`);
|
|
4911
|
+
}
|
|
4912
|
+
async function readCache(cacheDir, model) {
|
|
4913
|
+
try {
|
|
4914
|
+
const parsed = JSON.parse(await readFile4(cachePath(cacheDir, model), "utf8"));
|
|
4915
|
+
if (!parsed || typeof parsed !== "object")
|
|
4916
|
+
return void 0;
|
|
4917
|
+
const cached = parsed;
|
|
4918
|
+
if (cached.model !== model || typeof cached.fetchedAt !== "number" || !Array.isArray(cached.providers) || !cached.providers.every((provider) => typeof provider === "string" && provider.length > 0)) {
|
|
4919
|
+
return void 0;
|
|
4920
|
+
}
|
|
4921
|
+
return {
|
|
4922
|
+
model,
|
|
4923
|
+
fetchedAt: cached.fetchedAt,
|
|
4924
|
+
providers: cached.providers,
|
|
4925
|
+
bar: typeof cached.bar === "number" ? cached.bar : null
|
|
4926
|
+
};
|
|
4927
|
+
} catch {
|
|
4928
|
+
return void 0;
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
4931
|
+
async function writeCache(cacheDir, value) {
|
|
4932
|
+
await mkdir2(cacheDir, { recursive: true, mode: 448 });
|
|
4933
|
+
await writeFile3(cachePath(cacheDir, value.model), `${JSON.stringify(value, null, 2)}
|
|
4934
|
+
`, {
|
|
4935
|
+
mode: 384
|
|
4936
|
+
});
|
|
4937
|
+
}
|
|
4938
|
+
function decision(model, providers, bar, source, note) {
|
|
4939
|
+
const criteria = bar === null ? "fallback pair" : `uptime \u2265${bar}%, tools`;
|
|
4940
|
+
const suffix = [
|
|
4941
|
+
bar === OPENROUTER_UPTIME_BAR_RELAXED ? "bar lowered: fewer than 2 providers at 98%" : "",
|
|
4942
|
+
source === "cache" ? "cached" : "",
|
|
4943
|
+
source === "stale-cache" ? "stale cache" : "",
|
|
4944
|
+
note ?? ""
|
|
4945
|
+
].filter(Boolean).join("; ");
|
|
4946
|
+
return {
|
|
4947
|
+
model,
|
|
4948
|
+
routing: openRouterRoutingFor(providers),
|
|
4949
|
+
providers: [...providers],
|
|
4950
|
+
bar,
|
|
4951
|
+
source,
|
|
4952
|
+
line: `[body] openrouter routing for ${model}: ${providers.join(", ")} (${criteria}` + (suffix ? `; ${suffix})` : ")")
|
|
4953
|
+
};
|
|
4954
|
+
}
|
|
4955
|
+
async function resolveOpenRouterRouting(input) {
|
|
4956
|
+
const now2 = input.now ?? Date.now;
|
|
4957
|
+
const cached = await readCache(input.cacheDir, input.model);
|
|
4958
|
+
if (cached && now2() - cached.fetchedAt < OPENROUTER_ROUTING_CACHE_TTL_MS) {
|
|
4959
|
+
return decision(input.model, cached.providers, cached.bar, "cache");
|
|
4960
|
+
}
|
|
4961
|
+
let failure;
|
|
4962
|
+
try {
|
|
4963
|
+
const doFetch = input.fetchImpl ?? fetch;
|
|
4964
|
+
const response = await doFetch(`${OPENROUTER_ENDPOINTS_BASE_URL}/${input.model}/endpoints`, {
|
|
4965
|
+
signal: AbortSignal.timeout(input.timeoutMs ?? OPENROUTER_ROUTING_FETCH_TIMEOUT_MS)
|
|
4966
|
+
});
|
|
4967
|
+
if (!response.ok)
|
|
4968
|
+
throw new Error(`HTTP ${response.status}`);
|
|
4969
|
+
const { endpoints, contextLength } = parseOpenRouterEndpoints(await response.json());
|
|
4970
|
+
if (endpoints.length === 0)
|
|
4971
|
+
throw new Error("no endpoints listed");
|
|
4972
|
+
const selected = selectReliableOpenRouterProviders(endpoints, contextLength);
|
|
4973
|
+
if (selected.providers.length === 0) {
|
|
4974
|
+
return decision(input.model, OPENROUTER_FALLBACK_PROVIDERS, null, "fallback", "no provider met the bar");
|
|
4975
|
+
}
|
|
4976
|
+
await writeCache(input.cacheDir, {
|
|
4977
|
+
model: input.model,
|
|
4978
|
+
fetchedAt: now2(),
|
|
4979
|
+
providers: selected.providers,
|
|
4980
|
+
bar: selected.bar
|
|
4981
|
+
}).catch(() => void 0);
|
|
4982
|
+
return decision(input.model, selected.providers, selected.bar, "live");
|
|
4983
|
+
} catch (error) {
|
|
4984
|
+
failure = error instanceof Error ? error.message : String(error);
|
|
4985
|
+
}
|
|
4986
|
+
if (cached) {
|
|
4987
|
+
return decision(input.model, cached.providers, cached.bar, "stale-cache", `api unreachable: ${failure}`);
|
|
4988
|
+
}
|
|
4989
|
+
return decision(input.model, OPENROUTER_FALLBACK_PROVIDERS, null, "fallback", `api unreachable: ${failure}`);
|
|
4990
|
+
}
|
|
4991
|
+
function withOpenRouterModelRouting(value, pin) {
|
|
4992
|
+
const root = value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
|
|
4993
|
+
const applyOverride = (provider) => {
|
|
4994
|
+
if (!pin)
|
|
4995
|
+
return provider;
|
|
4996
|
+
const overrides = provider.modelOverrides && typeof provider.modelOverrides === "object" && !Array.isArray(provider.modelOverrides) ? { ...provider.modelOverrides } : {};
|
|
4997
|
+
const existing = overrides[pin.model];
|
|
4998
|
+
const override = existing && typeof existing === "object" && !Array.isArray(existing) ? { ...existing } : {};
|
|
4999
|
+
const compat = override.compat && typeof override.compat === "object" && !Array.isArray(override.compat) ? { ...override.compat } : {};
|
|
5000
|
+
override.compat = { ...compat, openRouterRouting: pin.routing };
|
|
5001
|
+
overrides[pin.model] = override;
|
|
5002
|
+
return { ...provider, modelOverrides: overrides };
|
|
5003
|
+
};
|
|
5004
|
+
if (Array.isArray(root.providers)) {
|
|
5005
|
+
if (!pin)
|
|
5006
|
+
return root;
|
|
5007
|
+
const providers2 = root.providers.map((provider) => provider && typeof provider === "object" ? { ...provider } : provider);
|
|
5008
|
+
const index = providers2.findIndex((provider) => provider && typeof provider === "object" && provider.name === "openrouter");
|
|
5009
|
+
const current = applyOverride(index >= 0 ? providers2[index] : { name: "openrouter" });
|
|
5010
|
+
if (index >= 0)
|
|
5011
|
+
providers2[index] = current;
|
|
5012
|
+
else
|
|
5013
|
+
providers2.push(current);
|
|
5014
|
+
root.providers = providers2;
|
|
5015
|
+
return root;
|
|
5016
|
+
}
|
|
5017
|
+
const providers = root.providers && typeof root.providers === "object" ? { ...root.providers } : {};
|
|
5018
|
+
if (pin) {
|
|
5019
|
+
const current = providers.openrouter && typeof providers.openrouter === "object" && !Array.isArray(providers.openrouter) ? { ...providers.openrouter } : {};
|
|
5020
|
+
providers.openrouter = applyOverride(current);
|
|
5021
|
+
}
|
|
5022
|
+
root.providers = providers;
|
|
5023
|
+
return root;
|
|
5024
|
+
}
|
|
5025
|
+
function openRouterRoutingInput(config, selection, fetchImpl) {
|
|
5026
|
+
const model = openRouterModelId(selection?.model, config.agentEnv);
|
|
5027
|
+
if (!model || !config.openRouterRoutingCacheDir)
|
|
5028
|
+
return {};
|
|
5029
|
+
return {
|
|
5030
|
+
openRouterRouting: {
|
|
5031
|
+
model,
|
|
5032
|
+
cacheDir: config.openRouterRoutingCacheDir,
|
|
5033
|
+
...fetchImpl ? { fetchImpl } : {}
|
|
5034
|
+
}
|
|
5035
|
+
};
|
|
5036
|
+
}
|
|
5037
|
+
|
|
4343
5038
|
// apps/body/dist/toml-section.js
|
|
4344
5039
|
function scanLine(line, openString) {
|
|
4345
5040
|
if (openString.kind) {
|
|
@@ -4523,54 +5218,21 @@ var PI_CUSTOM_MODEL_CONFIG = {
|
|
|
4523
5218
|
source: ".pi/agent/models.json",
|
|
4524
5219
|
target: "models.json"
|
|
4525
5220
|
};
|
|
4526
|
-
var OPENROUTER_RELIABLE_PROVIDERS = ["deepinfra", "novita"];
|
|
4527
|
-
function withReliableOpenRouterRouting(value) {
|
|
4528
|
-
const root = value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
|
|
4529
|
-
const routing = {
|
|
4530
|
-
only: [...OPENROUTER_RELIABLE_PROVIDERS],
|
|
4531
|
-
order: [...OPENROUTER_RELIABLE_PROVIDERS],
|
|
4532
|
-
allow_fallbacks: false,
|
|
4533
|
-
require_parameters: true
|
|
4534
|
-
};
|
|
4535
|
-
if (Array.isArray(root.providers)) {
|
|
4536
|
-
const providers2 = root.providers.map((provider) => provider && typeof provider === "object" ? { ...provider } : provider);
|
|
4537
|
-
const index = providers2.findIndex((provider) => provider && typeof provider === "object" && provider.name === "openrouter");
|
|
4538
|
-
const current2 = index >= 0 ? providers2[index] : { name: "openrouter" };
|
|
4539
|
-
const compat2 = current2.compat && typeof current2.compat === "object" && !Array.isArray(current2.compat) ? { ...current2.compat } : {};
|
|
4540
|
-
current2.compat = { ...compat2, openRouterRouting: routing };
|
|
4541
|
-
if (index >= 0)
|
|
4542
|
-
providers2[index] = current2;
|
|
4543
|
-
else
|
|
4544
|
-
providers2.push(current2);
|
|
4545
|
-
root.providers = providers2;
|
|
4546
|
-
return root;
|
|
4547
|
-
}
|
|
4548
|
-
const providers = root.providers && typeof root.providers === "object" ? { ...root.providers } : {};
|
|
4549
|
-
const current = providers.openrouter && typeof providers.openrouter === "object" && !Array.isArray(providers.openrouter) ? { ...providers.openrouter } : {};
|
|
4550
|
-
const compat = current.compat && typeof current.compat === "object" && !Array.isArray(current.compat) ? { ...current.compat } : {};
|
|
4551
|
-
current.compat = {
|
|
4552
|
-
...compat,
|
|
4553
|
-
openRouterRouting: routing
|
|
4554
|
-
};
|
|
4555
|
-
providers.openrouter = current;
|
|
4556
|
-
root.providers = providers;
|
|
4557
|
-
return root;
|
|
4558
|
-
}
|
|
4559
5221
|
var CODEX_ROOM_AGENT_LOCKDOWN_TOML = "[agents]\nenabled = false\n";
|
|
4560
5222
|
var CODEX_ROOM_WEB_SEARCH_TOML = "[features]\nstandalone_web_search = true\n";
|
|
4561
5223
|
var HOME_SUBDIRS = ["user", "claude", "codex", "grok", "pi", "state", "cache", "tmp"];
|
|
4562
5224
|
async function prepareRoomAgentHome(input) {
|
|
4563
|
-
const root =
|
|
4564
|
-
const operatorHome = input.operatorHome ??
|
|
5225
|
+
const root = resolve10(input.root);
|
|
5226
|
+
const operatorHome = input.operatorHome ?? homedir3();
|
|
4565
5227
|
try {
|
|
4566
|
-
await
|
|
5228
|
+
await mkdir3(root, { recursive: true, mode: 448 });
|
|
4567
5229
|
const rootStats = await lstat(root);
|
|
4568
5230
|
if (!rootStats.isDirectory() || rootStats.isSymbolicLink()) {
|
|
4569
5231
|
throw new AgentHomeSecurityError(`agent home root is not an ordinary directory: ${root}`);
|
|
4570
5232
|
}
|
|
4571
5233
|
for (const subdir of HOME_SUBDIRS) {
|
|
4572
|
-
const path =
|
|
4573
|
-
await
|
|
5234
|
+
const path = resolve10(root, subdir);
|
|
5235
|
+
await mkdir3(path, { recursive: true, mode: 448 });
|
|
4574
5236
|
await assertRealContainedDirectory(path, root);
|
|
4575
5237
|
}
|
|
4576
5238
|
} catch (error) {
|
|
@@ -4580,14 +5242,14 @@ async function prepareRoomAgentHome(input) {
|
|
|
4580
5242
|
return {};
|
|
4581
5243
|
}
|
|
4582
5244
|
for (const credential of SHARED_CREDENTIALS) {
|
|
4583
|
-
const source =
|
|
4584
|
-
const target =
|
|
5245
|
+
const source = resolve10(operatorHome, credential.source);
|
|
5246
|
+
const target = resolve10(root, credential.dir, credential.target);
|
|
4585
5247
|
if (!existsSync3(source) || existsSync3(target))
|
|
4586
5248
|
continue;
|
|
4587
5249
|
await symlink(source, target).catch(() => void 0);
|
|
4588
5250
|
}
|
|
4589
5251
|
const prior = agentHomeProvisionQueues.get(root) ?? Promise.resolve();
|
|
4590
|
-
const provision = prior.catch(() => void 0).then(() => provisionAgentSkillsAndMcp(root, operatorHome, input.skillReleaseId ?? runningBeelineReleaseId(), input.failClosed ?? false, input.sharedSkills ?? []));
|
|
5252
|
+
const provision = prior.catch(() => void 0).then(() => provisionAgentSkillsAndMcp(root, operatorHome, input.skillReleaseId ?? runningBeelineReleaseId(), input.failClosed ?? false, input.sharedSkills ?? [], input.openRouterRouting));
|
|
4591
5253
|
agentHomeProvisionQueues.set(root, provision);
|
|
4592
5254
|
try {
|
|
4593
5255
|
await provision;
|
|
@@ -4597,19 +5259,19 @@ async function prepareRoomAgentHome(input) {
|
|
|
4597
5259
|
}
|
|
4598
5260
|
return roomAgentHomeEnv(root);
|
|
4599
5261
|
}
|
|
4600
|
-
async function provisionAgentSkillsAndMcp(root, operatorHome, skillReleaseId, failClosed, sharedSkills) {
|
|
5262
|
+
async function provisionAgentSkillsAndMcp(root, operatorHome, skillReleaseId, failClosed, sharedSkills, openRouterRouting) {
|
|
4601
5263
|
const managedSkills = [
|
|
4602
5264
|
{ name: USING_BEELINE_SKILL_NAME, content: usingBeelineSkillMarkdown(skillReleaseId) }
|
|
4603
5265
|
];
|
|
4604
5266
|
const shared = await resolveSharedSkillSources(operatorHome, sharedSkills);
|
|
4605
5267
|
for (const dir of AGENT_SKILL_DIRS) {
|
|
4606
|
-
const target =
|
|
5268
|
+
const target = resolve10(root, dir, "skills");
|
|
4607
5269
|
await provisionManagedSkillsDir(target, managedSkills, shared, sharedSkills.length === 0);
|
|
4608
5270
|
}
|
|
4609
5271
|
for (const config of HARNESS_MCP_CONFIGS) {
|
|
4610
5272
|
try {
|
|
4611
|
-
const source =
|
|
4612
|
-
const target =
|
|
5273
|
+
const source = resolve10(operatorHome, config.toml);
|
|
5274
|
+
const target = resolve10(root, config.dir, "config.toml");
|
|
4613
5275
|
const mcpSection = existsSync3(source) ? filteredHarnessMcpToml(readFileSync4(source, "utf8")) : void 0;
|
|
4614
5276
|
const section = config.dir === "codex" ? [CODEX_ROOM_AGENT_LOCKDOWN_TOML, CODEX_ROOM_WEB_SEARCH_TOML, mcpSection].filter(Boolean).join("\n") : mcpSection;
|
|
4615
5277
|
if (!section) {
|
|
@@ -4624,8 +5286,8 @@ async function provisionAgentSkillsAndMcp(root, operatorHome, skillReleaseId, fa
|
|
|
4624
5286
|
}
|
|
4625
5287
|
}
|
|
4626
5288
|
try {
|
|
4627
|
-
const claudeJson =
|
|
4628
|
-
const claudeTarget =
|
|
5289
|
+
const claudeJson = resolve10(operatorHome, ".claude.json");
|
|
5290
|
+
const claudeTarget = resolve10(root, "claude", ".claude.json");
|
|
4629
5291
|
const mcpServers = existsSync3(claudeJson) ? readClaudeUserScopeMcpServers(claudeJson) : void 0;
|
|
4630
5292
|
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
|
4631
5293
|
await writeIsolatedHarnessFile(claudeTarget, `${JSON.stringify({ mcpServers }, null, 2)}
|
|
@@ -4640,22 +5302,28 @@ async function provisionAgentSkillsAndMcp(root, operatorHome, skillReleaseId, fa
|
|
|
4640
5302
|
}
|
|
4641
5303
|
try {
|
|
4642
5304
|
const settings2 = { permissions: { allow: ["WebSearch"] } };
|
|
4643
|
-
await writeIsolatedHarnessFile(
|
|
5305
|
+
await writeIsolatedHarnessFile(resolve10(root, "claude", "settings.json"), `${JSON.stringify(settings2, null, 2)}
|
|
4644
5306
|
`);
|
|
4645
5307
|
} catch (error) {
|
|
4646
5308
|
if (failClosed)
|
|
4647
5309
|
throw error;
|
|
4648
5310
|
console.warn("[body] claude web-search settings provisioning failed:", error);
|
|
4649
5311
|
}
|
|
4650
|
-
await provisionPiCustomModelConfig(root, operatorHome, failClosed);
|
|
5312
|
+
await provisionPiCustomModelConfig(root, operatorHome, failClosed, openRouterRouting);
|
|
4651
5313
|
}
|
|
4652
|
-
async function provisionPiCustomModelConfig(root, operatorHome, failClosed) {
|
|
4653
|
-
const source =
|
|
4654
|
-
const target =
|
|
5314
|
+
async function provisionPiCustomModelConfig(root, operatorHome, failClosed, openRouterRouting) {
|
|
5315
|
+
const source = resolve10(operatorHome, PI_CUSTOM_MODEL_CONFIG.source);
|
|
5316
|
+
const target = resolve10(root, "pi", PI_CUSTOM_MODEL_CONFIG.target);
|
|
5317
|
+
let decision2;
|
|
5318
|
+
if (openRouterRouting) {
|
|
5319
|
+
decision2 = await resolveOpenRouterRouting(openRouterRouting);
|
|
5320
|
+
console.log(decision2.line);
|
|
5321
|
+
}
|
|
5322
|
+
const pin = decision2 ? { model: decision2.model, routing: decision2.routing } : void 0;
|
|
4655
5323
|
try {
|
|
4656
5324
|
const sourceStats = await lstat(source).catch(() => void 0);
|
|
4657
5325
|
if (!sourceStats) {
|
|
4658
|
-
await writeIsolatedHarnessFile(target, `${JSON.stringify(
|
|
5326
|
+
await writeIsolatedHarnessFile(target, `${JSON.stringify(withOpenRouterModelRouting({}, pin), null, 2)}
|
|
4659
5327
|
`);
|
|
4660
5328
|
return;
|
|
4661
5329
|
}
|
|
@@ -4667,7 +5335,7 @@ async function provisionPiCustomModelConfig(root, operatorHome, failClosed) {
|
|
|
4667
5335
|
throw new AgentHomeSecurityError(`Pi custom model config resolves through a link: ${source}`);
|
|
4668
5336
|
}
|
|
4669
5337
|
const sourceValue = JSON.parse(readFileSync4(resolvedSource, "utf8"));
|
|
4670
|
-
await writeIsolatedHarnessFile(target, `${JSON.stringify(
|
|
5338
|
+
await writeIsolatedHarnessFile(target, `${JSON.stringify(withOpenRouterModelRouting(sourceValue, pin), null, 2)}
|
|
4671
5339
|
`);
|
|
4672
5340
|
} catch (error) {
|
|
4673
5341
|
await unlink(target).catch(() => void 0);
|
|
@@ -4702,16 +5370,16 @@ function filteredHarnessMcpToml(source) {
|
|
|
4702
5370
|
return extractTomlSections(source, ["mcp_servers"], excluded);
|
|
4703
5371
|
}
|
|
4704
5372
|
async function provisionManagedSkillsDir(target, managedSkills, sharedSkills, optionalShares) {
|
|
4705
|
-
const parent =
|
|
4706
|
-
await assertRealContainedDirectory(parent,
|
|
4707
|
-
const staged =
|
|
4708
|
-
await
|
|
5373
|
+
const parent = dirname4(target);
|
|
5374
|
+
await assertRealContainedDirectory(parent, dirname4(parent));
|
|
5375
|
+
const staged = resolve10(parent, `.skills.${process.pid}.${randomUUID()}.tmp`);
|
|
5376
|
+
await mkdir3(staged, { mode: 448 });
|
|
4709
5377
|
const names = new Set(managedSkills.map((skill) => skill.name));
|
|
4710
5378
|
try {
|
|
4711
5379
|
for (const skill of managedSkills) {
|
|
4712
|
-
const skillDir =
|
|
4713
|
-
await
|
|
4714
|
-
await writeIsolatedHarnessFile(
|
|
5380
|
+
const skillDir = resolve10(staged, skill.name);
|
|
5381
|
+
await mkdir3(skillDir, { recursive: true });
|
|
5382
|
+
await writeIsolatedHarnessFile(resolve10(skillDir, "SKILL.md"), skill.content);
|
|
4715
5383
|
}
|
|
4716
5384
|
for (const shared of sharedSkills) {
|
|
4717
5385
|
if (names.has(shared.name)) {
|
|
@@ -4719,7 +5387,7 @@ async function provisionManagedSkillsDir(target, managedSkills, sharedSkills, op
|
|
|
4719
5387
|
}
|
|
4720
5388
|
names.add(shared.name);
|
|
4721
5389
|
try {
|
|
4722
|
-
await copySafeSkillTree(shared.source,
|
|
5390
|
+
await copySafeSkillTree(shared.source, resolve10(staged, shared.name), shared.source);
|
|
4723
5391
|
} catch (error) {
|
|
4724
5392
|
if (!optionalShares)
|
|
4725
5393
|
throw error;
|
|
@@ -4740,20 +5408,20 @@ async function resolveSharedSkillSources(operatorHome, names) {
|
|
|
4740
5408
|
const seen = /* @__PURE__ */ new Set();
|
|
4741
5409
|
const resolved = [];
|
|
4742
5410
|
for (const relativeRoot of OPERATOR_SKILL_SOURCE_DIRS) {
|
|
4743
|
-
const sourceRoot =
|
|
5411
|
+
const sourceRoot = resolve10(operatorHome, relativeRoot);
|
|
4744
5412
|
const rootStats = await lstat(sourceRoot).catch(() => void 0);
|
|
4745
5413
|
if (!rootStats?.isDirectory() || rootStats.isSymbolicLink())
|
|
4746
5414
|
continue;
|
|
4747
5415
|
for (const entry of await readdir(sourceRoot)) {
|
|
4748
5416
|
if (!isSharedSkillName(entry) || seen.has(entry))
|
|
4749
5417
|
continue;
|
|
4750
|
-
const candidate =
|
|
5418
|
+
const candidate = resolve10(sourceRoot, entry);
|
|
4751
5419
|
try {
|
|
4752
5420
|
const candidateStats = await lstat(candidate);
|
|
4753
5421
|
if (!candidateStats.isDirectory() || candidateStats.isSymbolicLink())
|
|
4754
5422
|
continue;
|
|
4755
5423
|
assertContained(sourceRoot, candidate);
|
|
4756
|
-
const skillMd =
|
|
5424
|
+
const skillMd = resolve10(candidate, "SKILL.md");
|
|
4757
5425
|
const skillStats = await lstat(skillMd);
|
|
4758
5426
|
if (!skillStats.isFile() || skillStats.isSymbolicLink() || skillStats.nlink !== 1) {
|
|
4759
5427
|
throw new Error(`shared skill requires an ordinary SKILL.md: ${entry}`);
|
|
@@ -4777,8 +5445,8 @@ async function resolveExplicitSkillSources(operatorHome, names) {
|
|
|
4777
5445
|
for (const name of unique) {
|
|
4778
5446
|
const matches = [];
|
|
4779
5447
|
for (const relativeRoot of OPERATOR_SKILL_SOURCE_DIRS) {
|
|
4780
|
-
const sourceRoot =
|
|
4781
|
-
const candidate =
|
|
5448
|
+
const sourceRoot = resolve10(operatorHome, relativeRoot);
|
|
5449
|
+
const candidate = resolve10(sourceRoot, name);
|
|
4782
5450
|
const rootStats = await lstat(sourceRoot).catch(() => void 0);
|
|
4783
5451
|
const candidateStats = await lstat(candidate).catch(() => void 0);
|
|
4784
5452
|
if (!candidateStats)
|
|
@@ -4795,7 +5463,7 @@ async function resolveExplicitSkillSources(operatorHome, names) {
|
|
|
4795
5463
|
if (matches.length !== 1) {
|
|
4796
5464
|
throw new Error(matches.length === 0 ? `shared skill is unavailable: ${name}` : `shared skill source is ambiguous: ${name}`);
|
|
4797
5465
|
}
|
|
4798
|
-
const skillMd =
|
|
5466
|
+
const skillMd = resolve10(matches[0], "SKILL.md");
|
|
4799
5467
|
const skillStats = await lstat(skillMd).catch(() => void 0);
|
|
4800
5468
|
if (!skillStats?.isFile() || skillStats.isSymbolicLink() || skillStats.nlink !== 1) {
|
|
4801
5469
|
throw new Error(`shared skill requires an ordinary SKILL.md: ${name}`);
|
|
@@ -4806,7 +5474,7 @@ async function resolveExplicitSkillSources(operatorHome, names) {
|
|
|
4806
5474
|
}
|
|
4807
5475
|
function assertContained(root, candidate) {
|
|
4808
5476
|
const rel = relative(root, candidate);
|
|
4809
|
-
if (rel === ".." || rel.startsWith(`..${sep}`) ||
|
|
5477
|
+
if (rel === ".." || rel.startsWith(`..${sep}`) || resolve10(root, rel) !== resolve10(candidate)) {
|
|
4810
5478
|
throw new Error(`path escapes the agent skill boundary: ${candidate}`);
|
|
4811
5479
|
}
|
|
4812
5480
|
}
|
|
@@ -4821,7 +5489,7 @@ var BLOCKED_SHARED_FILENAMES = /^(?:\.env(?:\..*)?|auth\.json|\.credentials\.jso
|
|
|
4821
5489
|
async function copySafeSkillTree(source, target, sourceRoot) {
|
|
4822
5490
|
assertContained(sourceRoot, source);
|
|
4823
5491
|
const resolvedSource = await realpath(source);
|
|
4824
|
-
if (resolvedSource !==
|
|
5492
|
+
if (resolvedSource !== resolve10(source)) {
|
|
4825
5493
|
throw new Error(`shared skill path resolves through a link: ${source}`);
|
|
4826
5494
|
}
|
|
4827
5495
|
assertContained(sourceRoot, resolvedSource);
|
|
@@ -4832,11 +5500,11 @@ async function copySafeSkillTree(source, target, sourceRoot) {
|
|
|
4832
5500
|
throw new Error(`shared skill contains credential or configuration material: ${source}`);
|
|
4833
5501
|
}
|
|
4834
5502
|
if (stats.isDirectory()) {
|
|
4835
|
-
await
|
|
5503
|
+
await mkdir3(target, { mode: 448 });
|
|
4836
5504
|
for (const entry of await readdir(resolvedSource)) {
|
|
4837
5505
|
if (entry === "." || entry === "..")
|
|
4838
5506
|
throw new Error("invalid shared skill entry");
|
|
4839
|
-
await copySafeSkillTree(
|
|
5507
|
+
await copySafeSkillTree(resolve10(source, entry), resolve10(target, entry), sourceRoot);
|
|
4840
5508
|
}
|
|
4841
5509
|
return;
|
|
4842
5510
|
}
|
|
@@ -4844,34 +5512,34 @@ async function copySafeSkillTree(source, target, sourceRoot) {
|
|
|
4844
5512
|
throw new Error(`shared skill contains a nonordinary file: ${source}`);
|
|
4845
5513
|
}
|
|
4846
5514
|
await copyFile(resolvedSource, target);
|
|
4847
|
-
await
|
|
5515
|
+
await chmod2(target, 384);
|
|
4848
5516
|
}
|
|
4849
5517
|
async function writeIsolatedHarnessFile(path, content) {
|
|
4850
|
-
const parent =
|
|
5518
|
+
const parent = dirname4(path);
|
|
4851
5519
|
const parentStats = await lstat(parent);
|
|
4852
5520
|
if (!parentStats.isDirectory() || parentStats.isSymbolicLink()) {
|
|
4853
5521
|
throw new Error(`isolated harness parent is not a real directory: ${parent}`);
|
|
4854
5522
|
}
|
|
4855
|
-
const temporary =
|
|
5523
|
+
const temporary = resolve10(parent, `.${basename3(path)}.${process.pid}.${randomUUID()}.tmp`);
|
|
4856
5524
|
try {
|
|
4857
|
-
await
|
|
4858
|
-
await
|
|
5525
|
+
await writeFile4(temporary, content, { mode: 384, flag: "wx" });
|
|
5526
|
+
await chmod2(temporary, 384);
|
|
4859
5527
|
await rename(temporary, path);
|
|
4860
5528
|
} finally {
|
|
4861
5529
|
await unlink(temporary).catch(() => void 0);
|
|
4862
5530
|
}
|
|
4863
5531
|
}
|
|
4864
5532
|
function roomAgentHomeEnv(root) {
|
|
4865
|
-
const resolved =
|
|
5533
|
+
const resolved = resolve10(root);
|
|
4866
5534
|
return {
|
|
4867
|
-
HOME:
|
|
4868
|
-
CLAUDE_CONFIG_DIR:
|
|
4869
|
-
CODEX_HOME:
|
|
4870
|
-
GROK_HOME:
|
|
4871
|
-
PI_CODING_AGENT_DIR:
|
|
4872
|
-
XDG_STATE_HOME:
|
|
4873
|
-
XDG_CACHE_HOME:
|
|
4874
|
-
TMPDIR:
|
|
5535
|
+
HOME: resolve10(resolved, "user"),
|
|
5536
|
+
CLAUDE_CONFIG_DIR: resolve10(resolved, "claude"),
|
|
5537
|
+
CODEX_HOME: resolve10(resolved, "codex"),
|
|
5538
|
+
GROK_HOME: resolve10(resolved, "grok"),
|
|
5539
|
+
PI_CODING_AGENT_DIR: resolve10(resolved, "pi"),
|
|
5540
|
+
XDG_STATE_HOME: resolve10(resolved, "state"),
|
|
5541
|
+
XDG_CACHE_HOME: resolve10(resolved, "cache"),
|
|
5542
|
+
TMPDIR: resolve10(resolved, "tmp")
|
|
4875
5543
|
};
|
|
4876
5544
|
}
|
|
4877
5545
|
var HARNESS_STATE_ENV_VARS = [
|
|
@@ -4888,14 +5556,14 @@ function harnessStateDirsFromEnv(env) {
|
|
|
4888
5556
|
for (const name of HARNESS_STATE_ENV_VARS) {
|
|
4889
5557
|
const value = env[name];
|
|
4890
5558
|
if (value)
|
|
4891
|
-
stateDirs.push(
|
|
5559
|
+
stateDirs.push(resolve10(value));
|
|
4892
5560
|
}
|
|
4893
5561
|
const tmp = env.TMPDIR;
|
|
4894
|
-
return { stateDirs, ...tmp ? { tmpDir:
|
|
5562
|
+
return { stateDirs, ...tmp ? { tmpDir: resolve10(tmp) } : {} };
|
|
4895
5563
|
}
|
|
4896
5564
|
|
|
4897
5565
|
// apps/body/dist/attachment-delivery.js
|
|
4898
|
-
import { mkdir as
|
|
5566
|
+
import { mkdir as mkdir4, writeFile as writeFile5 } from "node:fs/promises";
|
|
4899
5567
|
import { basename as basename4, extname, join as join2 } from "node:path";
|
|
4900
5568
|
var MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
|
|
4901
5569
|
var FETCH_TIMEOUT_MS = 3e4;
|
|
@@ -4913,7 +5581,7 @@ async function deliverAttachments(attachments, dir, fetchImpl = fetch) {
|
|
|
4913
5581
|
if (!attachments.length)
|
|
4914
5582
|
return [];
|
|
4915
5583
|
const taken = /* @__PURE__ */ new Set();
|
|
4916
|
-
await
|
|
5584
|
+
await mkdir4(dir, { recursive: true });
|
|
4917
5585
|
return Promise.all(attachments.map(async (attachment, index) => {
|
|
4918
5586
|
const tooLarge = (bytes) => ({
|
|
4919
5587
|
attachment,
|
|
@@ -4934,7 +5602,7 @@ async function deliverAttachments(attachments, dir, fetchImpl = fetch) {
|
|
|
4934
5602
|
if (bytes.length > MAX_ATTACHMENT_BYTES)
|
|
4935
5603
|
return tooLarge(bytes.length);
|
|
4936
5604
|
const path = join2(dir, safeFileName(attachment, index, taken));
|
|
4937
|
-
await
|
|
5605
|
+
await writeFile5(path, bytes);
|
|
4938
5606
|
const mimeType = attachment.mimeType ?? response.headers.get("content-type") ?? "";
|
|
4939
5607
|
return {
|
|
4940
5608
|
attachment,
|
|
@@ -5046,9 +5714,70 @@ function sanitizeAgentReply(message) {
|
|
|
5046
5714
|
}
|
|
5047
5715
|
return lines.slice(1).join("\n").trim();
|
|
5048
5716
|
}
|
|
5717
|
+
var CORNER_OPEN_ECHO = /^(?:(?:ok(?:ay)?|done|sure|great|alright)[,.!:]?\s+)?(?:i(?:'ve| have|'ll| will|'m| am)?\s+)?(?:just\s+)?open(?:ed|ing)?\s+(?:up\s+)?(?:a\s+|the\s+|your\s+)?(?:new\s+|write(?:-enabled)?\s+|repository\s+)*corner\b/iu;
|
|
5718
|
+
var CORNER_OPEN_ECHO_MAX_CHARS = 320;
|
|
5719
|
+
function stripCornerOpenEcho(message) {
|
|
5720
|
+
const visible = message.trim();
|
|
5721
|
+
if (!visible)
|
|
5722
|
+
return "";
|
|
5723
|
+
const paragraphs = visible.split(/\n\s*\n/);
|
|
5724
|
+
const first = paragraphs[0].trim();
|
|
5725
|
+
if (first.length > CORNER_OPEN_ECHO_MAX_CHARS || !CORNER_OPEN_ECHO.test(first))
|
|
5726
|
+
return visible;
|
|
5727
|
+
return paragraphs.slice(1).join("\n\n").trim();
|
|
5728
|
+
}
|
|
5729
|
+
var STATUS_FILLER = new Set("a an the this that it its is are was were be been being has have had do does did now still remains remain remaining ready for review reviewing pr pull request all every both and or so to of in on at as with by no not nothing new further more else needed needs need required action actions change changes changed status state update updates updated waiting wait awaiting pending running progress idle done ok okay good great well fine confirmed confirm already again yet just currently right i we will can should would github remote upstream branch head latest current report reported noted note see looks look like everything nothing appears appear seems seem there here which what from without any".split(/\s+/));
|
|
5730
|
+
var STATUS_SYNONYMS = {
|
|
5731
|
+
passed: ["pass", "passed", "passing", "passes", "green", "succeeded", "success", "successful", "successfully"],
|
|
5732
|
+
failed: ["fail", "failed", "failing", "fails", "red", "broke", "broken", "failure", "failures"],
|
|
5733
|
+
check: ["check", "checks", "ci", "run", "runs", "workflow", "workflows", "build", "builds", "test", "tests", "suite"],
|
|
5734
|
+
started: ["start", "started", "starting", "began", "begun", "kicked", "off", "queued"],
|
|
5735
|
+
merged: ["merge", "merged", "merging", "landed", "closed"]
|
|
5736
|
+
};
|
|
5737
|
+
var STATUS_RESTATEMENT_MAX_CHARS = 200;
|
|
5738
|
+
var STATUS_RESTATEMENT_MAX_SENTENCES = 2;
|
|
5739
|
+
function statusWords(text2) {
|
|
5740
|
+
return (text2.toLowerCase().match(/[\p{L}\p{N}'-]+/gu) ?? []).map((word) => word.replace(/^'+|'+$/g, "").replace(/'s$/, ""));
|
|
5741
|
+
}
|
|
5742
|
+
function isCornerStatusRestatement(reply, systemLines) {
|
|
5743
|
+
const text2 = reply.trim();
|
|
5744
|
+
if (!text2)
|
|
5745
|
+
return true;
|
|
5746
|
+
if (text2.length > STATUS_RESTATEMENT_MAX_CHARS)
|
|
5747
|
+
return false;
|
|
5748
|
+
if (/https?:\/\/\S+/i.test(text2))
|
|
5749
|
+
return false;
|
|
5750
|
+
const sentences = text2.split(/[.!?]+(?:\s+|$)/).filter((part) => part.trim());
|
|
5751
|
+
if (sentences.length > STATUS_RESTATEMENT_MAX_SENTENCES)
|
|
5752
|
+
return false;
|
|
5753
|
+
const admitted = new Set(STATUS_FILLER);
|
|
5754
|
+
for (const line of systemLines) {
|
|
5755
|
+
for (const word of statusWords(line)) {
|
|
5756
|
+
admitted.add(word);
|
|
5757
|
+
for (const synonym of STATUS_SYNONYMS[word] ?? [])
|
|
5758
|
+
admitted.add(synonym);
|
|
5759
|
+
}
|
|
5760
|
+
}
|
|
5761
|
+
return statusWords(text2).every((word) => !word || admitted.has(word));
|
|
5762
|
+
}
|
|
5763
|
+
|
|
5764
|
+
// apps/body/dist/turn-failure-reason.js
|
|
5765
|
+
var TURN_FAILURE_REASON_MAX = 200;
|
|
5766
|
+
function redactToolDetail(value) {
|
|
5767
|
+
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]");
|
|
5768
|
+
}
|
|
5769
|
+
function distillTurnFailureReason(error) {
|
|
5770
|
+
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);
|
|
5771
|
+
const firstLine = raw.split(/\r?\n/).map((line) => line.trim()).find((line) => line && !/^at\s/.test(line)) ?? "";
|
|
5772
|
+
const stripped = firstLine.replace(/^(?:[A-Za-z]*Error|Error):\s*/, "").replace(/\s+/g, " ");
|
|
5773
|
+
const clean4 = redactToolDetail(stripped).trim();
|
|
5774
|
+
if (!clean4)
|
|
5775
|
+
return "turn failed";
|
|
5776
|
+
return clean4.length > TURN_FAILURE_REASON_MAX ? `${clean4.slice(0, TURN_FAILURE_REASON_MAX - 1)}\u2026` : clean4;
|
|
5777
|
+
}
|
|
5049
5778
|
|
|
5050
5779
|
// apps/body/dist/room-session.js
|
|
5051
|
-
import { resolve as
|
|
5780
|
+
import { resolve as resolve11 } from "node:path";
|
|
5052
5781
|
|
|
5053
5782
|
// apps/body/dist/read-only-policy.js
|
|
5054
5783
|
var READ_ONLY_MCP_SERVER_NAME = "beeline-readonly-mcp";
|
|
@@ -5180,7 +5909,11 @@ function beelineAgentMcpServer(config, api, context) {
|
|
|
5180
5909
|
{ name: "BEELINE_DAEMON_ROOM_ID", value: context.roomId },
|
|
5181
5910
|
{ name: "BEELINE_DAEMON_WORKSPACE_ID", value: context.workspaceId },
|
|
5182
5911
|
...context.cornerId ? [{ name: "BEELINE_DAEMON_CORNER_ID", value: context.cornerId }] : [],
|
|
5183
|
-
...context.attachRoot ? [{ name: "BEELINE_ATTACH_ROOT", value: context.attachRoot }] : []
|
|
5912
|
+
...context.attachRoot ? [{ name: "BEELINE_ATTACH_ROOT", value: context.attachRoot }] : [],
|
|
5913
|
+
...context.grantRunner ? [
|
|
5914
|
+
{ name: "BEELINE_GRANT_RUNNER_URL", value: context.grantRunner.url },
|
|
5915
|
+
{ name: "BEELINE_GRANT_RUNNER_TOKEN", value: context.grantRunner.token }
|
|
5916
|
+
] : []
|
|
5184
5917
|
]
|
|
5185
5918
|
};
|
|
5186
5919
|
}
|
|
@@ -5194,14 +5927,14 @@ function readOnlyMcpServer(config, cwd, agentMemoryDir) {
|
|
|
5194
5927
|
command: config.readonlyMcpCommand,
|
|
5195
5928
|
args: [...config.readonlyMcpArgs ?? []],
|
|
5196
5929
|
env: [
|
|
5197
|
-
{ name: "BEELINE_READONLY_ROOT", value:
|
|
5930
|
+
{ name: "BEELINE_READONLY_ROOT", value: resolve11(cwd) },
|
|
5198
5931
|
...config.agentHomeRoot ? [
|
|
5199
5932
|
{
|
|
5200
5933
|
name: "BEELINE_READONLY_AGENT_SKILLS_ROOT",
|
|
5201
|
-
value:
|
|
5934
|
+
value: resolve11(config.agentHomeRoot, skillDir, "skills")
|
|
5202
5935
|
}
|
|
5203
5936
|
] : [],
|
|
5204
|
-
...agentMemoryDir ? [{ name: "BEELINE_READONLY_AGENT_MEMORY_ROOT", value:
|
|
5937
|
+
...agentMemoryDir ? [{ name: "BEELINE_READONLY_AGENT_MEMORY_ROOT", value: resolve11(agentMemoryDir) }] : []
|
|
5205
5938
|
]
|
|
5206
5939
|
};
|
|
5207
5940
|
}
|
|
@@ -5209,8 +5942,8 @@ function readOnlyMcpServer(config, cwd, agentMemoryDir) {
|
|
|
5209
5942
|
// apps/body/dist/bwrap-sandbox.js
|
|
5210
5943
|
import { spawnSync } from "node:child_process";
|
|
5211
5944
|
import { lstatSync as lstatSync2 } from "node:fs";
|
|
5212
|
-
import { homedir as
|
|
5213
|
-
import { isAbsolute as isAbsolute2, relative as relative2, resolve as
|
|
5945
|
+
import { homedir as homedir4 } from "node:os";
|
|
5946
|
+
import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve12 } from "node:path";
|
|
5214
5947
|
var DEFAULT_SANDBOX_POLICY = "bwrap";
|
|
5215
5948
|
function isSandboxPolicy(value) {
|
|
5216
5949
|
return value === "bwrap" || value === "off";
|
|
@@ -5242,12 +5975,12 @@ var HARNESS_HOME_STATE_DIRS = [
|
|
|
5242
5975
|
dirs: [".grok"]
|
|
5243
5976
|
}
|
|
5244
5977
|
];
|
|
5245
|
-
function harnessHomeStateDirs(agentCommand, home =
|
|
5978
|
+
function harnessHomeStateDirs(agentCommand, home = homedir4()) {
|
|
5246
5979
|
if (!agentCommand)
|
|
5247
5980
|
return [];
|
|
5248
5981
|
for (const { match, dirs } of HARNESS_HOME_STATE_DIRS) {
|
|
5249
5982
|
if (match.test(agentCommand))
|
|
5250
|
-
return dirs.map((dir) =>
|
|
5983
|
+
return dirs.map((dir) => resolve12(home, dir));
|
|
5251
5984
|
}
|
|
5252
5985
|
return [];
|
|
5253
5986
|
}
|
|
@@ -5259,7 +5992,7 @@ var KNOWN_CREDENTIAL_MASK_PATHS = [
|
|
|
5259
5992
|
".git-credentials",
|
|
5260
5993
|
".secrets.env"
|
|
5261
5994
|
];
|
|
5262
|
-
function credentialMaskPaths(extraPaths, home =
|
|
5995
|
+
function credentialMaskPaths(extraPaths, home = homedir4(), stat3 = (path) => {
|
|
5263
5996
|
try {
|
|
5264
5997
|
const info = lstatSync2(path);
|
|
5265
5998
|
return { isDirectory: info.isDirectory() };
|
|
@@ -5267,10 +6000,10 @@ function credentialMaskPaths(extraPaths, home = homedir3(), stat3 = (path) => {
|
|
|
5267
6000
|
return void 0;
|
|
5268
6001
|
}
|
|
5269
6002
|
}, requiredPaths = []) {
|
|
5270
|
-
const required = new Set(requiredPaths.map((path) =>
|
|
6003
|
+
const required = new Set(requiredPaths.map((path) => resolve12(path)));
|
|
5271
6004
|
const candidates = [
|
|
5272
|
-
...KNOWN_CREDENTIAL_MASK_PATHS.map((entry) =>
|
|
5273
|
-
...(extraPaths ?? []).map((entry) =>
|
|
6005
|
+
...KNOWN_CREDENTIAL_MASK_PATHS.map((entry) => resolve12(home, entry)),
|
|
6006
|
+
...(extraPaths ?? []).map((entry) => resolve12(entry))
|
|
5274
6007
|
];
|
|
5275
6008
|
const seen = /* @__PURE__ */ new Set();
|
|
5276
6009
|
const masks = [];
|
|
@@ -5297,7 +6030,7 @@ function normalize(paths) {
|
|
|
5297
6030
|
for (const path of paths) {
|
|
5298
6031
|
if (!path)
|
|
5299
6032
|
continue;
|
|
5300
|
-
seen.add(
|
|
6033
|
+
seen.add(resolve12(path));
|
|
5301
6034
|
}
|
|
5302
6035
|
return Array.from(seen).sort();
|
|
5303
6036
|
}
|
|
@@ -5335,7 +6068,7 @@ function sandboxMountPlan(spec) {
|
|
|
5335
6068
|
writable,
|
|
5336
6069
|
quotaTmpfs: spec.workbench ? [
|
|
5337
6070
|
{
|
|
5338
|
-
target:
|
|
6071
|
+
target: resolve12(spec.workbench.dir),
|
|
5339
6072
|
maxBytes: spec.workbench.maxBytes,
|
|
5340
6073
|
maxInodes: spec.workbench.maxInodes,
|
|
5341
6074
|
blockGit: true
|
|
@@ -5390,7 +6123,7 @@ function buildBwrapArgv(input) {
|
|
|
5390
6123
|
for (const binding of quotaTmpfs) {
|
|
5391
6124
|
args.push("--dir", binding.target, "--size", String(binding.maxBytes), "--tmpfs", binding.target);
|
|
5392
6125
|
if (binding.blockGit)
|
|
5393
|
-
args.push("--ro-bind", "/dev/null",
|
|
6126
|
+
args.push("--ro-bind", "/dev/null", resolve12(binding.target, ".git"));
|
|
5394
6127
|
}
|
|
5395
6128
|
args.push("--chdir", input.cwd);
|
|
5396
6129
|
args.push("--die-with-parent");
|
|
@@ -5454,13 +6187,176 @@ function detectBwrapSandbox(options = {}) {
|
|
|
5454
6187
|
};
|
|
5455
6188
|
}
|
|
5456
6189
|
|
|
6190
|
+
// apps/body/dist/pi-turn-record.js
|
|
6191
|
+
import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
|
|
6192
|
+
import { resolve as resolve13 } from "node:path";
|
|
6193
|
+
function summarizeProviderError(errorMessage2) {
|
|
6194
|
+
const trimmed = errorMessage2.trim();
|
|
6195
|
+
const statusMatch = /^(\d{3}):\s*([\s\S]*)$/.exec(trimmed);
|
|
6196
|
+
const status = statusMatch ? Number(statusMatch[1]) : void 0;
|
|
6197
|
+
let body = statusMatch ? statusMatch[2].trim() : trimmed;
|
|
6198
|
+
if (body.startsWith("{")) {
|
|
6199
|
+
try {
|
|
6200
|
+
const parsed = JSON.parse(body);
|
|
6201
|
+
const message = typeof parsed.message === "string" ? parsed.message : typeof parsed.error === "string" ? parsed.error : typeof parsed.error?.message === "string" ? parsed.error.message : void 0;
|
|
6202
|
+
if (message)
|
|
6203
|
+
body = message;
|
|
6204
|
+
} catch {
|
|
6205
|
+
}
|
|
6206
|
+
}
|
|
6207
|
+
const firstLine = body.split(/\r?\n/).find((line) => line.trim()) ?? "";
|
|
6208
|
+
const reason = `provider error${status === void 0 ? "" : ` ${status}`}${firstLine ? `: ${firstLine.trim()}` : ""}`;
|
|
6209
|
+
return status === void 0 ? { reason } : { reason, status };
|
|
6210
|
+
}
|
|
6211
|
+
async function sessionFileFromMap(home, sessionId) {
|
|
6212
|
+
try {
|
|
6213
|
+
const raw = await readFile5(resolve13(home, ".pi", "pi-acp", "session-map.json"), "utf8");
|
|
6214
|
+
const map = JSON.parse(raw);
|
|
6215
|
+
const file = map.sessions?.[sessionId]?.sessionFile;
|
|
6216
|
+
return typeof file === "string" && file ? file : void 0;
|
|
6217
|
+
} catch {
|
|
6218
|
+
return void 0;
|
|
6219
|
+
}
|
|
6220
|
+
}
|
|
6221
|
+
async function sessionFileFromLayout(piDir, sessionId) {
|
|
6222
|
+
const sessionsRoot = resolve13(piDir, "sessions");
|
|
6223
|
+
const suffix = `_${sessionId}.jsonl`;
|
|
6224
|
+
let projects;
|
|
6225
|
+
try {
|
|
6226
|
+
projects = await readdir2(sessionsRoot);
|
|
6227
|
+
} catch {
|
|
6228
|
+
return void 0;
|
|
6229
|
+
}
|
|
6230
|
+
for (const project of projects) {
|
|
6231
|
+
const dir = resolve13(sessionsRoot, project);
|
|
6232
|
+
let files;
|
|
6233
|
+
try {
|
|
6234
|
+
files = await readdir2(dir);
|
|
6235
|
+
} catch {
|
|
6236
|
+
continue;
|
|
6237
|
+
}
|
|
6238
|
+
const match = files.find((file) => file.endsWith(suffix));
|
|
6239
|
+
if (match)
|
|
6240
|
+
return resolve13(dir, match);
|
|
6241
|
+
}
|
|
6242
|
+
return void 0;
|
|
6243
|
+
}
|
|
6244
|
+
function messageText(content) {
|
|
6245
|
+
if (!Array.isArray(content))
|
|
6246
|
+
return "";
|
|
6247
|
+
return content.map((block2) => block2 && typeof block2 === "object" && block2.type === "text" ? String(block2.text ?? "") : "").join("").trim();
|
|
6248
|
+
}
|
|
6249
|
+
async function readPiTurnRecord(input) {
|
|
6250
|
+
const piDir = input.agentEnv.PI_CODING_AGENT_DIR;
|
|
6251
|
+
if (!piDir || !input.sessionId)
|
|
6252
|
+
return void 0;
|
|
6253
|
+
const file = (input.agentEnv.HOME ? await sessionFileFromMap(input.agentEnv.HOME, input.sessionId) : void 0) ?? await sessionFileFromLayout(piDir, input.sessionId);
|
|
6254
|
+
if (!file)
|
|
6255
|
+
return void 0;
|
|
6256
|
+
let raw;
|
|
6257
|
+
try {
|
|
6258
|
+
raw = await readFile5(file, "utf8");
|
|
6259
|
+
} catch {
|
|
6260
|
+
return void 0;
|
|
6261
|
+
}
|
|
6262
|
+
let sawUser = false;
|
|
6263
|
+
let last;
|
|
6264
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
6265
|
+
if (!line.trim())
|
|
6266
|
+
continue;
|
|
6267
|
+
let entry;
|
|
6268
|
+
try {
|
|
6269
|
+
entry = JSON.parse(line);
|
|
6270
|
+
} catch {
|
|
6271
|
+
continue;
|
|
6272
|
+
}
|
|
6273
|
+
if (entry.type !== "message" || !entry.message)
|
|
6274
|
+
continue;
|
|
6275
|
+
if (entry.message.role === "user") {
|
|
6276
|
+
sawUser = true;
|
|
6277
|
+
last = void 0;
|
|
6278
|
+
} else if (entry.message.role === "assistant") {
|
|
6279
|
+
last = entry.message;
|
|
6280
|
+
}
|
|
6281
|
+
}
|
|
6282
|
+
if (!sawUser && !last)
|
|
6283
|
+
return void 0;
|
|
6284
|
+
if (!last)
|
|
6285
|
+
return { kind: "missing" };
|
|
6286
|
+
if (last.stopReason === "error") {
|
|
6287
|
+
return {
|
|
6288
|
+
kind: "error",
|
|
6289
|
+
...summarizeProviderError(typeof last.errorMessage === "string" && last.errorMessage.trim() ? last.errorMessage : "unknown error")
|
|
6290
|
+
};
|
|
6291
|
+
}
|
|
6292
|
+
const text2 = messageText(last.content);
|
|
6293
|
+
if (text2)
|
|
6294
|
+
return { kind: "answer", text: text2 };
|
|
6295
|
+
return {
|
|
6296
|
+
kind: "empty",
|
|
6297
|
+
stopReason: typeof last.stopReason === "string" ? last.stopReason : "unknown"
|
|
6298
|
+
};
|
|
6299
|
+
}
|
|
6300
|
+
|
|
6301
|
+
// apps/body/dist/empty-turn.js
|
|
6302
|
+
async function explainEmptyAgentTurn(input) {
|
|
6303
|
+
const streamFact = describeEmptyTurn(input.result, input.agentLabel);
|
|
6304
|
+
if (!isPiAcpHarness(input.agentLabel))
|
|
6305
|
+
return { reason: streamFact };
|
|
6306
|
+
const record2 = await readPiTurnRecord({ agentEnv: input.agentEnv, sessionId: input.sessionId });
|
|
6307
|
+
if (!record2)
|
|
6308
|
+
return { reason: `pi left no readable turn record; ${streamFact}` };
|
|
6309
|
+
switch (record2.kind) {
|
|
6310
|
+
case "error":
|
|
6311
|
+
return { reason: record2.reason, record: record2 };
|
|
6312
|
+
case "empty":
|
|
6313
|
+
return {
|
|
6314
|
+
reason: `the model ended its turn with no text (stop reason ${record2.stopReason})`,
|
|
6315
|
+
record: record2
|
|
6316
|
+
};
|
|
6317
|
+
case "answer":
|
|
6318
|
+
return {
|
|
6319
|
+
recoveredText: record2.text,
|
|
6320
|
+
reason: "pi recorded the answer but the ACP stream delivered no text",
|
|
6321
|
+
record: record2
|
|
6322
|
+
};
|
|
6323
|
+
case "missing":
|
|
6324
|
+
return { reason: `pi recorded no assistant message for this turn; ${streamFact}`, record: record2 };
|
|
6325
|
+
}
|
|
6326
|
+
}
|
|
6327
|
+
function isAccountOrProviderRefusal(record2) {
|
|
6328
|
+
if (!record2 || record2.kind !== "error" || record2.status === void 0)
|
|
6329
|
+
return false;
|
|
6330
|
+
return [401, 402, 403, 407, 408, 429].includes(record2.status) || record2.status >= 500;
|
|
6331
|
+
}
|
|
6332
|
+
|
|
6333
|
+
// apps/body/dist/corner-checks.js
|
|
6334
|
+
var COMPLETED_CHECK_NOTE = /\b(passed|failed) a check\b/i;
|
|
6335
|
+
var STARTED_CHECK_NOTE = /\bstarted a check\b/i;
|
|
6336
|
+
function verbOf(item) {
|
|
6337
|
+
return item.systemEvent?.verb ?? item.body;
|
|
6338
|
+
}
|
|
6339
|
+
function completedCheckNote(item) {
|
|
6340
|
+
if (item.type !== "system")
|
|
6341
|
+
return void 0;
|
|
6342
|
+
const match = COMPLETED_CHECK_NOTE.exec(verbOf(item));
|
|
6343
|
+
return match ? match[1].toLowerCase() : void 0;
|
|
6344
|
+
}
|
|
6345
|
+
function isCheckStartNote(item) {
|
|
6346
|
+
return item.type === "system" && STARTED_CHECK_NOTE.test(verbOf(item));
|
|
6347
|
+
}
|
|
6348
|
+
function checksStateFromLifecycle(lifecycle) {
|
|
6349
|
+
const state = lifecycle?.checksSummary?.status ?? lifecycle?.checks;
|
|
6350
|
+
return state === "passing" || state === "failing" || state === "pending" ? state : void 0;
|
|
6351
|
+
}
|
|
6352
|
+
|
|
5457
6353
|
// apps/body/dist/runtime.js
|
|
5458
|
-
import { randomBytes as
|
|
5459
|
-
import { execFile } from "node:child_process";
|
|
6354
|
+
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
6355
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
5460
6356
|
import { closeSync, openSync } from "node:fs";
|
|
5461
|
-
import { mkdir as
|
|
5462
|
-
import { homedir as
|
|
5463
|
-
import { dirname as
|
|
6357
|
+
import { mkdir as mkdir5, readFile as readFile6, readdir as readdir3, rename as rename2, stat, writeFile as writeFile6 } from "node:fs/promises";
|
|
6358
|
+
import { homedir as homedir5 } from "node:os";
|
|
6359
|
+
import { dirname as dirname5, resolve as resolve14 } from "node:path";
|
|
5464
6360
|
import { spawn as spawn2 } from "node:child_process";
|
|
5465
6361
|
import { promisify } from "node:util";
|
|
5466
6362
|
|
|
@@ -5608,7 +6504,7 @@ function createHasher(hashCons, info = {}) {
|
|
|
5608
6504
|
Object.assign(hashC, info);
|
|
5609
6505
|
return Object.freeze(hashC);
|
|
5610
6506
|
}
|
|
5611
|
-
function
|
|
6507
|
+
function randomBytes2(bytesLength = 32) {
|
|
5612
6508
|
anumber(bytesLength, "bytesLength");
|
|
5613
6509
|
const cr = typeof globalThis === "object" ? globalThis.crypto : null;
|
|
5614
6510
|
if (typeof cr?.getRandomValues !== "function")
|
|
@@ -5926,7 +6822,7 @@ var bytesToHex2 = bytesToHex;
|
|
|
5926
6822
|
var concatBytes2 = (...arrays) => concatBytes(...arrays);
|
|
5927
6823
|
var hexToBytes2 = (hex) => hexToBytes(hex);
|
|
5928
6824
|
var isBytes2 = isBytes;
|
|
5929
|
-
var
|
|
6825
|
+
var randomBytes3 = (bytesLength) => randomBytes2(bytesLength);
|
|
5930
6826
|
var _0n = /* @__PURE__ */ BigInt(0);
|
|
5931
6827
|
var _1n = /* @__PURE__ */ BigInt(1);
|
|
5932
6828
|
var atitle2 = (title) => title ? `"${title}" ` : "";
|
|
@@ -6518,18 +7414,18 @@ function validateTableBytes(numPoints, fpBytes) {
|
|
|
6518
7414
|
if (bytes > TABLE_BYTES_MAX)
|
|
6519
7415
|
throw new Error("invalid window size: table would need ~" + Math.ceil(bytes / 2 ** 20) + " MiB, max " + TABLE_BYTES_MAX / 2 ** 20 + " MiB");
|
|
6520
7416
|
}
|
|
6521
|
-
function probeRandomBytes(
|
|
6522
|
-
if (
|
|
7417
|
+
function probeRandomBytes(randomBytes7, length) {
|
|
7418
|
+
if (randomBytes7 === void 0)
|
|
6523
7419
|
return void 0;
|
|
6524
|
-
afunction(
|
|
7420
|
+
afunction(randomBytes7, "randomBytes");
|
|
6525
7421
|
try {
|
|
6526
|
-
const probe =
|
|
7422
|
+
const probe = randomBytes7(length);
|
|
6527
7423
|
if (!isBytes2(probe) || probe.length !== length)
|
|
6528
7424
|
return void 0;
|
|
6529
7425
|
} catch {
|
|
6530
7426
|
return void 0;
|
|
6531
7427
|
}
|
|
6532
|
-
return
|
|
7428
|
+
return randomBytes7;
|
|
6533
7429
|
}
|
|
6534
7430
|
function validateMSMPoints(points, c2) {
|
|
6535
7431
|
aarray(points, "points");
|
|
@@ -6622,9 +7518,9 @@ var ScalarMultiplier = class {
|
|
|
6622
7518
|
baseCanBeBlinded;
|
|
6623
7519
|
bits;
|
|
6624
7520
|
// Parametrized with a given Point class (not individual point)
|
|
6625
|
-
constructor(Point,
|
|
7521
|
+
constructor(Point, randomBytes7) {
|
|
6626
7522
|
validatePointCons(Point);
|
|
6627
|
-
this.randomBytes = probeRandomBytes(
|
|
7523
|
+
this.randomBytes = probeRandomBytes(randomBytes7, BLIND_BYTES);
|
|
6628
7524
|
this.Point = Point;
|
|
6629
7525
|
this.BASE = Point.BASE;
|
|
6630
7526
|
this.ZERO = Point.ZERO;
|
|
@@ -6912,7 +7808,7 @@ function weierstrass(params, extraOpts = {}) {
|
|
|
6912
7808
|
randomBytes: "function"
|
|
6913
7809
|
});
|
|
6914
7810
|
const { endo, allowInfinityPoint } = extraOpts;
|
|
6915
|
-
const
|
|
7811
|
+
const randomBytes7 = extraOpts.randomBytes === void 0 ? randomBytes3 : extraOpts.randomBytes;
|
|
6916
7812
|
if (endo) {
|
|
6917
7813
|
if (!Fp.is0(CURVE.a) || typeof endo.beta !== "bigint" || !Array.isArray(endo.basises)) {
|
|
6918
7814
|
throw new Error('invalid endo: expected "beta": bigint and "basises": array');
|
|
@@ -7324,7 +8220,7 @@ function weierstrass(params, extraOpts = {}) {
|
|
|
7324
8220
|
}
|
|
7325
8221
|
}
|
|
7326
8222
|
const normalize2 = (points) => normalizeZ(Point, points);
|
|
7327
|
-
const wnaf = new ScalarMultiplier(Point,
|
|
8223
|
+
const wnaf = new ScalarMultiplier(Point, randomBytes7);
|
|
7328
8224
|
if (wnaf.bits >= 6)
|
|
7329
8225
|
Point.BASE.precompute(6);
|
|
7330
8226
|
Object.freeze(Point.prototype);
|
|
@@ -7433,7 +8329,7 @@ function challenge(...args) {
|
|
|
7433
8329
|
function schnorrGetPublicKey(secretKey) {
|
|
7434
8330
|
return schnorrGetExtPubKey(secretKey).bytes;
|
|
7435
8331
|
}
|
|
7436
|
-
function schnorrSign(message, secretKey, auxRand =
|
|
8332
|
+
function schnorrSign(message, secretKey, auxRand = randomBytes2(32)) {
|
|
7437
8333
|
const { Fn, BASE } = Pointk1;
|
|
7438
8334
|
const m2 = abytes2(message, void 0, "message");
|
|
7439
8335
|
const { bytes: px, scalar: d } = schnorrGetExtPubKey(secretKey);
|
|
@@ -7483,7 +8379,7 @@ var schnorr = /* @__PURE__ */ (() => {
|
|
|
7483
8379
|
const size = 32;
|
|
7484
8380
|
const seedLength = 48;
|
|
7485
8381
|
const randomSecretKey = (seed) => {
|
|
7486
|
-
seed = seed === void 0 ?
|
|
8382
|
+
seed = seed === void 0 ? randomBytes2(seedLength) : seed;
|
|
7487
8383
|
return mapHashToField(abytes2(seed, seedLength, "seed"), secp256k1_CURVE.n);
|
|
7488
8384
|
};
|
|
7489
8385
|
return Object.freeze({
|
|
@@ -7630,7 +8526,7 @@ function createHasher2(hashCons, info = {}) {
|
|
|
7630
8526
|
Object.assign(hashC, info);
|
|
7631
8527
|
return Object.freeze(hashC);
|
|
7632
8528
|
}
|
|
7633
|
-
function
|
|
8529
|
+
function randomBytes4(bytesLength = 32) {
|
|
7634
8530
|
const cr = typeof globalThis === "object" ? globalThis.crypto : null;
|
|
7635
8531
|
if (typeof cr?.getRandomValues !== "function")
|
|
7636
8532
|
throw new Error("crypto.getRandomValues must be defined");
|
|
@@ -9326,7 +10222,7 @@ function getWLengths2(Fp, Fn) {
|
|
|
9326
10222
|
}
|
|
9327
10223
|
function ecdh(Point, ecdhOpts = {}) {
|
|
9328
10224
|
const { Fn } = Point;
|
|
9329
|
-
const randomBytes_ = ecdhOpts.randomBytes ||
|
|
10225
|
+
const randomBytes_ = ecdhOpts.randomBytes || randomBytes4;
|
|
9330
10226
|
const lengths = Object.assign(getWLengths2(Point.Fp, Fn), { seed: getMinHashLength2(Fn.ORDER) });
|
|
9331
10227
|
function isValidSecretKey(secretKey) {
|
|
9332
10228
|
try {
|
|
@@ -9391,7 +10287,7 @@ function ecdsa2(Point, hash, ecdsaOpts = {}) {
|
|
|
9391
10287
|
bits2int_modN: "function"
|
|
9392
10288
|
});
|
|
9393
10289
|
ecdsaOpts = Object.assign({}, ecdsaOpts);
|
|
9394
|
-
const
|
|
10290
|
+
const randomBytes7 = ecdsaOpts.randomBytes || randomBytes4;
|
|
9395
10291
|
const hmac2 = ecdsaOpts.hmac || ((key, msg) => hmac(hash, key, msg));
|
|
9396
10292
|
const { Fp, Fn } = Point;
|
|
9397
10293
|
const { ORDER: CURVE_ORDER, BITS: fnBits } = Fn;
|
|
@@ -9533,7 +10429,7 @@ function ecdsa2(Point, hash, ecdsaOpts = {}) {
|
|
|
9533
10429
|
throw new Error("invalid private key");
|
|
9534
10430
|
const seedArgs = [int2octets(d), int2octets(h1int)];
|
|
9535
10431
|
if (extraEntropy != null && extraEntropy !== false) {
|
|
9536
|
-
const e = extraEntropy === true ?
|
|
10432
|
+
const e = extraEntropy === true ? randomBytes7(lengths.secretKey) : extraEntropy;
|
|
9537
10433
|
seedArgs.push(abytes3(e, void 0, "extraEntropy"));
|
|
9538
10434
|
}
|
|
9539
10435
|
const seed = concatBytes3(...seedArgs);
|
|
@@ -9700,7 +10596,7 @@ function challenge2(...args) {
|
|
|
9700
10596
|
function schnorrGetPublicKey2(secretKey) {
|
|
9701
10597
|
return schnorrGetExtPubKey2(secretKey).bytes;
|
|
9702
10598
|
}
|
|
9703
|
-
function schnorrSign2(message, secretKey, auxRand =
|
|
10599
|
+
function schnorrSign2(message, secretKey, auxRand = randomBytes4(32)) {
|
|
9704
10600
|
const { Fn } = Pointk12;
|
|
9705
10601
|
const m2 = abytes3(message, void 0, "message");
|
|
9706
10602
|
const { bytes: px, scalar: d } = schnorrGetExtPubKey2(secretKey);
|
|
@@ -9742,7 +10638,7 @@ function schnorrVerify2(signature, message, publicKey) {
|
|
|
9742
10638
|
var schnorr2 = /* @__PURE__ */ (() => {
|
|
9743
10639
|
const size = 32;
|
|
9744
10640
|
const seedLength = 48;
|
|
9745
|
-
const randomSecretKey = (seed =
|
|
10641
|
+
const randomSecretKey = (seed = randomBytes4(seedLength)) => {
|
|
9746
10642
|
return mapHashToField2(seed, secp256k1_CURVE2.n);
|
|
9747
10643
|
};
|
|
9748
10644
|
return {
|
|
@@ -12106,7 +13002,7 @@ function encrypt2(secretKey, pubkey, text2) {
|
|
|
12106
13002
|
const privkey = secretKey instanceof Uint8Array ? secretKey : hexToBytes3(secretKey);
|
|
12107
13003
|
const key = secp256k1.getSharedSecret(privkey, hexToBytes3("02" + pubkey));
|
|
12108
13004
|
const normalizedKey = getNormalizedX(key);
|
|
12109
|
-
let iv = Uint8Array.from(
|
|
13005
|
+
let iv = Uint8Array.from(randomBytes4(16));
|
|
12110
13006
|
let plaintext = utf8Encoder.encode(text2);
|
|
12111
13007
|
let ciphertext = cbc(normalizedKey, iv).encrypt(plaintext);
|
|
12112
13008
|
let ctb64 = base64.encode(new Uint8Array(ciphertext));
|
|
@@ -12477,7 +13373,7 @@ function decodePayload(payload) {
|
|
|
12477
13373
|
mac: data.subarray(-32)
|
|
12478
13374
|
};
|
|
12479
13375
|
}
|
|
12480
|
-
function encrypt22(plaintext, conversationKey, nonce =
|
|
13376
|
+
function encrypt22(plaintext, conversationKey, nonce = randomBytes4(32)) {
|
|
12481
13377
|
const { chacha_key, chacha_nonce, hmac_key } = getMessageKeys(conversationKey, nonce);
|
|
12482
13378
|
const padded = pad(plaintext);
|
|
12483
13379
|
const ciphertext = chacha20(chacha_key, chacha_nonce, padded);
|
|
@@ -13992,23 +14888,23 @@ function decodeNsec(nsec) {
|
|
|
13992
14888
|
}
|
|
13993
14889
|
|
|
13994
14890
|
// apps/body/dist/runtime.js
|
|
13995
|
-
var execFileAsync = promisify(
|
|
14891
|
+
var execFileAsync = promisify(execFile2);
|
|
13996
14892
|
var DEFAULT_AGENT_IDENTITY_NAME = "beeline-agent";
|
|
13997
14893
|
var DEFAULT_BODY_IDENTITY_NAME = "beeline-body";
|
|
13998
14894
|
var DEFAULT_DAEMON_MONOLITH_BASE_URL = "https://server.usebeeline.app";
|
|
13999
14895
|
function defaultSupervisorRoot(env = process.env) {
|
|
14000
|
-
return
|
|
14896
|
+
return resolve14(env.XDG_STATE_HOME ?? resolve14(homedir5(), ".local", "state"));
|
|
14001
14897
|
}
|
|
14002
14898
|
function runtimeDirectory(supervisorRoot, publicKey) {
|
|
14003
14899
|
if (!/^[0-9a-f]{64}$/i.test(publicKey))
|
|
14004
14900
|
throw new Error("invalid agent public key");
|
|
14005
|
-
return
|
|
14901
|
+
return resolve14(supervisorRoot, "beeline", "agents", publicKey.toLowerCase());
|
|
14006
14902
|
}
|
|
14007
14903
|
function runtimeConfigPath(supervisorRoot, publicKey) {
|
|
14008
|
-
return
|
|
14904
|
+
return resolve14(runtimeDirectory(supervisorRoot, publicKey), "runtime.json");
|
|
14009
14905
|
}
|
|
14010
14906
|
function identityFromKey(value, name) {
|
|
14011
|
-
const secretKey = value ? value.startsWith("nsec1") ? decodeNsec(value) : Uint8Array.from(Buffer.from(value, "hex")) :
|
|
14907
|
+
const secretKey = value ? value.startsWith("nsec1") ? decodeNsec(value) : Uint8Array.from(Buffer.from(value, "hex")) : randomBytes6(32);
|
|
14012
14908
|
if (secretKey.length !== 32)
|
|
14013
14909
|
throw new Error("identity secret key must be 32 bytes");
|
|
14014
14910
|
return { name, secretKey, publicKey: getPublicKey2(secretKey) };
|
|
@@ -14035,15 +14931,15 @@ function runtimeAgentCommand(runtime) {
|
|
|
14035
14931
|
}
|
|
14036
14932
|
async function writeRuntimeRecord(runtime) {
|
|
14037
14933
|
const path = runtimeConfigPath(runtime.supervisorRoot, runtime.agent.publicKey);
|
|
14038
|
-
await
|
|
14934
|
+
await mkdir5(dirname5(path), { recursive: true, mode: 448 });
|
|
14039
14935
|
const staged = `${path}.${process.pid}.tmp`;
|
|
14040
|
-
await
|
|
14936
|
+
await writeFile6(staged, `${JSON.stringify(runtime, null, 2)}
|
|
14041
14937
|
`, { mode: 384 });
|
|
14042
14938
|
await rename2(staged, path);
|
|
14043
14939
|
return path;
|
|
14044
14940
|
}
|
|
14045
14941
|
async function readRuntimeRecord(path) {
|
|
14046
|
-
const parsed = JSON.parse(await
|
|
14942
|
+
const parsed = JSON.parse(await readFile6(path, "utf8"));
|
|
14047
14943
|
if (parsed.version !== 2 || !parsed.agent || !parsed.body || !parsed.communityId) {
|
|
14048
14944
|
throw new Error(`invalid agent runtime record: ${path}`);
|
|
14049
14945
|
}
|
|
@@ -14061,7 +14957,7 @@ async function migrateRuntimeRecordAccessPolicy(path) {
|
|
|
14061
14957
|
return { runtime, migrated: true };
|
|
14062
14958
|
}
|
|
14063
14959
|
async function stageMonolithAgentRuntime(input) {
|
|
14064
|
-
const supervisorRoot = input.supervisorRoot ?
|
|
14960
|
+
const supervisorRoot = input.supervisorRoot ? resolve14(input.supervisorRoot) : defaultSupervisorRoot();
|
|
14065
14961
|
const configPath = runtimeConfigPath(supervisorRoot, input.agentIdentity.publicKey);
|
|
14066
14962
|
const configuredBaseUrl = input.monolithBaseUrl ?? DEFAULT_DAEMON_MONOLITH_BASE_URL;
|
|
14067
14963
|
const baseUrl = new URL(configuredBaseUrl).origin;
|
|
@@ -14106,9 +15002,9 @@ async function stageMonolithAgentRuntime(input) {
|
|
|
14106
15002
|
return { runtime, configPath };
|
|
14107
15003
|
}
|
|
14108
15004
|
async function runtimePaths(root) {
|
|
14109
|
-
const agents =
|
|
14110
|
-
const entries = await
|
|
14111
|
-
return entries.filter((entry) => entry.isDirectory()).map((entry) =>
|
|
15005
|
+
const agents = resolve14(root, "beeline", "agents");
|
|
15006
|
+
const entries = await readdir3(agents, { withFileTypes: true }).catch(() => []);
|
|
15007
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => resolve14(agents, entry.name, "runtime.json"));
|
|
14112
15008
|
}
|
|
14113
15009
|
async function findAgentRuntimeConfigPaths(env = process.env, _cwd = process.cwd()) {
|
|
14114
15010
|
return runtimePaths(defaultSupervisorRoot(env));
|
|
@@ -14117,13 +15013,13 @@ async function findRuntimeConfigPaths(cwd = process.cwd(), env = process.env) {
|
|
|
14117
15013
|
return findAgentRuntimeConfigPaths(env, cwd);
|
|
14118
15014
|
}
|
|
14119
15015
|
async function resolveRuntimeConfigPath(path) {
|
|
14120
|
-
return
|
|
15016
|
+
return resolve14(path);
|
|
14121
15017
|
}
|
|
14122
15018
|
async function selectRuntimeConfigPaths(options) {
|
|
14123
15019
|
const hostScope = true;
|
|
14124
15020
|
const configs = await options.findHostRuntimes(options.cwd);
|
|
14125
15021
|
const requestedPubkey = options.requestedPubkey;
|
|
14126
|
-
const paths = requestedPubkey ? configs.filter((path) =>
|
|
15022
|
+
const paths = requestedPubkey ? configs.filter((path) => dirname5(path).endsWith(requestedPubkey)) : [...new Set(configs)];
|
|
14127
15023
|
if (!paths.length)
|
|
14128
15024
|
throw new Error(options.noRuntimeMessage(hostScope));
|
|
14129
15025
|
if (options.requestedPubkey && paths.length > 1)
|
|
@@ -14132,7 +15028,7 @@ async function selectRuntimeConfigPaths(options) {
|
|
|
14132
15028
|
}
|
|
14133
15029
|
async function runtimeDaemonPid(configPath) {
|
|
14134
15030
|
try {
|
|
14135
|
-
const pid = Number((await
|
|
15031
|
+
const pid = Number((await readFile6(resolve14(dirname5(configPath), "daemon.pid"), "utf8")).trim());
|
|
14136
15032
|
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
14137
15033
|
return null;
|
|
14138
15034
|
process.kill(pid, 0);
|
|
@@ -14143,13 +15039,13 @@ async function runtimeDaemonPid(configPath) {
|
|
|
14143
15039
|
}
|
|
14144
15040
|
async function daemonIsThisRuntime(pid, configPath) {
|
|
14145
15041
|
try {
|
|
14146
|
-
const argv = (await
|
|
15042
|
+
const argv = (await readFile6(`/proc/${pid}/cmdline`, "utf8")).split("\0").filter(Boolean);
|
|
14147
15043
|
const flag = argv.lastIndexOf("--config");
|
|
14148
|
-
return flag > 0 && argv[flag - 1] === "daemon" &&
|
|
15044
|
+
return flag > 0 && argv[flag - 1] === "daemon" && resolve14(argv[flag + 1]) === resolve14(configPath);
|
|
14149
15045
|
} catch {
|
|
14150
15046
|
try {
|
|
14151
15047
|
const { stdout: stdout6 } = await execFileAsync("ps", ["-p", String(pid), "-o", "command="]);
|
|
14152
|
-
return stdout6.includes(" daemon ") && stdout6.includes(
|
|
15048
|
+
return stdout6.includes(" daemon ") && stdout6.includes(resolve14(configPath));
|
|
14153
15049
|
} catch {
|
|
14154
15050
|
return false;
|
|
14155
15051
|
}
|
|
@@ -14175,14 +15071,14 @@ async function stopRuntimeDaemon(path, opts = {}) {
|
|
|
14175
15071
|
throw new Error(`agent daemon ${pid} did not stop after ${timeout}ms`);
|
|
14176
15072
|
}
|
|
14177
15073
|
async function launchRuntimeDaemon(configPath, opts = {}) {
|
|
14178
|
-
const directory =
|
|
14179
|
-
await
|
|
15074
|
+
const directory = dirname5(configPath);
|
|
15075
|
+
await mkdir5(directory, { recursive: true, mode: 448 });
|
|
14180
15076
|
const foreground = opts.foreground === true;
|
|
14181
|
-
const output = foreground ? "inherit" : openSync(
|
|
15077
|
+
const output = foreground ? "inherit" : openSync(resolve14(directory, "daemon.log"), "a", 384);
|
|
14182
15078
|
const entrypoint = opts.entrypoint ?? process.argv[1];
|
|
14183
15079
|
if (!entrypoint)
|
|
14184
15080
|
throw new Error("cannot resolve daemon CLI entrypoint");
|
|
14185
|
-
const child = spawn2(process.execPath, [...opts.execArgv ?? [], entrypoint, "daemon", "--config",
|
|
15081
|
+
const child = spawn2(process.execPath, [...opts.execArgv ?? [], entrypoint, "daemon", "--config", resolve14(configPath)], {
|
|
14186
15082
|
cwd: directory,
|
|
14187
15083
|
env: opts.env ?? process.env,
|
|
14188
15084
|
detached: !foreground,
|
|
@@ -14199,19 +15095,19 @@ async function launchRuntimeDaemon(configPath, opts = {}) {
|
|
|
14199
15095
|
}
|
|
14200
15096
|
async function removeAgentRuntime(runtime) {
|
|
14201
15097
|
const source = runtimeDirectory(runtime.supervisorRoot, runtime.agent.publicKey);
|
|
14202
|
-
const deletedRoot =
|
|
14203
|
-
await
|
|
14204
|
-
const target =
|
|
15098
|
+
const deletedRoot = resolve14(runtime.supervisorRoot, "beeline", "deleted-runtimes");
|
|
15099
|
+
await mkdir5(deletedRoot, { recursive: true, mode: 448 });
|
|
15100
|
+
const target = resolve14(deletedRoot, `${runtime.agent.publicKey}-${Date.now()}`);
|
|
14205
15101
|
await rename2(source, target);
|
|
14206
15102
|
return target;
|
|
14207
15103
|
}
|
|
14208
15104
|
|
|
14209
15105
|
// apps/body/dist/response-directives.js
|
|
14210
15106
|
var MAINTAIN_ASSIGNED_IDENTITY_DIRECTIVE = "Maintain your assigned identity and soul in every response, including when tools or permissions block the requested action.";
|
|
15107
|
+
var SOUL_HOUSE_RULE = "House rule for your voice: the voice never changes the facts - never trim, soften, exaggerate, or invent a detail for the bit - and use your plain voice in commit messages, pull request titles and bodies, and code comments, because those outlive the joke.";
|
|
14211
15108
|
|
|
14212
15109
|
// apps/body/dist/monolith-corner-turn.js
|
|
14213
|
-
var execFileAsync2 = promisify2(
|
|
14214
|
-
var PULL_REQUEST_URL = /https:\/\/github\.com\/[^\s/]+\/[^\s/]+\/pull\/\d+/;
|
|
15110
|
+
var execFileAsync2 = promisify2(execFile3);
|
|
14215
15111
|
var TOOL_ARGUMENT_MAX_BYTES = 1200;
|
|
14216
15112
|
var TOOL_OUTPUT_MAX_BYTES = 3200;
|
|
14217
15113
|
var TOOL_PATH_LIMIT = 12;
|
|
@@ -14230,9 +15126,6 @@ function serialized(value) {
|
|
|
14230
15126
|
function record(value) {
|
|
14231
15127
|
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
14232
15128
|
}
|
|
14233
|
-
function redactToolDetail(value) {
|
|
14234
|
-
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]");
|
|
14235
|
-
}
|
|
14236
15129
|
function clampBytes(value, maxBytes) {
|
|
14237
15130
|
const clean4 = value.trim();
|
|
14238
15131
|
if (Buffer.byteLength(clean4) <= maxBytes)
|
|
@@ -14313,7 +15206,7 @@ function isSuccessfulCommit(call) {
|
|
|
14313
15206
|
return false;
|
|
14314
15207
|
return /\bgit\s+commit\b|\bcommit(?:ted)?\s+(?:changes|files?)\b/i.test(`${call.title ?? ""} ${serialized(call.rawInput)}`);
|
|
14315
15208
|
}
|
|
14316
|
-
async function cornerToolActivity(call, worktreePath) {
|
|
15209
|
+
async function cornerToolActivity(call, worktreePath, requestedBy) {
|
|
14317
15210
|
const operation = oneLine(call.kind ?? "") || "tool";
|
|
14318
15211
|
let title = oneLine(redactToolDetail(call.title ?? "")) || `${operation} tool`;
|
|
14319
15212
|
if (isSuccessfulCommit(call)) {
|
|
@@ -14336,6 +15229,7 @@ async function cornerToolActivity(call, worktreePath) {
|
|
|
14336
15229
|
status: resultStatus(call),
|
|
14337
15230
|
...argumentsSummary,
|
|
14338
15231
|
...output ? { output } : {},
|
|
15232
|
+
...requestedBy ? { requestedBy } : {},
|
|
14339
15233
|
...paths.length ? { files: paths.map((path) => ({ path })) } : {}
|
|
14340
15234
|
};
|
|
14341
15235
|
}
|
|
@@ -14348,6 +15242,8 @@ var MonolithCornerTurnLoop = class {
|
|
|
14348
15242
|
agent;
|
|
14349
15243
|
client;
|
|
14350
15244
|
sessionId;
|
|
15245
|
+
/** The live session's environment, read back for pi's own turn record. */
|
|
15246
|
+
agentEnv = {};
|
|
14351
15247
|
turnIdentityInstructions = "";
|
|
14352
15248
|
busy = false;
|
|
14353
15249
|
forcedStop = false;
|
|
@@ -14355,9 +15251,19 @@ var MonolithCornerTurnLoop = class {
|
|
|
14355
15251
|
activityTail = Promise.resolve();
|
|
14356
15252
|
/** Session scratch directory attachments are downloaded into (`TMPDIR/beeline-attachments`). */
|
|
14357
15253
|
attachmentDir;
|
|
15254
|
+
/** The turn in flight and who asked for it, for ledger rows and the grant runner. */
|
|
15255
|
+
currentTurn;
|
|
15256
|
+
memberNames = /* @__PURE__ */ new Map();
|
|
15257
|
+
/** The last server check state that started a turn; the same state never starts another. */
|
|
15258
|
+
lastChecksState;
|
|
14358
15259
|
constructor(options) {
|
|
14359
15260
|
this.options = options;
|
|
14360
15261
|
this.agent = runtimeIdentity(options.runtime.agent);
|
|
15262
|
+
options.grantRunner?.register(options.cornerId, {
|
|
15263
|
+
workspaceId: options.workspaceId,
|
|
15264
|
+
cwd: options.worktreePath,
|
|
15265
|
+
turn: () => this.currentTurn
|
|
15266
|
+
});
|
|
14361
15267
|
}
|
|
14362
15268
|
isBusy() {
|
|
14363
15269
|
return this.busy;
|
|
@@ -14376,11 +15282,13 @@ var MonolithCornerTurnLoop = class {
|
|
|
14376
15282
|
this.client.sessionCancel(this.sessionId);
|
|
14377
15283
|
await this.options.scheduler.forceSuspend(this.options.cornerId);
|
|
14378
15284
|
}
|
|
14379
|
-
roster() {
|
|
14380
|
-
|
|
15285
|
+
async roster() {
|
|
15286
|
+
const roster = await this.options.api.execute("getWorkspaceRoster", {
|
|
14381
15287
|
agentId: this.agent.publicKey,
|
|
14382
15288
|
workspaceId: this.options.workspaceId
|
|
14383
15289
|
});
|
|
15290
|
+
this.memberNames = new Map(roster.members.map((member) => [member.identityId, member.name]));
|
|
15291
|
+
return roster;
|
|
14384
15292
|
}
|
|
14385
15293
|
async activate() {
|
|
14386
15294
|
if (this.client?.isAlive && this.sessionId)
|
|
@@ -14392,30 +15300,32 @@ var MonolithCornerTurnLoop = class {
|
|
|
14392
15300
|
}),
|
|
14393
15301
|
this.roster()
|
|
14394
15302
|
]);
|
|
14395
|
-
await
|
|
15303
|
+
await mkdir6(this.options.worktreePath, { recursive: true });
|
|
15304
|
+
const selection = configuration.model || configuration.effort ? { model: configuration.model, effort: configuration.effort } : this.options.config.modelSelection;
|
|
14396
15305
|
const homeOverlay = this.options.config.agentHomeRoot ? await prepareRoomAgentHome({
|
|
14397
15306
|
root: this.options.config.agentHomeRoot,
|
|
14398
15307
|
sharedSkills: this.options.config.sharedSkills ?? [],
|
|
14399
|
-
...this.options.config.operatorHome ? { operatorHome: this.options.config.operatorHome } : {}
|
|
15308
|
+
...this.options.config.operatorHome ? { operatorHome: this.options.config.operatorHome } : {},
|
|
15309
|
+
...openRouterRoutingInput(this.options.config, selection, this.options.fetchImpl)
|
|
14400
15310
|
}) : {};
|
|
14401
15311
|
const command = this.options.config.agentCommand ?? this.options.config.agentBinary;
|
|
14402
|
-
const selection = configuration.model || configuration.effort ? { model: configuration.model, effort: configuration.effort } : this.options.config.modelSelection;
|
|
14403
15312
|
const agentEnv = {
|
|
14404
15313
|
...this.options.config.agentEnv,
|
|
14405
15314
|
...homeOverlay,
|
|
14406
15315
|
GH_TOKEN: this.options.githubToken,
|
|
14407
15316
|
GITHUB_TOKEN: this.options.githubToken
|
|
14408
15317
|
};
|
|
15318
|
+
this.agentEnv = agentEnv;
|
|
14409
15319
|
const agentArgs = agentArgsWithModelSelection({
|
|
14410
15320
|
kind: this.options.config.agentKind,
|
|
14411
15321
|
command,
|
|
14412
15322
|
args: this.options.config.agentArgs ?? []
|
|
14413
15323
|
}, selection);
|
|
14414
|
-
const operatorHome = this.options.config.operatorHome ??
|
|
15324
|
+
const operatorHome = this.options.config.operatorHome ?? homedir6();
|
|
14415
15325
|
const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
|
|
14416
15326
|
this.attachmentDir = tmpDir ? join4(tmpDir, "beeline-attachments") : void 0;
|
|
14417
15327
|
const homeStateDirs = harnessHomeStateDirs(command, agentEnv.HOME ?? operatorHome);
|
|
14418
|
-
await Promise.all(homeStateDirs.map((dir) =>
|
|
15328
|
+
await Promise.all(homeStateDirs.map((dir) => mkdir6(dir, { recursive: true })));
|
|
14419
15329
|
const spawnCommand = wrapAgentCommand({
|
|
14420
15330
|
bwrapPath: this.options.config.bwrapPath,
|
|
14421
15331
|
spec: {
|
|
@@ -14461,13 +15371,17 @@ var MonolithCornerTurnLoop = class {
|
|
|
14461
15371
|
roomId: this.options.parentRoomId,
|
|
14462
15372
|
workspaceId: this.options.workspaceId,
|
|
14463
15373
|
cornerId: this.options.cornerId,
|
|
14464
|
-
attachRoot: this.options.worktreePath
|
|
15374
|
+
attachRoot: this.options.worktreePath,
|
|
15375
|
+
...this.options.grantRunnerEndpoint ? { grantRunner: this.options.grantRunnerEndpoint } : {}
|
|
14465
15376
|
})
|
|
14466
15377
|
];
|
|
14467
15378
|
const self = roster.members.find((member) => member.identityId === this.agent.publicKey);
|
|
14468
15379
|
const persona = configuration.soul ?? self?.soul;
|
|
14469
15380
|
const identityInstructions = `Your Beeline identity is ${self?.name ?? this.agent.name}.`;
|
|
14470
|
-
const personaInstructions =
|
|
15381
|
+
const personaInstructions = [
|
|
15382
|
+
...persona?.instructions ? [`Human-authored Workspace persona: ${persona.name}. ${persona.instructions}`] : [],
|
|
15383
|
+
SOUL_HOUSE_RULE
|
|
15384
|
+
].join("\n");
|
|
14471
15385
|
this.turnIdentityInstructions = harnessHonorsSessionSystemPrompt(command) ? "" : [identityInstructions, personaInstructions].filter(Boolean).join("\n\n");
|
|
14472
15386
|
const opened = await this.client.sessionNew({
|
|
14473
15387
|
cwd: this.options.worktreePath,
|
|
@@ -14483,6 +15397,7 @@ var MonolithCornerTurnLoop = class {
|
|
|
14483
15397
|
"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.",
|
|
14484
15398
|
"If any human in this corner says hold or do not merge, do not merge until a later human explicitly resumes it.",
|
|
14485
15399
|
"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.",
|
|
15400
|
+
'GitHub check and merge notes are server lines already in the corner: never restate them (no "checks passed", "CI is green", "PR ready for review"). On a checks turn, say nothing unless you act - a merge or a pushed fix - and then one short line about that.',
|
|
14486
15401
|
"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.",
|
|
14487
15402
|
"Never push directly to the target branch. Never merge a different pull request."
|
|
14488
15403
|
].filter(Boolean).join("\n\n")
|
|
@@ -14506,8 +15421,14 @@ var MonolithCornerTurnLoop = class {
|
|
|
14506
15421
|
}
|
|
14507
15422
|
};
|
|
14508
15423
|
}
|
|
14509
|
-
async prompt(requestId, trigger, attachments = []) {
|
|
15424
|
+
async prompt(requestId, trigger, attachments = [], requestedById, restates) {
|
|
14510
15425
|
const { api, cornerId } = this.options;
|
|
15426
|
+
const spoken = (text2) => restates && isCornerStatusRestatement(text2, restates) ? "" : text2;
|
|
15427
|
+
const requester = requestedById ? {
|
|
15428
|
+
pubkey: requestedById,
|
|
15429
|
+
...this.memberNames.get(requestedById) ? { name: this.memberNames.get(requestedById) } : {}
|
|
15430
|
+
} : void 0;
|
|
15431
|
+
this.currentTurn = { requestId, ...requester ? { requester } : {} };
|
|
14511
15432
|
await api.execute("postAgentTurnReceipt", {
|
|
14512
15433
|
agentId: this.agent.publicKey,
|
|
14513
15434
|
roomId: cornerId,
|
|
@@ -14526,6 +15447,9 @@ var MonolithCornerTurnLoop = class {
|
|
|
14526
15447
|
this.attachmentDir && attachments.length ? deliverAttachments(attachments, join4(this.attachmentDir, requestId.replace(/[^\w-]/g, "_")), this.options.fetchImpl) : Promise.resolve([])
|
|
14527
15448
|
]);
|
|
14528
15449
|
const names = new Map(roster.members.map((member) => [member.identityId, member.name]));
|
|
15450
|
+
const requestedBy = requester && !requester.name && names.get(requester.pubkey) ? { ...requester, name: names.get(requester.pubkey) } : requester;
|
|
15451
|
+
if (requestedBy)
|
|
15452
|
+
this.currentTurn = { requestId, requester: requestedBy };
|
|
14529
15453
|
const transcript = conversation.items.slice(-120).map((message) => `${names.get(message.authorId) ?? "Beeline"} [${message.type}]: ${message.body}`).join("\n");
|
|
14530
15454
|
const prompt = [
|
|
14531
15455
|
this.turnIdentityInstructions,
|
|
@@ -14557,7 +15481,7 @@ ${trigger}`, ...attachmentPromptLines(attachments, delivered)].join("\n"),
|
|
|
14557
15481
|
const start = narrationPostedChars;
|
|
14558
15482
|
narrationPostedChars = segmentEnd;
|
|
14559
15483
|
narrationSegments += 1;
|
|
14560
|
-
const trimmed = segment.trim();
|
|
15484
|
+
const trimmed = spoken(segment.trim());
|
|
14561
15485
|
if (!trimmed)
|
|
14562
15486
|
return;
|
|
14563
15487
|
this.activityTail = this.activityTail.catch(() => void 0).then(async () => {
|
|
@@ -14578,7 +15502,7 @@ ${trigger}`, ...attachmentPromptLines(attachments, delivered)].join("\n"),
|
|
|
14578
15502
|
return;
|
|
14579
15503
|
publishedToolCalls.add(key);
|
|
14580
15504
|
this.activityTail = this.activityTail.catch(() => void 0).then(async () => {
|
|
14581
|
-
const activity = await cornerToolActivity(call, this.options.worktreePath);
|
|
15505
|
+
const activity = await cornerToolActivity(call, this.options.worktreePath, requestedBy);
|
|
14582
15506
|
await api.execute("postAgentActivity", {
|
|
14583
15507
|
agentId: this.agent.publicKey,
|
|
14584
15508
|
roomId: cornerId,
|
|
@@ -14605,10 +15529,21 @@ ${trigger}`, ...attachmentPromptLines(attachments, delivered)].join("\n"),
|
|
|
14605
15529
|
await this.activityTail;
|
|
14606
15530
|
await this.draftTail;
|
|
14607
15531
|
await this.activityTail;
|
|
14608
|
-
|
|
14609
|
-
if (!reply)
|
|
14610
|
-
|
|
14611
|
-
|
|
15532
|
+
let reply = stripAgentReplyPreamble(result.agentText).trim();
|
|
15533
|
+
if (!reply) {
|
|
15534
|
+
const explained = await explainEmptyAgentTurn({
|
|
15535
|
+
agentLabel: this.options.config.agentCommand ?? this.options.config.agentBinary,
|
|
15536
|
+
agentEnv: this.agentEnv,
|
|
15537
|
+
sessionId,
|
|
15538
|
+
result
|
|
15539
|
+
});
|
|
15540
|
+
reply = explained.recoveredText ? stripAgentReplyPreamble(explained.recoveredText).trim() : "";
|
|
15541
|
+
if (!reply && !(restates && !isAccountOrProviderRefusal(explained.record))) {
|
|
15542
|
+
throw new Error(explained.reason);
|
|
15543
|
+
}
|
|
15544
|
+
console.warn(`[thin-core] corner ${cornerId} turn ${requestId}: ${explained.reason}`);
|
|
15545
|
+
}
|
|
15546
|
+
const durableTail = spoken(narrationPostedChars > 0 ? stripAgentReplyPreamble(result.agentText.slice(narrationPostedChars)).trim() : reply);
|
|
14612
15547
|
if (durableTail) {
|
|
14613
15548
|
await api.execute("postRoomMessage", {
|
|
14614
15549
|
roomId: cornerId,
|
|
@@ -14617,17 +15552,6 @@ ${trigger}`, ...attachmentPromptLines(attachments, delivered)].join("\n"),
|
|
|
14617
15552
|
presentation: "message"
|
|
14618
15553
|
});
|
|
14619
15554
|
}
|
|
14620
|
-
const pullRequest = reply.match(PULL_REQUEST_URL)?.[0];
|
|
14621
|
-
const alreadyReady = conversation.items.some((item) => /\bPR ready for review\b/i.test(item.body));
|
|
14622
|
-
if (pullRequest && !alreadyReady) {
|
|
14623
|
-
await api.execute("postRoomMessage", {
|
|
14624
|
-
roomId: cornerId,
|
|
14625
|
-
requestId,
|
|
14626
|
-
text: `PR ready for review
|
|
14627
|
-
${pullRequest}`,
|
|
14628
|
-
presentation: "system"
|
|
14629
|
-
});
|
|
14630
|
-
}
|
|
14631
15555
|
await api.execute("retractAgentLiveOutput", {
|
|
14632
15556
|
agentId: this.agent.publicKey,
|
|
14633
15557
|
roomId: cornerId,
|
|
@@ -14648,12 +15572,28 @@ ${pullRequest}`,
|
|
|
14648
15572
|
roomId: cornerId,
|
|
14649
15573
|
requestId,
|
|
14650
15574
|
status: "failed",
|
|
14651
|
-
generationId: `${this.agent.publicKey}:${cornerId}
|
|
15575
|
+
generationId: `${this.agent.publicKey}:${cornerId}`,
|
|
15576
|
+
reason: distillTurnFailureReason(error)
|
|
14652
15577
|
});
|
|
14653
15578
|
throw error;
|
|
14654
15579
|
} finally {
|
|
14655
15580
|
this.busy = false;
|
|
15581
|
+
this.currentTurn = void 0;
|
|
15582
|
+
}
|
|
15583
|
+
}
|
|
15584
|
+
/** The server's check state for this head, or the notes' own verdict when the server carries none. */
|
|
15585
|
+
async checksState(notes) {
|
|
15586
|
+
try {
|
|
15587
|
+
const restore = await this.options.api.execute("getCornerRestoreState", {
|
|
15588
|
+
cornerId: this.options.cornerId
|
|
15589
|
+
});
|
|
15590
|
+
const fromServer = checksStateFromLifecycle(restore.lifecycle);
|
|
15591
|
+
if (fromServer)
|
|
15592
|
+
return fromServer;
|
|
15593
|
+
} catch (error) {
|
|
15594
|
+
console.error(`[thin-core] corner ${this.options.cornerId} check state read failed:`, error);
|
|
14656
15595
|
}
|
|
15596
|
+
return notes.some((note) => completedCheckNote(note) === "failed") ? "failing" : "passing";
|
|
14657
15597
|
}
|
|
14658
15598
|
async run() {
|
|
14659
15599
|
const { api, cornerId, signal } = this.options;
|
|
@@ -14675,6 +15615,7 @@ ${pullRequest}`,
|
|
|
14675
15615
|
await this.options.onCloseRequested();
|
|
14676
15616
|
return;
|
|
14677
15617
|
}
|
|
15618
|
+
const checkNotes = [];
|
|
14678
15619
|
for (const item of inbox.items) {
|
|
14679
15620
|
if (item.type === "message") {
|
|
14680
15621
|
if (item.authorId === this.agent.publicKey)
|
|
@@ -14685,12 +15626,30 @@ ${pullRequest}`,
|
|
|
14685
15626
|
});
|
|
14686
15627
|
if (!authority.member || authority.principalKind !== "human")
|
|
14687
15628
|
continue;
|
|
14688
|
-
await this.prompt(item.id, item.body, item.attachments);
|
|
15629
|
+
await this.prompt(item.id, item.body, item.attachments, item.authorId);
|
|
15630
|
+
pollWithoutWait = true;
|
|
15631
|
+
continue;
|
|
15632
|
+
}
|
|
15633
|
+
const grantDecision = item.type === "system" && item.mentionIds.includes(this.agent.publicKey) && parseGrantDecisionLine(item.body) !== void 0;
|
|
15634
|
+
if (grantDecision) {
|
|
15635
|
+
await this.prompt(item.id, `${item.body}
|
|
15636
|
+
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);
|
|
14689
15637
|
pollWithoutWait = true;
|
|
14690
15638
|
continue;
|
|
14691
15639
|
}
|
|
14692
|
-
if (
|
|
14693
|
-
|
|
15640
|
+
if (isCheckStartNote(item)) {
|
|
15641
|
+
this.lastChecksState = void 0;
|
|
15642
|
+
continue;
|
|
15643
|
+
}
|
|
15644
|
+
if (completedCheckNote(item))
|
|
15645
|
+
checkNotes.push(item);
|
|
15646
|
+
}
|
|
15647
|
+
if (checkNotes.length) {
|
|
15648
|
+
const state = await this.checksState(checkNotes);
|
|
15649
|
+
if (state && state !== "pending" && state !== this.lastChecksState) {
|
|
15650
|
+
this.lastChecksState = state;
|
|
15651
|
+
const lines = checkNotes.map((note) => note.body);
|
|
15652
|
+
await this.prompt(checkNotes[checkNotes.length - 1].id, lines.join("\n"), [], void 0, lines);
|
|
14694
15653
|
pollWithoutWait = true;
|
|
14695
15654
|
}
|
|
14696
15655
|
}
|
|
@@ -14707,6 +15666,7 @@ ${pullRequest}`,
|
|
|
14707
15666
|
}
|
|
14708
15667
|
}
|
|
14709
15668
|
} finally {
|
|
15669
|
+
this.options.grantRunner?.unregister(cornerId);
|
|
14710
15670
|
await this.options.scheduler.suspend(cornerId);
|
|
14711
15671
|
}
|
|
14712
15672
|
}
|
|
@@ -14726,13 +15686,13 @@ async function wait(ms, signal) {
|
|
|
14726
15686
|
}
|
|
14727
15687
|
|
|
14728
15688
|
// apps/body/dist/monolith-room-turn.js
|
|
14729
|
-
import { mkdir as
|
|
14730
|
-
import { homedir as
|
|
15689
|
+
import { mkdir as mkdir7 } from "node:fs/promises";
|
|
15690
|
+
import { homedir as homedir7 } from "node:os";
|
|
14731
15691
|
import { join as join5 } from "node:path";
|
|
14732
15692
|
|
|
14733
15693
|
// packages/api-contract/dist/scheduled-prompts.js
|
|
14734
15694
|
var SCHEDULE_SCHEDULER_NAME = "Beeline Scheduler";
|
|
14735
|
-
var
|
|
15695
|
+
var SCHEDULE_RAN_VERB = "ran a schedule for";
|
|
14736
15696
|
|
|
14737
15697
|
// apps/body/dist/monolith-room-turn.js
|
|
14738
15698
|
function isRoomMcpPermissionRequest(request) {
|
|
@@ -14744,14 +15704,25 @@ function roomPrincipalMayAddressAgent(authority, humanPermitted) {
|
|
|
14744
15704
|
return authority.member && (authority.principalKind === "agent" || authority.principalKind === "human" && humanPermitted);
|
|
14745
15705
|
}
|
|
14746
15706
|
function isScheduledPrompt(item, agentId) {
|
|
14747
|
-
return item.type === "system" && item.
|
|
15707
|
+
return item.type === "system" && item.systemEvent?.verb === SCHEDULE_RAN_VERB && item.mentionIds.includes(agentId);
|
|
15708
|
+
}
|
|
15709
|
+
function inboxItemPromptBody(item, agentId) {
|
|
15710
|
+
return isScheduledPrompt(item, agentId) ? item.systemEvent?.consequence ?? item.body : item.body;
|
|
15711
|
+
}
|
|
15712
|
+
function isGrantDecisionLine(item, agentId) {
|
|
15713
|
+
return item.type === "system" && item.mentionIds.includes(agentId) && parseGrantDecisionLine(item.body) !== void 0;
|
|
14748
15714
|
}
|
|
14749
15715
|
function inboxItemTriggersTurn(item, agentId) {
|
|
14750
15716
|
if (item.authorId === agentId)
|
|
14751
15717
|
return false;
|
|
14752
15718
|
if (!item.mentionIds.includes(agentId))
|
|
14753
15719
|
return false;
|
|
14754
|
-
return item.type === "message" || isScheduledPrompt(item, agentId);
|
|
15720
|
+
return item.type === "message" || isScheduledPrompt(item, agentId) || isGrantDecisionLine(item, agentId);
|
|
15721
|
+
}
|
|
15722
|
+
function pendingGrantToolCall(call) {
|
|
15723
|
+
if (!/(?:^|[._:/-])request_grant$/i.test(call.title ?? ""))
|
|
15724
|
+
return false;
|
|
15725
|
+
return /pending, card posted/i.test(typeof call.content === "string" ? call.content : JSON.stringify(call.content ?? ""));
|
|
14755
15726
|
}
|
|
14756
15727
|
function escapeRegExp(value) {
|
|
14757
15728
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -14789,6 +15760,8 @@ var MonolithRoomTurnLoop = class {
|
|
|
14789
15760
|
agent;
|
|
14790
15761
|
client;
|
|
14791
15762
|
sessionId;
|
|
15763
|
+
/** The live session's environment, read back for pi's own turn record. */
|
|
15764
|
+
agentEnv = {};
|
|
14792
15765
|
busy = false;
|
|
14793
15766
|
turnInstructionPrefix = "";
|
|
14794
15767
|
draftTail = Promise.resolve();
|
|
@@ -14798,13 +15771,36 @@ var MonolithRoomTurnLoop = class {
|
|
|
14798
15771
|
attachmentDir;
|
|
14799
15772
|
/** Local copies already delivered this session, by message id, so transcript renders reuse them. */
|
|
14800
15773
|
deliveredAttachments = /* @__PURE__ */ new Map();
|
|
15774
|
+
/** Names from the latest roster read, for ledger bylines the runner writes. */
|
|
15775
|
+
memberNames = /* @__PURE__ */ new Map();
|
|
15776
|
+
/** The request id of the turn that paused on a grant card, until its decision arrives. */
|
|
15777
|
+
pausedOnGrantRequestId;
|
|
14801
15778
|
constructor(options) {
|
|
14802
15779
|
this.options = options;
|
|
14803
15780
|
this.agent = runtimeIdentity(options.runtime.agent);
|
|
15781
|
+
options.grantRunner?.register(options.roomId, {
|
|
15782
|
+
workspaceId: options.workspaceId,
|
|
15783
|
+
cwd: options.cwd,
|
|
15784
|
+
turn: () => this.currentTurnForRunner()
|
|
15785
|
+
});
|
|
14804
15786
|
}
|
|
14805
15787
|
isBusy() {
|
|
14806
15788
|
return this.busy;
|
|
14807
15789
|
}
|
|
15790
|
+
/** The turn a `request_grant` paused, if any (cleared when its decision resumes it). */
|
|
15791
|
+
pausedGrantRequestId() {
|
|
15792
|
+
return this.pausedOnGrantRequestId;
|
|
15793
|
+
}
|
|
15794
|
+
currentTurnForRunner() {
|
|
15795
|
+
const active = this.activeTurn;
|
|
15796
|
+
if (!active)
|
|
15797
|
+
return void 0;
|
|
15798
|
+
return { requestId: active.item.id, requester: this.requesterOf(active.item.authorId) };
|
|
15799
|
+
}
|
|
15800
|
+
requesterOf(authorId) {
|
|
15801
|
+
const name = this.memberNames.get(authorId);
|
|
15802
|
+
return { pubkey: authorId, ...name ? { name } : {} };
|
|
15803
|
+
}
|
|
14808
15804
|
currentPrincipalCanDrive(_workspaceId, principalId) {
|
|
14809
15805
|
return Promise.resolve(isSenderPermitted(this.options.config.accessPolicy ?? LEGACY_ACCESS_POLICY, principalId, this.options.config.accessOwnerPubkey, this.options.config.accessAllowlist));
|
|
14810
15806
|
}
|
|
@@ -14818,11 +15814,13 @@ var MonolithRoomTurnLoop = class {
|
|
|
14818
15814
|
this.client.sessionCancel(this.sessionId);
|
|
14819
15815
|
await this.options.scheduler.forceSuspend(this.options.roomId);
|
|
14820
15816
|
}
|
|
14821
|
-
roster() {
|
|
14822
|
-
|
|
15817
|
+
async roster() {
|
|
15818
|
+
const roster = await this.options.api.execute("getWorkspaceRoster", {
|
|
14823
15819
|
agentId: this.agent.publicKey,
|
|
14824
15820
|
workspaceId: this.options.workspaceId
|
|
14825
15821
|
});
|
|
15822
|
+
this.memberNames = new Map(roster.members.map((member) => [member.identityId, member.name]));
|
|
15823
|
+
return roster;
|
|
14826
15824
|
}
|
|
14827
15825
|
/** Download a message's attachments into the session scratch directory once. */
|
|
14828
15826
|
async deliver(item) {
|
|
@@ -14847,25 +15845,27 @@ var MonolithRoomTurnLoop = class {
|
|
|
14847
15845
|
this.options.api.execute("getRoomRepositoryState", { roomId: this.options.roomId })
|
|
14848
15846
|
]);
|
|
14849
15847
|
const directMessage = Array.isArray(repositoryState.directParticipants) && repositoryState.directParticipants.length === 2;
|
|
14850
|
-
await
|
|
15848
|
+
await mkdir7(this.options.cwd, { recursive: true });
|
|
15849
|
+
const selection = configuration.model || configuration.effort ? { model: configuration.model, effort: configuration.effort } : this.options.config.modelSelection;
|
|
14851
15850
|
const homeOverlay = this.options.config.agentHomeRoot ? await prepareRoomAgentHome({
|
|
14852
15851
|
root: this.options.config.agentHomeRoot,
|
|
14853
15852
|
sharedSkills: this.options.config.sharedSkills ?? [],
|
|
14854
|
-
...this.options.config.operatorHome ? { operatorHome: this.options.config.operatorHome } : {}
|
|
15853
|
+
...this.options.config.operatorHome ? { operatorHome: this.options.config.operatorHome } : {},
|
|
15854
|
+
...openRouterRoutingInput(this.options.config, selection, this.options.fetchImpl)
|
|
14855
15855
|
}) : {};
|
|
14856
15856
|
const command = this.options.config.agentCommand ?? this.options.config.agentBinary;
|
|
14857
|
-
const selection = configuration.model || configuration.effort ? { model: configuration.model, effort: configuration.effort } : this.options.config.modelSelection;
|
|
14858
15857
|
const agentEnv = { ...this.options.config.agentEnv, ...homeOverlay };
|
|
15858
|
+
this.agentEnv = agentEnv;
|
|
14859
15859
|
const agentArgs = agentArgsWithModelSelection({
|
|
14860
15860
|
kind: this.options.config.agentKind,
|
|
14861
15861
|
command,
|
|
14862
15862
|
args: this.options.config.agentArgs ?? []
|
|
14863
15863
|
}, selection);
|
|
14864
|
-
const operatorHome = this.options.config.operatorHome ??
|
|
15864
|
+
const operatorHome = this.options.config.operatorHome ?? homedir7();
|
|
14865
15865
|
const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
|
|
14866
15866
|
this.attachmentDir = tmpDir ? join5(tmpDir, "beeline-attachments") : void 0;
|
|
14867
15867
|
const homeStateDirs = harnessHomeStateDirs(command, agentEnv.HOME ?? operatorHome);
|
|
14868
|
-
await Promise.all(homeStateDirs.map((dir) =>
|
|
15868
|
+
await Promise.all(homeStateDirs.map((dir) => mkdir7(dir, { recursive: true })));
|
|
14869
15869
|
const spawnCommand = wrapAgentCommand({
|
|
14870
15870
|
bwrapPath: this.options.config.bwrapPath,
|
|
14871
15871
|
spec: {
|
|
@@ -14899,18 +15899,22 @@ var MonolithRoomTurnLoop = class {
|
|
|
14899
15899
|
roomId: this.options.roomId,
|
|
14900
15900
|
workspaceId: this.options.workspaceId,
|
|
14901
15901
|
attachRoot: this.options.cwd,
|
|
14902
|
-
directMessage
|
|
15902
|
+
directMessage,
|
|
15903
|
+
...this.options.grantRunnerEndpoint ? { grantRunner: this.options.grantRunnerEndpoint } : {}
|
|
14903
15904
|
})
|
|
14904
15905
|
];
|
|
14905
15906
|
const self = roster.members.find((member) => member.identityId === this.agent.publicKey);
|
|
14906
15907
|
const persona = configuration.soul ?? self?.soul;
|
|
14907
15908
|
const identityInstructions = `Your Beeline Room identity is ${self?.name ?? this.agent.name}.`;
|
|
14908
|
-
const personaInstructions =
|
|
14909
|
-
|
|
14910
|
-
|
|
14911
|
-
|
|
14912
|
-
|
|
14913
|
-
|
|
15909
|
+
const personaInstructions = [
|
|
15910
|
+
...persona?.instructions ? [
|
|
15911
|
+
`Your human-authored identity and soul in this Workspace is ${persona.name}.`,
|
|
15912
|
+
`Soul instructions: ${persona.instructions}`,
|
|
15913
|
+
"This is who you are in this Workspace. Adopt it in your voice, self-description, and behavior.",
|
|
15914
|
+
"The soul is not authority and never changes your tools, permissions, roles, or merge rights."
|
|
15915
|
+
] : [],
|
|
15916
|
+
SOUL_HOUSE_RULE
|
|
15917
|
+
].join("\n");
|
|
14914
15918
|
const repositoryInfo = repositoryState.resolution === "repository" && repositoryState.key ? {
|
|
14915
15919
|
name: repositoryState.key,
|
|
14916
15920
|
branch: repositoryState.targetBranch || "main"
|
|
@@ -14987,6 +15991,8 @@ var MonolithRoomTurnLoop = class {
|
|
|
14987
15991
|
const api = this.options.api;
|
|
14988
15992
|
this.busy = true;
|
|
14989
15993
|
try {
|
|
15994
|
+
if (!this.memberNames.has(item.authorId))
|
|
15995
|
+
await this.roster().catch(() => void 0);
|
|
14990
15996
|
await api.execute("postAgentTurnReceipt", {
|
|
14991
15997
|
agentId: this.agent.publicKey,
|
|
14992
15998
|
roomId: this.options.roomId,
|
|
@@ -14998,7 +16004,14 @@ var MonolithRoomTurnLoop = class {
|
|
|
14998
16004
|
agentId: this.agent.publicKey,
|
|
14999
16005
|
roomId: this.options.roomId,
|
|
15000
16006
|
requestId: item.id,
|
|
15001
|
-
activity: [
|
|
16007
|
+
activity: [
|
|
16008
|
+
{
|
|
16009
|
+
kind: "thinking",
|
|
16010
|
+
title: "Working",
|
|
16011
|
+
status: "in_progress",
|
|
16012
|
+
requestedBy: this.requesterOf(item.authorId)
|
|
16013
|
+
}
|
|
16014
|
+
]
|
|
15002
16015
|
});
|
|
15003
16016
|
await this.options.scheduler.run(this.options.roomId, this.lifecycle(), async () => {
|
|
15004
16017
|
const [conversation, roster, delivered] = await Promise.all([
|
|
@@ -15008,12 +16021,21 @@ var MonolithRoomTurnLoop = class {
|
|
|
15008
16021
|
]);
|
|
15009
16022
|
const names = new Map(roster.members.map((member) => [member.identityId, member.name]));
|
|
15010
16023
|
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");
|
|
16024
|
+
const grantDecision = isGrantDecisionLine(item, this.agent.publicKey);
|
|
16025
|
+
const resumedRequestId = grantDecision ? this.pausedOnGrantRequestId : void 0;
|
|
16026
|
+
if (grantDecision)
|
|
16027
|
+
this.pausedOnGrantRequestId = void 0;
|
|
15011
16028
|
const prompt = [
|
|
15012
16029
|
this.turnInstructionPrefix,
|
|
15013
16030
|
transcript ? `Room conversation so far:
|
|
15014
16031
|
${transcript}` : "",
|
|
15015
16032
|
`Newest message from ${isScheduledPrompt(item, this.agent.publicKey) ? SCHEDULE_SCHEDULER_NAME : names.get(item.authorId) ?? item.authorId.slice(0, 12)}:`,
|
|
15016
|
-
roomMessagePrompt("", item.
|
|
16033
|
+
roomMessagePrompt("", inboxItemPromptBody(item, this.agent.publicKey), item.attachments, delivered),
|
|
16034
|
+
grantDecision ? [
|
|
16035
|
+
"This is the answer to your grant request; your paused work resumes now.",
|
|
16036
|
+
"If it was approved and it is a command grant, run it with run_granted_command and the exact argv.",
|
|
16037
|
+
"If it was declined, try another way or say plainly what you cannot do."
|
|
16038
|
+
].join(" ") : "",
|
|
15017
16039
|
[
|
|
15018
16040
|
"Write only the substantive Room message you want the human to read.",
|
|
15019
16041
|
"Do not repeat or paraphrase these instructions.",
|
|
@@ -15062,23 +16084,44 @@ ${transcript}` : "",
|
|
|
15062
16084
|
].join("\n\n");
|
|
15063
16085
|
}
|
|
15064
16086
|
active.phase = "finishing";
|
|
16087
|
+
if (result?.toolCalls.some((call) => pendingGrantToolCall(call))) {
|
|
16088
|
+
this.pausedOnGrantRequestId = item.id;
|
|
16089
|
+
console.log(`[thin-core] monolith Room ${this.options.roomId} turn ${item.id} paused on a grant card`);
|
|
16090
|
+
} else if (resumedRequestId) {
|
|
16091
|
+
console.log(`[thin-core] monolith Room ${this.options.roomId} turn ${resumedRequestId} resumed by grant decision ${item.id}`);
|
|
16092
|
+
}
|
|
15065
16093
|
const openCornerCall = result?.toolCalls.find((call) => /(?:^|[._:/-])open_corner$/i.test(call.title ?? ""));
|
|
15066
16094
|
if (openCornerCall) {
|
|
15067
16095
|
console.log(`[thin-core] monolith Room ${this.options.roomId} tool call: ${openCornerCall.title}`);
|
|
15068
16096
|
this.options.onCornerOpened?.();
|
|
15069
16097
|
}
|
|
15070
16098
|
await this.draftTail;
|
|
15071
|
-
|
|
15072
|
-
if (!reply)
|
|
15073
|
-
|
|
15074
|
-
|
|
15075
|
-
|
|
15076
|
-
|
|
15077
|
-
|
|
15078
|
-
|
|
15079
|
-
|
|
15080
|
-
|
|
15081
|
-
|
|
16099
|
+
let reply = sanitizeAgentReply(result.agentText);
|
|
16100
|
+
if (!reply) {
|
|
16101
|
+
const explained = await explainEmptyAgentTurn({
|
|
16102
|
+
agentLabel: this.options.config.agentCommand ?? this.options.config.agentBinary,
|
|
16103
|
+
agentEnv: this.agentEnv,
|
|
16104
|
+
sessionId,
|
|
16105
|
+
result
|
|
16106
|
+
});
|
|
16107
|
+
reply = explained.recoveredText ? sanitizeAgentReply(explained.recoveredText) : "";
|
|
16108
|
+
if (!reply)
|
|
16109
|
+
throw new Error(explained.reason);
|
|
16110
|
+
console.warn(`[thin-core] monolith Room ${this.options.roomId} turn ${item.id}: ${explained.reason}`);
|
|
16111
|
+
}
|
|
16112
|
+
if (openCornerCall && !/failed|error|denied/i.test(openCornerCall.status ?? "")) {
|
|
16113
|
+
reply = stripCornerOpenEcho(reply);
|
|
16114
|
+
}
|
|
16115
|
+
if (reply) {
|
|
16116
|
+
await api.execute("postRoomMessage", {
|
|
16117
|
+
roomId: this.options.roomId,
|
|
16118
|
+
requestId: item.id,
|
|
16119
|
+
triggerMessageId: item.id,
|
|
16120
|
+
text: reply,
|
|
16121
|
+
presentation: "message",
|
|
16122
|
+
mentionIds: agentReplyMentionIds(reply, roster, this.agent.publicKey)
|
|
16123
|
+
});
|
|
16124
|
+
}
|
|
15082
16125
|
await api.execute("retractAgentLiveOutput", {
|
|
15083
16126
|
agentId: this.agent.publicKey,
|
|
15084
16127
|
roomId: this.options.roomId,
|
|
@@ -15099,7 +16142,8 @@ ${transcript}` : "",
|
|
|
15099
16142
|
roomId: this.options.roomId,
|
|
15100
16143
|
requestId: item.id,
|
|
15101
16144
|
status: "failed",
|
|
15102
|
-
generationId: `${this.agent.publicKey}:${this.options.roomId}
|
|
16145
|
+
generationId: `${this.agent.publicKey}:${this.options.roomId}`,
|
|
16146
|
+
reason: distillTurnFailureReason(error)
|
|
15103
16147
|
});
|
|
15104
16148
|
throw error;
|
|
15105
16149
|
} finally {
|
|
@@ -15138,7 +16182,7 @@ ${transcript}` : "",
|
|
|
15138
16182
|
for (const item of inbox.items) {
|
|
15139
16183
|
if (!inboxItemTriggersTurn(item, this.agent.publicKey))
|
|
15140
16184
|
continue;
|
|
15141
|
-
if (!isScheduledPrompt(item, this.agent.publicKey)) {
|
|
16185
|
+
if (!isScheduledPrompt(item, this.agent.publicKey) && !isGrantDecisionLine(item, this.agent.publicKey)) {
|
|
15142
16186
|
const authority = await api.execute("getRoomAuthority", {
|
|
15143
16187
|
roomId,
|
|
15144
16188
|
principalId: item.authorId
|
|
@@ -15168,6 +16212,7 @@ ${transcript}` : "",
|
|
|
15168
16212
|
}
|
|
15169
16213
|
} finally {
|
|
15170
16214
|
clearInterval(heartbeat);
|
|
16215
|
+
this.options.grantRunner?.unregister(roomId);
|
|
15171
16216
|
if (this.activeTurn?.phase === "prompting" && this.client && this.sessionId) {
|
|
15172
16217
|
this.client.sessionCancel(this.sessionId);
|
|
15173
16218
|
}
|
|
@@ -15547,7 +16592,7 @@ async function mapWithConcurrency(values, limit, visit) {
|
|
|
15547
16592
|
}
|
|
15548
16593
|
}));
|
|
15549
16594
|
}
|
|
15550
|
-
var execFileAsync3 = promisify3(
|
|
16595
|
+
var execFileAsync3 = promisify3(execFile4);
|
|
15551
16596
|
var RoomRuntimeCoordinator = class {
|
|
15552
16597
|
configPath;
|
|
15553
16598
|
baseConfig;
|
|
@@ -15567,6 +16612,9 @@ var RoomRuntimeCoordinator = class {
|
|
|
15567
16612
|
workspaceRemovalConfirmations = 0;
|
|
15568
16613
|
roomRemovalConfirmations = /* @__PURE__ */ new Map();
|
|
15569
16614
|
confirmationPending = false;
|
|
16615
|
+
/** One command-grant runner per daemon; Rooms and corners register their checkouts on it. */
|
|
16616
|
+
grantRunner;
|
|
16617
|
+
grantRunnerServer;
|
|
15570
16618
|
constructor(runtime, configPath, baseConfig, options) {
|
|
15571
16619
|
this.configPath = configPath;
|
|
15572
16620
|
this.baseConfig = baseConfig;
|
|
@@ -15576,6 +16624,11 @@ var RoomRuntimeCoordinator = class {
|
|
|
15576
16624
|
this.runtime = runtime;
|
|
15577
16625
|
this.agent = runtimeIdentity(runtime.agent);
|
|
15578
16626
|
this.now = options.now ?? Date.now;
|
|
16627
|
+
this.grantRunner = new GrantCommandRunner({
|
|
16628
|
+
api: options.daemonApi,
|
|
16629
|
+
agentId: this.agent.publicKey
|
|
16630
|
+
});
|
|
16631
|
+
this.grantRunnerServer = new GrantRunnerServer(this.grantRunner);
|
|
15579
16632
|
this.watchdogStaleMs = options.watchdogStaleMs ?? DEFAULT_ROOM_WATCHDOG_STALE_MS;
|
|
15580
16633
|
this.reconcileHeartbeatMs = options.reconcileHeartbeatMs ?? DEFAULT_RECONCILE_HEARTBEAT_MS;
|
|
15581
16634
|
this.drainDeadlineMs = options.drainDeadlineMs ?? DEFAULT_DRAIN_DEADLINE_MS;
|
|
@@ -15602,7 +16655,15 @@ var RoomRuntimeCoordinator = class {
|
|
|
15602
16655
|
return this.reconcileHeartbeatMs;
|
|
15603
16656
|
}
|
|
15604
16657
|
isWorkspaceIdle() {
|
|
15605
|
-
return
|
|
16658
|
+
return this.activeTurnCount() === 0;
|
|
16659
|
+
}
|
|
16660
|
+
/** Turns executing right now. Serving a Room or corner with no turn in flight is idle. */
|
|
16661
|
+
activeTurnCount() {
|
|
16662
|
+
let count = 0;
|
|
16663
|
+
for (const room of this.running.values())
|
|
16664
|
+
if (room.body.isBusy())
|
|
16665
|
+
count += 1;
|
|
16666
|
+
return count;
|
|
15606
16667
|
}
|
|
15607
16668
|
quiesceForUpdateIfIdle() {
|
|
15608
16669
|
if (!this.isWorkspaceIdle())
|
|
@@ -15698,13 +16759,13 @@ var RoomRuntimeCoordinator = class {
|
|
|
15698
16759
|
return this.runtime.rooms.find((room) => room.channelId === roomId);
|
|
15699
16760
|
}
|
|
15700
16761
|
roomRoot(roomId) {
|
|
15701
|
-
return this.roomRecord(roomId)?.root ??
|
|
16762
|
+
return this.roomRecord(roomId)?.root ?? resolve15(dirname6(this.configPath), "rooms", roomId);
|
|
15702
16763
|
}
|
|
15703
16764
|
roomAgentHomeRoot(workspaceRoot, required = false) {
|
|
15704
16765
|
const flag = process.env.BUZZY_BODY_ROOM_HOME;
|
|
15705
16766
|
if (!required && flag === "0")
|
|
15706
16767
|
return void 0;
|
|
15707
|
-
const home =
|
|
16768
|
+
const home = resolve15(workspaceRoot, "agent-home");
|
|
15708
16769
|
if (!required && flag !== "1" && !existsSync4(home) && existsSync4(workspaceRoot))
|
|
15709
16770
|
return void 0;
|
|
15710
16771
|
try {
|
|
@@ -15721,19 +16782,30 @@ var RoomRuntimeCoordinator = class {
|
|
|
15721
16782
|
return {
|
|
15722
16783
|
...this.baseConfig,
|
|
15723
16784
|
workspaceRoot,
|
|
15724
|
-
agentPrivateRoot:
|
|
15725
|
-
agentMemoryRoot:
|
|
16785
|
+
agentPrivateRoot: resolve15(workspaceRoot, "agent-private"),
|
|
16786
|
+
agentMemoryRoot: resolve15(dirname6(this.configPath), "memory"),
|
|
16787
|
+
openRouterRoutingCacheDir: openRouterRoutingCacheDir(dirname6(this.configPath)),
|
|
15726
16788
|
...agentHomeRoot ? { agentHomeRoot } : {}
|
|
15727
16789
|
};
|
|
15728
16790
|
}
|
|
16791
|
+
/** The loopback door for run_granted_command; started once, on first use. */
|
|
16792
|
+
grantRunnerEndpoint() {
|
|
16793
|
+
return this.grantRunnerServer.start().catch((error) => {
|
|
16794
|
+
console.error("[thin-core] grant runner unavailable; run_granted_command is off:", error);
|
|
16795
|
+
return void 0;
|
|
16796
|
+
});
|
|
16797
|
+
}
|
|
15729
16798
|
async startRoom(roomId) {
|
|
15730
16799
|
const controller = new AbortController();
|
|
15731
16800
|
const cwd = await this.materializeRoomCheckout(roomId);
|
|
16801
|
+
const grantRunnerEndpoint = await this.grantRunnerEndpoint();
|
|
15732
16802
|
const startedAt = this.now();
|
|
15733
16803
|
const loop = new MonolithRoomTurnLoop({
|
|
15734
16804
|
roomId,
|
|
15735
16805
|
workspaceId: this.runtime.communityId,
|
|
15736
16806
|
cwd,
|
|
16807
|
+
grantRunner: this.grantRunner,
|
|
16808
|
+
...grantRunnerEndpoint ? { grantRunnerEndpoint } : {},
|
|
15737
16809
|
runtime: this.runtime,
|
|
15738
16810
|
config: this.roomConfig(roomId),
|
|
15739
16811
|
api: this.options.daemonApi,
|
|
@@ -15778,12 +16850,12 @@ var RoomRuntimeCoordinator = class {
|
|
|
15778
16850
|
return this.roomRoot(roomId);
|
|
15779
16851
|
const remote = roomCheckoutRemote(repository.remote);
|
|
15780
16852
|
const targetBranch = repository.targetBranch || "main";
|
|
15781
|
-
const checkoutId =
|
|
15782
|
-
const path =
|
|
15783
|
-
await
|
|
16853
|
+
const checkoutId = createHash2("sha256").update(`${remote}\0${targetBranch}`).digest("hex").slice(0, 24);
|
|
16854
|
+
const path = resolve15(this.runtime.supervisorRoot, "beeline", "room-checkouts", checkoutId);
|
|
16855
|
+
await mkdir8(dirname6(path), { recursive: true, mode: 448 });
|
|
15784
16856
|
const token = remote.startsWith("https://github.com/") ? await this.options.daemonApi.execute("getRoomGitHubToken", { roomId }) : void 0;
|
|
15785
16857
|
const env = token ? githubGitEnv(token.token) : process.env;
|
|
15786
|
-
if (!existsSync4(
|
|
16858
|
+
if (!existsSync4(resolve15(path, ".git"))) {
|
|
15787
16859
|
await execFileAsync3("git", ["clone", "--no-checkout", remote, path], {
|
|
15788
16860
|
env,
|
|
15789
16861
|
maxBuffer: 4 * 1024 * 1024
|
|
@@ -15845,9 +16917,12 @@ var RoomRuntimeCoordinator = class {
|
|
|
15845
16917
|
});
|
|
15846
16918
|
}
|
|
15847
16919
|
const controller = new AbortController();
|
|
16920
|
+
const grantRunnerEndpoint = await this.grantRunnerEndpoint();
|
|
15848
16921
|
const startedAt = this.now();
|
|
15849
16922
|
const loop = new MonolithCornerTurnLoop({
|
|
15850
16923
|
cornerId: corner.cornerId,
|
|
16924
|
+
grantRunner: this.grantRunner,
|
|
16925
|
+
...grantRunnerEndpoint ? { grantRunnerEndpoint } : {},
|
|
15851
16926
|
parentRoomId: corner.parentRoomId,
|
|
15852
16927
|
workspaceId: this.runtime.communityId,
|
|
15853
16928
|
objective,
|
|
@@ -15900,13 +16975,13 @@ var RoomRuntimeCoordinator = class {
|
|
|
15900
16975
|
}
|
|
15901
16976
|
async materializeCornerWorktree(input) {
|
|
15902
16977
|
const remote = githubHttpsRemote(input.remote);
|
|
15903
|
-
const repositoryHash =
|
|
15904
|
-
const gitCommonDir =
|
|
15905
|
-
const path =
|
|
15906
|
-
await
|
|
15907
|
-
await
|
|
16978
|
+
const repositoryHash = createHash2("sha256").update(remote).digest("hex").slice(0, 24);
|
|
16979
|
+
const gitCommonDir = resolve15(this.runtime.supervisorRoot, "beeline", "repositories", `${repositoryHash}.git`);
|
|
16980
|
+
const path = resolve15(this.runtime.supervisorRoot, "beeline", "corners", input.cornerId);
|
|
16981
|
+
await mkdir8(dirname6(gitCommonDir), { recursive: true, mode: 448 });
|
|
16982
|
+
await mkdir8(dirname6(path), { recursive: true, mode: 448 });
|
|
15908
16983
|
const authEnv = githubGitEnv(input.token);
|
|
15909
|
-
if (!existsSync4(
|
|
16984
|
+
if (!existsSync4(resolve15(gitCommonDir, "HEAD"))) {
|
|
15910
16985
|
await execFileAsync3("git", ["clone", "--bare", remote, gitCommonDir], {
|
|
15911
16986
|
env: authEnv,
|
|
15912
16987
|
maxBuffer: 4 * 1024 * 1024
|
|
@@ -15919,7 +16994,7 @@ var RoomRuntimeCoordinator = class {
|
|
|
15919
16994
|
"origin",
|
|
15920
16995
|
`+refs/heads/${input.targetBranch}:refs/remotes/origin/${input.targetBranch}`
|
|
15921
16996
|
], { env: authEnv, maxBuffer: 4 * 1024 * 1024 });
|
|
15922
|
-
if (!existsSync4(
|
|
16997
|
+
if (!existsSync4(resolve15(path, ".git"))) {
|
|
15923
16998
|
await rm3(path, { recursive: true, force: true });
|
|
15924
16999
|
await execFileAsync3("git", [
|
|
15925
17000
|
`--git-dir=${gitCommonDir}`,
|
|
@@ -15956,7 +17031,7 @@ var RoomRuntimeCoordinator = class {
|
|
|
15956
17031
|
`${this.agent.publicKey.slice(0, 16)}@users.noreply.github.com`
|
|
15957
17032
|
]);
|
|
15958
17033
|
const top = await execFileAsync3("git", ["-C", path, "rev-parse", "--show-toplevel"]);
|
|
15959
|
-
if (
|
|
17034
|
+
if (resolve15(top.stdout.trim()) !== resolve15(path)) {
|
|
15960
17035
|
throw new Error(`corner worktree escaped its isolated root: ${top.stdout.trim()}`);
|
|
15961
17036
|
}
|
|
15962
17037
|
return { path, gitCommonDir };
|
|
@@ -16023,6 +17098,7 @@ var RoomRuntimeCoordinator = class {
|
|
|
16023
17098
|
await Promise.allSettled(rooms.map((room) => room.body.forceRecoverRoom()));
|
|
16024
17099
|
await drained;
|
|
16025
17100
|
}
|
|
17101
|
+
await this.grantRunnerServer.close();
|
|
16026
17102
|
await this.scheduler.dispose();
|
|
16027
17103
|
}
|
|
16028
17104
|
};
|
|
@@ -16081,6 +17157,9 @@ var ThinDaemonCore = class {
|
|
|
16081
17157
|
isWorkspaceIdle() {
|
|
16082
17158
|
return this.roomRuntime.isWorkspaceIdle();
|
|
16083
17159
|
}
|
|
17160
|
+
activeTurnCount() {
|
|
17161
|
+
return this.roomRuntime.activeTurnCount();
|
|
17162
|
+
}
|
|
16084
17163
|
quiesceForUpdateIfIdle() {
|
|
16085
17164
|
return this.roomRuntime.quiesceForUpdateIfIdle();
|
|
16086
17165
|
}
|
|
@@ -16127,7 +17206,7 @@ var ThinDaemonCore = class {
|
|
|
16127
17206
|
};
|
|
16128
17207
|
|
|
16129
17208
|
// apps/body/dist/daemon-api-client.js
|
|
16130
|
-
import { resolve as
|
|
17209
|
+
import { resolve as resolve16 } from "node:path";
|
|
16131
17210
|
var DaemonApiError = class extends Error {
|
|
16132
17211
|
status;
|
|
16133
17212
|
retryable;
|
|
@@ -16186,8 +17265,8 @@ var DaemonApiClient = class {
|
|
|
16186
17265
|
};
|
|
16187
17266
|
async function activateDaemonTransport(path, fetchImpl = fetch) {
|
|
16188
17267
|
const runtime = await readRuntimeRecord(path);
|
|
16189
|
-
const expectedPath =
|
|
16190
|
-
if (
|
|
17268
|
+
const expectedPath = resolve16(runtimeDirectory(runtime.supervisorRoot, runtime.agent.publicKey), "runtime.json");
|
|
17269
|
+
if (resolve16(path) !== expectedPath) {
|
|
16191
17270
|
throw new Error(`refusing daemon token exchange outside canonical runtime path: ${path}`);
|
|
16192
17271
|
}
|
|
16193
17272
|
const transport = runtime.transport;
|
|
@@ -16226,17 +17305,17 @@ async function activateDaemonTransport(path, fetchImpl = fetch) {
|
|
|
16226
17305
|
}
|
|
16227
17306
|
|
|
16228
17307
|
// apps/body/dist/start-command.js
|
|
16229
|
-
import { dirname as
|
|
17308
|
+
import { dirname as dirname8 } from "node:path";
|
|
16230
17309
|
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
16231
17310
|
|
|
16232
17311
|
// apps/body/dist/systemd.js
|
|
16233
|
-
import { execFile as
|
|
16234
|
-
import { mkdir as
|
|
16235
|
-
import { homedir as
|
|
16236
|
-
import { dirname as
|
|
17312
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
17313
|
+
import { mkdir as mkdir9, readFile as readFile7, writeFile as writeFile7 } from "node:fs/promises";
|
|
17314
|
+
import { homedir as homedir8 } from "node:os";
|
|
17315
|
+
import { dirname as dirname7, resolve as resolve17 } from "node:path";
|
|
16237
17316
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
16238
17317
|
import { promisify as promisify4 } from "node:util";
|
|
16239
|
-
var execFileAsync4 = promisify4(
|
|
17318
|
+
var execFileAsync4 = promisify4(execFile5);
|
|
16240
17319
|
var DELIBERATE_REMOVAL_EXIT_STATUS = 78;
|
|
16241
17320
|
var DAEMON_DISTRESS_EXIT_STATUS = 77;
|
|
16242
17321
|
var UNKNOWN_AGENT_EXIT_STATUS = 79;
|
|
@@ -16274,10 +17353,10 @@ WantedBy=default.target
|
|
|
16274
17353
|
`;
|
|
16275
17354
|
}
|
|
16276
17355
|
function isCanonicalInstalledLauncher(env = process.env, invocationPath = process.argv[1]) {
|
|
16277
|
-
const home = env.HOME?.trim() ||
|
|
16278
|
-
const expectedLibDir =
|
|
17356
|
+
const home = env.HOME?.trim() || homedir8();
|
|
17357
|
+
const expectedLibDir = resolve17(home, ".local", "lib", "beeline");
|
|
16279
17358
|
const expectedPrefix = `${expectedLibDir}/`;
|
|
16280
|
-
return
|
|
17359
|
+
return resolve17(env.BEELINE_LIB_DIR?.trim() || "/") === expectedLibDir && Boolean(invocationPath) && resolve17(invocationPath).startsWith(expectedPrefix);
|
|
16281
17360
|
}
|
|
16282
17361
|
function assertCanonicalInstalledLauncher(env, invocationPath) {
|
|
16283
17362
|
if (isCanonicalInstalledLauncher(env, invocationPath))
|
|
@@ -16285,8 +17364,8 @@ function assertCanonicalInstalledLauncher(env, invocationPath) {
|
|
|
16285
17364
|
throw new Error("refusing to modify the shared Beeline systemd unit outside the canonical ~/.local/bin/beeline launcher");
|
|
16286
17365
|
}
|
|
16287
17366
|
function systemdUserUnitPath(env = process.env) {
|
|
16288
|
-
const configRoot = env.XDG_CONFIG_HOME?.trim() ||
|
|
16289
|
-
return
|
|
17367
|
+
const configRoot = env.XDG_CONFIG_HOME?.trim() || resolve17(homedir8(), ".config");
|
|
17368
|
+
return resolve17(configRoot, "systemd", "user", SYSTEMD_UNIT_NAME);
|
|
16290
17369
|
}
|
|
16291
17370
|
var runSystemctl = async (args) => {
|
|
16292
17371
|
const result = await execFileAsync4("systemctl", ["--user", ...args], {
|
|
@@ -16302,10 +17381,10 @@ async function installAgentService(publicKey, options = {}) {
|
|
|
16302
17381
|
assertCanonicalInstalledLauncher(env, options.invocationPath);
|
|
16303
17382
|
const path = systemdUserUnitPath(env);
|
|
16304
17383
|
const content = agentServiceUnit();
|
|
16305
|
-
const existing = await
|
|
17384
|
+
const existing = await readFile7(path, "utf8").catch(() => "");
|
|
16306
17385
|
if (existing !== content) {
|
|
16307
|
-
await
|
|
16308
|
-
await
|
|
17386
|
+
await mkdir9(dirname7(path), { recursive: true, mode: 448 });
|
|
17387
|
+
await writeFile7(path, content, { mode: 384 });
|
|
16309
17388
|
}
|
|
16310
17389
|
const run2 = options.run ?? runSystemctl;
|
|
16311
17390
|
await run2(["daemon-reload"]);
|
|
@@ -16357,6 +17436,9 @@ async function notify(fields) {
|
|
|
16357
17436
|
return;
|
|
16358
17437
|
await execFileAsync4("systemd-notify", fields, { timeout: SYSTEMD_COMMAND_TIMEOUT_MS });
|
|
16359
17438
|
}
|
|
17439
|
+
async function extendSystemdStartTimeout(ms) {
|
|
17440
|
+
await notify([`EXTEND_TIMEOUT_USEC=${Math.max(0, Math.round(ms)) * 1e3}`]).catch(() => void 0);
|
|
17441
|
+
}
|
|
16360
17442
|
var SystemdNotifier = class {
|
|
16361
17443
|
async ready(status) {
|
|
16362
17444
|
await notify(["--ready", `--status=${status}`]);
|
|
@@ -16443,7 +17525,7 @@ async function runStartCommand(args, interactiveUi) {
|
|
|
16443
17525
|
continue;
|
|
16444
17526
|
}
|
|
16445
17527
|
const spinnerHandle = spinner();
|
|
16446
|
-
spinnerHandle.start(`Starting ${
|
|
17528
|
+
spinnerHandle.start(`Starting ${dirname8(path)}\u2026`);
|
|
16447
17529
|
try {
|
|
16448
17530
|
await startRuntime(path, spinnerHandle);
|
|
16449
17531
|
spinnerHandle.stop(import_picocolors.default.green("Started."));
|
|
@@ -16459,9 +17541,9 @@ async function runStartCommand(args, interactiveUi) {
|
|
|
16459
17541
|
|
|
16460
17542
|
// apps/body/dist/connect-command.js
|
|
16461
17543
|
import { spawn as spawn5 } from "node:child_process";
|
|
16462
|
-
import { createHash as
|
|
16463
|
-
import { chmod as chmod4, mkdir as
|
|
16464
|
-
import { dirname as
|
|
17544
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
17545
|
+
import { chmod as chmod4, mkdir as mkdir11, readFile as readFile9, unlink as unlink2, writeFile as writeFile9 } from "node:fs/promises";
|
|
17546
|
+
import { dirname as dirname10, resolve as resolve19 } from "node:path";
|
|
16465
17547
|
import { stdin as stdin3, stdout as stdout4 } from "node:process";
|
|
16466
17548
|
|
|
16467
17549
|
// packages/api-contract/dist/agent-pairing-code.js
|
|
@@ -16771,63 +17853,6 @@ async function pairDevice(grant, options = {}) {
|
|
|
16771
17853
|
return { runtime: activated.runtime, configPath: staged.configPath, pid };
|
|
16772
17854
|
}
|
|
16773
17855
|
|
|
16774
|
-
// apps/body/dist/provider-key-store.js
|
|
16775
|
-
import { chmod as chmod2, mkdir as mkdir8, readFile as readFile3, writeFile as writeFile5 } from "node:fs/promises";
|
|
16776
|
-
import { homedir as homedir8 } from "node:os";
|
|
16777
|
-
import { dirname as dirname7, resolve as resolve13 } from "node:path";
|
|
16778
|
-
var PROVIDER_KEY_ENV_VARS = {
|
|
16779
|
-
openrouter: "OPENROUTER_API_KEY",
|
|
16780
|
-
openai: "OPENAI_API_KEY",
|
|
16781
|
-
anthropic: "ANTHROPIC_API_KEY",
|
|
16782
|
-
google: "GOOGLE_API_KEY",
|
|
16783
|
-
xai: "XAI_API_KEY"
|
|
16784
|
-
};
|
|
16785
|
-
var GOOGLE_ENV_ALIAS = "GEMINI_API_KEY";
|
|
16786
|
-
function providerKeyStorePath(env = process.env) {
|
|
16787
|
-
const configRoot = env.XDG_CONFIG_HOME?.trim() || resolve13(homedir8(), ".config");
|
|
16788
|
-
return resolve13(configRoot, "beeline", "providers.json");
|
|
16789
|
-
}
|
|
16790
|
-
async function readProviderKeyStore(env = process.env) {
|
|
16791
|
-
const path = providerKeyStorePath(env);
|
|
16792
|
-
const raw = await readFile3(path, "utf8").catch(() => void 0);
|
|
16793
|
-
if (!raw)
|
|
16794
|
-
return {};
|
|
16795
|
-
try {
|
|
16796
|
-
const parsed = JSON.parse(raw);
|
|
16797
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
16798
|
-
return {};
|
|
16799
|
-
const entries = Object.entries(parsed).filter((entry) => entry[0] in PROVIDER_KEY_ENV_VARS && typeof entry[1] === "string" && entry[1].length > 0);
|
|
16800
|
-
return Object.fromEntries(entries);
|
|
16801
|
-
} catch {
|
|
16802
|
-
return {};
|
|
16803
|
-
}
|
|
16804
|
-
}
|
|
16805
|
-
async function readSavedProviderKey(provider, env = process.env) {
|
|
16806
|
-
return (await readProviderKeyStore(env))[provider];
|
|
16807
|
-
}
|
|
16808
|
-
async function saveProviderKey(provider, key, env = process.env) {
|
|
16809
|
-
const path = providerKeyStorePath(env);
|
|
16810
|
-
const store = { ...await readProviderKeyStore(env), [provider]: key };
|
|
16811
|
-
await mkdir8(dirname7(path), { recursive: true, mode: 448 });
|
|
16812
|
-
await writeFile5(path, `${JSON.stringify(store, null, 2)}
|
|
16813
|
-
`, { mode: 384 });
|
|
16814
|
-
await chmod2(path, 384);
|
|
16815
|
-
}
|
|
16816
|
-
function providerKeyFromEnvironment(provider, env = process.env) {
|
|
16817
|
-
const primary = env[PROVIDER_KEY_ENV_VARS[provider]]?.trim();
|
|
16818
|
-
if (primary)
|
|
16819
|
-
return primary;
|
|
16820
|
-
if (provider === "google")
|
|
16821
|
-
return env[GOOGLE_ENV_ALIAS]?.trim() || void 0;
|
|
16822
|
-
return void 0;
|
|
16823
|
-
}
|
|
16824
|
-
function maskProviderKey(key) {
|
|
16825
|
-
const trimmed = key.trim();
|
|
16826
|
-
if (trimmed.length <= 9)
|
|
16827
|
-
return "\u2026";
|
|
16828
|
-
return `${trimmed.slice(0, 6)}\u2026${trimmed.slice(-3)}`;
|
|
16829
|
-
}
|
|
16830
|
-
|
|
16831
17856
|
// apps/body/dist/connect-command.js
|
|
16832
17857
|
init_self_update();
|
|
16833
17858
|
init_self_update_manifest();
|
|
@@ -16874,14 +17899,32 @@ function brassEnabled(env, output) {
|
|
|
16874
17899
|
return "256";
|
|
16875
17900
|
return "plain";
|
|
16876
17901
|
}
|
|
17902
|
+
var BRASS_TRUECOLOR = "\x1B[38;2;215;175;95m";
|
|
17903
|
+
var BRASS_256 = "\x1B[38;5;179m";
|
|
16877
17904
|
function brass(value, env = process.env, output = stdout4) {
|
|
16878
17905
|
const level = brassEnabled(env, output);
|
|
16879
17906
|
if (level === "truecolor")
|
|
16880
|
-
return
|
|
17907
|
+
return `${BRASS_TRUECOLOR}${value}\x1B[39m`;
|
|
16881
17908
|
if (level === "256")
|
|
16882
|
-
return
|
|
17909
|
+
return `${BRASS_256}${value}\x1B[39m`;
|
|
16883
17910
|
return value;
|
|
16884
17911
|
}
|
|
17912
|
+
function brassRails(text2, env = process.env, output = stdout4) {
|
|
17913
|
+
const level = brassEnabled(env, output);
|
|
17914
|
+
if (level === "plain")
|
|
17915
|
+
return text2;
|
|
17916
|
+
return text2.replace(/\u001b\[(?:36|96)m/g, level === "truecolor" ? BRASS_TRUECOLOR : BRASS_256);
|
|
17917
|
+
}
|
|
17918
|
+
function paintWizardBrass(output = stdout4) {
|
|
17919
|
+
const original = output.write;
|
|
17920
|
+
const patched = function write(chunk, ...rest) {
|
|
17921
|
+
return original.call(this, typeof chunk === "string" ? brassRails(chunk, process.env, output) : chunk, ...rest);
|
|
17922
|
+
};
|
|
17923
|
+
output.write = patched;
|
|
17924
|
+
return () => {
|
|
17925
|
+
output.write = original;
|
|
17926
|
+
};
|
|
17927
|
+
}
|
|
16885
17928
|
var clackPrompts = {
|
|
16886
17929
|
async select(input) {
|
|
16887
17930
|
clackPromptOutput();
|
|
@@ -16906,12 +17949,12 @@ var clackPrompts = {
|
|
|
16906
17949
|
}), "Connection cancelled.");
|
|
16907
17950
|
}
|
|
16908
17951
|
};
|
|
16909
|
-
function connectModelPickerFromAxes(axes, fallbackModel, harness) {
|
|
17952
|
+
function connectModelPickerFromAxes(axes, fallbackModel, harness, rawAxes = axes) {
|
|
16910
17953
|
const filtered = axes.find((axis) => axis.category === "model");
|
|
16911
17954
|
if (filtered?.options.length) {
|
|
16912
17955
|
return { currentValue: filtered.currentValue, options: filtered.options };
|
|
16913
17956
|
}
|
|
16914
|
-
const raw =
|
|
17957
|
+
const raw = rawAxes.find((axis) => axis.category === "model" && axis.options.length);
|
|
16915
17958
|
if (raw) {
|
|
16916
17959
|
return { currentValue: raw.currentValue, options: raw.options };
|
|
16917
17960
|
}
|
|
@@ -16929,7 +17972,7 @@ async function loadConnectModelCatalog(input) {
|
|
|
16929
17972
|
...input.apiKey ? { apiKey: input.apiKey } : {},
|
|
16930
17973
|
model: defaultConnectModel(input.harness, input.provider)
|
|
16931
17974
|
}));
|
|
16932
|
-
return connectModelPickerFromAxes(catalog.catalog, defaultConnectModel(input.harness, input.provider), input.harness);
|
|
17975
|
+
return connectModelPickerFromAxes(catalog.catalog, defaultConnectModel(input.harness, input.provider), input.harness, catalog.raw);
|
|
16933
17976
|
}
|
|
16934
17977
|
async function collectConnectWizard(prompts = clackPrompts, loadModels = loadConnectModelCatalog, keyStore = fileConnectKeyStore, env = process.env, verifyKey = (input) => verifyProviderKey(input)) {
|
|
16935
17978
|
const harness = await prompts.select({
|
|
@@ -17006,25 +18049,11 @@ async function collectConnectWizard(prompts = clackPrompts, loadModels = loadCon
|
|
|
17006
18049
|
placeholder: "Type to filter available models\u2026",
|
|
17007
18050
|
maxItems: 12
|
|
17008
18051
|
});
|
|
17009
|
-
const name = await prompts.text({
|
|
17010
|
-
message: brass("Agent name"),
|
|
17011
|
-
validate: (value) => {
|
|
17012
|
-
if (!value.trim())
|
|
17013
|
-
return "Agent name is required";
|
|
17014
|
-
return isReasonableAgentName(value) ? void 0 : `Use letters, spaces, hyphens, or apostrophes (${AGENT_NAME_MAX_LENGTH} characters max)`;
|
|
17015
|
-
}
|
|
17016
|
-
});
|
|
17017
|
-
const soul = await prompts.text({
|
|
17018
|
-
message: brass("Input soul"),
|
|
17019
|
-
validate: (value) => value.trim() ? void 0 : "Soul is required"
|
|
17020
|
-
});
|
|
17021
18052
|
return {
|
|
17022
|
-
name: name.trim().replace(/\s+/g, " "),
|
|
17023
18053
|
harness,
|
|
17024
18054
|
...provider ? { provider } : {},
|
|
17025
18055
|
...apiKey ? { apiKey: apiKey.trim() } : {},
|
|
17026
|
-
model: model.trim()
|
|
17027
|
-
soul: soul.trim()
|
|
18056
|
+
model: model.trim()
|
|
17028
18057
|
};
|
|
17029
18058
|
}
|
|
17030
18059
|
async function jsonRequest(url, body, fetchImpl) {
|
|
@@ -17044,17 +18073,25 @@ function requestConnectGrant(baseUrl, pairingCode, selection, fetchImpl) {
|
|
|
17044
18073
|
const normalizedPairingCode = normalizeAgentPairingCode(pairingCode);
|
|
17045
18074
|
if (!normalizedPairingCode)
|
|
17046
18075
|
throw new Error("invalid pairing code");
|
|
17047
|
-
const avatarSeed =
|
|
18076
|
+
const avatarSeed = createHash4("sha256").update(normalizedPairingCode.toUpperCase()).digest("hex").slice(0, 32);
|
|
17048
18077
|
return jsonRequest(`${baseUrl}/auth/agent/connect`, {
|
|
17049
18078
|
pairing_code: normalizedPairingCode,
|
|
17050
18079
|
harness: selection.harness,
|
|
17051
18080
|
...selection.provider ? { provider: selection.provider } : {},
|
|
17052
18081
|
model: selection.model,
|
|
17053
|
-
|
|
17054
|
-
avatar_seed: avatarSeed,
|
|
17055
|
-
agent_name: selection.name
|
|
18082
|
+
avatar_seed: avatarSeed
|
|
17056
18083
|
}, fetchImpl);
|
|
17057
18084
|
}
|
|
18085
|
+
async function renameConnectedAgent(baseUrl, pairingCode, name, fetchImpl) {
|
|
18086
|
+
const normalizedPairingCode = normalizeAgentPairingCode(pairingCode);
|
|
18087
|
+
if (!normalizedPairingCode)
|
|
18088
|
+
throw new Error("invalid pairing code");
|
|
18089
|
+
const renamed = await jsonRequest(`${baseUrl}/auth/agent/connect/name`, { pairing_code: normalizedPairingCode, agent_name: name.trim().replace(/\s+/g, " ") }, fetchImpl);
|
|
18090
|
+
return renamed.agent_name;
|
|
18091
|
+
}
|
|
18092
|
+
function seededIdentityLine(grant) {
|
|
18093
|
+
return grant.agent_face ? `${grant.agent_name} the ${grant.agent_face}` : grant.agent_name;
|
|
18094
|
+
}
|
|
17058
18095
|
async function installCurrentRelease(fetchImpl) {
|
|
17059
18096
|
const manifestUrl = resolveManifestUrl(process.env);
|
|
17060
18097
|
const response = await fetchImpl(manifestUrl, { signal: AbortSignal.timeout(3e4) });
|
|
@@ -17069,7 +18106,7 @@ async function installCurrentRelease(fetchImpl) {
|
|
|
17069
18106
|
});
|
|
17070
18107
|
await activateRelease(layout, releaseId);
|
|
17071
18108
|
return {
|
|
17072
|
-
binary:
|
|
18109
|
+
binary: resolve19(layout.binDir, "beeline"),
|
|
17073
18110
|
version: published.version ?? releaseId
|
|
17074
18111
|
};
|
|
17075
18112
|
}
|
|
@@ -17092,8 +18129,8 @@ function providerEnvironment(selection) {
|
|
|
17092
18129
|
};
|
|
17093
18130
|
}
|
|
17094
18131
|
async function writePrivateJson(path, value) {
|
|
17095
|
-
await
|
|
17096
|
-
await
|
|
18132
|
+
await mkdir11(dirname10(path), { recursive: true, mode: 448 });
|
|
18133
|
+
await writeFile9(path, `${JSON.stringify(value, null, 2)}
|
|
17097
18134
|
`, { mode: 384 });
|
|
17098
18135
|
await chmod4(path, 384);
|
|
17099
18136
|
}
|
|
@@ -17101,10 +18138,10 @@ async function writeProviderEnv(selection, agentPubkey) {
|
|
|
17101
18138
|
const values = providerEnvironment(selection);
|
|
17102
18139
|
if (Object.keys(values).length === 0)
|
|
17103
18140
|
return void 0;
|
|
17104
|
-
const path =
|
|
17105
|
-
await
|
|
18141
|
+
const path = resolve19(defaultSupervisorRoot(process.env), "beeline", "connect", `${agentPubkey}.env`);
|
|
18142
|
+
await mkdir11(dirname10(path), { recursive: true, mode: 448 });
|
|
17106
18143
|
const contents = Object.entries(values).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join("\n");
|
|
17107
|
-
await
|
|
18144
|
+
await writeFile9(path, `${contents}
|
|
17108
18145
|
`, { mode: 384 });
|
|
17109
18146
|
await chmod4(path, 384);
|
|
17110
18147
|
return path;
|
|
@@ -17147,18 +18184,26 @@ async function runConnectCommand(code, options = {}) {
|
|
|
17147
18184
|
if (!stdin3.isTTY || !stdout4.isTTY) {
|
|
17148
18185
|
throw new Error("`usebeeline connect` needs an interactive terminal");
|
|
17149
18186
|
}
|
|
18187
|
+
const restoreRails = paintWizardBrass();
|
|
18188
|
+
try {
|
|
18189
|
+
await runConnectWizard(code, options.fetchImpl ?? fetch);
|
|
18190
|
+
} finally {
|
|
18191
|
+
restoreRails();
|
|
18192
|
+
}
|
|
18193
|
+
}
|
|
18194
|
+
async function runConnectWizard(code, fetchImpl) {
|
|
17150
18195
|
intro(brass("Beeline connect"));
|
|
17151
|
-
const fetchImpl = options.fetchImpl ?? fetch;
|
|
17152
18196
|
const pairingCode = code?.trim() || await clackPrompts.text({
|
|
17153
18197
|
message: brass("Pairing code from the app"),
|
|
17154
18198
|
validate: (value) => normalizeAgentPairingCode(value) ? void 0 : "Enter the pairing code shown in the app"
|
|
17155
18199
|
});
|
|
17156
18200
|
const selection = await collectConnectWizard(clackPrompts, loadConnectModelCatalog, fileConnectKeyStore, process.env, (input) => verifyProviderKey({ ...input, fetchImpl }));
|
|
17157
18201
|
const baseUrl = (process.env.BEELINE_AUTH_URL ?? "https://server.usebeeline.app").replace(/\/$/, "");
|
|
17158
|
-
const
|
|
18202
|
+
const claimed = await brassSpinner("Connecting to your Beeline Workspace\u2026", () => requestConnectGrant(baseUrl, pairingCode, selection, fetchImpl), (connectedGrant) => `Connected to ${connectedGrant.workspace_name}`);
|
|
18203
|
+
const grant = { ...claimed, agent_name: await confirmSeededName(baseUrl, pairingCode, claimed, fetchImpl) };
|
|
17159
18204
|
const installedRelease = await brassSpinner("Installing the Beeline daemon\u2026", () => installCurrentRelease(fetchImpl), (release) => `Installed Beeline helper ${release.version}`);
|
|
17160
18205
|
const llmEnvFile = await writeProviderEnv(selection, grant.agent_pubkey);
|
|
17161
|
-
const grantPath =
|
|
18206
|
+
const grantPath = resolve19(defaultSupervisorRoot(process.env), "beeline", "connect", `grant-${process.pid}-${Date.now()}.json`);
|
|
17162
18207
|
await writePrivateJson(grantPath, {
|
|
17163
18208
|
agentSecretKey: grant.agent_secret_key,
|
|
17164
18209
|
bodySecretKey: grant.body_secret_key,
|
|
@@ -17175,10 +18220,38 @@ async function runConnectCommand(code, options = {}) {
|
|
|
17175
18220
|
});
|
|
17176
18221
|
await brassSpinner("Starting your agent\u2026", () => runInstalledFinish(installedRelease.binary, grantPath), () => `Started ${grant.agent_name}`);
|
|
17177
18222
|
console.log("");
|
|
17178
|
-
console.log(`${brass("Agent")} ${grant
|
|
18223
|
+
console.log(`${brass("Agent")} ${seededIdentityLine(grant)}`);
|
|
17179
18224
|
console.log(`${brass("Workspace")} ${grant.workspace_name}`);
|
|
17180
18225
|
outro(brass("Say hi in the app."));
|
|
17181
18226
|
}
|
|
18227
|
+
async function confirmSeededName(baseUrl, pairingCode, grant, fetchImpl, prompts = clackPrompts) {
|
|
18228
|
+
log.info(brass(`Your agent is ${seededIdentityLine(grant)}.`));
|
|
18229
|
+
const choice = await prompts.select({
|
|
18230
|
+
message: brass("Name"),
|
|
18231
|
+
initialValue: "keep",
|
|
18232
|
+
options: [
|
|
18233
|
+
{ value: "keep", label: `Keep ${grant.agent_name}`, hint: "default" },
|
|
18234
|
+
{ value: "rename", label: "Rename this agent" }
|
|
18235
|
+
]
|
|
18236
|
+
});
|
|
18237
|
+
if (choice === "keep")
|
|
18238
|
+
return grant.agent_name;
|
|
18239
|
+
const renamed = await prompts.text({
|
|
18240
|
+
message: brass("Agent name"),
|
|
18241
|
+
initialValue: grant.agent_name,
|
|
18242
|
+
validate: (value) => {
|
|
18243
|
+
if (!value.trim())
|
|
18244
|
+
return "Agent name is required";
|
|
18245
|
+
return isReasonableAgentName(value) ? void 0 : `Use letters, spaces, hyphens, or apostrophes (${AGENT_NAME_MAX_LENGTH} characters max)`;
|
|
18246
|
+
}
|
|
18247
|
+
});
|
|
18248
|
+
try {
|
|
18249
|
+
return await renameConnectedAgent(baseUrl, pairingCode, renamed, fetchImpl);
|
|
18250
|
+
} catch (error) {
|
|
18251
|
+
log.warn(brass(`Could not rename this agent (${error instanceof Error ? error.message : String(error)}); it stays ${grant.agent_name}. Rename it in the app.`));
|
|
18252
|
+
return grant.agent_name;
|
|
18253
|
+
}
|
|
18254
|
+
}
|
|
17182
18255
|
function isDevicePairingGrant(value) {
|
|
17183
18256
|
if (!value || typeof value !== "object")
|
|
17184
18257
|
return false;
|
|
@@ -17209,11 +18282,22 @@ async function runConnectFinishCommand(path) {
|
|
|
17209
18282
|
throw new Error("connect-finish may run only from the canonical installed Beeline launcher");
|
|
17210
18283
|
}
|
|
17211
18284
|
try {
|
|
17212
|
-
const grant = JSON.parse(await
|
|
18285
|
+
const grant = JSON.parse(await readFile9(resolve19(path), "utf8"));
|
|
17213
18286
|
if (!isDevicePairingGrant(grant))
|
|
17214
18287
|
throw new Error("device connection grant is invalid");
|
|
17215
|
-
await completeDevicePairing(grant);
|
|
17216
|
-
await unlink2(
|
|
18288
|
+
const connected = await completeDevicePairing(grant);
|
|
18289
|
+
await unlink2(resolve19(path));
|
|
18290
|
+
const providerEnv = grant.llmEnvFile ? await readFile9(grant.llmEnvFile, "utf8").catch(() => "") : "";
|
|
18291
|
+
const model = openRouterModelId(grant.model, {
|
|
18292
|
+
OPENROUTER_API_KEY: /^OPENROUTER_API_KEY=\S/m.test(providerEnv) ? "set" : ""
|
|
18293
|
+
});
|
|
18294
|
+
if (model) {
|
|
18295
|
+
const decision2 = await resolveOpenRouterRouting({
|
|
18296
|
+
model,
|
|
18297
|
+
cacheDir: openRouterRoutingCacheDir(dirname10(connected.configPath))
|
|
18298
|
+
});
|
|
18299
|
+
console.log(decision2.line);
|
|
18300
|
+
}
|
|
17217
18301
|
} catch (error) {
|
|
17218
18302
|
throw new ConnectFailureError(connectPlainFailure(error));
|
|
17219
18303
|
}
|
|
@@ -17227,20 +18311,20 @@ init_self_update_manifest();
|
|
|
17227
18311
|
// apps/body/dist/managed-update.js
|
|
17228
18312
|
init_self_update();
|
|
17229
18313
|
import { spawn as spawn6 } from "node:child_process";
|
|
17230
|
-
import { mkdir as
|
|
17231
|
-
import { dirname as
|
|
18314
|
+
import { mkdir as mkdir13, rm as rm5, stat as stat2, writeFile as writeFile11 } from "node:fs/promises";
|
|
18315
|
+
import { dirname as dirname12, resolve as resolve21 } from "node:path";
|
|
17232
18316
|
|
|
17233
18317
|
// apps/body/dist/update-rollback-alert.js
|
|
17234
|
-
import { mkdir as
|
|
17235
|
-
import { dirname as
|
|
18318
|
+
import { mkdir as mkdir12, readFile as readFile10, rename as rename4, writeFile as writeFile10 } from "node:fs/promises";
|
|
18319
|
+
import { dirname as dirname11, resolve as resolve20 } from "node:path";
|
|
17236
18320
|
function updateRollbackAlertPath(runtimeDir) {
|
|
17237
|
-
return
|
|
18321
|
+
return resolve20(runtimeDir, "update-rollback-alert.json");
|
|
17238
18322
|
}
|
|
17239
18323
|
async function writeAlert(runtimeDir, alert) {
|
|
17240
18324
|
const path = updateRollbackAlertPath(runtimeDir);
|
|
17241
18325
|
const staged = `${path}.${process.pid}.tmp`;
|
|
17242
|
-
await
|
|
17243
|
-
await
|
|
18326
|
+
await mkdir12(dirname11(path), { recursive: true });
|
|
18327
|
+
await writeFile10(staged, `${JSON.stringify(alert, null, 2)}
|
|
17244
18328
|
`, { mode: 384 });
|
|
17245
18329
|
await rename4(staged, path);
|
|
17246
18330
|
}
|
|
@@ -17252,7 +18336,7 @@ async function queueUpdateRollbackAlert(runtimeDir, releaseId, now2 = Date.now()
|
|
|
17252
18336
|
}
|
|
17253
18337
|
async function readUpdateRollbackAlert(runtimeDir) {
|
|
17254
18338
|
try {
|
|
17255
|
-
const value = JSON.parse(await
|
|
18339
|
+
const value = JSON.parse(await readFile10(updateRollbackAlertPath(runtimeDir), "utf8"));
|
|
17256
18340
|
if (value.version !== 1 || typeof value.releaseId !== "string")
|
|
17257
18341
|
return void 0;
|
|
17258
18342
|
return value;
|
|
@@ -17277,13 +18361,13 @@ var LOCK_STALE_MS = UPDATE_WORKER_DEADLINE_MS + 5 * 6e4;
|
|
|
17277
18361
|
var DEFAULT_UPDATE_INITIAL_DELAY_MS = 0;
|
|
17278
18362
|
async function withInstallLock(layout, work, options = {}) {
|
|
17279
18363
|
const now2 = options.now ?? Date.now;
|
|
17280
|
-
const lock =
|
|
18364
|
+
const lock = resolve21(layout.releasesRoot, ".state", "install.lock");
|
|
17281
18365
|
const deadline = now2() + (options.waitMs ?? 1e4);
|
|
17282
|
-
await
|
|
18366
|
+
await mkdir13(dirname12(lock), { recursive: true });
|
|
17283
18367
|
for (; ; ) {
|
|
17284
18368
|
try {
|
|
17285
|
-
await
|
|
17286
|
-
await
|
|
18369
|
+
await mkdir13(lock);
|
|
18370
|
+
await writeFile11(resolve21(lock, "owner"), `${process.pid}
|
|
17287
18371
|
${now2()}
|
|
17288
18372
|
`, "utf8");
|
|
17289
18373
|
break;
|
|
@@ -17320,7 +18404,6 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
|
|
|
17320
18404
|
#requested = false;
|
|
17321
18405
|
#restartRequest;
|
|
17322
18406
|
#stagedReleaseId;
|
|
17323
|
-
#drainDeadlineLogged = false;
|
|
17324
18407
|
constructor(options) {
|
|
17325
18408
|
this.#layout = options.layout;
|
|
17326
18409
|
this.#loadedRelease = options.loadedRelease;
|
|
@@ -17407,7 +18490,7 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
|
|
|
17407
18490
|
if (!attempt || attempt.releaseId !== desiredRelease || attempt.status !== "pending") {
|
|
17408
18491
|
const from = await readInstalledBundleIdentity({
|
|
17409
18492
|
...this.#layout,
|
|
17410
|
-
libDir:
|
|
18493
|
+
libDir: resolve21(this.#layout.releasesRoot, this.#loadedRelease)
|
|
17411
18494
|
}).catch(() => void 0) ?? {};
|
|
17412
18495
|
const to = await readInstalledBundleIdentity(this.#layout).catch(() => void 0) ?? {};
|
|
17413
18496
|
const record2 = {
|
|
@@ -17444,11 +18527,13 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
|
|
|
17444
18527
|
}
|
|
17445
18528
|
/**
|
|
17446
18529
|
* Resolve one handoff tick against the daemon's authoritative turn registry.
|
|
17447
|
-
*
|
|
17448
|
-
* `quiesceIfIdle` closes intake in the same
|
|
17449
|
-
* proves idle, so a new turn cannot race
|
|
17450
|
-
*
|
|
17451
|
-
*
|
|
18530
|
+
* Busy means a turn is executing right now; serving Rooms and corners with
|
|
18531
|
+
* no turn in flight is idle. `quiesceIfIdle` closes intake in the same
|
|
18532
|
+
* synchronous transition that proves idle, so a new turn cannot race
|
|
18533
|
+
* activation. A staged release stays inert while a turn runs — until the
|
|
18534
|
+
* absolute drain deadline, when the restart is forced (`forced: true`) and
|
|
18535
|
+
* the caller cancels the running turn; the convergence contract outranks a
|
|
18536
|
+
* stuck turn. `ManagedUpdateDrain` enforces that deadline with a timer.
|
|
17452
18537
|
*/
|
|
17453
18538
|
async restartRequest(quiesceIfIdle) {
|
|
17454
18539
|
const activeDrift = await this.check();
|
|
@@ -17457,12 +18542,11 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
|
|
|
17457
18542
|
const request = this.#restartRequest;
|
|
17458
18543
|
if (!request)
|
|
17459
18544
|
throw new Error("update drift was detected without an in-memory restart request");
|
|
18545
|
+
let forced = false;
|
|
17460
18546
|
if (!quiesceIfIdle()) {
|
|
17461
|
-
if (this.#now()
|
|
17462
|
-
|
|
17463
|
-
|
|
17464
|
-
}
|
|
17465
|
-
return { kind: "waiting", request };
|
|
18547
|
+
if (this.#now() < request.drainDeadlineAt)
|
|
18548
|
+
return { kind: "waiting", request };
|
|
18549
|
+
forced = true;
|
|
17466
18550
|
}
|
|
17467
18551
|
if (this.#stagedReleaseId) {
|
|
17468
18552
|
const stagedReleaseId = this.#stagedReleaseId;
|
|
@@ -17482,7 +18566,7 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
|
|
|
17482
18566
|
this.#restartRequest = void 0;
|
|
17483
18567
|
await this.#journalDrift(stagedReleaseId);
|
|
17484
18568
|
}
|
|
17485
|
-
return { kind: "restart", request: this.#restartRequest ?? request };
|
|
18569
|
+
return { kind: "restart", request: this.#restartRequest ?? request, forced };
|
|
17486
18570
|
}
|
|
17487
18571
|
};
|
|
17488
18572
|
async function coordinateManagedUpdateHandoff(update, quiesceIfIdle, restart, waiting = async () => void 0) {
|
|
@@ -17493,9 +18577,88 @@ async function coordinateManagedUpdateHandoff(update, quiesceIfIdle, restart, wa
|
|
|
17493
18577
|
await waiting(next.request);
|
|
17494
18578
|
return "waiting-for-idle";
|
|
17495
18579
|
}
|
|
17496
|
-
await restart(next.request);
|
|
18580
|
+
await restart(next.request, next.forced ? "forced" : "drained");
|
|
17497
18581
|
return "restarting";
|
|
17498
18582
|
}
|
|
18583
|
+
var UPDATE_DRAIN_WAIT_LOG_INTERVAL_MS = 6e4;
|
|
18584
|
+
var ManagedUpdateDrain = class {
|
|
18585
|
+
#options;
|
|
18586
|
+
#now;
|
|
18587
|
+
#log;
|
|
18588
|
+
#setTimer;
|
|
18589
|
+
#clearTimer;
|
|
18590
|
+
#deadlineTimer;
|
|
18591
|
+
#waitLogTimer;
|
|
18592
|
+
#resolving;
|
|
18593
|
+
#restarting = false;
|
|
18594
|
+
constructor(options) {
|
|
18595
|
+
this.#options = options;
|
|
18596
|
+
this.#now = options.now ?? Date.now;
|
|
18597
|
+
this.#log = options.log ?? ((line) => console.log(line));
|
|
18598
|
+
this.#setTimer = options.setTimer ?? ((callback, ms) => setTimeout(callback, ms));
|
|
18599
|
+
this.#clearTimer = options.clearTimer ?? ((handle) => clearTimeout(handle));
|
|
18600
|
+
}
|
|
18601
|
+
/** Called from every completed core progress tick. */
|
|
18602
|
+
tick() {
|
|
18603
|
+
return this.#resolve();
|
|
18604
|
+
}
|
|
18605
|
+
#resolve() {
|
|
18606
|
+
if (this.#restarting)
|
|
18607
|
+
return Promise.resolve("restarting");
|
|
18608
|
+
if (this.#resolving)
|
|
18609
|
+
return this.#resolving;
|
|
18610
|
+
this.#resolving = this.#step().finally(() => {
|
|
18611
|
+
this.#resolving = void 0;
|
|
18612
|
+
});
|
|
18613
|
+
return this.#resolving;
|
|
18614
|
+
}
|
|
18615
|
+
async #step() {
|
|
18616
|
+
const { update, quiesceIfIdle, activeTurnCount } = this.#options;
|
|
18617
|
+
let armed;
|
|
18618
|
+
const progress = await coordinateManagedUpdateHandoff(update, quiesceIfIdle, async (request, mode) => {
|
|
18619
|
+
this.#disarm();
|
|
18620
|
+
if (mode === "forced") {
|
|
18621
|
+
this.#log(`[thin-core] update restart forced: drain deadline reached with ${activeTurnCount()} active turn(s); cancelling them and restarting onto ${request.desiredRelease}`);
|
|
18622
|
+
}
|
|
18623
|
+
await this.#options.restart(request, mode);
|
|
18624
|
+
this.#restarting = true;
|
|
18625
|
+
}, async (request) => {
|
|
18626
|
+
armed = request;
|
|
18627
|
+
await this.#options.waiting?.(request);
|
|
18628
|
+
});
|
|
18629
|
+
if (armed && !this.#deadlineTimer)
|
|
18630
|
+
this.#arm(armed);
|
|
18631
|
+
return progress;
|
|
18632
|
+
}
|
|
18633
|
+
#arm(request) {
|
|
18634
|
+
this.#logWaiting(request);
|
|
18635
|
+
this.#scheduleWaitLog(request);
|
|
18636
|
+
this.#deadlineTimer = this.#setTimer(() => void this.#resolve().catch((error) => {
|
|
18637
|
+
this.#log(`[thin-core] update restart at the drain deadline failed; retrying on the next tick: ${error instanceof Error ? error.message : String(error)}`);
|
|
18638
|
+
}), Math.max(0, request.drainDeadlineAt - this.#now()));
|
|
18639
|
+
}
|
|
18640
|
+
#scheduleWaitLog(request) {
|
|
18641
|
+
this.#waitLogTimer = this.#setTimer(() => {
|
|
18642
|
+
this.#waitLogTimer = void 0;
|
|
18643
|
+
if (this.#restarting)
|
|
18644
|
+
return;
|
|
18645
|
+
this.#logWaiting(request);
|
|
18646
|
+
this.#scheduleWaitLog(request);
|
|
18647
|
+
}, UPDATE_DRAIN_WAIT_LOG_INTERVAL_MS);
|
|
18648
|
+
}
|
|
18649
|
+
#logWaiting(request) {
|
|
18650
|
+
const minutesLeft = Math.max(0, Math.ceil((request.drainDeadlineAt - this.#now()) / 6e4));
|
|
18651
|
+
this.#log(`[thin-core] update restart waiting: ${this.#options.activeTurnCount()} active turn(s); deadline in ${minutesLeft}m`);
|
|
18652
|
+
}
|
|
18653
|
+
#disarm() {
|
|
18654
|
+
if (this.#deadlineTimer !== void 0)
|
|
18655
|
+
this.#clearTimer(this.#deadlineTimer);
|
|
18656
|
+
if (this.#waitLogTimer !== void 0)
|
|
18657
|
+
this.#clearTimer(this.#waitLogTimer);
|
|
18658
|
+
this.#deadlineTimer = void 0;
|
|
18659
|
+
this.#waitLogTimer = void 0;
|
|
18660
|
+
}
|
|
18661
|
+
};
|
|
17499
18662
|
function numberEnv(env, name, fallback) {
|
|
17500
18663
|
const value = Number(env[name]);
|
|
17501
18664
|
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
@@ -17614,7 +18777,7 @@ async function proveLoadedReleaseReady(layout, runtimeDir, loadedRelease, option
|
|
|
17614
18777
|
});
|
|
17615
18778
|
if (!accepted)
|
|
17616
18779
|
return false;
|
|
17617
|
-
await
|
|
18780
|
+
await writeFile11(resolve21(runtimeDir, "daemon-ready.json"), `${JSON.stringify({
|
|
17618
18781
|
readyAt: (options.now ?? Date.now)(),
|
|
17619
18782
|
loadedRelease,
|
|
17620
18783
|
functionalProof: options.functionalProof
|
|
@@ -17819,16 +18982,16 @@ async function runUpdateCommand(args) {
|
|
|
17819
18982
|
init_self_update();
|
|
17820
18983
|
|
|
17821
18984
|
// apps/body/dist/daemon-failure.js
|
|
17822
|
-
import { mkdir as
|
|
17823
|
-
import { dirname as
|
|
18985
|
+
import { mkdir as mkdir14, readFile as readFile11, rename as rename5, rm as rm6, writeFile as writeFile12 } from "node:fs/promises";
|
|
18986
|
+
import { dirname as dirname13, resolve as resolve22 } from "node:path";
|
|
17824
18987
|
var DAEMON_FAILURE_LIMIT = 3;
|
|
17825
18988
|
var DAEMON_FAILURE_WINDOW_MS = 5 * 6e4;
|
|
17826
18989
|
function daemonFailurePath(runtimeDir) {
|
|
17827
|
-
return
|
|
18990
|
+
return resolve22(runtimeDir, "daemon-distress.json");
|
|
17828
18991
|
}
|
|
17829
18992
|
async function readFailureRecord(runtimeDir) {
|
|
17830
18993
|
try {
|
|
17831
|
-
const value = JSON.parse(await
|
|
18994
|
+
const value = JSON.parse(await readFile11(daemonFailurePath(runtimeDir), "utf8"));
|
|
17832
18995
|
if (value.version !== 1 || !Array.isArray(value.failures) || value.failures.some((failure) => typeof failure !== "number") || typeof value.lastError !== "string") {
|
|
17833
18996
|
return void 0;
|
|
17834
18997
|
}
|
|
@@ -17840,8 +19003,8 @@ async function readFailureRecord(runtimeDir) {
|
|
|
17840
19003
|
async function writeFailureRecord(runtimeDir, record2) {
|
|
17841
19004
|
const path = daemonFailurePath(runtimeDir);
|
|
17842
19005
|
const staged = `${path}.${process.pid}.tmp`;
|
|
17843
|
-
await
|
|
17844
|
-
await
|
|
19006
|
+
await mkdir14(dirname13(path), { recursive: true, mode: 448 });
|
|
19007
|
+
await writeFile12(staged, `${JSON.stringify(record2, null, 2)}
|
|
17845
19008
|
`, { mode: 384 });
|
|
17846
19009
|
await rename5(staged, path);
|
|
17847
19010
|
}
|
|
@@ -17863,21 +19026,45 @@ async function clearDaemonStartFailures(runtimeDir) {
|
|
|
17863
19026
|
}
|
|
17864
19027
|
|
|
17865
19028
|
// apps/body/dist/update-functional-probe.js
|
|
17866
|
-
import { mkdir as
|
|
19029
|
+
import { mkdir as mkdir15, rm as rm7 } from "node:fs/promises";
|
|
17867
19030
|
import { homedir as homedir10 } from "node:os";
|
|
17868
|
-
import { resolve as
|
|
19031
|
+
import { resolve as resolve23 } from "node:path";
|
|
17869
19032
|
var UPDATE_PROBE_SESSION_TIMEOUT_MS = 1e4;
|
|
17870
19033
|
var UPDATE_PROBE_SESSION_OPEN_TIMEOUT_MS = 2e4;
|
|
17871
19034
|
var UPDATE_PROBE_TURN_TIMEOUT_MS = 45e3;
|
|
17872
19035
|
var UpdateFunctionalProbeError = class extends Error {
|
|
17873
19036
|
reason;
|
|
17874
19037
|
code = "BEELINE_UPDATE_FUNCTIONAL_PROBE_FAILED";
|
|
19038
|
+
/** Present when the turn failed on a provider refusal with an HTTP status. */
|
|
19039
|
+
providerRefusal;
|
|
17875
19040
|
constructor(reason, detail, options) {
|
|
17876
19041
|
super(`functional update probe failed (${reason}): ${detail}`, options);
|
|
17877
19042
|
this.reason = reason;
|
|
17878
19043
|
this.name = "UpdateFunctionalProbeError";
|
|
19044
|
+
this.providerRefusal = options?.providerRefusal;
|
|
17879
19045
|
}
|
|
17880
19046
|
};
|
|
19047
|
+
async function probeOutcome(run2) {
|
|
19048
|
+
try {
|
|
19049
|
+
await run2();
|
|
19050
|
+
return { kind: "served" };
|
|
19051
|
+
} catch (error) {
|
|
19052
|
+
if (error instanceof UpdateFunctionalProbeError && error.providerRefusal) {
|
|
19053
|
+
return { kind: "refused", ...error.providerRefusal };
|
|
19054
|
+
}
|
|
19055
|
+
return { kind: "unavailable", reason: error instanceof Error ? error.message : String(error) };
|
|
19056
|
+
}
|
|
19057
|
+
}
|
|
19058
|
+
function describeCurrentReleaseOutcome(outcome) {
|
|
19059
|
+
switch (outcome.kind) {
|
|
19060
|
+
case "served":
|
|
19061
|
+
return "the current release answered";
|
|
19062
|
+
case "refused":
|
|
19063
|
+
return `the current release got a different refusal (${outcome.reason})`;
|
|
19064
|
+
case "unavailable":
|
|
19065
|
+
return `the current release could not be compared (${outcome.reason})`;
|
|
19066
|
+
}
|
|
19067
|
+
}
|
|
17881
19068
|
async function runUpdateFunctionalProbe(input) {
|
|
17882
19069
|
const command = input.config.agentCommand ?? input.config.agentBinary;
|
|
17883
19070
|
const harness = input.config.agentKind ?? command;
|
|
@@ -17887,11 +19074,11 @@ async function runUpdateFunctionalProbe(input) {
|
|
|
17887
19074
|
if (input.sandboxRequired && !input.config.bwrapPath) {
|
|
17888
19075
|
throw new UpdateFunctionalProbeError("sandbox-unavailable", "the configured bubblewrap boundary did not pass its startup self-test");
|
|
17889
19076
|
}
|
|
17890
|
-
const root =
|
|
17891
|
-
const cwd =
|
|
17892
|
-
const homeRoot =
|
|
19077
|
+
const root = input.probeRoot ?? resolve23(input.runtimeDir, "update-functional-probe");
|
|
19078
|
+
const cwd = resolve23(root, "checkout");
|
|
19079
|
+
const homeRoot = resolve23(root, "agent-home");
|
|
17893
19080
|
await rm7(root, { recursive: true, force: true });
|
|
17894
|
-
await
|
|
19081
|
+
await mkdir15(cwd, { recursive: true, mode: 448 });
|
|
17895
19082
|
let client;
|
|
17896
19083
|
try {
|
|
17897
19084
|
const agentEnv = {
|
|
@@ -17901,7 +19088,8 @@ async function runUpdateFunctionalProbe(input) {
|
|
|
17901
19088
|
operatorHome: input.config.operatorHome ?? homedir10(),
|
|
17902
19089
|
sharedSkills: input.config.sharedSkills ?? [],
|
|
17903
19090
|
skillReleaseId: input.releaseId,
|
|
17904
|
-
failClosed: true
|
|
19091
|
+
failClosed: true,
|
|
19092
|
+
...openRouterRoutingInput({ ...input.config, openRouterRoutingCacheDir: openRouterRoutingCacheDir(input.runtimeDir) }, input.config.modelSelection)
|
|
17905
19093
|
})
|
|
17906
19094
|
};
|
|
17907
19095
|
const selectedAgent = {
|
|
@@ -17913,11 +19101,12 @@ async function runUpdateFunctionalProbe(input) {
|
|
|
17913
19101
|
command,
|
|
17914
19102
|
args: agentArgsWithModelSelection(selectedAgent, input.config.modelSelection)
|
|
17915
19103
|
};
|
|
19104
|
+
let modelAnswer = {};
|
|
17916
19105
|
if (input.config.bwrapPath) {
|
|
17917
19106
|
const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
|
|
17918
19107
|
const operatorHome = input.config.operatorHome ?? homedir10();
|
|
17919
19108
|
const homeStateDirs = harnessHomeStateDirs(command, agentEnv.HOME ?? operatorHome);
|
|
17920
|
-
await Promise.all(homeStateDirs.map((dir) =>
|
|
19109
|
+
await Promise.all(homeStateDirs.map((dir) => mkdir15(dir, { recursive: true })));
|
|
17921
19110
|
spawnCommand = wrapAgentCommand({
|
|
17922
19111
|
bwrapPath: input.config.bwrapPath,
|
|
17923
19112
|
spec: {
|
|
@@ -17956,10 +19145,39 @@ async function runUpdateFunctionalProbe(input) {
|
|
|
17956
19145
|
}
|
|
17957
19146
|
try {
|
|
17958
19147
|
const served = await client.sessionPrompt(opened.sessionId, "Reply READY.", input.turnTimeoutMs ?? UPDATE_PROBE_TURN_TIMEOUT_MS);
|
|
17959
|
-
if (
|
|
17960
|
-
|
|
19148
|
+
if (served.agentText.trim()) {
|
|
19149
|
+
modelAnswer = { modelAnswer: "served" };
|
|
19150
|
+
} else {
|
|
19151
|
+
const explained = await explainEmptyAgentTurn({
|
|
19152
|
+
agentLabel: command,
|
|
19153
|
+
agentEnv,
|
|
19154
|
+
sessionId: opened.sessionId,
|
|
19155
|
+
result: served
|
|
19156
|
+
});
|
|
19157
|
+
const modelSide = isAccountOrProviderRefusal(explained.record) || explained.record?.kind === "empty";
|
|
19158
|
+
if (!modelSide) {
|
|
19159
|
+
const refusal = explained.record?.kind === "error" && explained.record.status !== void 0 ? { status: explained.record.status, reason: explained.record.reason } : void 0;
|
|
19160
|
+
const detail = `the harness completed a session/prompt without an agent answer: ${explained.reason}`;
|
|
19161
|
+
if (!refusal || !input.compareWithCurrentRelease) {
|
|
19162
|
+
throw new UpdateFunctionalProbeError("turn-failed", detail, {
|
|
19163
|
+
...refusal ? { providerRefusal: refusal } : {}
|
|
19164
|
+
});
|
|
19165
|
+
}
|
|
19166
|
+
const current = await input.compareWithCurrentRelease(refusal);
|
|
19167
|
+
if (current.kind !== "refused" || current.status !== refusal.status) {
|
|
19168
|
+
throw new UpdateFunctionalProbeError("turn-failed", `${detail}; ${describeCurrentReleaseOutcome(current)}`, { providerRefusal: refusal });
|
|
19169
|
+
}
|
|
19170
|
+
const reason = `${explained.reason} (the current release gets the same ${refusal.status})`;
|
|
19171
|
+
console.warn(`[body] update probe: the provider refused this release and the current release alike (${refusal.reason}); that refusal is not this bundle's doing, so the probe passes as inconclusive`);
|
|
19172
|
+
modelAnswer = { modelAnswer: "unavailable", modelAnswerReason: reason };
|
|
19173
|
+
} else {
|
|
19174
|
+
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`);
|
|
19175
|
+
modelAnswer = { modelAnswer: "unavailable", modelAnswerReason: explained.reason };
|
|
19176
|
+
}
|
|
17961
19177
|
}
|
|
17962
19178
|
} catch (error) {
|
|
19179
|
+
if (error instanceof UpdateFunctionalProbeError)
|
|
19180
|
+
throw error;
|
|
17963
19181
|
throw new UpdateFunctionalProbeError("turn-failed", error instanceof Error ? error.message : String(error), { cause: error });
|
|
17964
19182
|
}
|
|
17965
19183
|
} catch (error) {
|
|
@@ -17972,7 +19190,8 @@ async function runUpdateFunctionalProbe(input) {
|
|
|
17972
19190
|
sandboxed: Boolean(input.config.bwrapPath),
|
|
17973
19191
|
sessionStarted: true,
|
|
17974
19192
|
turnCompleted: true,
|
|
17975
|
-
nativeTools: []
|
|
19193
|
+
nativeTools: [],
|
|
19194
|
+
...modelAnswer
|
|
17976
19195
|
};
|
|
17977
19196
|
} finally {
|
|
17978
19197
|
await client?.stop().catch(() => void 0);
|
|
@@ -17980,9 +19199,140 @@ async function runUpdateFunctionalProbe(input) {
|
|
|
17980
19199
|
}
|
|
17981
19200
|
}
|
|
17982
19201
|
|
|
19202
|
+
// apps/body/dist/current-release-probe.js
|
|
19203
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
19204
|
+
import { dirname as dirname14, join as join7 } from "node:path";
|
|
19205
|
+
init_self_update();
|
|
19206
|
+
var CURRENT_RELEASE_PROBE_TIMEOUT_MS = 8e4;
|
|
19207
|
+
var UPDATE_PROBE_COMMAND = "update-probe";
|
|
19208
|
+
function parseReport(line) {
|
|
19209
|
+
let parsed;
|
|
19210
|
+
try {
|
|
19211
|
+
parsed = JSON.parse(line);
|
|
19212
|
+
} catch {
|
|
19213
|
+
return void 0;
|
|
19214
|
+
}
|
|
19215
|
+
if (!parsed || typeof parsed !== "object")
|
|
19216
|
+
return void 0;
|
|
19217
|
+
const report = parsed;
|
|
19218
|
+
if (report.probe === "served")
|
|
19219
|
+
return { probe: "served" };
|
|
19220
|
+
if (report.probe === "refused" && typeof report.status === "number" && typeof report.reason === "string") {
|
|
19221
|
+
return { probe: "refused", status: report.status, reason: report.reason };
|
|
19222
|
+
}
|
|
19223
|
+
if (report.probe === "failed" && typeof report.reason === "string") {
|
|
19224
|
+
return { probe: "failed", reason: report.reason };
|
|
19225
|
+
}
|
|
19226
|
+
return void 0;
|
|
19227
|
+
}
|
|
19228
|
+
function outcomeFromReport(report) {
|
|
19229
|
+
switch (report.probe) {
|
|
19230
|
+
case "served":
|
|
19231
|
+
return { kind: "served" };
|
|
19232
|
+
case "refused":
|
|
19233
|
+
return { kind: "refused", status: report.status, reason: report.reason };
|
|
19234
|
+
case "failed":
|
|
19235
|
+
return { kind: "unavailable", reason: report.reason };
|
|
19236
|
+
}
|
|
19237
|
+
}
|
|
19238
|
+
async function probeReleaseInSubprocess(input) {
|
|
19239
|
+
const bundleDir = join7(input.layout.releasesRoot, input.releaseId);
|
|
19240
|
+
const entrypoint = await resolveBundleEntrypoint(bundleDir);
|
|
19241
|
+
if (!entrypoint) {
|
|
19242
|
+
return { kind: "unavailable", reason: `release ${input.releaseId} has no runnable CLI entrypoint` };
|
|
19243
|
+
}
|
|
19244
|
+
const timeoutMs = input.timeoutMs ?? CURRENT_RELEASE_PROBE_TIMEOUT_MS;
|
|
19245
|
+
return new Promise((resolve26) => {
|
|
19246
|
+
const child = spawn7(input.execPath ?? process.execPath, [entrypoint, UPDATE_PROBE_COMMAND, "--config", input.runtimeConfigPath], { env: input.env ?? process.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
19247
|
+
let stdout6 = "";
|
|
19248
|
+
let stderr = "";
|
|
19249
|
+
let settled = false;
|
|
19250
|
+
const finish = (outcome) => {
|
|
19251
|
+
if (settled)
|
|
19252
|
+
return;
|
|
19253
|
+
settled = true;
|
|
19254
|
+
clearTimeout(timer);
|
|
19255
|
+
resolve26(outcome);
|
|
19256
|
+
};
|
|
19257
|
+
const timer = setTimeout(() => {
|
|
19258
|
+
child.kill("SIGKILL");
|
|
19259
|
+
finish({
|
|
19260
|
+
kind: "unavailable",
|
|
19261
|
+
reason: `release ${input.releaseId} did not finish its probe within ${timeoutMs}ms`
|
|
19262
|
+
});
|
|
19263
|
+
}, timeoutMs);
|
|
19264
|
+
child.stdout.on("data", (chunk) => {
|
|
19265
|
+
stdout6 += chunk.toString();
|
|
19266
|
+
});
|
|
19267
|
+
child.stderr.on("data", (chunk) => {
|
|
19268
|
+
stderr += chunk.toString();
|
|
19269
|
+
});
|
|
19270
|
+
child.on("error", (error) => {
|
|
19271
|
+
finish({ kind: "unavailable", reason: `release ${input.releaseId} could not be spawned: ${error.message}` });
|
|
19272
|
+
});
|
|
19273
|
+
child.on("close", (code, signal) => {
|
|
19274
|
+
const lines = stdout6.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
19275
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
19276
|
+
const report = parseReport(lines[index]);
|
|
19277
|
+
if (report) {
|
|
19278
|
+
finish(outcomeFromReport(report));
|
|
19279
|
+
return;
|
|
19280
|
+
}
|
|
19281
|
+
}
|
|
19282
|
+
const tail = stderr.trim().split(/\r?\n/).slice(-3).join(" | ").slice(0, 300);
|
|
19283
|
+
finish({
|
|
19284
|
+
kind: "unavailable",
|
|
19285
|
+
reason: `release ${input.releaseId} printed no probe report (exit ${signal ?? code})` + (tail ? `: ${tail}` : "")
|
|
19286
|
+
});
|
|
19287
|
+
});
|
|
19288
|
+
});
|
|
19289
|
+
}
|
|
19290
|
+
async function runUpdateProbeCommand(args, options = {}) {
|
|
19291
|
+
const env = options.env ?? process.env;
|
|
19292
|
+
const write = options.write ?? ((line) => process.stdout.write(`${line}
|
|
19293
|
+
`));
|
|
19294
|
+
const configFlag = args.indexOf("--config");
|
|
19295
|
+
const configArg = configFlag >= 0 ? args[configFlag + 1] : void 0;
|
|
19296
|
+
if (!configArg)
|
|
19297
|
+
throw new Error(`${UPDATE_PROBE_COMMAND} requires --config <runtime.json>`);
|
|
19298
|
+
const configPath = await resolveRuntimeConfigPath(configArg);
|
|
19299
|
+
const runtime = await readRuntimeRecord(configPath);
|
|
19300
|
+
const agent = runtimeAgentCommand(runtime);
|
|
19301
|
+
const config = loadBodyConfig({
|
|
19302
|
+
workspaceRoot: join7(dirname14(configPath), "workspace"),
|
|
19303
|
+
llmEnvFile: runtime.llmEnvFile,
|
|
19304
|
+
env: { ...env, BUZZ_AGENT_BIN: agent.command, BUZZ_DEV_MCP_BIN: runtime.mcpBinary },
|
|
19305
|
+
agent
|
|
19306
|
+
});
|
|
19307
|
+
if (runtime.sharedSkills)
|
|
19308
|
+
config.sharedSkills = [...runtime.sharedSkills];
|
|
19309
|
+
if (runtime.modelSelection)
|
|
19310
|
+
config.modelSelection = runtime.modelSelection;
|
|
19311
|
+
config.runtimeConfigPath = configPath;
|
|
19312
|
+
const sandbox = detectBwrapSandbox({ ...runtime.sandbox ? { policy: runtime.sandbox } : {}, env });
|
|
19313
|
+
if (sandbox.path)
|
|
19314
|
+
config.bwrapPath = sandbox.path;
|
|
19315
|
+
if (runtime.sandboxMaskPaths?.length) {
|
|
19316
|
+
config.sandboxMaskPaths = [...config.sandboxMaskPaths ?? [], ...runtime.sandboxMaskPaths];
|
|
19317
|
+
}
|
|
19318
|
+
const layout = beelineInstallLayout(env);
|
|
19319
|
+
const releaseId = (layout && await activeReleaseId(layout).catch(() => void 0)) ?? "unknown";
|
|
19320
|
+
const runtimeDir = dirname14(configPath);
|
|
19321
|
+
const outcome = await probeOutcome(() => (options.probe ?? runUpdateFunctionalProbe)({
|
|
19322
|
+
config,
|
|
19323
|
+
runtimeDir,
|
|
19324
|
+
releaseId,
|
|
19325
|
+
sandboxRequired: runtime.sandbox !== "off",
|
|
19326
|
+
// The successor's probe still holds `<runtimeDir>/update-functional-probe`.
|
|
19327
|
+
probeRoot: join7(runtimeDir, "current-release-probe")
|
|
19328
|
+
}));
|
|
19329
|
+
const report = outcome.kind === "served" ? { probe: "served" } : outcome.kind === "refused" ? { probe: "refused", status: outcome.status, reason: outcome.reason } : { probe: "failed", reason: outcome.reason };
|
|
19330
|
+
write(JSON.stringify(report));
|
|
19331
|
+
}
|
|
19332
|
+
|
|
17983
19333
|
// apps/body/dist/release-status.js
|
|
17984
|
-
import { readFile as
|
|
17985
|
-
import { resolve as
|
|
19334
|
+
import { readFile as readFile12, readdir as readdir4, rename as rename6, writeFile as writeFile13 } from "node:fs/promises";
|
|
19335
|
+
import { resolve as resolve24 } from "node:path";
|
|
17986
19336
|
var DAEMON_RELEASE_STATUS_FILE = "release-status.json";
|
|
17987
19337
|
var RELEASE_VERSION = /^v\d+\.\d+\.\d+$/;
|
|
17988
19338
|
var SOURCE_SHA = /^[0-9a-f]{7,64}$/;
|
|
@@ -17999,9 +19349,9 @@ async function writeDaemonReleaseStatus(runtimeDir, agentPubkey, identity, optio
|
|
|
17999
19349
|
pid: options.pid ?? process.pid,
|
|
18000
19350
|
readyAt: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
|
|
18001
19351
|
};
|
|
18002
|
-
const target =
|
|
19352
|
+
const target = resolve24(runtimeDir, DAEMON_RELEASE_STATUS_FILE);
|
|
18003
19353
|
const temporary = `${target}.${status.pid}.tmp`;
|
|
18004
|
-
await
|
|
19354
|
+
await writeFile13(temporary, `${JSON.stringify(status, null, 2)}
|
|
18005
19355
|
`, { mode: 384 });
|
|
18006
19356
|
await rename6(temporary, target);
|
|
18007
19357
|
return status;
|
|
@@ -18042,7 +19392,7 @@ var DaemonExitError = class extends Error {
|
|
|
18042
19392
|
};
|
|
18043
19393
|
async function runStoredDaemon(pathOrPointer) {
|
|
18044
19394
|
const configPath = await resolveRuntimeConfigPath(pathOrPointer);
|
|
18045
|
-
daemonFailureRuntimeDir =
|
|
19395
|
+
daemonFailureRuntimeDir = dirname15(configPath);
|
|
18046
19396
|
const accessMigration = await migrateRuntimeRecordAccessPolicy(configPath);
|
|
18047
19397
|
let runtime = accessMigration.runtime;
|
|
18048
19398
|
if (!runtime.transport) {
|
|
@@ -18054,7 +19404,7 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
18054
19404
|
runtime = activated.runtime;
|
|
18055
19405
|
const daemonApi = activated.client;
|
|
18056
19406
|
const agent = runtimeAgentCommand(runtime);
|
|
18057
|
-
await
|
|
19407
|
+
await writeFile14(resolve25(dirname15(configPath), "daemon.pid"), `${process.pid}
|
|
18058
19408
|
`, { mode: 384 });
|
|
18059
19409
|
const env = {
|
|
18060
19410
|
...process.env,
|
|
@@ -18062,7 +19412,7 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
18062
19412
|
BUZZ_DEV_MCP_BIN: runtime.mcpBinary
|
|
18063
19413
|
};
|
|
18064
19414
|
const config = loadBodyConfig({
|
|
18065
|
-
workspaceRoot:
|
|
19415
|
+
workspaceRoot: resolve25(dirname15(configPath), "workspace"),
|
|
18066
19416
|
llmEnvFile: runtime.llmEnvFile,
|
|
18067
19417
|
env,
|
|
18068
19418
|
agent
|
|
@@ -18097,7 +19447,7 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
18097
19447
|
const stop = () => controller.abort();
|
|
18098
19448
|
process.once("SIGINT", stop);
|
|
18099
19449
|
process.once("SIGTERM", stop);
|
|
18100
|
-
const runtimeDir =
|
|
19450
|
+
const runtimeDir = dirname15(configPath);
|
|
18101
19451
|
const layout = beelineInstallLayout(process.env);
|
|
18102
19452
|
const notifier = new SystemdNotifier();
|
|
18103
19453
|
let rollbackAlertDrain;
|
|
@@ -18143,11 +19493,28 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
18143
19493
|
let stoppingStatus = "daemon stopped";
|
|
18144
19494
|
try {
|
|
18145
19495
|
const core = new ThinDaemonCore(runtime, configPath, config, { daemonApi });
|
|
19496
|
+
const updateDrain = update ? new ManagedUpdateDrain({
|
|
19497
|
+
update,
|
|
19498
|
+
quiesceIfIdle: () => core.quiesceForUpdateIfIdle(),
|
|
19499
|
+
activeTurnCount: () => core.activeTurnCount(),
|
|
19500
|
+
restart: async ({ desiredRelease, drainDeadlineAt }, mode) => {
|
|
19501
|
+
if (mode === "forced")
|
|
19502
|
+
await core.prepareForForcedUpdateRestart();
|
|
19503
|
+
core.setDrainDeadlineAt(drainDeadlineAt);
|
|
19504
|
+
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()}`;
|
|
19505
|
+
await notifier.stopping(stoppingStatus);
|
|
19506
|
+
controller.abort();
|
|
19507
|
+
},
|
|
19508
|
+
waiting: async ({ desiredRelease, drainDeadlineAt }) => {
|
|
19509
|
+
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()}`);
|
|
19510
|
+
}
|
|
19511
|
+
}) : void 0;
|
|
18146
19512
|
const result = await core.run({
|
|
18147
19513
|
signal: controller.signal,
|
|
18148
19514
|
onEstablished: async () => {
|
|
18149
19515
|
let functionalProof;
|
|
18150
19516
|
if (layout && pendingSuccessor) {
|
|
19517
|
+
const currentReleaseId = (await readUpdateAttempt(layout))?.previousReleaseId;
|
|
18151
19518
|
const gate = await gateManagedSuccessor({
|
|
18152
19519
|
layout,
|
|
18153
19520
|
runtimeDir,
|
|
@@ -18157,7 +19524,18 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
18157
19524
|
config,
|
|
18158
19525
|
runtimeDir,
|
|
18159
19526
|
releaseId: loadedRelease ?? "unknown",
|
|
18160
|
-
sandboxRequired: runtime.sandbox !== "off"
|
|
19527
|
+
sandboxRequired: runtime.sandbox !== "off",
|
|
19528
|
+
...currentReleaseId ? {
|
|
19529
|
+
compareWithCurrentRelease: async (refusal) => {
|
|
19530
|
+
console.warn(`[thin-core] successor probe refused by the provider (${refusal.reason}); probing the current release ${currentReleaseId} for the same refusal`);
|
|
19531
|
+
await extendSystemdStartTimeout(CURRENT_RELEASE_PROBE_TIMEOUT_MS + 15e3);
|
|
19532
|
+
return probeReleaseInSubprocess({
|
|
19533
|
+
layout,
|
|
19534
|
+
releaseId: currentReleaseId,
|
|
19535
|
+
runtimeConfigPath: configPath
|
|
19536
|
+
});
|
|
19537
|
+
}
|
|
19538
|
+
} : {}
|
|
18161
19539
|
})
|
|
18162
19540
|
});
|
|
18163
19541
|
if (gate.kind === "failed") {
|
|
@@ -18166,28 +19544,26 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
18166
19544
|
}
|
|
18167
19545
|
functionalProof = gate.proof;
|
|
18168
19546
|
pendingSuccessor = false;
|
|
18169
|
-
console.log(`[thin-core] successor functional probe passed on exact release ${loadedRelease}: ${functionalProof?.harness ?? "unknown"} session/new + turn`);
|
|
19547
|
+
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})` : ""));
|
|
18170
19548
|
}
|
|
18171
19549
|
await clearDaemonStartFailures(runtimeDir);
|
|
18172
19550
|
await writeDaemonReleaseStatus(runtimeDir, runtime.agent.publicKey, loadedReleaseIdentity);
|
|
18173
19551
|
await notifier.ready(`ready; loaded_release=${loadedRelease ?? "development"}`);
|
|
18174
19552
|
ready = true;
|
|
19553
|
+
void syncAgentModelCatalog({
|
|
19554
|
+
api: daemonApi,
|
|
19555
|
+
agent,
|
|
19556
|
+
agentEnv: config.agentEnv,
|
|
19557
|
+
agentId: runtime.agent.publicKey,
|
|
19558
|
+
workspaceId: runtime.communityId,
|
|
19559
|
+
runtimeDir,
|
|
19560
|
+
...runtime.modelSelection ? { runtimeSelection: runtime.modelSelection } : {}
|
|
19561
|
+
});
|
|
18175
19562
|
},
|
|
18176
19563
|
onProgress: async (status) => {
|
|
18177
19564
|
void drainRollbackAlert(core.activeRoomIds()[0] ?? runtime.rooms[0]?.channelId);
|
|
18178
19565
|
await notifier.progress(`loaded_release=${loadedRelease ?? "development"}; ${status}`);
|
|
18179
|
-
|
|
18180
|
-
return;
|
|
18181
|
-
await coordinateManagedUpdateHandoff(update, () => core.quiesceForUpdateIfIdle(), async ({ desiredRelease, drainDeadlineAt }) => {
|
|
18182
|
-
if (Date.now() >= drainDeadlineAt)
|
|
18183
|
-
await core.prepareForForcedUpdateRestart();
|
|
18184
|
-
core.setDrainDeadlineAt(drainDeadlineAt);
|
|
18185
|
-
stoppingStatus = `update pending, converging; loaded_release=${loadedRelease ?? "unknown"}; desired_release=${desiredRelease}; active work drained; intake quiesced; exit_deadline=${new Date(drainDeadlineAt).toISOString()}`;
|
|
18186
|
-
await notifier.stopping(stoppingStatus);
|
|
18187
|
-
controller.abort();
|
|
18188
|
-
}, async ({ desiredRelease, drainDeadlineAt }) => {
|
|
18189
|
-
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()}`);
|
|
18190
|
-
});
|
|
19566
|
+
await updateDrain?.tick();
|
|
18191
19567
|
}
|
|
18192
19568
|
});
|
|
18193
19569
|
if (result === "agent-removed") {
|
|
@@ -18209,8 +19585,8 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
18209
19585
|
throw error;
|
|
18210
19586
|
} finally {
|
|
18211
19587
|
await notifier.stopping(stoppingStatus).catch(() => void 0);
|
|
18212
|
-
const pidPath =
|
|
18213
|
-
const recorded = Number((await
|
|
19588
|
+
const pidPath = resolve25(dirname15(configPath), "daemon.pid");
|
|
19589
|
+
const recorded = Number((await readFile13(pidPath, "utf8").catch(() => "")).trim());
|
|
18214
19590
|
if (recorded === process.pid) {
|
|
18215
19591
|
await unlink3(pidPath).catch(() => void 0);
|
|
18216
19592
|
}
|
|
@@ -18266,20 +19642,24 @@ async function main() {
|
|
|
18266
19642
|
const agentPubkey = agentFlag >= 0 ? args[agentFlag + 1] : void 0;
|
|
18267
19643
|
if (!configPath && agentPubkey) {
|
|
18268
19644
|
const configs = await findAgentRuntimeConfigPaths(process.env, process.cwd());
|
|
18269
|
-
configPath = configs.find((candidate) =>
|
|
19645
|
+
configPath = configs.find((candidate) => dirname15(candidate).endsWith(agentPubkey));
|
|
18270
19646
|
}
|
|
18271
19647
|
if (!configPath && agentPubkey) {
|
|
18272
19648
|
throw new DaemonExitError(`unknown agent ${agentPubkey}: no durable runtime exists; refusing systemd restart loop`, UNKNOWN_AGENT_EXIT_STATUS);
|
|
18273
19649
|
}
|
|
18274
19650
|
if (!configPath)
|
|
18275
19651
|
throw new Error("daemon requires --config <runtime.json> or --agent <pubkey>");
|
|
18276
|
-
await runStoredDaemon(
|
|
19652
|
+
await runStoredDaemon(resolve25(configPath));
|
|
18277
19653
|
return;
|
|
18278
19654
|
}
|
|
18279
19655
|
if (command === "update") {
|
|
18280
19656
|
await runUpdateCommand(args);
|
|
18281
19657
|
return;
|
|
18282
19658
|
}
|
|
19659
|
+
if (command === UPDATE_PROBE_COMMAND) {
|
|
19660
|
+
await runUpdateProbeCommand(args);
|
|
19661
|
+
return;
|
|
19662
|
+
}
|
|
18283
19663
|
if (command === "start") {
|
|
18284
19664
|
await runStartCommand(args, interactiveUi);
|
|
18285
19665
|
return;
|
|
@@ -18290,7 +19670,7 @@ async function main() {
|
|
|
18290
19670
|
if (!agentPubkey)
|
|
18291
19671
|
throw new Error("stop requires --agent <pubkey>");
|
|
18292
19672
|
const configs = await findAgentRuntimeConfigPaths(process.env, process.cwd());
|
|
18293
|
-
const configPath = configs.find((candidate) =>
|
|
19673
|
+
const configPath = configs.find((candidate) => dirname15(candidate).endsWith(agentPubkey));
|
|
18294
19674
|
if (!configPath)
|
|
18295
19675
|
throw new Error(`no stored runtime found for agent ${agentPubkey}`);
|
|
18296
19676
|
const runtime = await readRuntimeRecord(configPath);
|