usebeeline 0.0.123 → 0.0.124
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/README.md +1 -1
- package/dist/usebeeline.mjs +1843 -1412
- package/package.json +1 -1
package/dist/usebeeline.mjs
CHANGED
|
@@ -11315,10 +11315,10 @@ __export(self_update_exports, {
|
|
|
11315
11315
|
});
|
|
11316
11316
|
import { createHash as createHash8 } from "node:crypto";
|
|
11317
11317
|
import { constants as fsConstants2 } from "node:fs";
|
|
11318
|
-
import { access as access2, chmod as chmod6, lstat as lstat4, mkdir as mkdir18, open, readFile as readFile11, rename as rename6, rm as
|
|
11319
|
-
import { spawn as
|
|
11318
|
+
import { access as access2, chmod as chmod6, lstat as lstat4, mkdir as mkdir18, open, readFile as readFile11, rename as rename6, rm as rm7, symlink as symlink3, writeFile as writeFile11 } from "node:fs/promises";
|
|
11319
|
+
import { spawn as spawn9 } from "node:child_process";
|
|
11320
11320
|
import { homedir as homedir12 } from "node:os";
|
|
11321
|
-
import { dirname as dirname16, join as
|
|
11321
|
+
import { dirname as dirname16, join as join14, resolve as resolve27 } from "node:path";
|
|
11322
11322
|
function anchorLayout(rawLibDir) {
|
|
11323
11323
|
const libDir = resolve27(rawLibDir);
|
|
11324
11324
|
const segments = libDir.split(/[/\\]/);
|
|
@@ -11391,7 +11391,7 @@ function hostPlatformKey() {
|
|
|
11391
11391
|
return `${os}-${arch}`;
|
|
11392
11392
|
}
|
|
11393
11393
|
function bundleJsonCandidates(bundleDir) {
|
|
11394
|
-
return [
|
|
11394
|
+
return [join14(bundleDir, "lib", "beeline", "bundle.json"), join14(bundleDir, "bundle.json")];
|
|
11395
11395
|
}
|
|
11396
11396
|
async function readBundleJson(bundleDir) {
|
|
11397
11397
|
let raw;
|
|
@@ -11415,7 +11415,7 @@ async function readBundleJson(bundleDir) {
|
|
|
11415
11415
|
}
|
|
11416
11416
|
}
|
|
11417
11417
|
function updateStatePath(layout) {
|
|
11418
|
-
return
|
|
11418
|
+
return join14(layout.releasesRoot, ".state", "update-state.json");
|
|
11419
11419
|
}
|
|
11420
11420
|
async function readUpdateState(layout) {
|
|
11421
11421
|
try {
|
|
@@ -11425,7 +11425,7 @@ async function readUpdateState(layout) {
|
|
|
11425
11425
|
}
|
|
11426
11426
|
}
|
|
11427
11427
|
async function writeUpdateState(layout, state) {
|
|
11428
|
-
await mkdir18(
|
|
11428
|
+
await mkdir18(join14(layout.releasesRoot, ".state"), { recursive: true });
|
|
11429
11429
|
await writeFile11(updateStatePath(layout), `${JSON.stringify(state, null, 2)}
|
|
11430
11430
|
`, "utf8");
|
|
11431
11431
|
}
|
|
@@ -11489,7 +11489,7 @@ async function fetchText(url, fetchImpl) {
|
|
|
11489
11489
|
}
|
|
11490
11490
|
function run(command, args, timeoutMs) {
|
|
11491
11491
|
return new Promise((resolveRun) => {
|
|
11492
|
-
const child =
|
|
11492
|
+
const child = spawn9(command, args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
11493
11493
|
let stderr = "";
|
|
11494
11494
|
child.stderr?.setEncoding("utf8");
|
|
11495
11495
|
child.stderr?.on("data", (chunk) => {
|
|
@@ -11507,7 +11507,7 @@ function run(command, args, timeoutMs) {
|
|
|
11507
11507
|
});
|
|
11508
11508
|
}
|
|
11509
11509
|
function entrypointCandidates(bundleDir) {
|
|
11510
|
-
return [
|
|
11510
|
+
return [join14(bundleDir, BUNDLE_ENTRYPOINT), join14(bundleDir, "beeline-cli.mjs")];
|
|
11511
11511
|
}
|
|
11512
11512
|
async function resolveBundleEntrypoint(bundleDir) {
|
|
11513
11513
|
for (const candidate of entrypointCandidates(bundleDir)) {
|
|
@@ -11526,8 +11526,8 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
|
|
|
11526
11526
|
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
11527
11527
|
const log2 = opts.logger ?? ((line) => console.log(`[body] self-update: ${line}`));
|
|
11528
11528
|
const releaseId = sanitizeReleaseId(published.commit ?? published.version ?? `release-${Date.now()}`);
|
|
11529
|
-
const releaseDir =
|
|
11530
|
-
const okMarker =
|
|
11529
|
+
const releaseDir = join14(layout.releasesRoot, releaseId);
|
|
11530
|
+
const okMarker = join14(releaseDir, ".stage-ok");
|
|
11531
11531
|
let previouslyVerified = false;
|
|
11532
11532
|
try {
|
|
11533
11533
|
const recorded = await readFile11(okMarker, "utf8");
|
|
@@ -11537,7 +11537,7 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
|
|
|
11537
11537
|
} catch {
|
|
11538
11538
|
}
|
|
11539
11539
|
await mkdir18(releaseDir, { recursive: true });
|
|
11540
|
-
const tempArchive =
|
|
11540
|
+
const tempArchive = join14(layout.releasesRoot, `.download-${releaseId}-${process.pid}.tar.gz`);
|
|
11541
11541
|
try {
|
|
11542
11542
|
log2(`downloading ${published.file}`);
|
|
11543
11543
|
const response = await fetchImpl(archiveUrlFor(manifestUrl, published.file), {
|
|
@@ -11559,7 +11559,7 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
|
|
|
11559
11559
|
}
|
|
11560
11560
|
await writeFile11(tempArchive, Buffer.concat(chunks), { mode: 384 });
|
|
11561
11561
|
const entries = (await new Promise((resolveList, rejectList) => {
|
|
11562
|
-
const child =
|
|
11562
|
+
const child = spawn9("tar", ["-tzf", tempArchive], { stdio: ["ignore", "pipe", "inherit"] });
|
|
11563
11563
|
let out = "";
|
|
11564
11564
|
child.stdout?.on("data", (chunk) => {
|
|
11565
11565
|
out += chunk.toString("utf8");
|
|
@@ -11577,13 +11577,13 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
|
|
|
11577
11577
|
throw new Error(`extracting bundle failed: ${extract2.stderr}`);
|
|
11578
11578
|
for (const relative3 of requiredBundlePaths()) {
|
|
11579
11579
|
try {
|
|
11580
|
-
await access2(
|
|
11580
|
+
await access2(join14(releaseDir, relative3), fsConstants2.F_OK);
|
|
11581
11581
|
} catch {
|
|
11582
11582
|
throw new Error(`staged bundle is missing ${relative3}`);
|
|
11583
11583
|
}
|
|
11584
11584
|
}
|
|
11585
11585
|
if (opts.smokeTestCli !== false) {
|
|
11586
|
-
const probe = await run(process.execPath, [
|
|
11586
|
+
const probe = await run(process.execPath, [join14(releaseDir, BUNDLE_ENTRYPOINT), "--version"], 6e4);
|
|
11587
11587
|
if (probe.status !== 0) {
|
|
11588
11588
|
throw new Error(`staged bundle failed its startup smoke test (--version exited ${probe.status})${probe.stderr ? `: ${probe.stderr.trim()}` : ""}`);
|
|
11589
11589
|
}
|
|
@@ -11594,10 +11594,10 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
|
|
|
11594
11594
|
return releaseId;
|
|
11595
11595
|
} catch (error) {
|
|
11596
11596
|
if (!previouslyVerified)
|
|
11597
|
-
await
|
|
11597
|
+
await rm7(releaseDir, { recursive: true, force: true });
|
|
11598
11598
|
throw error;
|
|
11599
11599
|
} finally {
|
|
11600
|
-
await
|
|
11600
|
+
await rm7(tempArchive, { force: true });
|
|
11601
11601
|
}
|
|
11602
11602
|
}
|
|
11603
11603
|
function forwarderScript(tool) {
|
|
@@ -11621,8 +11621,8 @@ async function replaceFile(path, contents, mode) {
|
|
|
11621
11621
|
await rename6(temp, path);
|
|
11622
11622
|
}
|
|
11623
11623
|
async function activateRelease(layout, releaseId) {
|
|
11624
|
-
const releaseDir =
|
|
11625
|
-
await access2(
|
|
11624
|
+
const releaseDir = join14(layout.releasesRoot, releaseId);
|
|
11625
|
+
await access2(join14(releaseDir, BUNDLE_ENTRYPOINT), fsConstants2.F_OK);
|
|
11626
11626
|
await mkdir18(layout.releasesRoot, { recursive: true });
|
|
11627
11627
|
await mkdir18(layout.binDir, { recursive: true });
|
|
11628
11628
|
let previousReleaseId = await activeReleaseId(layout);
|
|
@@ -11630,12 +11630,12 @@ async function activateRelease(layout, releaseId) {
|
|
|
11630
11630
|
if (kind === "directory") {
|
|
11631
11631
|
const legacyIdentity = await readBundleJson(layout.libDir);
|
|
11632
11632
|
const legacyId = sanitizeReleaseId(legacyIdentity?.commit ?? legacyIdentity?.version ?? `legacy-${Date.now()}`);
|
|
11633
|
-
const legacyDir =
|
|
11633
|
+
const legacyDir = join14(layout.releasesRoot, legacyId);
|
|
11634
11634
|
try {
|
|
11635
11635
|
await access2(legacyDir, fsConstants2.F_OK);
|
|
11636
11636
|
previousReleaseId = `${legacyId}-${Date.now()}`;
|
|
11637
|
-
await rename6(layout.libDir,
|
|
11638
|
-
await normalizeLegacyBundleShape(
|
|
11637
|
+
await rename6(layout.libDir, join14(layout.releasesRoot, previousReleaseId));
|
|
11638
|
+
await normalizeLegacyBundleShape(join14(layout.releasesRoot, previousReleaseId));
|
|
11639
11639
|
} catch {
|
|
11640
11640
|
await rename6(layout.libDir, legacyDir);
|
|
11641
11641
|
await normalizeLegacyBundleShape(legacyDir);
|
|
@@ -11643,19 +11643,19 @@ async function activateRelease(layout, releaseId) {
|
|
|
11643
11643
|
}
|
|
11644
11644
|
}
|
|
11645
11645
|
const tempLink = `${layout.libDir}.new-${process.pid}`;
|
|
11646
|
-
await
|
|
11647
|
-
await symlink3(
|
|
11646
|
+
await rm7(tempLink, { force: true });
|
|
11647
|
+
await symlink3(join14("beeline-releases", releaseId), tempLink);
|
|
11648
11648
|
await rename6(tempLink, layout.libDir);
|
|
11649
11649
|
await fsyncDir(dirname16(layout.libDir));
|
|
11650
11650
|
await writeBinForwarders(layout, releaseDir);
|
|
11651
11651
|
return { previousReleaseId };
|
|
11652
11652
|
}
|
|
11653
11653
|
async function normalizeLegacyBundleShape(bundleDir) {
|
|
11654
|
-
const innerLib =
|
|
11654
|
+
const innerLib = join14(bundleDir, "lib", "beeline");
|
|
11655
11655
|
let anyFlat = false;
|
|
11656
11656
|
for (const name of LEGACY_FLAT_BUNDLE_FILES) {
|
|
11657
11657
|
try {
|
|
11658
|
-
await access2(
|
|
11658
|
+
await access2(join14(bundleDir, name), fsConstants2.F_OK);
|
|
11659
11659
|
anyFlat = true;
|
|
11660
11660
|
break;
|
|
11661
11661
|
} catch {
|
|
@@ -11666,11 +11666,11 @@ async function normalizeLegacyBundleShape(bundleDir) {
|
|
|
11666
11666
|
await mkdir18(innerLib, { recursive: true });
|
|
11667
11667
|
for (const name of LEGACY_FLAT_BUNDLE_FILES) {
|
|
11668
11668
|
try {
|
|
11669
|
-
await access2(
|
|
11669
|
+
await access2(join14(innerLib, name), fsConstants2.F_OK);
|
|
11670
11670
|
continue;
|
|
11671
11671
|
} catch {
|
|
11672
11672
|
}
|
|
11673
|
-
await rename6(
|
|
11673
|
+
await rename6(join14(bundleDir, name), join14(innerLib, name)).catch(() => void 0);
|
|
11674
11674
|
}
|
|
11675
11675
|
}
|
|
11676
11676
|
async function writeBinForwarders(layout, activeBundleRoot) {
|
|
@@ -11679,13 +11679,13 @@ async function writeBinForwarders(layout, activeBundleRoot) {
|
|
|
11679
11679
|
...Object.entries(FORWARDER_ALIASES).map(([alias, tool]) => [alias, tool])
|
|
11680
11680
|
];
|
|
11681
11681
|
for (const [name, tool] of entries) {
|
|
11682
|
-
const target =
|
|
11682
|
+
const target = join14(activeBundleRoot, "bin", tool);
|
|
11683
11683
|
try {
|
|
11684
11684
|
await access2(target, fsConstants2.X_OK);
|
|
11685
11685
|
} catch {
|
|
11686
11686
|
continue;
|
|
11687
11687
|
}
|
|
11688
|
-
await replaceFile(
|
|
11688
|
+
await replaceFile(join14(layout.binDir, name), forwarderScript(tool), 493);
|
|
11689
11689
|
}
|
|
11690
11690
|
}
|
|
11691
11691
|
async function repairInstallForwarders(layout, opts = {}) {
|
|
@@ -11694,7 +11694,7 @@ async function repairInstallForwarders(layout, opts = {}) {
|
|
|
11694
11694
|
const forwarderHealthy = async (name, tool) => {
|
|
11695
11695
|
let current;
|
|
11696
11696
|
try {
|
|
11697
|
-
current = await readFile11(
|
|
11697
|
+
current = await readFile11(join14(layout.binDir, name), "utf8");
|
|
11698
11698
|
} catch {
|
|
11699
11699
|
current = void 0;
|
|
11700
11700
|
}
|
|
@@ -11713,19 +11713,19 @@ async function repairInstallForwarders(layout, opts = {}) {
|
|
|
11713
11713
|
return true;
|
|
11714
11714
|
}
|
|
11715
11715
|
async function rollbackToPreviousRelease(layout, previousReleaseId) {
|
|
11716
|
-
const releaseDir =
|
|
11716
|
+
const releaseDir = join14(layout.releasesRoot, previousReleaseId);
|
|
11717
11717
|
const entrypoint = await resolveBundleEntrypoint(releaseDir);
|
|
11718
11718
|
if (!entrypoint) {
|
|
11719
11719
|
throw new Error(`release ${previousReleaseId} has no runnable CLI entrypoint`);
|
|
11720
11720
|
}
|
|
11721
11721
|
const tempLink = `${layout.libDir}.rollback-${process.pid}`;
|
|
11722
|
-
await
|
|
11723
|
-
await symlink3(
|
|
11722
|
+
await rm7(tempLink, { force: true });
|
|
11723
|
+
await symlink3(join14("beeline-releases", previousReleaseId), tempLink);
|
|
11724
11724
|
await rename6(tempLink, layout.libDir);
|
|
11725
11725
|
await fsyncDir(dirname16(layout.libDir));
|
|
11726
11726
|
}
|
|
11727
11727
|
function updateAttemptPath(layout) {
|
|
11728
|
-
return
|
|
11728
|
+
return join14(layout.releasesRoot, ".state", "update-attempt.json");
|
|
11729
11729
|
}
|
|
11730
11730
|
async function readUpdateAttempt(layout) {
|
|
11731
11731
|
try {
|
|
@@ -11739,7 +11739,7 @@ async function readUpdateAttempt(layout) {
|
|
|
11739
11739
|
}
|
|
11740
11740
|
}
|
|
11741
11741
|
async function writeUpdateAttempt(layout, record3) {
|
|
11742
|
-
await mkdir18(
|
|
11742
|
+
await mkdir18(join14(layout.releasesRoot, ".state"), { recursive: true });
|
|
11743
11743
|
const path = updateAttemptPath(layout);
|
|
11744
11744
|
const staged = `${path}.${process.pid}.tmp`;
|
|
11745
11745
|
await writeFile11(staged, `${JSON.stringify(record3, null, 2)}
|
|
@@ -13438,6 +13438,7 @@ import { accessSync as accessSync2, constants as constants2, existsSync as exist
|
|
|
13438
13438
|
import { basename as basename2, dirname as dirname2, resolve as resolve2 } from "node:path";
|
|
13439
13439
|
|
|
13440
13440
|
// apps/body/dist/agent-command.js
|
|
13441
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
13441
13442
|
import { accessSync, constants, existsSync as existsSync2, readdirSync } from "node:fs";
|
|
13442
13443
|
import { homedir } from "node:os";
|
|
13443
13444
|
import { delimiter, isAbsolute, join, resolve } from "node:path";
|
|
@@ -14040,17 +14041,44 @@ var AGENT_EXECUTABLES = {
|
|
|
14040
14041
|
var ADAPTER_INSTALL_COMMANDS = {
|
|
14041
14042
|
codex: {
|
|
14042
14043
|
command: "npm",
|
|
14043
|
-
args: ["install", "-g", "@agentclientprotocol/codex-acp"]
|
|
14044
|
+
args: ["install", "-g", "@agentclientprotocol/codex-acp@latest"]
|
|
14044
14045
|
},
|
|
14045
14046
|
claude: {
|
|
14046
14047
|
command: "npm",
|
|
14047
|
-
args: ["install", "-g", "@agentclientprotocol/claude-agent-acp"]
|
|
14048
|
+
args: ["install", "-g", "@agentclientprotocol/claude-agent-acp@latest"]
|
|
14048
14049
|
},
|
|
14049
14050
|
pi: {
|
|
14050
14051
|
command: "npm",
|
|
14051
|
-
args: ["install", "-g", "pi-acp"]
|
|
14052
|
+
args: ["install", "-g", "pi-acp@latest"]
|
|
14052
14053
|
}
|
|
14053
14054
|
};
|
|
14055
|
+
function latestAdapterInstallCommand(kind) {
|
|
14056
|
+
return kind === "codex" || kind === "claude" || kind === "pi" ? ADAPTER_INSTALL_COMMANDS[kind] : void 0;
|
|
14057
|
+
}
|
|
14058
|
+
async function runAdapterInstall(install, opts = {}) {
|
|
14059
|
+
await new Promise((resolveInstall, rejectInstall) => {
|
|
14060
|
+
const child = spawn3(install.command, install.args, {
|
|
14061
|
+
...opts.cwd ? { cwd: opts.cwd } : {},
|
|
14062
|
+
env: opts.env ?? process.env,
|
|
14063
|
+
stdio: opts.stdio ?? "inherit"
|
|
14064
|
+
});
|
|
14065
|
+
child.once("error", rejectInstall);
|
|
14066
|
+
child.once("exit", (code, signal) => {
|
|
14067
|
+
if (code === 0)
|
|
14068
|
+
resolveInstall();
|
|
14069
|
+
else {
|
|
14070
|
+
rejectInstall(new Error(signal ? `${install.command} was terminated by ${signal}` : `${install.command} exited with status ${code ?? "unknown"}`));
|
|
14071
|
+
}
|
|
14072
|
+
});
|
|
14073
|
+
});
|
|
14074
|
+
}
|
|
14075
|
+
async function installLatestAgentAdapter(kind, opts = {}) {
|
|
14076
|
+
const install = latestAdapterInstallCommand(kind);
|
|
14077
|
+
if (!install)
|
|
14078
|
+
return false;
|
|
14079
|
+
await runAdapterInstall(install, opts);
|
|
14080
|
+
return true;
|
|
14081
|
+
}
|
|
14054
14082
|
function adapterInstallHint(kind) {
|
|
14055
14083
|
return formatAdapterInstallCommand(ADAPTER_INSTALL_COMMANDS[kind]);
|
|
14056
14084
|
}
|
|
@@ -14208,7 +14236,7 @@ function resolveAgentCommand(opts) {
|
|
|
14208
14236
|
throw new Error("--agent-command may only be used with --agent custom");
|
|
14209
14237
|
}
|
|
14210
14238
|
if (typedKind === "codex") {
|
|
14211
|
-
requireExecutable("codex", env, cwd, "Codex CLI not found. Install it with `npm install -g @openai/codex`, then retry with `--agent codex`.");
|
|
14239
|
+
requireExecutable("codex", env, cwd, "Codex CLI not found. Install it with `npm install -g @openai/codex@latest`, then retry with `--agent codex`.");
|
|
14212
14240
|
const command2 = requireExecutable("codex-acp", env, cwd, `Codex ACP adapter not found. Install it with \`${adapterInstallHint("codex")}\`, then retry with \`--agent codex\`.`);
|
|
14213
14241
|
return { kind: typedKind, command: command2, args: [] };
|
|
14214
14242
|
}
|
|
@@ -14225,7 +14253,7 @@ function resolveAgentCommand(opts) {
|
|
|
14225
14253
|
return { kind: typedKind, command: command2, args: ["acp"] };
|
|
14226
14254
|
}
|
|
14227
14255
|
if (typedKind === "pi") {
|
|
14228
|
-
requireExecutable("pi", env, cwd, "Pi coding agent not found. Install it with `npm install -g @mariozechner/pi-coding-agent`, then retry with `--agent pi`.");
|
|
14256
|
+
requireExecutable("pi", env, cwd, "Pi coding agent not found. Install it with `npm install -g @mariozechner/pi-coding-agent@latest`, then retry with `--agent pi`.");
|
|
14229
14257
|
const command2 = requireExecutable("pi-acp", env, cwd, `Pi needs an ACP adapter. Install it with \`${adapterInstallHint("pi")}\`, then retry with \`--agent pi\`.`);
|
|
14230
14258
|
return { kind: typedKind, command: command2, args: [] };
|
|
14231
14259
|
}
|
|
@@ -14607,7 +14635,7 @@ function parseSandboxMaskEnv(env) {
|
|
|
14607
14635
|
}
|
|
14608
14636
|
|
|
14609
14637
|
// apps/body/dist/squire-host.js
|
|
14610
|
-
import { spawn as
|
|
14638
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
14611
14639
|
import { existsSync as existsSync4, lstatSync, mkdirSync } from "node:fs";
|
|
14612
14640
|
import { createRequire as createRequire2 } from "node:module";
|
|
14613
14641
|
import { homedir as homedir2 } from "node:os";
|
|
@@ -14706,7 +14734,7 @@ WantedBy=default.target
|
|
|
14706
14734
|
function spawnSquireServer(env, locateNpx) {
|
|
14707
14735
|
const launch = locateNpx ? squireServerCommand() : { command: "npx", args: [...SQUIRE_SERVER_ARGS], pathPrefix: "" };
|
|
14708
14736
|
const path = [launch.pathPrefix, env.PATH || "/usr/bin:/bin"].filter(Boolean).join(":");
|
|
14709
|
-
const child =
|
|
14737
|
+
const child = spawn4(launch.command, launch.args, {
|
|
14710
14738
|
env: { ...env, PATH: path },
|
|
14711
14739
|
stdio: "inherit"
|
|
14712
14740
|
});
|
|
@@ -14745,1317 +14773,1317 @@ var DEFAULT_ACCESS_POLICY = DEFAULT_AGENT_ACCESS_POLICY;
|
|
|
14745
14773
|
var LEGACY_ACCESS_POLICY = "everyone";
|
|
14746
14774
|
var ACCESS_REFUSAL_WINDOW_MS = 60 * 60 * 1e3;
|
|
14747
14775
|
|
|
14748
|
-
// apps/body/dist/model-
|
|
14749
|
-
|
|
14750
|
-
|
|
14751
|
-
|
|
14752
|
-
|
|
14753
|
-
// apps/body/dist/acp.js
|
|
14754
|
-
import { spawn as spawn4 } from "node:child_process";
|
|
14755
|
-
import { EventEmitter } from "node:events";
|
|
14756
|
-
|
|
14757
|
-
// apps/body/dist/harness-capabilities.js
|
|
14758
|
-
function harnessHonorsSessionSystemPrompt(agentCommand) {
|
|
14759
|
-
return Boolean(agentCommand && /(^|[/\\])claude-(agent|code)-acp(\.[a-z]+)?$/i.test(agentCommand));
|
|
14776
|
+
// apps/body/dist/model-types.js
|
|
14777
|
+
var ALLOWED = /* @__PURE__ */ new Set(["model", "thought_level", "effort", "reasoning_effort"]);
|
|
14778
|
+
function isAllowedAgentModelConfigCategory(category) {
|
|
14779
|
+
return ALLOWED.has(category);
|
|
14760
14780
|
}
|
|
14761
|
-
|
|
14762
|
-
|
|
14763
|
-
|
|
14764
|
-
|
|
14765
|
-
|
|
14766
|
-
|
|
14767
|
-
|
|
14768
|
-
|
|
14769
|
-
|
|
14770
|
-
|
|
14771
|
-
|
|
14772
|
-
|
|
14773
|
-
return ["agent", "edit", "code"];
|
|
14781
|
+
|
|
14782
|
+
// apps/body/dist/model-config.js
|
|
14783
|
+
import { lstatSync as lstatSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
14784
|
+
import { basename as basename3, resolve as resolve4 } from "node:path";
|
|
14785
|
+
var GROK_SESSION_MODEL_AXIS_ID = "beeline:grok-session-model";
|
|
14786
|
+
var GROK_LAUNCH_EFFORT_AXIS_ID = "beeline:grok-launch-reasoning-effort";
|
|
14787
|
+
function isGrokAgentCommand(agent) {
|
|
14788
|
+
if (agent.kind === "grok")
|
|
14789
|
+
return true;
|
|
14790
|
+
if (agent.kind && agent.kind !== "custom")
|
|
14791
|
+
return false;
|
|
14792
|
+
return /^grok(?:\.[a-z0-9]+)?$/i.test(basename3(agent.command));
|
|
14774
14793
|
}
|
|
14775
|
-
function
|
|
14776
|
-
|
|
14777
|
-
|
|
14794
|
+
function agentArgsWithModelSelection(agent, selection) {
|
|
14795
|
+
const original = [...agent.args];
|
|
14796
|
+
if (!selection || !isGrokAgentCommand(agent) || !original.includes("agent") || !original.includes("stdio")) {
|
|
14797
|
+
return original;
|
|
14778
14798
|
}
|
|
14779
|
-
|
|
14780
|
-
|
|
14781
|
-
|
|
14782
|
-
|
|
14783
|
-
|
|
14784
|
-
|
|
14785
|
-
// `settingSources: ['user']` admits the sanitized user MCP copy
|
|
14786
|
-
// while rejecting repository/local/plugin MCP. `strictMcpConfig` cannot be
|
|
14787
|
-
// used because it would reject the user copy too. Account-bound claude.ai
|
|
14788
|
-
// cloud connectors remain off independently.
|
|
14789
|
-
enforcement: "config-isolated",
|
|
14790
|
-
note: "claude-agent-acp forwards _meta.claudeCode.options to the Claude Agent SDK; user-only settingSources loads MCP from the isolated CLAUDE_CONFIG_DIR without repository/local settings, while disableClaudeAiConnectors keeps account-bound connectors off",
|
|
14791
|
-
sessionMeta: {
|
|
14792
|
-
claudeCode: {
|
|
14793
|
-
options: {
|
|
14794
|
-
// Only the sanitized user config in the isolated CLAUDE_CONFIG_DIR;
|
|
14795
|
-
// never a repository's .mcp.json, local settings, or plugins.
|
|
14796
|
-
settingSources: ["user"],
|
|
14797
|
-
// Account-bound claude.ai cloud connectors are not fetched at all.
|
|
14798
|
-
settings: CLAUDE_TOOL_SCOPE_SETTINGS
|
|
14799
|
-
}
|
|
14800
|
-
}
|
|
14801
|
-
},
|
|
14802
|
-
metaSystemPrompt: true
|
|
14803
|
-
};
|
|
14804
|
-
var PROFILES = [
|
|
14805
|
-
{ match: /(^|[/\\])claude-(agent|code)-acp(\.[a-z]+)?$/i, profile: CLAUDE_PROFILE },
|
|
14806
|
-
{
|
|
14807
|
-
match: /(^|[/\\])codex-acp(\.[a-z]+)?$/i,
|
|
14808
|
-
profile: {
|
|
14809
|
-
enforcement: "config-isolated",
|
|
14810
|
-
note: "codex-acp merges the session's MCP servers into $CODEX_HOME/config.toml's own [mcp_servers] instead of replacing them, so only an isolated CODEX_HOME scopes the session; the ChatGPT account's codex_apps connectors survive either way"
|
|
14811
|
-
}
|
|
14812
|
-
},
|
|
14813
|
-
{
|
|
14814
|
-
match: /(^|[/\\])pi-acp(\.[a-z]+)?$/i,
|
|
14815
|
-
profile: {
|
|
14816
|
-
enforcement: "config-isolated",
|
|
14817
|
-
note: "pi-acp 0.0.33 still stores session/new mcpServers and never reads them; pi 0.85.1 itself has no MCP client, while optional pi-mcp-adapter reads $PI_CODING_AGENT_DIR/mcp.json and isolated homes exclude the settings.json that would load it"
|
|
14818
|
-
}
|
|
14819
|
-
},
|
|
14820
|
-
{
|
|
14821
|
-
match: /(^|[/\\])grok(\.[a-z]+)?$/i,
|
|
14822
|
-
profile: {
|
|
14823
|
-
enforcement: "config-isolated",
|
|
14824
|
-
note: "grok loads the operator's own ~/.grok MCP servers even when session/new passes an empty mcpServers list (measured live), so only an isolated GROK_HOME scopes the session; no account-bound connector surface was found"
|
|
14825
|
-
}
|
|
14826
|
-
},
|
|
14827
|
-
{
|
|
14828
|
-
match: /(^|[/\\])buzz-agent(\.[a-z]+)?$/i,
|
|
14829
|
-
profile: {
|
|
14830
|
-
enforcement: "allowlisted",
|
|
14831
|
-
note: "buzz-agent mounts only the MCP servers the daemon passes; it has no operator-global tool config"
|
|
14799
|
+
const stripped = [];
|
|
14800
|
+
for (let index = 0; index < original.length; index += 1) {
|
|
14801
|
+
const arg = original[index];
|
|
14802
|
+
if (arg === "-m" || arg === "--model" || arg === "--reasoning-effort") {
|
|
14803
|
+
index += 1;
|
|
14804
|
+
continue;
|
|
14832
14805
|
}
|
|
14833
|
-
|
|
14834
|
-
|
|
14835
|
-
match: CURSOR_HARNESS_COMMAND,
|
|
14836
|
-
profile: {
|
|
14837
|
-
enforcement: "config-isolated",
|
|
14838
|
-
note: "cursor-acp-bridge writes session/new mcpServers into the isolated CURSOR_HOME mcp.json and the isolated $HOME/.cursor/mcp.json cursor-agent loads; it never writes the operator ~/.cursor"
|
|
14806
|
+
if (arg.startsWith("--model=") || arg.startsWith("--reasoning-effort=") || /^-m=/.test(arg)) {
|
|
14807
|
+
continue;
|
|
14839
14808
|
}
|
|
14809
|
+
stripped.push(arg);
|
|
14840
14810
|
}
|
|
14841
|
-
|
|
14842
|
-
|
|
14843
|
-
|
|
14844
|
-
|
|
14845
|
-
|
|
14846
|
-
|
|
14847
|
-
return
|
|
14811
|
+
const stdioIndex = stripped.lastIndexOf("stdio");
|
|
14812
|
+
const configured = [
|
|
14813
|
+
...selection.model ? ["--model", selection.model] : [],
|
|
14814
|
+
...selection.effort ? ["--reasoning-effort", selection.effort] : []
|
|
14815
|
+
];
|
|
14816
|
+
stripped.splice(stdioIndex, 0, ...configured);
|
|
14817
|
+
return stripped;
|
|
14848
14818
|
}
|
|
14849
|
-
function
|
|
14850
|
-
if (
|
|
14851
|
-
return
|
|
14852
|
-
|
|
14853
|
-
|
|
14854
|
-
return profile;
|
|
14855
|
-
}
|
|
14819
|
+
function advertisedChoiceId(choice) {
|
|
14820
|
+
if (typeof choice.id === "string" && choice.id.length > 0)
|
|
14821
|
+
return choice.id;
|
|
14822
|
+
if (typeof choice.value === "string" && choice.value.length > 0)
|
|
14823
|
+
return choice.value;
|
|
14856
14824
|
return void 0;
|
|
14857
14825
|
}
|
|
14858
|
-
|
|
14859
|
-
|
|
14860
|
-
|
|
14861
|
-
|
|
14862
|
-
|
|
14863
|
-
|
|
14864
|
-
|
|
14865
|
-
|
|
14866
|
-
|
|
14867
|
-
|
|
14868
|
-
}
|
|
14869
|
-
return delta;
|
|
14826
|
+
function sortModelChoicesNewestFirst(choices) {
|
|
14827
|
+
return choices.map((choice, index) => ({ choice, index })).sort((left, right) => {
|
|
14828
|
+
const leftParts = left.choice.id.match(/\d+/g)?.map(Number) ?? [];
|
|
14829
|
+
const rightParts = right.choice.id.match(/\d+/g)?.map(Number) ?? [];
|
|
14830
|
+
for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) {
|
|
14831
|
+
const delta = (rightParts[index] ?? -1) - (leftParts[index] ?? -1);
|
|
14832
|
+
if (delta !== 0)
|
|
14833
|
+
return delta;
|
|
14834
|
+
}
|
|
14835
|
+
return left.index - right.index;
|
|
14836
|
+
}).map(({ choice }) => choice);
|
|
14870
14837
|
}
|
|
14871
|
-
function
|
|
14872
|
-
const
|
|
14873
|
-
const
|
|
14874
|
-
for (
|
|
14875
|
-
|
|
14876
|
-
if (!/error handling notification/i.test(line)) {
|
|
14877
|
-
kept.push(line);
|
|
14838
|
+
function parseAdvertisedConfigOptions(raw, preferredModelId, includeLaunchEffort = false) {
|
|
14839
|
+
const configOptions = raw?.configOptions;
|
|
14840
|
+
const result = [];
|
|
14841
|
+
for (const entry of Array.isArray(configOptions) ? configOptions : []) {
|
|
14842
|
+
if (!entry || typeof entry !== "object")
|
|
14878
14843
|
continue;
|
|
14844
|
+
const record3 = entry;
|
|
14845
|
+
const id = record3.id;
|
|
14846
|
+
const category = record3.category;
|
|
14847
|
+
if (typeof id !== "string" || typeof category !== "string")
|
|
14848
|
+
continue;
|
|
14849
|
+
const rawChoices = Array.isArray(record3.options) ? record3.options : [];
|
|
14850
|
+
const choices = [];
|
|
14851
|
+
for (const rawChoice of rawChoices) {
|
|
14852
|
+
if (!rawChoice || typeof rawChoice !== "object")
|
|
14853
|
+
continue;
|
|
14854
|
+
const choice = rawChoice;
|
|
14855
|
+
const choiceId = advertisedChoiceId(choice);
|
|
14856
|
+
if (!choiceId)
|
|
14857
|
+
continue;
|
|
14858
|
+
choices.push({
|
|
14859
|
+
id: choiceId,
|
|
14860
|
+
...typeof choice.name === "string" ? { name: choice.name } : {}
|
|
14861
|
+
});
|
|
14879
14862
|
}
|
|
14880
|
-
|
|
14881
|
-
|
|
14882
|
-
|
|
14883
|
-
|
|
14884
|
-
|
|
14885
|
-
|
|
14886
|
-
}
|
|
14887
|
-
while (depth > 0 && index + 1 < lines.length) {
|
|
14888
|
-
index += 1;
|
|
14889
|
-
block2.push(lines[index]);
|
|
14890
|
-
depth += braceDelta(lines[index]);
|
|
14891
|
-
}
|
|
14892
|
-
if (!/notifications\/initialized/.test(block2.join("\n")))
|
|
14893
|
-
kept.push(...block2);
|
|
14894
|
-
}
|
|
14895
|
-
return kept.join("\n").trim();
|
|
14896
|
-
}
|
|
14897
|
-
function flattenHarnessCause(tail) {
|
|
14898
|
-
return meaningfulHarnessStderr(tail).replace(/\s+/g, " ").trim();
|
|
14899
|
-
}
|
|
14900
|
-
var AcpRequestTimeoutError = class extends Error {
|
|
14901
|
-
method;
|
|
14902
|
-
timeoutMs;
|
|
14903
|
-
inactivity;
|
|
14904
|
-
code = "ACP_REQUEST_TIMEOUT";
|
|
14905
|
-
constructor(method, timeoutMs, stderrTail = "", inactivity = false, detail = "") {
|
|
14906
|
-
const meaningful = flattenHarnessCause(stderrTail);
|
|
14907
|
-
const suffix = meaningful ? `; harness stderr: ${meaningful}` : "";
|
|
14908
|
-
super(`ACP ${method} timed out after ${timeoutMs}ms${inactivity ? " of inactivity" : ""}${detail ? ` (${detail})` : ""}${suffix}`);
|
|
14909
|
-
this.method = method;
|
|
14910
|
-
this.timeoutMs = timeoutMs;
|
|
14911
|
-
this.inactivity = inactivity;
|
|
14912
|
-
this.name = "AcpRequestTimeoutError";
|
|
14863
|
+
result.push({
|
|
14864
|
+
id,
|
|
14865
|
+
category,
|
|
14866
|
+
...typeof record3.currentValue === "string" ? { currentValue: record3.currentValue } : {},
|
|
14867
|
+
options: category === "model" ? sortModelChoicesNewestFirst(choices) : choices
|
|
14868
|
+
});
|
|
14913
14869
|
}
|
|
14914
|
-
|
|
14915
|
-
|
|
14916
|
-
|
|
14917
|
-
|
|
14918
|
-
|
|
14919
|
-
|
|
14920
|
-
|
|
14921
|
-
|
|
14922
|
-
|
|
14923
|
-
|
|
14924
|
-
if (
|
|
14925
|
-
|
|
14870
|
+
const models = raw?.models;
|
|
14871
|
+
if (!models || typeof models !== "object")
|
|
14872
|
+
return result;
|
|
14873
|
+
const modelState = models;
|
|
14874
|
+
const availableModels = Array.isArray(modelState.availableModels) ? modelState.availableModels : [];
|
|
14875
|
+
const modelChoices = [];
|
|
14876
|
+
for (const entry of availableModels) {
|
|
14877
|
+
if (!entry || typeof entry !== "object")
|
|
14878
|
+
continue;
|
|
14879
|
+
const model = entry;
|
|
14880
|
+
if (typeof model.modelId !== "string" || !model.modelId)
|
|
14881
|
+
continue;
|
|
14882
|
+
modelChoices.push({
|
|
14883
|
+
id: model.modelId,
|
|
14884
|
+
...typeof model.name === "string" ? { name: model.name } : {}
|
|
14885
|
+
});
|
|
14926
14886
|
}
|
|
14927
|
-
|
|
14928
|
-
|
|
14929
|
-
|
|
14930
|
-
|
|
14931
|
-
|
|
14932
|
-
|
|
14933
|
-
|
|
14934
|
-
|
|
14935
|
-
|
|
14936
|
-
|
|
14937
|
-
|
|
14938
|
-
|
|
14939
|
-
|
|
14940
|
-
const
|
|
14941
|
-
const
|
|
14942
|
-
for (const entry of
|
|
14887
|
+
const currentModelId = typeof modelState.currentModelId === "string" ? modelState.currentModelId : void 0;
|
|
14888
|
+
if (modelChoices.length > 0 && !result.some((axis) => axis.category === "model")) {
|
|
14889
|
+
result.push({
|
|
14890
|
+
id: GROK_SESSION_MODEL_AXIS_ID,
|
|
14891
|
+
category: "model",
|
|
14892
|
+
...currentModelId ? { currentValue: currentModelId } : {},
|
|
14893
|
+
options: sortModelChoicesNewestFirst(modelChoices)
|
|
14894
|
+
});
|
|
14895
|
+
}
|
|
14896
|
+
const effortModelId = preferredModelId ?? currentModelId;
|
|
14897
|
+
const currentModel = availableModels.find((entry) => entry && typeof entry === "object" && entry.modelId === effortModelId);
|
|
14898
|
+
const meta = currentModel?._meta;
|
|
14899
|
+
const modelMeta = meta && typeof meta === "object" ? meta : void 0;
|
|
14900
|
+
const efforts = Array.isArray(modelMeta?.reasoningEfforts) ? modelMeta.reasoningEfforts : [];
|
|
14901
|
+
const effortChoices = [];
|
|
14902
|
+
for (const entry of efforts) {
|
|
14943
14903
|
if (!entry || typeof entry !== "object")
|
|
14944
14904
|
continue;
|
|
14945
|
-
const
|
|
14946
|
-
const
|
|
14947
|
-
if (!
|
|
14905
|
+
const effort = entry;
|
|
14906
|
+
const id = advertisedChoiceId(effort);
|
|
14907
|
+
if (!id)
|
|
14948
14908
|
continue;
|
|
14949
|
-
|
|
14950
|
-
|
|
14951
|
-
|
|
14952
|
-
|
|
14953
|
-
|
|
14909
|
+
effortChoices.push({
|
|
14910
|
+
id,
|
|
14911
|
+
...typeof effort.label === "string" ? { name: effort.label } : typeof effort.name === "string" ? { name: effort.name } : {}
|
|
14912
|
+
});
|
|
14913
|
+
}
|
|
14914
|
+
const hasEffortAxis = result.some((axis) => ["thought_level", "effort", "reasoning_effort"].includes(axis.category));
|
|
14915
|
+
if (includeLaunchEffort && effortChoices.length > 0 && !hasEffortAxis) {
|
|
14916
|
+
const currentEffort = typeof modelMeta?.reasoningEffort === "string" ? modelMeta.reasoningEffort : efforts.find((entry) => entry && typeof entry === "object" && entry.default)?.value;
|
|
14954
14917
|
result.push({
|
|
14955
|
-
|
|
14956
|
-
|
|
14957
|
-
...
|
|
14918
|
+
id: GROK_LAUNCH_EFFORT_AXIS_ID,
|
|
14919
|
+
category: "reasoning_effort",
|
|
14920
|
+
...typeof currentEffort === "string" ? { currentValue: currentEffort } : {},
|
|
14921
|
+
options: effortChoices
|
|
14958
14922
|
});
|
|
14959
|
-
if (result.length >= 200)
|
|
14960
|
-
break;
|
|
14961
14923
|
}
|
|
14962
14924
|
return result;
|
|
14963
14925
|
}
|
|
14964
|
-
function
|
|
14965
|
-
|
|
14966
|
-
return "";
|
|
14967
|
-
const content = update.content;
|
|
14968
|
-
if (content?.type === "text")
|
|
14969
|
-
return content.text ?? "";
|
|
14970
|
-
if (typeof update.content === "string")
|
|
14971
|
-
return update.content;
|
|
14972
|
-
return "";
|
|
14973
|
-
}
|
|
14974
|
-
var CHUNK_CONTINUES_PREVIOUS_WORD = /^[\s'\u2018\u2019\u02bc.,!?;:%)\]}-]/;
|
|
14975
|
-
function continuesPreviousWord(current, delta) {
|
|
14976
|
-
if (/\s$/.test(current))
|
|
14977
|
-
return false;
|
|
14978
|
-
return CHUNK_CONTINUES_PREVIOUS_WORD.test(delta);
|
|
14979
|
-
}
|
|
14980
|
-
var PI_ACP_HARNESS = /(^|[/\\])pi-acp(?:\.[a-z]+)?$/i;
|
|
14981
|
-
function isPiAcpHarness(agentLabel) {
|
|
14982
|
-
return Boolean(agentLabel && PI_ACP_HARNESS.test(agentLabel));
|
|
14926
|
+
function filterAllowedModelConfigOptions(options) {
|
|
14927
|
+
return options.filter((option) => isAllowedAgentModelConfigCategory(option.category));
|
|
14983
14928
|
}
|
|
14984
|
-
|
|
14985
|
-
|
|
14929
|
+
var PROVIDER_CREDENTIAL_ENV_HINTS = {
|
|
14930
|
+
openai: ["OPENAI_API_KEY", "OPENAI_COMPAT_API_KEY"],
|
|
14931
|
+
openrouter: ["OPENROUTER_API_KEY"],
|
|
14932
|
+
anthropic: ["ANTHROPIC_API_KEY"],
|
|
14933
|
+
google: ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
|
|
14934
|
+
groq: ["GROQ_API_KEY"],
|
|
14935
|
+
mistral: ["MISTRAL_API_KEY"],
|
|
14936
|
+
deepseek: ["DEEPSEEK_API_KEY"],
|
|
14937
|
+
xai: ["XAI_API_KEY"],
|
|
14938
|
+
cohere: ["COHERE_API_KEY"],
|
|
14939
|
+
together: ["TOGETHER_API_KEY"],
|
|
14940
|
+
fireworks: ["FIREWORKS_API_KEY"]
|
|
14941
|
+
};
|
|
14942
|
+
function credentialedProviders(env) {
|
|
14943
|
+
const held = /* @__PURE__ */ new Set();
|
|
14944
|
+
for (const [provider, names] of Object.entries(PROVIDER_CREDENTIAL_ENV_HINTS)) {
|
|
14945
|
+
if (names.some((name) => Boolean(env[name]?.trim())))
|
|
14946
|
+
held.add(provider);
|
|
14947
|
+
}
|
|
14948
|
+
for (const provider of credentialedPiCustomProviders(env))
|
|
14949
|
+
held.add(provider);
|
|
14950
|
+
return held;
|
|
14986
14951
|
}
|
|
14987
|
-
function
|
|
14988
|
-
const
|
|
14989
|
-
|
|
14990
|
-
|
|
14991
|
-
|
|
14992
|
-
|
|
14993
|
-
|
|
14994
|
-
|
|
14995
|
-
|
|
14952
|
+
function credentialedPiCustomProviders(env) {
|
|
14953
|
+
const agentDir = env.PI_CODING_AGENT_DIR?.trim() ? resolve4(env.PI_CODING_AGENT_DIR) : env.HOME?.trim() ? resolve4(env.HOME, ".pi/agent") : void 0;
|
|
14954
|
+
const held = /* @__PURE__ */ new Set();
|
|
14955
|
+
if (!agentDir)
|
|
14956
|
+
return held;
|
|
14957
|
+
const path = resolve4(agentDir, "models.json");
|
|
14958
|
+
try {
|
|
14959
|
+
const stats = lstatSync2(path);
|
|
14960
|
+
if (!stats.isFile() || stats.isSymbolicLink() || stats.nlink !== 1 || stats.size > 1048576) {
|
|
14961
|
+
return held;
|
|
14996
14962
|
}
|
|
14997
|
-
|
|
14998
|
-
|
|
14999
|
-
|
|
14963
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
14964
|
+
const providers = parsed.providers;
|
|
14965
|
+
const remember = (name, config) => {
|
|
14966
|
+
if (typeof name !== "string" || !name.trim() || !config || typeof config !== "object")
|
|
14967
|
+
return;
|
|
14968
|
+
const apiKey = config.apiKey;
|
|
14969
|
+
if (typeof apiKey === "string" && apiKey.trim())
|
|
14970
|
+
held.add(name.trim());
|
|
14971
|
+
};
|
|
14972
|
+
if (Array.isArray(providers)) {
|
|
14973
|
+
for (const provider of providers) {
|
|
14974
|
+
const record3 = provider;
|
|
14975
|
+
remember(record3?.name ?? record3?.id, record3);
|
|
14976
|
+
}
|
|
14977
|
+
} else if (providers && typeof providers === "object") {
|
|
14978
|
+
for (const [name, config] of Object.entries(providers)) {
|
|
14979
|
+
remember(name, config);
|
|
14980
|
+
}
|
|
15000
14981
|
}
|
|
15001
|
-
|
|
15002
|
-
lastWasText = true;
|
|
14982
|
+
} catch {
|
|
15003
14983
|
}
|
|
15004
|
-
|
|
15005
|
-
|
|
15006
|
-
|
|
15007
|
-
|
|
15008
|
-
|
|
15009
|
-
|
|
15010
|
-
|
|
15011
|
-
return
|
|
14984
|
+
return held;
|
|
14985
|
+
}
|
|
14986
|
+
function filterModelOptionsByCredentials(options, env) {
|
|
14987
|
+
const held = credentialedProviders(env);
|
|
14988
|
+
return options.map((option) => {
|
|
14989
|
+
if (option.category !== "model")
|
|
14990
|
+
return option;
|
|
14991
|
+
return {
|
|
14992
|
+
...option,
|
|
14993
|
+
options: option.options.filter((choice) => {
|
|
14994
|
+
const slash = choice.id.indexOf("/");
|
|
14995
|
+
if (slash < 0)
|
|
14996
|
+
return true;
|
|
14997
|
+
return held.has(choice.id.slice(0, slash));
|
|
14998
|
+
})
|
|
14999
|
+
};
|
|
15012
15000
|
});
|
|
15013
15001
|
}
|
|
15014
|
-
var
|
|
15015
|
-
|
|
15016
|
-
|
|
15017
|
-
|
|
15018
|
-
/[([](?:attempt|try)\s*\d+\s*\/\s*\d+(?:,\s*(?:waiting|backoff)\s*\d+(?:\.\d+)?s)?[))]\s*/gi,
|
|
15019
|
-
// Standalone narration words with their optional trailing ellipsis.
|
|
15020
|
-
/retrying\s*\.{0,3}/gi,
|
|
15021
|
-
/retried\s*\.{0,3}/gi,
|
|
15022
|
-
/retry\s+finished,?\s*/gi,
|
|
15023
|
-
/retry\s+failed,?\s*/gi,
|
|
15024
|
-
/resuming\.?/gi,
|
|
15025
|
-
/waiting\s+\d+(?:\.\d+)?s/gi
|
|
15026
|
-
];
|
|
15027
|
-
function isPureRetryNarration(text2) {
|
|
15028
|
-
const trimmed = text2.trim();
|
|
15029
|
-
if (!trimmed)
|
|
15030
|
-
return false;
|
|
15031
|
-
let rest = trimmed;
|
|
15032
|
-
for (const fragment of RETRY_NARRATION_FRAGMENTS) {
|
|
15033
|
-
rest = rest.replace(fragment, "");
|
|
15002
|
+
var DisallowedModelConfigOptionError = class extends Error {
|
|
15003
|
+
constructor(configId) {
|
|
15004
|
+
super(`model config option "${configId}" is not in the allow-listed catalog`);
|
|
15005
|
+
this.name = "DisallowedModelConfigOptionError";
|
|
15034
15006
|
}
|
|
15035
|
-
|
|
15036
|
-
|
|
15037
|
-
|
|
15038
|
-
|
|
15039
|
-
|
|
15040
|
-
|
|
15041
|
-
|
|
15042
|
-
}
|
|
15043
|
-
|
|
15044
|
-
|
|
15045
|
-
|
|
15046
|
-
|
|
15047
|
-
|
|
15048
|
-
|
|
15049
|
-
counts.set(kind, (counts.get(kind) ?? 0) + 1);
|
|
15007
|
+
};
|
|
15008
|
+
var ModelSelectionUnavailableError = class extends Error {
|
|
15009
|
+
label;
|
|
15010
|
+
value;
|
|
15011
|
+
reason;
|
|
15012
|
+
guidance;
|
|
15013
|
+
constructor(input) {
|
|
15014
|
+
const recovery = input.guidance ? ` ${input.guidance}` : " Choose one of the values in the live harness catalog.";
|
|
15015
|
+
super(`${input.label} "${input.value}" is unavailable.${recovery}`);
|
|
15016
|
+
this.name = "ModelSelectionUnavailableError";
|
|
15017
|
+
this.label = input.label;
|
|
15018
|
+
this.value = input.value;
|
|
15019
|
+
this.reason = input.reason;
|
|
15020
|
+
this.guidance = input.guidance;
|
|
15050
15021
|
}
|
|
15051
|
-
|
|
15052
|
-
|
|
15053
|
-
|
|
15054
|
-
|
|
15022
|
+
};
|
|
15023
|
+
function withEffectiveCurrentValues(options, selection) {
|
|
15024
|
+
if (!selection || !selection.model && !selection.effort)
|
|
15025
|
+
return options;
|
|
15026
|
+
return options.map((option) => {
|
|
15027
|
+
const target = modelSelectionTargets(selection).find((entry) => entry.categories.includes(option.category));
|
|
15028
|
+
if (!target?.value || option.currentValue === target.value)
|
|
15029
|
+
return option;
|
|
15030
|
+
return { ...option, currentValue: target.value };
|
|
15031
|
+
});
|
|
15055
15032
|
}
|
|
15056
|
-
function
|
|
15057
|
-
|
|
15058
|
-
|
|
15059
|
-
|
|
15060
|
-
|
|
15033
|
+
function modelSelectionTargets(selection) {
|
|
15034
|
+
return [
|
|
15035
|
+
{ categories: ["model"], label: "model", value: selection.model },
|
|
15036
|
+
{
|
|
15037
|
+
categories: ["thought_level", "effort", "reasoning_effort"],
|
|
15038
|
+
label: "effort",
|
|
15039
|
+
value: selection.effort
|
|
15040
|
+
}
|
|
15041
|
+
];
|
|
15061
15042
|
}
|
|
15062
|
-
|
|
15063
|
-
|
|
15064
|
-
|
|
15065
|
-
|
|
15066
|
-
|
|
15067
|
-
|
|
15068
|
-
|
|
15069
|
-
|
|
15070
|
-
|
|
15071
|
-
|
|
15072
|
-
"progress_update"
|
|
15073
|
-
]);
|
|
15074
|
-
function agentStreamSnapshot(updates, agentLabel) {
|
|
15075
|
-
const completedMessages = [];
|
|
15076
|
-
let messageText2 = "";
|
|
15077
|
-
let lastWasMessage = false;
|
|
15078
|
-
let thoughtText2 = "";
|
|
15079
|
-
let thoughtRunOpen = false;
|
|
15080
|
-
for (const { update } of updates) {
|
|
15081
|
-
const kind = typeof update.sessionUpdate === "string" ? update.sessionUpdate : "";
|
|
15082
|
-
if (kind === "agent_message_chunk") {
|
|
15083
|
-
const delta = normalizeStreamDelta(agentMessageChunkText(update), agentLabel);
|
|
15084
|
-
if (!delta)
|
|
15085
|
-
continue;
|
|
15086
|
-
if (!lastWasMessage && messageText2 && !/\s$/.test(messageText2) && !CHUNK_CONTINUES_PREVIOUS_WORD.test(delta)) {
|
|
15087
|
-
if (!completedMessages.includes(messageText2))
|
|
15088
|
-
completedMessages.push(messageText2);
|
|
15089
|
-
messageText2 = "";
|
|
15090
|
-
}
|
|
15091
|
-
messageText2 += delta;
|
|
15092
|
-
lastWasMessage = true;
|
|
15093
|
-
thoughtRunOpen = false;
|
|
15043
|
+
function assertModelConfigAxisAllowed(configId, advertisedOptions) {
|
|
15044
|
+
const axis = advertisedOptions.find((option) => option.id === configId);
|
|
15045
|
+
if (!axis || !isAllowedAgentModelConfigCategory(axis.category)) {
|
|
15046
|
+
throw new DisallowedModelConfigOptionError(configId);
|
|
15047
|
+
}
|
|
15048
|
+
}
|
|
15049
|
+
async function applyAgentModelSelectionWithUpdatedCatalog(client, sessionId, advertisedOptions, selection) {
|
|
15050
|
+
let currentOptions = advertisedOptions;
|
|
15051
|
+
for (const target of modelSelectionTargets(selection)) {
|
|
15052
|
+
if (!target.value)
|
|
15094
15053
|
continue;
|
|
15054
|
+
const axis = currentOptions.find((option) => target.categories.includes(option.category));
|
|
15055
|
+
if (!axis)
|
|
15056
|
+
continue;
|
|
15057
|
+
assertModelConfigAxisAllowed(axis.id, currentOptions);
|
|
15058
|
+
if (!axis.options.some((choice) => choice.id === target.value)) {
|
|
15059
|
+
throw new ModelSelectionUnavailableError({
|
|
15060
|
+
label: target.label,
|
|
15061
|
+
value: target.value,
|
|
15062
|
+
reason: "not-advertised"
|
|
15063
|
+
});
|
|
15095
15064
|
}
|
|
15096
|
-
|
|
15097
|
-
if (
|
|
15098
|
-
|
|
15099
|
-
|
|
15100
|
-
|
|
15101
|
-
|
|
15102
|
-
|
|
15103
|
-
|
|
15104
|
-
|
|
15105
|
-
|
|
15106
|
-
|
|
15107
|
-
|
|
15108
|
-
|
|
15109
|
-
|
|
15110
|
-
|
|
15111
|
-
|
|
15065
|
+
try {
|
|
15066
|
+
if (axis.id === GROK_SESSION_MODEL_AXIS_ID) {
|
|
15067
|
+
if (!client.setModel)
|
|
15068
|
+
throw new Error("ACP client does not support session/set_model");
|
|
15069
|
+
await client.setModel(sessionId, target.value);
|
|
15070
|
+
} else if (axis.id === GROK_LAUNCH_EFFORT_AXIS_ID) {
|
|
15071
|
+
if (axis.currentValue !== target.value) {
|
|
15072
|
+
throw new Error(`Grok started with reasoning effort "${axis.currentValue ?? "unknown"}", not "${target.value}"`);
|
|
15073
|
+
}
|
|
15074
|
+
} else {
|
|
15075
|
+
const updated = await client.setConfigOption(sessionId, axis.id, target.value);
|
|
15076
|
+
const refreshed = filterAllowedModelConfigOptions(parseAdvertisedConfigOptions(updated, selection.model));
|
|
15077
|
+
if (refreshed.length > 0)
|
|
15078
|
+
currentOptions = refreshed;
|
|
15079
|
+
}
|
|
15080
|
+
} catch (error) {
|
|
15081
|
+
throw new ModelSelectionUnavailableError({
|
|
15082
|
+
label: target.label,
|
|
15083
|
+
value: target.value,
|
|
15084
|
+
reason: "provider-refused",
|
|
15085
|
+
guidance: error instanceof Error ? error.message : String(error)
|
|
15086
|
+
});
|
|
15112
15087
|
}
|
|
15113
15088
|
}
|
|
15114
|
-
return
|
|
15115
|
-
messageText: messageText2,
|
|
15116
|
-
...thoughtText2 || completedMessages.length ? { thoughtText: thoughtText2 || completedMessages.at(-1) } : {}
|
|
15117
|
-
};
|
|
15089
|
+
return currentOptions;
|
|
15118
15090
|
}
|
|
15119
|
-
function
|
|
15120
|
-
|
|
15121
|
-
|
|
15122
|
-
|
|
15123
|
-
|
|
15124
|
-
|
|
15125
|
-
|
|
15126
|
-
|
|
15091
|
+
async function applyAgentModelSelection(client, sessionId, advertisedOptions, selection) {
|
|
15092
|
+
await applyAgentModelSelectionWithUpdatedCatalog(client, sessionId, advertisedOptions, selection);
|
|
15093
|
+
}
|
|
15094
|
+
|
|
15095
|
+
// apps/body/dist/model-catalog.js
|
|
15096
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
15097
|
+
import { tmpdir } from "node:os";
|
|
15098
|
+
import { resolve as resolve5 } from "node:path";
|
|
15099
|
+
|
|
15100
|
+
// apps/body/dist/acp.js
|
|
15101
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
15102
|
+
import { EventEmitter } from "node:events";
|
|
15103
|
+
|
|
15104
|
+
// apps/body/dist/harness-capabilities.js
|
|
15105
|
+
function harnessHonorsSessionSystemPrompt(agentCommand) {
|
|
15106
|
+
return Boolean(agentCommand && /(^|[/\\])claude-(agent|code)-acp(\.[a-z]+)?$/i.test(agentCommand));
|
|
15107
|
+
}
|
|
15108
|
+
var GROK_WARM_SESSION_IDLE_MS = 30 * 6e4;
|
|
15109
|
+
function cornerAutonomyModeCandidates(agentCommand) {
|
|
15110
|
+
if (agentCommand && /(^|[/\\])codex-acp(\.[a-z]+)?$/i.test(agentCommand)) {
|
|
15111
|
+
return ["agent-full-access"];
|
|
15112
|
+
}
|
|
15113
|
+
if (agentCommand && /(^|[/\\])claude-(agent|code)-acp(\.[a-z]+)?$/i.test(agentCommand)) {
|
|
15114
|
+
return ["bypassPermissions"];
|
|
15115
|
+
}
|
|
15116
|
+
if (agentCommand && /(^|[/\\])grok(\.[a-z]+)?$/i.test(agentCommand))
|
|
15117
|
+
return [];
|
|
15118
|
+
if (agentCommand && /(^|[/\\])pi-acp(\.[a-z]+)?$/i.test(agentCommand))
|
|
15119
|
+
return [];
|
|
15120
|
+
return ["agent", "edit", "code"];
|
|
15121
|
+
}
|
|
15122
|
+
function roomModeCandidates(agentCommand, options = {}) {
|
|
15123
|
+
if (options.osSandbox && agentCommand && /(^|[/\\])codex-acp(\.[a-z]+)?$/i.test(agentCommand)) {
|
|
15124
|
+
return ["agent-full-access"];
|
|
15125
|
+
}
|
|
15126
|
+
return ["read-only", "readonly"];
|
|
15127
|
+
}
|
|
15128
|
+
|
|
15129
|
+
// apps/body/dist/harness-tool-scope.js
|
|
15130
|
+
var CLAUDE_TOOL_SCOPE_SETTINGS = { disableClaudeAiConnectors: true };
|
|
15131
|
+
var CLAUDE_PROFILE = {
|
|
15132
|
+
// `settingSources: ['user']` admits the sanitized user MCP copy
|
|
15133
|
+
// while rejecting repository/local/plugin MCP. `strictMcpConfig` cannot be
|
|
15134
|
+
// used because it would reject the user copy too. Account-bound claude.ai
|
|
15135
|
+
// cloud connectors remain off independently.
|
|
15136
|
+
enforcement: "config-isolated",
|
|
15137
|
+
note: "claude-agent-acp forwards _meta.claudeCode.options to the Claude Agent SDK; user-only settingSources loads MCP from the isolated CLAUDE_CONFIG_DIR without repository/local settings, while disableClaudeAiConnectors keeps account-bound connectors off",
|
|
15138
|
+
sessionMeta: {
|
|
15139
|
+
claudeCode: {
|
|
15140
|
+
options: {
|
|
15141
|
+
// Only the sanitized user config in the isolated CLAUDE_CONFIG_DIR;
|
|
15142
|
+
// never a repository's .mcp.json, local settings, or plugins.
|
|
15143
|
+
settingSources: ["user"],
|
|
15144
|
+
// Account-bound claude.ai cloud connectors are not fetched at all.
|
|
15145
|
+
settings: CLAUDE_TOOL_SCOPE_SETTINGS
|
|
15146
|
+
}
|
|
15127
15147
|
}
|
|
15128
|
-
|
|
15129
|
-
|
|
15130
|
-
|
|
15131
|
-
|
|
15132
|
-
|
|
15148
|
+
},
|
|
15149
|
+
metaSystemPrompt: true
|
|
15150
|
+
};
|
|
15151
|
+
var PROFILES = [
|
|
15152
|
+
{ match: /(^|[/\\])claude-(agent|code)-acp(\.[a-z]+)?$/i, profile: CLAUDE_PROFILE },
|
|
15153
|
+
{
|
|
15154
|
+
match: /(^|[/\\])codex-acp(\.[a-z]+)?$/i,
|
|
15155
|
+
profile: {
|
|
15156
|
+
enforcement: "config-isolated",
|
|
15157
|
+
note: "codex-acp merges the session's MCP servers into $CODEX_HOME/config.toml's own [mcp_servers] instead of replacing them, so only an isolated CODEX_HOME scopes the session; the ChatGPT account's codex_apps connectors survive either way"
|
|
15158
|
+
}
|
|
15159
|
+
},
|
|
15160
|
+
{
|
|
15161
|
+
match: /(^|[/\\])pi-acp(\.[a-z]+)?$/i,
|
|
15162
|
+
profile: {
|
|
15163
|
+
enforcement: "config-isolated",
|
|
15164
|
+
note: "pi-acp 0.0.33 still stores session/new mcpServers and never reads them; pi 0.85.1 itself has no MCP client, while optional pi-mcp-adapter reads $PI_CODING_AGENT_DIR/mcp.json and isolated homes exclude the settings.json that would load it"
|
|
15165
|
+
}
|
|
15166
|
+
},
|
|
15167
|
+
{
|
|
15168
|
+
match: /(^|[/\\])grok(\.[a-z]+)?$/i,
|
|
15169
|
+
profile: {
|
|
15170
|
+
enforcement: "config-isolated",
|
|
15171
|
+
note: "grok loads the operator's own ~/.grok MCP servers even when session/new passes an empty mcpServers list (measured live), so only an isolated GROK_HOME scopes the session; no account-bound connector surface was found"
|
|
15172
|
+
}
|
|
15173
|
+
},
|
|
15174
|
+
{
|
|
15175
|
+
match: /(^|[/\\])buzz-agent(\.[a-z]+)?$/i,
|
|
15176
|
+
profile: {
|
|
15177
|
+
enforcement: "allowlisted",
|
|
15178
|
+
note: "buzz-agent mounts only the MCP servers the daemon passes; it has no operator-global tool config"
|
|
15179
|
+
}
|
|
15180
|
+
},
|
|
15181
|
+
{
|
|
15182
|
+
match: CURSOR_HARNESS_COMMAND,
|
|
15183
|
+
profile: {
|
|
15184
|
+
enforcement: "config-isolated",
|
|
15185
|
+
note: "cursor-acp-bridge writes session/new mcpServers into the isolated CURSOR_HOME mcp.json and the isolated $HOME/.cursor/mcp.json cursor-agent loads; it never writes the operator ~/.cursor"
|
|
15133
15186
|
}
|
|
15134
|
-
const key = id ?? activeAnonymous;
|
|
15135
|
-
const previous = calls.get(key);
|
|
15136
|
-
calls.set(key, {
|
|
15137
|
-
...previous,
|
|
15138
|
-
id: key,
|
|
15139
|
-
...typeof update.title === "string" ? { title: update.title } : {},
|
|
15140
|
-
...typeof update.kind === "string" ? { kind: update.kind } : {},
|
|
15141
|
-
...typeof update.status === "string" ? { status: update.status } : {},
|
|
15142
|
-
...sessionUpdate === "tool_result" ? { resultReceived: true } : {},
|
|
15143
|
-
..."rawInput" in update ? { rawInput: update.rawInput } : {},
|
|
15144
|
-
..."content" in update ? { content: update.content } : {},
|
|
15145
|
-
..."rawOutput" in update ? { rawOutput: update.rawOutput } : {},
|
|
15146
|
-
..."locations" in update ? { locations: update.locations } : {}
|
|
15147
|
-
});
|
|
15148
|
-
if (!id && sessionUpdate === "tool_result")
|
|
15149
|
-
activeAnonymous = void 0;
|
|
15150
15187
|
}
|
|
15151
|
-
|
|
15188
|
+
];
|
|
15189
|
+
function sessionToolScopeMeta(agentCommand) {
|
|
15190
|
+
const meta = matchProfile(agentCommand)?.sessionMeta;
|
|
15191
|
+
return meta ? JSON.parse(JSON.stringify(meta)) : void 0;
|
|
15152
15192
|
}
|
|
15153
|
-
|
|
15154
|
-
|
|
15155
|
-
|
|
15156
|
-
|
|
15157
|
-
|
|
15158
|
-
|
|
15159
|
-
|
|
15160
|
-
|
|
15161
|
-
|
|
15162
|
-
/** Latest `available_commands_update` per session, keyed by sessionId. */
|
|
15163
|
-
sessionCommands = /* @__PURE__ */ new Map();
|
|
15164
|
-
supportsStandardSteering = false;
|
|
15165
|
-
supportsSessionLoading = false;
|
|
15166
|
-
supportsImagePrompts = false;
|
|
15167
|
-
alive = false;
|
|
15168
|
-
/** Bounded tail of recent stderr, so a spawn/exit failure's rejection text
|
|
15169
|
-
* carries the real reason (e.g. a harness's own "missing API key" notice)
|
|
15170
|
-
* instead of just the bare exit code, in the daemon's log. */
|
|
15171
|
-
stderrTail = "";
|
|
15172
|
-
agentEnv;
|
|
15173
|
-
agentCommand;
|
|
15174
|
-
agentLabel;
|
|
15175
|
-
agentArgs;
|
|
15176
|
-
agentCwd;
|
|
15177
|
-
inheritProcessEnv;
|
|
15178
|
-
autoApprove;
|
|
15179
|
-
osSandbox;
|
|
15180
|
-
permissionHandler;
|
|
15181
|
-
permissionAllowlist;
|
|
15182
|
-
commandsHandler;
|
|
15183
|
-
constructor(opts) {
|
|
15184
|
-
super();
|
|
15185
|
-
const command = opts.agentCommand ?? opts.agentBinary;
|
|
15186
|
-
if (!command)
|
|
15187
|
-
throw new Error("ACP agent command is required");
|
|
15188
|
-
this.agentCommand = command;
|
|
15189
|
-
this.agentLabel = opts.agentLabel ?? command;
|
|
15190
|
-
this.agentArgs = [...opts.agentArgs ?? []];
|
|
15191
|
-
this.agentEnv = opts.agentEnv;
|
|
15192
|
-
if (opts.agentCwd)
|
|
15193
|
-
this.agentCwd = opts.agentCwd;
|
|
15194
|
-
this.inheritProcessEnv = opts.inheritProcessEnv ?? process.env.BUZZY_BODY_AGENT_ENV_INHERIT === "1";
|
|
15195
|
-
this.autoApprove = opts.autoApprovePermissions ?? true;
|
|
15196
|
-
this.osSandbox = opts.osSandbox ?? false;
|
|
15197
|
-
this.permissionHandler = opts.permissionHandler;
|
|
15198
|
-
this.permissionAllowlist = opts.permissionAllowlist;
|
|
15199
|
-
this.commandsHandler = opts.onCommands;
|
|
15193
|
+
function harnessReadsMetaSystemPrompt(agentCommand) {
|
|
15194
|
+
return matchProfile(agentCommand)?.metaSystemPrompt === true;
|
|
15195
|
+
}
|
|
15196
|
+
function matchProfile(agentCommand) {
|
|
15197
|
+
if (!agentCommand)
|
|
15198
|
+
return void 0;
|
|
15199
|
+
for (const { match, profile } of PROFILES) {
|
|
15200
|
+
if (match.test(agentCommand))
|
|
15201
|
+
return profile;
|
|
15200
15202
|
}
|
|
15201
|
-
|
|
15202
|
-
|
|
15203
|
-
|
|
15204
|
-
|
|
15205
|
-
|
|
15206
|
-
|
|
15207
|
-
|
|
15208
|
-
|
|
15209
|
-
|
|
15210
|
-
|
|
15211
|
-
|
|
15212
|
-
|
|
15213
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
15214
|
-
});
|
|
15215
|
-
this.alive = true;
|
|
15216
|
-
this.stderrTail = "";
|
|
15217
|
-
this.child.stderr.setEncoding("utf8");
|
|
15218
|
-
this.child.stderr.on("data", (chunk) => {
|
|
15219
|
-
this.stderrTail = (this.stderrTail + chunk).slice(-STDERR_TAIL_MAX_CHARS2);
|
|
15220
|
-
this.emit("stderr", chunk);
|
|
15221
|
-
});
|
|
15222
|
-
this.child.stdout.setEncoding("utf8");
|
|
15223
|
-
this.child.stdout.on("data", (chunk) => this.onData(chunk));
|
|
15224
|
-
this.child.on("exit", (code, signal) => {
|
|
15225
|
-
this.alive = false;
|
|
15226
|
-
const meaningful = flattenHarnessCause(this.stderrTail);
|
|
15227
|
-
const stderrSuffix = meaningful ? `: ${meaningful}` : "";
|
|
15228
|
-
for (const [, p] of this.pending) {
|
|
15229
|
-
this.clearTimer(p);
|
|
15230
|
-
p.reject(new Error(`ACP agent ${this.agentLabel} exited code=${code} signal=${signal}${stderrSuffix}`));
|
|
15231
|
-
}
|
|
15232
|
-
this.pending.clear();
|
|
15233
|
-
this.activeRunIds.clear();
|
|
15234
|
-
this.activePromptSessions.clear();
|
|
15235
|
-
this.toolCallMetadata.clear();
|
|
15236
|
-
this.sessionCommands.clear();
|
|
15237
|
-
this.emit("exit", { code, signal });
|
|
15238
|
-
});
|
|
15239
|
-
this.child.on("error", (err) => {
|
|
15240
|
-
this.alive = false;
|
|
15241
|
-
for (const [, p] of this.pending) {
|
|
15242
|
-
clearTimeout(p.timer);
|
|
15243
|
-
p.reject(err);
|
|
15244
|
-
}
|
|
15245
|
-
this.pending.clear();
|
|
15246
|
-
if (this.listenerCount("error") > 0)
|
|
15247
|
-
this.emit("error", err);
|
|
15248
|
-
});
|
|
15249
|
-
const initResult = await this.request("initialize", {
|
|
15250
|
-
protocolVersion: 1,
|
|
15251
|
-
clientCapabilities: {}
|
|
15252
|
-
}, timeoutMs);
|
|
15253
|
-
const initMeta = initResult._meta;
|
|
15254
|
-
const steering = initMeta?.steering;
|
|
15255
|
-
this.supportsStandardSteering = steering?.supported === true;
|
|
15256
|
-
const agentCapabilities = initResult.agentCapabilities;
|
|
15257
|
-
this.supportsSessionLoading = agentCapabilities?.loadSession === true;
|
|
15258
|
-
this.supportsImagePrompts = agentCapabilities?.promptCapabilities?.image === true;
|
|
15259
|
-
this.emit("initialized", initResult);
|
|
15260
|
-
if (!isPiAcpHarness(this.agentLabel))
|
|
15261
|
-
this.notify("notifications/initialized", {});
|
|
15203
|
+
return void 0;
|
|
15204
|
+
}
|
|
15205
|
+
|
|
15206
|
+
// apps/body/dist/acp.js
|
|
15207
|
+
var STDERR_TAIL_MAX_CHARS2 = 2e3;
|
|
15208
|
+
function braceDelta(line) {
|
|
15209
|
+
let delta = 0;
|
|
15210
|
+
for (const char of line) {
|
|
15211
|
+
if (char === "{")
|
|
15212
|
+
delta += 1;
|
|
15213
|
+
else if (char === "}")
|
|
15214
|
+
delta -= 1;
|
|
15262
15215
|
}
|
|
15263
|
-
|
|
15264
|
-
|
|
15265
|
-
|
|
15266
|
-
|
|
15267
|
-
|
|
15268
|
-
|
|
15269
|
-
|
|
15270
|
-
|
|
15271
|
-
|
|
15272
|
-
|
|
15273
|
-
} catch {
|
|
15216
|
+
return delta;
|
|
15217
|
+
}
|
|
15218
|
+
function meaningfulHarnessStderr(tail) {
|
|
15219
|
+
const lines = tail.split(/\r?\n/);
|
|
15220
|
+
const kept = [];
|
|
15221
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
15222
|
+
const line = lines[index];
|
|
15223
|
+
if (!/error handling notification/i.test(line)) {
|
|
15224
|
+
kept.push(line);
|
|
15225
|
+
continue;
|
|
15274
15226
|
}
|
|
15275
|
-
|
|
15276
|
-
|
|
15277
|
-
|
|
15278
|
-
|
|
15227
|
+
const block2 = [line];
|
|
15228
|
+
let depth = braceDelta(line);
|
|
15229
|
+
if (depth === 0 && lines[index + 1]?.trimStart().startsWith("{")) {
|
|
15230
|
+
index += 1;
|
|
15231
|
+
block2.push(lines[index]);
|
|
15232
|
+
depth = braceDelta(lines[index]);
|
|
15279
15233
|
}
|
|
15280
|
-
|
|
15281
|
-
|
|
15282
|
-
|
|
15283
|
-
|
|
15234
|
+
while (depth > 0 && index + 1 < lines.length) {
|
|
15235
|
+
index += 1;
|
|
15236
|
+
block2.push(lines[index]);
|
|
15237
|
+
depth += braceDelta(lines[index]);
|
|
15284
15238
|
}
|
|
15285
|
-
|
|
15286
|
-
|
|
15287
|
-
this.activeRunIds.clear();
|
|
15288
|
-
this.activePromptSessions.clear();
|
|
15289
|
-
this.toolCallMetadata.clear();
|
|
15290
|
-
}
|
|
15291
|
-
get isAlive() {
|
|
15292
|
-
return this.alive;
|
|
15293
|
-
}
|
|
15294
|
-
/** Host pid of the harness wrapper (bwrap when enabled). */
|
|
15295
|
-
processPid() {
|
|
15296
|
-
return this.child?.pid;
|
|
15239
|
+
if (!/notifications\/initialized/.test(block2.join("\n")))
|
|
15240
|
+
kept.push(...block2);
|
|
15297
15241
|
}
|
|
15298
|
-
|
|
15299
|
-
|
|
15300
|
-
|
|
15242
|
+
return kept.join("\n").trim();
|
|
15243
|
+
}
|
|
15244
|
+
function flattenHarnessCause(tail) {
|
|
15245
|
+
return meaningfulHarnessStderr(tail).replace(/\s+/g, " ").trim();
|
|
15246
|
+
}
|
|
15247
|
+
var AcpRequestTimeoutError = class extends Error {
|
|
15248
|
+
method;
|
|
15249
|
+
timeoutMs;
|
|
15250
|
+
inactivity;
|
|
15251
|
+
code = "ACP_REQUEST_TIMEOUT";
|
|
15252
|
+
constructor(method, timeoutMs, stderrTail = "", inactivity = false, detail = "") {
|
|
15253
|
+
const meaningful = flattenHarnessCause(stderrTail);
|
|
15254
|
+
const suffix = meaningful ? `; harness stderr: ${meaningful}` : "";
|
|
15255
|
+
super(`ACP ${method} timed out after ${timeoutMs}ms${inactivity ? " of inactivity" : ""}${detail ? ` (${detail})` : ""}${suffix}`);
|
|
15256
|
+
this.method = method;
|
|
15257
|
+
this.timeoutMs = timeoutMs;
|
|
15258
|
+
this.inactivity = inactivity;
|
|
15259
|
+
this.name = "AcpRequestTimeoutError";
|
|
15301
15260
|
}
|
|
15302
|
-
|
|
15303
|
-
|
|
15304
|
-
|
|
15261
|
+
};
|
|
15262
|
+
function killChildProcessGroup(child, signal) {
|
|
15263
|
+
if (!child.pid)
|
|
15264
|
+
return;
|
|
15265
|
+
try {
|
|
15266
|
+
if (process.platform === "win32")
|
|
15267
|
+
child.kill(signal);
|
|
15268
|
+
else
|
|
15269
|
+
process.kill(-child.pid, signal);
|
|
15270
|
+
} catch (error) {
|
|
15271
|
+
if (error.code !== "ESRCH")
|
|
15272
|
+
throw error;
|
|
15305
15273
|
}
|
|
15306
|
-
|
|
15307
|
-
|
|
15308
|
-
|
|
15274
|
+
}
|
|
15275
|
+
function promptPayloadNote(prompt) {
|
|
15276
|
+
if (typeof prompt === "string")
|
|
15277
|
+
return "";
|
|
15278
|
+
const images = prompt.filter((block2) => block2.type === "image");
|
|
15279
|
+
if (!images.length)
|
|
15280
|
+
return "";
|
|
15281
|
+
const bytes = images.reduce((total, block2) => total + Math.floor(block2.data.length * 3 / 4), 0);
|
|
15282
|
+
return `prompt carried ${images.length} inline image${images.length === 1 ? "" : "s"}, ${Math.round(bytes / 1024)} KB`;
|
|
15283
|
+
}
|
|
15284
|
+
function parseAvailableCommands(value) {
|
|
15285
|
+
if (!Array.isArray(value))
|
|
15286
|
+
return [];
|
|
15287
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15288
|
+
const result = [];
|
|
15289
|
+
for (const entry of value) {
|
|
15290
|
+
if (!entry || typeof entry !== "object")
|
|
15291
|
+
continue;
|
|
15292
|
+
const record3 = entry;
|
|
15293
|
+
const rawName = typeof record3.name === "string" ? record3.name.trim().replace(/^\/+/, "") : "";
|
|
15294
|
+
if (!rawName || rawName.length > 80 || seen.has(rawName))
|
|
15295
|
+
continue;
|
|
15296
|
+
seen.add(rawName);
|
|
15297
|
+
const description = typeof record3.description === "string" && record3.description.trim() ? record3.description.trim().slice(0, 300) : void 0;
|
|
15298
|
+
const input = record3.input;
|
|
15299
|
+
const hintRaw = input && typeof input.hint === "string" ? input.hint : void 0;
|
|
15300
|
+
const inputHint = hintRaw && hintRaw.trim() ? hintRaw.trim().slice(0, 120) : void 0;
|
|
15301
|
+
result.push({
|
|
15302
|
+
name: rawName,
|
|
15303
|
+
...description ? { description } : {},
|
|
15304
|
+
...inputHint ? { inputHint } : {}
|
|
15305
|
+
});
|
|
15306
|
+
if (result.length >= 200)
|
|
15307
|
+
break;
|
|
15309
15308
|
}
|
|
15310
|
-
|
|
15311
|
-
|
|
15312
|
-
|
|
15313
|
-
|
|
15314
|
-
|
|
15315
|
-
|
|
15316
|
-
|
|
15317
|
-
|
|
15318
|
-
|
|
15319
|
-
|
|
15320
|
-
|
|
15321
|
-
|
|
15322
|
-
|
|
15323
|
-
|
|
15324
|
-
|
|
15325
|
-
|
|
15326
|
-
|
|
15309
|
+
return result;
|
|
15310
|
+
}
|
|
15311
|
+
function agentMessageChunkText(update) {
|
|
15312
|
+
if (update.sessionUpdate !== "agent_message_chunk")
|
|
15313
|
+
return "";
|
|
15314
|
+
const content = update.content;
|
|
15315
|
+
if (content?.type === "text")
|
|
15316
|
+
return content.text ?? "";
|
|
15317
|
+
if (typeof update.content === "string")
|
|
15318
|
+
return update.content;
|
|
15319
|
+
return "";
|
|
15320
|
+
}
|
|
15321
|
+
var CHUNK_CONTINUES_PREVIOUS_WORD = /^[\s'\u2018\u2019\u02bc.,!?;:%)\]}-]/;
|
|
15322
|
+
function continuesPreviousWord(current, delta) {
|
|
15323
|
+
if (/\s$/.test(current))
|
|
15324
|
+
return false;
|
|
15325
|
+
return CHUNK_CONTINUES_PREVIOUS_WORD.test(delta);
|
|
15326
|
+
}
|
|
15327
|
+
var PI_ACP_HARNESS = /(^|[/\\])pi-acp(?:\.[a-z]+)?$/i;
|
|
15328
|
+
function isPiAcpHarness(agentLabel) {
|
|
15329
|
+
return Boolean(agentLabel && PI_ACP_HARNESS.test(agentLabel));
|
|
15330
|
+
}
|
|
15331
|
+
function normalizeStreamDelta(text2, _agentLabel) {
|
|
15332
|
+
return text2;
|
|
15333
|
+
}
|
|
15334
|
+
function agentMessageRuns(updates, agentLabel) {
|
|
15335
|
+
const runs = [];
|
|
15336
|
+
let current = "";
|
|
15337
|
+
let lastWasText = false;
|
|
15338
|
+
for (const u4 of updates) {
|
|
15339
|
+
const delta = normalizeStreamDelta(agentMessageChunkText(u4.update), agentLabel);
|
|
15340
|
+
if (!delta) {
|
|
15341
|
+
lastWasText = false;
|
|
15342
|
+
continue;
|
|
15327
15343
|
}
|
|
15328
|
-
if (
|
|
15329
|
-
|
|
15330
|
-
|
|
15331
|
-
const sessionId = raw.sessionId;
|
|
15332
|
-
if (!sessionId)
|
|
15333
|
-
throw new Error("session/new missing sessionId");
|
|
15334
|
-
await this.applySessionMode(sessionId, raw, opts.mode);
|
|
15335
|
-
return { sessionId, raw };
|
|
15336
|
-
}
|
|
15337
|
-
/**
|
|
15338
|
-
* Reopen a provider-persisted logical conversation in this new ACP process.
|
|
15339
|
-
* Unlike `session/new`, load accepts no system prompt: the harness restores
|
|
15340
|
-
* its own conversation and instructions instead of receiving transcript
|
|
15341
|
-
* text from Body again.
|
|
15342
|
-
*/
|
|
15343
|
-
async sessionLoad(opts) {
|
|
15344
|
-
if (!this.supportsSessionLoading) {
|
|
15345
|
-
throw new Error("ACP agent does not advertise session/load");
|
|
15344
|
+
if (!lastWasText && current && !continuesPreviousWord(current, delta)) {
|
|
15345
|
+
runs.push(current);
|
|
15346
|
+
current = "";
|
|
15346
15347
|
}
|
|
15347
|
-
|
|
15348
|
-
|
|
15349
|
-
cwd: opts.cwd,
|
|
15350
|
-
mcpServers: (opts.mcpServers ?? []).map((server) => ({
|
|
15351
|
-
name: server.name,
|
|
15352
|
-
command: server.command,
|
|
15353
|
-
args: server.args ?? [],
|
|
15354
|
-
env: server.env ?? []
|
|
15355
|
-
}))
|
|
15356
|
-
};
|
|
15357
|
-
const meta = sessionToolScopeMeta(this.agentLabel);
|
|
15358
|
-
if (meta)
|
|
15359
|
-
params._meta = meta;
|
|
15360
|
-
const result = await this.request("session/load", params);
|
|
15361
|
-
const raw = result && typeof result === "object" ? result : {};
|
|
15362
|
-
await this.applySessionMode(opts.sessionId, raw, opts.mode);
|
|
15363
|
-
return { sessionId: opts.sessionId, raw };
|
|
15348
|
+
current += delta;
|
|
15349
|
+
lastWasText = true;
|
|
15364
15350
|
}
|
|
15365
|
-
|
|
15366
|
-
|
|
15367
|
-
|
|
15368
|
-
|
|
15369
|
-
|
|
15370
|
-
|
|
15371
|
-
|
|
15372
|
-
|
|
15373
|
-
|
|
15351
|
+
if (current)
|
|
15352
|
+
runs.push(current);
|
|
15353
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15354
|
+
return runs.filter((run2) => {
|
|
15355
|
+
if (seen.has(run2))
|
|
15356
|
+
return false;
|
|
15357
|
+
seen.add(run2);
|
|
15358
|
+
return true;
|
|
15359
|
+
});
|
|
15360
|
+
}
|
|
15361
|
+
var RETRY_NARRATION_FRAGMENTS = [
|
|
15362
|
+
// `Retrying (attempt 2/3, waiting 4s)` — the pi/ox-alpha shape.
|
|
15363
|
+
/retrying\s*\((?:attempt|try)\s*\d+\s*\/\s*\d+(?:,\s*(?:waiting|backoff)\s*\d+(?:\.\d+)?s)?\)/gi,
|
|
15364
|
+
// A bare `(attempt 2/3...)` or `[attempt 2/3]` qualifier on its own.
|
|
15365
|
+
/[([](?:attempt|try)\s*\d+\s*\/\s*\d+(?:,\s*(?:waiting|backoff)\s*\d+(?:\.\d+)?s)?[))]\s*/gi,
|
|
15366
|
+
// Standalone narration words with their optional trailing ellipsis.
|
|
15367
|
+
/retrying\s*\.{0,3}/gi,
|
|
15368
|
+
/retried\s*\.{0,3}/gi,
|
|
15369
|
+
/retry\s+finished,?\s*/gi,
|
|
15370
|
+
/retry\s+failed,?\s*/gi,
|
|
15371
|
+
/resuming\.?/gi,
|
|
15372
|
+
/waiting\s+\d+(?:\.\d+)?s/gi
|
|
15373
|
+
];
|
|
15374
|
+
function isPureRetryNarration(text2) {
|
|
15375
|
+
const trimmed = text2.trim();
|
|
15376
|
+
if (!trimmed)
|
|
15377
|
+
return false;
|
|
15378
|
+
let rest = trimmed;
|
|
15379
|
+
for (const fragment of RETRY_NARRATION_FRAGMENTS) {
|
|
15380
|
+
rest = rest.replace(fragment, "");
|
|
15374
15381
|
}
|
|
15375
|
-
|
|
15376
|
-
|
|
15377
|
-
|
|
15378
|
-
|
|
15379
|
-
|
|
15380
|
-
|
|
15381
|
-
|
|
15382
|
-
|
|
15383
|
-
|
|
15384
|
-
|
|
15385
|
-
|
|
15386
|
-
|
|
15387
|
-
|
|
15388
|
-
|
|
15389
|
-
|
|
15390
|
-
* arguments remain valid.
|
|
15391
|
-
*/
|
|
15392
|
-
async sessionPrompt(sessionId, text2, timeoutMs = 12e4, onChunk, onActivity, onToolCalls) {
|
|
15393
|
-
const updates = [];
|
|
15394
|
-
let promptRunId;
|
|
15395
|
-
let requestId;
|
|
15396
|
-
const onUpdate = (u4) => {
|
|
15397
|
-
if (u4.sessionId !== sessionId)
|
|
15398
|
-
return;
|
|
15399
|
-
updates.push(u4);
|
|
15400
|
-
promptRunId ??= this.activeRunIdFromUpdate(u4.update);
|
|
15401
|
-
if (requestId !== void 0)
|
|
15402
|
-
this.resetPendingIdleTimeout(requestId);
|
|
15403
|
-
onActivity?.(agentStreamSnapshot(updates, this.agentLabel));
|
|
15404
|
-
onToolCalls?.(toolCallEntries(updates));
|
|
15405
|
-
if (onChunk) {
|
|
15406
|
-
const delta = agentMessageChunkText(u4.update);
|
|
15407
|
-
if (delta) {
|
|
15408
|
-
const runs = agentMessageRuns(updates, this.agentLabel);
|
|
15409
|
-
onChunk(delta, runs.join("\n\n"), runs.at(-1), runs);
|
|
15410
|
-
}
|
|
15411
|
-
}
|
|
15412
|
-
};
|
|
15413
|
-
this.on("session/update", onUpdate);
|
|
15414
|
-
this.activePromptSessions.add(sessionId);
|
|
15415
|
-
try {
|
|
15416
|
-
const result = await this.request("session/prompt", {
|
|
15417
|
-
sessionId,
|
|
15418
|
-
prompt: typeof text2 === "string" ? [{ type: "text", text: text2 }] : [...text2]
|
|
15419
|
-
}, timeoutMs, (id) => {
|
|
15420
|
-
requestId = id;
|
|
15421
|
-
}, promptPayloadNote(text2));
|
|
15422
|
-
const agentText = finalAgentMessageText(updates, this.agentLabel);
|
|
15423
|
-
const toolCalls = toolCallEntries(updates);
|
|
15424
|
-
return {
|
|
15425
|
-
stopReason: result?.stopReason ?? "end_turn",
|
|
15426
|
-
updates,
|
|
15427
|
-
agentText,
|
|
15428
|
-
toolCalls
|
|
15429
|
-
};
|
|
15430
|
-
} finally {
|
|
15431
|
-
this.activePromptSessions.delete(sessionId);
|
|
15432
|
-
if (promptRunId && this.activeRunIds.get(sessionId) === promptRunId) {
|
|
15433
|
-
this.activeRunIds.delete(sessionId);
|
|
15434
|
-
}
|
|
15435
|
-
this.off("session/update", onUpdate);
|
|
15436
|
-
}
|
|
15382
|
+
return !/[\w]/.test(rest);
|
|
15383
|
+
}
|
|
15384
|
+
function finalAgentMessageText(updates, agentLabel) {
|
|
15385
|
+
const last = agentMessageRuns(updates, agentLabel).at(-1);
|
|
15386
|
+
if (!last || isPureRetryNarration(last))
|
|
15387
|
+
return "";
|
|
15388
|
+
return last;
|
|
15389
|
+
}
|
|
15390
|
+
function describeEmptyTurn(result, agentLabel) {
|
|
15391
|
+
const counts = /* @__PURE__ */ new Map();
|
|
15392
|
+
for (const { update } of result.updates) {
|
|
15393
|
+
const kind = typeof update.sessionUpdate === "string" ? update.sessionUpdate : "unknown";
|
|
15394
|
+
if (kind === "session_info_update" || kind === "available_commands_update")
|
|
15395
|
+
continue;
|
|
15396
|
+
counts.set(kind, (counts.get(kind) ?? 0) + 1);
|
|
15437
15397
|
}
|
|
15438
|
-
|
|
15439
|
-
|
|
15440
|
-
|
|
15441
|
-
|
|
15442
|
-
|
|
15443
|
-
|
|
15444
|
-
|
|
15445
|
-
|
|
15446
|
-
|
|
15447
|
-
|
|
15448
|
-
|
|
15449
|
-
|
|
15450
|
-
|
|
15398
|
+
const stream = counts.size ? `the stream carried only ${[...counts].map(([kind, count]) => `${kind}\xD7${count}`).join(", ")}` : "the stream carried no content updates";
|
|
15399
|
+
const lastRun = agentMessageRuns(result.updates, agentLabel).at(-1);
|
|
15400
|
+
const narration = lastRun && isPureRetryNarration(lastRun) ? `; the last message was retry narration "${lastRun.trim().slice(0, 80)}"` : "";
|
|
15401
|
+
return `harness ended the turn (${result.stopReason}) with no answer text; ${stream}${narration}`;
|
|
15402
|
+
}
|
|
15403
|
+
function updateText(update) {
|
|
15404
|
+
const content = update.content;
|
|
15405
|
+
if (typeof content === "string")
|
|
15406
|
+
return content;
|
|
15407
|
+
return content?.type === "text" ? content.text ?? "" : "";
|
|
15408
|
+
}
|
|
15409
|
+
var THOUGHT_UPDATE_TYPES = /* @__PURE__ */ new Set([
|
|
15410
|
+
"agent_thought_chunk",
|
|
15411
|
+
"agent_thought",
|
|
15412
|
+
"reasoning",
|
|
15413
|
+
"reasoning_chunk",
|
|
15414
|
+
"thinking",
|
|
15415
|
+
"thinking_chunk",
|
|
15416
|
+
"analysis",
|
|
15417
|
+
"analysis_chunk",
|
|
15418
|
+
"progress",
|
|
15419
|
+
"progress_update"
|
|
15420
|
+
]);
|
|
15421
|
+
function agentStreamSnapshot(updates, agentLabel) {
|
|
15422
|
+
const completedMessages = [];
|
|
15423
|
+
let messageText2 = "";
|
|
15424
|
+
let lastWasMessage = false;
|
|
15425
|
+
let thoughtText2 = "";
|
|
15426
|
+
let thoughtRunOpen = false;
|
|
15427
|
+
for (const { update } of updates) {
|
|
15428
|
+
const kind = typeof update.sessionUpdate === "string" ? update.sessionUpdate : "";
|
|
15429
|
+
if (kind === "agent_message_chunk") {
|
|
15430
|
+
const delta = normalizeStreamDelta(agentMessageChunkText(update), agentLabel);
|
|
15431
|
+
if (!delta)
|
|
15432
|
+
continue;
|
|
15433
|
+
if (!lastWasMessage && messageText2 && !/\s$/.test(messageText2) && !CHUNK_CONTINUES_PREVIOUS_WORD.test(delta)) {
|
|
15434
|
+
if (!completedMessages.includes(messageText2))
|
|
15435
|
+
completedMessages.push(messageText2);
|
|
15436
|
+
messageText2 = "";
|
|
15451
15437
|
}
|
|
15452
|
-
|
|
15453
|
-
|
|
15454
|
-
|
|
15455
|
-
|
|
15456
|
-
}
|
|
15457
|
-
const expectedRunId = await this.waitForActiveRun(sessionId, Math.min(timeoutMs, 5e3));
|
|
15458
|
-
const raw = await this.request("_goose/unstable/session/steer", {
|
|
15459
|
-
sessionId,
|
|
15460
|
-
prompt: [{ type: "text", text: text2 }],
|
|
15461
|
-
expectedRunId
|
|
15462
|
-
}, timeoutMs);
|
|
15463
|
-
if (!raw.runId || !raw.messageId) {
|
|
15464
|
-
throw new Error("ACP session steer response missing runId or messageId");
|
|
15438
|
+
messageText2 += delta;
|
|
15439
|
+
lastWasMessage = true;
|
|
15440
|
+
thoughtRunOpen = false;
|
|
15441
|
+
continue;
|
|
15465
15442
|
}
|
|
15466
|
-
|
|
15467
|
-
|
|
15468
|
-
|
|
15469
|
-
|
|
15470
|
-
return this.activeRunIds.get(sessionId) ?? (this.activePromptSessions.has(sessionId) ? `session:${sessionId}` : void 0);
|
|
15471
|
-
}
|
|
15472
|
-
sessionCancel(sessionId) {
|
|
15473
|
-
this.notify("session/cancel", { sessionId });
|
|
15474
|
-
}
|
|
15475
|
-
/**
|
|
15476
|
-
* The portable model/effort setter, confirmed round-tripping on pi/codex/
|
|
15477
|
-
* claude (report `data/buzzy-multiagent-runtimes/report.md` §3.2). The
|
|
15478
|
-
* param is `configId`, not `optionId`. Harnesses that advertise this shape
|
|
15479
|
-
* must use it; `session/set_model` is reserved for the distinct standard
|
|
15480
|
-
* `session/new.models` shape. Returns the runtime's updated `configOptions`.
|
|
15481
|
-
* Callers must validate `configId`/`value` against the
|
|
15482
|
-
* allow-listed catalog before calling this — see
|
|
15483
|
-
* `apps/body/src/model-config.ts`'s `assertModelConfigOptionAllowed`; this
|
|
15484
|
-
* method performs no policy checks of its own.
|
|
15485
|
-
*/
|
|
15486
|
-
async setConfigOption(sessionId, configId, value) {
|
|
15487
|
-
return this.request("session/set_config_option", { sessionId, configId, value });
|
|
15488
|
-
}
|
|
15489
|
-
/**
|
|
15490
|
-
* Standard ACP model setter used only when `session/new.models` supplied the
|
|
15491
|
-
* corresponding safe model axis. Most Beeline harnesses use configOptions;
|
|
15492
|
-
* Grok currently exposes models through this standard method instead.
|
|
15493
|
-
*/
|
|
15494
|
-
async setModel(sessionId, modelId) {
|
|
15495
|
-
return this.request("session/set_model", { sessionId, modelId });
|
|
15496
|
-
}
|
|
15497
|
-
clearTimer(p) {
|
|
15498
|
-
if (p.timer) {
|
|
15499
|
-
clearTimeout(p.timer);
|
|
15500
|
-
p.timer = void 0;
|
|
15443
|
+
if (lastWasMessage && messageText2) {
|
|
15444
|
+
if (!completedMessages.includes(messageText2))
|
|
15445
|
+
completedMessages.push(messageText2);
|
|
15446
|
+
messageText2 = "";
|
|
15501
15447
|
}
|
|
15502
|
-
|
|
15503
|
-
|
|
15504
|
-
|
|
15505
|
-
|
|
15506
|
-
* delta, session/update, tool call). A request only times out after this
|
|
15507
|
-
* much wall-clock time with zero activity, not after a fixed deadline —
|
|
15508
|
-
* so an actively-working turn can run indefinitely while a genuinely
|
|
15509
|
-
* wedged one still gets caught.
|
|
15510
|
-
*/
|
|
15511
|
-
resetPendingIdleTimeout(id) {
|
|
15512
|
-
const p = this.pending.get(id);
|
|
15513
|
-
if (!p || p.idleTimeoutMs === void 0)
|
|
15514
|
-
return;
|
|
15515
|
-
this.clearTimer(p);
|
|
15516
|
-
const { idleTimeoutMs, method, reject, detail } = p;
|
|
15517
|
-
p.timer = setTimeout(() => {
|
|
15518
|
-
this.pending.delete(id);
|
|
15519
|
-
reject(new AcpRequestTimeoutError(method ?? "request", idleTimeoutMs, this.stderrTail, true, detail ?? ""));
|
|
15520
|
-
}, idleTimeoutMs);
|
|
15521
|
-
}
|
|
15522
|
-
onData(chunk) {
|
|
15523
|
-
this.buf += chunk;
|
|
15524
|
-
let nl;
|
|
15525
|
-
while ((nl = this.buf.indexOf("\n")) >= 0) {
|
|
15526
|
-
const line = this.buf.slice(0, nl).trim();
|
|
15527
|
-
this.buf = this.buf.slice(nl + 1);
|
|
15528
|
-
if (!line)
|
|
15529
|
-
continue;
|
|
15530
|
-
let msg;
|
|
15531
|
-
try {
|
|
15532
|
-
msg = JSON.parse(line);
|
|
15533
|
-
} catch {
|
|
15448
|
+
lastWasMessage = false;
|
|
15449
|
+
if (THOUGHT_UPDATE_TYPES.has(kind)) {
|
|
15450
|
+
const delta = normalizeStreamDelta(updateText(update), agentLabel);
|
|
15451
|
+
if (!delta)
|
|
15534
15452
|
continue;
|
|
15535
|
-
|
|
15536
|
-
|
|
15453
|
+
if (!thoughtRunOpen)
|
|
15454
|
+
thoughtText2 = "";
|
|
15455
|
+
thoughtText2 += delta;
|
|
15456
|
+
thoughtRunOpen = true;
|
|
15457
|
+
} else {
|
|
15458
|
+
thoughtRunOpen = false;
|
|
15537
15459
|
}
|
|
15538
15460
|
}
|
|
15539
|
-
|
|
15540
|
-
|
|
15541
|
-
|
|
15542
|
-
|
|
15543
|
-
|
|
15544
|
-
|
|
15545
|
-
|
|
15546
|
-
|
|
15547
|
-
|
|
15548
|
-
|
|
15549
|
-
|
|
15550
|
-
|
|
15551
|
-
|
|
15552
|
-
const meaningful = flattenHarnessCause(this.stderrTail);
|
|
15553
|
-
const extra = [details, meaningful ? `harness stderr: ${meaningful}` : ""].filter(Boolean).join("; ");
|
|
15554
|
-
p.reject(new Error(`ACP error ${err.code}: ${err.message}${extra ? `; ${extra}` : ""}`));
|
|
15555
|
-
} else {
|
|
15556
|
-
p.resolve(msg.result);
|
|
15557
|
-
}
|
|
15558
|
-
return;
|
|
15559
|
-
}
|
|
15560
|
-
if (method && id !== void 0) {
|
|
15561
|
-
if (method === "session/request_permission") {
|
|
15562
|
-
void this.handlePermission(id, msg.params);
|
|
15563
|
-
return;
|
|
15564
|
-
}
|
|
15565
|
-
this.write({ jsonrpc: "2.0", id, result: {} });
|
|
15566
|
-
return;
|
|
15461
|
+
return {
|
|
15462
|
+
messageText: messageText2,
|
|
15463
|
+
...thoughtText2 || completedMessages.length ? { thoughtText: thoughtText2 || completedMessages.at(-1) } : {}
|
|
15464
|
+
};
|
|
15465
|
+
}
|
|
15466
|
+
function toolCallEntries(updates) {
|
|
15467
|
+
const calls = /* @__PURE__ */ new Map();
|
|
15468
|
+
let anonymous = 0;
|
|
15469
|
+
let activeAnonymous;
|
|
15470
|
+
for (const { update } of updates) {
|
|
15471
|
+
const sessionUpdate = update.sessionUpdate;
|
|
15472
|
+
if (sessionUpdate !== "tool_call" && sessionUpdate !== "tool_call_update" && sessionUpdate !== "tool_result") {
|
|
15473
|
+
continue;
|
|
15567
15474
|
}
|
|
15568
|
-
|
|
15569
|
-
|
|
15570
|
-
|
|
15571
|
-
|
|
15572
|
-
|
|
15573
|
-
this.sessionCommands.set(params.sessionId, commands);
|
|
15574
|
-
this.commandsHandler?.(commands);
|
|
15575
|
-
this.emit("commands", { sessionId: params.sessionId, commands });
|
|
15576
|
-
}
|
|
15577
|
-
const toolCallId = params.update.toolCallId;
|
|
15578
|
-
const sessionUpdate = params.update.sessionUpdate;
|
|
15579
|
-
if (typeof toolCallId === "string" && (sessionUpdate === "tool_call" || sessionUpdate === "tool_call_update")) {
|
|
15580
|
-
const metadataKey = `${params.sessionId}\0${toolCallId}`;
|
|
15581
|
-
const status = params.update.status;
|
|
15582
|
-
if (status === "completed" || status === "failed") {
|
|
15583
|
-
this.toolCallMetadata.delete(metadataKey);
|
|
15584
|
-
} else {
|
|
15585
|
-
const existing = this.toolCallMetadata.get(metadataKey);
|
|
15586
|
-
this.toolCallMetadata.set(metadataKey, {
|
|
15587
|
-
...existing,
|
|
15588
|
-
toolCallId,
|
|
15589
|
-
...typeof params.update.title === "string" ? { title: params.update.title } : {},
|
|
15590
|
-
...typeof params.update.kind === "string" ? { kind: params.update.kind } : {},
|
|
15591
|
-
..."rawInput" in params.update ? { rawInput: params.update.rawInput } : {},
|
|
15592
|
-
// `content`/`locations` carry the touched file paths for adapters
|
|
15593
|
-
// whose permission request omits them (codex-acp's file-change
|
|
15594
|
-
// request has no rawInput at all) — the corner worktree guard in
|
|
15595
|
-
// `session-sandbox.ts` reads them.
|
|
15596
|
-
..."content" in params.update ? { content: params.update.content } : {},
|
|
15597
|
-
..."locations" in params.update ? { locations: params.update.locations } : {}
|
|
15598
|
-
});
|
|
15599
|
-
}
|
|
15600
|
-
}
|
|
15601
|
-
const u4 = {
|
|
15602
|
-
sessionId: params.sessionId,
|
|
15603
|
-
update: params.update
|
|
15604
|
-
};
|
|
15605
|
-
const activeRunId = this.activeRunIdFromUpdate(params.update);
|
|
15606
|
-
if (activeRunId)
|
|
15607
|
-
this.activeRunIds.set(params.sessionId, activeRunId);
|
|
15608
|
-
this.emit("session/update", u4);
|
|
15609
|
-
}
|
|
15610
|
-
return;
|
|
15475
|
+
const id = typeof update.toolCallId === "string" ? update.toolCallId : void 0;
|
|
15476
|
+
if (id)
|
|
15477
|
+
activeAnonymous = void 0;
|
|
15478
|
+
if (!id && (sessionUpdate === "tool_call" || !activeAnonymous)) {
|
|
15479
|
+
activeAnonymous = `anonymous-${anonymous++}`;
|
|
15611
15480
|
}
|
|
15481
|
+
const key = id ?? activeAnonymous;
|
|
15482
|
+
const previous = calls.get(key);
|
|
15483
|
+
calls.set(key, {
|
|
15484
|
+
...previous,
|
|
15485
|
+
id: key,
|
|
15486
|
+
...typeof update.title === "string" ? { title: update.title } : {},
|
|
15487
|
+
...typeof update.kind === "string" ? { kind: update.kind } : {},
|
|
15488
|
+
...typeof update.status === "string" ? { status: update.status } : {},
|
|
15489
|
+
...sessionUpdate === "tool_result" ? { resultReceived: true } : {},
|
|
15490
|
+
..."rawInput" in update ? { rawInput: update.rawInput } : {},
|
|
15491
|
+
..."content" in update ? { content: update.content } : {},
|
|
15492
|
+
..."rawOutput" in update ? { rawOutput: update.rawOutput } : {},
|
|
15493
|
+
..."locations" in update ? { locations: update.locations } : {}
|
|
15494
|
+
});
|
|
15495
|
+
if (!id && sessionUpdate === "tool_result")
|
|
15496
|
+
activeAnonymous = void 0;
|
|
15612
15497
|
}
|
|
15613
|
-
|
|
15614
|
-
|
|
15615
|
-
|
|
15616
|
-
|
|
15498
|
+
return [...calls.values()];
|
|
15499
|
+
}
|
|
15500
|
+
var AcpClient = class extends EventEmitter {
|
|
15501
|
+
child = null;
|
|
15502
|
+
buf = "";
|
|
15503
|
+
nextId = 1;
|
|
15504
|
+
pending = /* @__PURE__ */ new Map();
|
|
15505
|
+
activeRunIds = /* @__PURE__ */ new Map();
|
|
15506
|
+
activePromptSessions = /* @__PURE__ */ new Set();
|
|
15507
|
+
/** Tool metadata arrives in session/update just before a sparse permission request. */
|
|
15508
|
+
toolCallMetadata = /* @__PURE__ */ new Map();
|
|
15509
|
+
/** Latest `available_commands_update` per session, keyed by sessionId. */
|
|
15510
|
+
sessionCommands = /* @__PURE__ */ new Map();
|
|
15511
|
+
supportsStandardSteering = false;
|
|
15512
|
+
supportsSessionLoading = false;
|
|
15513
|
+
supportsImagePrompts = false;
|
|
15514
|
+
alive = false;
|
|
15515
|
+
/** Bounded tail of recent stderr, so a spawn/exit failure's rejection text
|
|
15516
|
+
* carries the real reason (e.g. a harness's own "missing API key" notice)
|
|
15517
|
+
* instead of just the bare exit code, in the daemon's log. */
|
|
15518
|
+
stderrTail = "";
|
|
15519
|
+
agentEnv;
|
|
15520
|
+
agentCommand;
|
|
15521
|
+
agentLabel;
|
|
15522
|
+
agentArgs;
|
|
15523
|
+
agentCwd;
|
|
15524
|
+
inheritProcessEnv;
|
|
15525
|
+
autoApprove;
|
|
15526
|
+
osSandbox;
|
|
15527
|
+
permissionHandler;
|
|
15528
|
+
permissionAllowlist;
|
|
15529
|
+
commandsHandler;
|
|
15530
|
+
constructor(opts) {
|
|
15531
|
+
super();
|
|
15532
|
+
const command = opts.agentCommand ?? opts.agentBinary;
|
|
15533
|
+
if (!command)
|
|
15534
|
+
throw new Error("ACP agent command is required");
|
|
15535
|
+
this.agentCommand = command;
|
|
15536
|
+
this.agentLabel = opts.agentLabel ?? command;
|
|
15537
|
+
this.agentArgs = [...opts.agentArgs ?? []];
|
|
15538
|
+
this.agentEnv = opts.agentEnv;
|
|
15539
|
+
if (opts.agentCwd)
|
|
15540
|
+
this.agentCwd = opts.agentCwd;
|
|
15541
|
+
this.inheritProcessEnv = opts.inheritProcessEnv ?? process.env.BUZZY_BODY_AGENT_ENV_INHERIT === "1";
|
|
15542
|
+
this.autoApprove = opts.autoApprovePermissions ?? true;
|
|
15543
|
+
this.osSandbox = opts.osSandbox ?? false;
|
|
15544
|
+
this.permissionHandler = opts.permissionHandler;
|
|
15545
|
+
this.permissionAllowlist = opts.permissionAllowlist;
|
|
15546
|
+
this.commandsHandler = opts.onCommands;
|
|
15617
15547
|
}
|
|
15618
|
-
|
|
15619
|
-
|
|
15620
|
-
|
|
15621
|
-
|
|
15622
|
-
|
|
15623
|
-
|
|
15624
|
-
|
|
15625
|
-
|
|
15626
|
-
|
|
15627
|
-
|
|
15628
|
-
|
|
15629
|
-
|
|
15630
|
-
|
|
15631
|
-
};
|
|
15632
|
-
const timer = setTimeout(() => {
|
|
15633
|
-
cleanup();
|
|
15634
|
-
reject(new Error(`ACP session ${sessionId} has no active run to steer`));
|
|
15635
|
-
}, timeoutMs);
|
|
15636
|
-
const cleanup = () => {
|
|
15637
|
-
clearTimeout(timer);
|
|
15638
|
-
this.off("session/update", onUpdate);
|
|
15639
|
-
};
|
|
15640
|
-
this.on("session/update", onUpdate);
|
|
15548
|
+
async start(timeoutMs = 6e4) {
|
|
15549
|
+
if (this.alive)
|
|
15550
|
+
return;
|
|
15551
|
+
this.child = spawn5(this.agentCommand, this.agentArgs, {
|
|
15552
|
+
// agentEnv is the child's whole environment: buildAgentEnv's allowlist is
|
|
15553
|
+
// a real boundary, not a decorative one layered over a full inherit.
|
|
15554
|
+
env: this.inheritProcessEnv ? { ...process.env, ...this.agentEnv } : this.agentEnv,
|
|
15555
|
+
...this.agentCwd ? { cwd: this.agentCwd } : {},
|
|
15556
|
+
// The harness and every tool it spawns share a disposable process
|
|
15557
|
+
// group. A hard turn deadline can therefore retire the whole tree,
|
|
15558
|
+
// never just the ACP parent while a shell/compiler keeps running.
|
|
15559
|
+
detached: process.platform !== "win32",
|
|
15560
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
15641
15561
|
});
|
|
15642
|
-
|
|
15643
|
-
|
|
15644
|
-
|
|
15645
|
-
|
|
15646
|
-
|
|
15647
|
-
|
|
15648
|
-
|
|
15649
|
-
|
|
15650
|
-
|
|
15651
|
-
|
|
15652
|
-
|
|
15653
|
-
|
|
15654
|
-
|
|
15655
|
-
|
|
15656
|
-
|
|
15562
|
+
this.alive = true;
|
|
15563
|
+
this.stderrTail = "";
|
|
15564
|
+
this.child.stderr.setEncoding("utf8");
|
|
15565
|
+
this.child.stderr.on("data", (chunk) => {
|
|
15566
|
+
this.stderrTail = (this.stderrTail + chunk).slice(-STDERR_TAIL_MAX_CHARS2);
|
|
15567
|
+
this.emit("stderr", chunk);
|
|
15568
|
+
});
|
|
15569
|
+
this.child.stdout.setEncoding("utf8");
|
|
15570
|
+
this.child.stdout.on("data", (chunk) => this.onData(chunk));
|
|
15571
|
+
this.child.on("exit", (code, signal) => {
|
|
15572
|
+
this.alive = false;
|
|
15573
|
+
const meaningful = flattenHarnessCause(this.stderrTail);
|
|
15574
|
+
const stderrSuffix = meaningful ? `: ${meaningful}` : "";
|
|
15575
|
+
for (const [, p] of this.pending) {
|
|
15576
|
+
this.clearTimer(p);
|
|
15577
|
+
p.reject(new Error(`ACP agent ${this.agentLabel} exited code=${code} signal=${signal}${stderrSuffix}`));
|
|
15657
15578
|
}
|
|
15658
|
-
|
|
15659
|
-
|
|
15660
|
-
|
|
15661
|
-
|
|
15662
|
-
|
|
15663
|
-
|
|
15579
|
+
this.pending.clear();
|
|
15580
|
+
this.activeRunIds.clear();
|
|
15581
|
+
this.activePromptSessions.clear();
|
|
15582
|
+
this.toolCallMetadata.clear();
|
|
15583
|
+
this.sessionCommands.clear();
|
|
15584
|
+
this.emit("exit", { code, signal });
|
|
15585
|
+
});
|
|
15586
|
+
this.child.on("error", (err) => {
|
|
15587
|
+
this.alive = false;
|
|
15588
|
+
for (const [, p] of this.pending) {
|
|
15589
|
+
clearTimeout(p.timer);
|
|
15590
|
+
p.reject(err);
|
|
15591
|
+
}
|
|
15592
|
+
this.pending.clear();
|
|
15593
|
+
if (this.listenerCount("error") > 0)
|
|
15594
|
+
this.emit("error", err);
|
|
15595
|
+
});
|
|
15596
|
+
const initResult = await this.request("initialize", {
|
|
15597
|
+
protocolVersion: 1,
|
|
15598
|
+
clientCapabilities: {}
|
|
15599
|
+
}, timeoutMs);
|
|
15600
|
+
const initMeta = initResult._meta;
|
|
15601
|
+
const steering = initMeta?.steering;
|
|
15602
|
+
this.supportsStandardSteering = steering?.supported === true;
|
|
15603
|
+
const agentCapabilities = initResult.agentCapabilities;
|
|
15604
|
+
this.supportsSessionLoading = agentCapabilities?.loadSession === true;
|
|
15605
|
+
this.supportsImagePrompts = agentCapabilities?.promptCapabilities?.image === true;
|
|
15606
|
+
this.emit("initialized", initResult);
|
|
15607
|
+
if (!isPiAcpHarness(this.agentLabel))
|
|
15608
|
+
this.notify("notifications/initialized", {});
|
|
15609
|
+
}
|
|
15610
|
+
async stop() {
|
|
15611
|
+
if (!this.alive)
|
|
15612
|
+
return;
|
|
15613
|
+
const child = this.child;
|
|
15614
|
+
if (!child)
|
|
15615
|
+
return;
|
|
15616
|
+
try {
|
|
15617
|
+
if (this.child?.stdin.writable) {
|
|
15618
|
+
this.notify("shutdown", {});
|
|
15664
15619
|
}
|
|
15620
|
+
} catch {
|
|
15665
15621
|
}
|
|
15666
|
-
|
|
15667
|
-
|
|
15668
|
-
|
|
15669
|
-
|
|
15670
|
-
this.toolCallMetadata.delete(metadataKey);
|
|
15671
|
-
this.write({
|
|
15672
|
-
jsonrpc: "2.0",
|
|
15673
|
-
id,
|
|
15674
|
-
result: {
|
|
15675
|
-
outcome: { outcome: "selected", optionId: allow.optionId }
|
|
15676
|
-
}
|
|
15677
|
-
});
|
|
15678
|
-
return;
|
|
15679
|
-
}
|
|
15622
|
+
await new Promise((r2) => setTimeout(r2, 300));
|
|
15623
|
+
try {
|
|
15624
|
+
killChildProcessGroup(child, "SIGTERM");
|
|
15625
|
+
} catch {
|
|
15680
15626
|
}
|
|
15681
|
-
|
|
15682
|
-
|
|
15683
|
-
|
|
15684
|
-
|
|
15685
|
-
this.write({
|
|
15686
|
-
jsonrpc: "2.0",
|
|
15687
|
-
id,
|
|
15688
|
-
result: {
|
|
15689
|
-
outcome: { outcome: "selected", optionId: reject.optionId }
|
|
15690
|
-
}
|
|
15691
|
-
});
|
|
15692
|
-
return;
|
|
15627
|
+
await new Promise((r2) => setTimeout(r2, 500));
|
|
15628
|
+
try {
|
|
15629
|
+
killChildProcessGroup(child, "SIGKILL");
|
|
15630
|
+
} catch {
|
|
15693
15631
|
}
|
|
15694
|
-
|
|
15695
|
-
|
|
15696
|
-
this.
|
|
15697
|
-
|
|
15698
|
-
|
|
15699
|
-
|
|
15700
|
-
|
|
15632
|
+
this.child = null;
|
|
15633
|
+
this.alive = false;
|
|
15634
|
+
this.activeRunIds.clear();
|
|
15635
|
+
this.activePromptSessions.clear();
|
|
15636
|
+
this.toolCallMetadata.clear();
|
|
15637
|
+
}
|
|
15638
|
+
get isAlive() {
|
|
15639
|
+
return this.alive;
|
|
15640
|
+
}
|
|
15641
|
+
/** Host pid of the harness wrapper (bwrap when enabled). */
|
|
15642
|
+
processPid() {
|
|
15643
|
+
return this.child?.pid;
|
|
15644
|
+
}
|
|
15645
|
+
/** The latest command list this harness advertised for a session, if any. */
|
|
15646
|
+
sessionCommandsFor(sessionId) {
|
|
15647
|
+
return this.sessionCommands.get(sessionId) ?? [];
|
|
15648
|
+
}
|
|
15649
|
+
/** Whether this live ACP process advertised the stable `session/load` method. */
|
|
15650
|
+
canLoadSession() {
|
|
15651
|
+
return this.supportsSessionLoading;
|
|
15652
|
+
}
|
|
15653
|
+
/** Whether this live ACP process accepts `image` prompt blocks (`promptCapabilities.image`). */
|
|
15654
|
+
canPromptWithImages() {
|
|
15655
|
+
return this.supportsImagePrompts;
|
|
15656
|
+
}
|
|
15657
|
+
async sessionNew(opts) {
|
|
15658
|
+
const params = {
|
|
15659
|
+
cwd: opts.cwd,
|
|
15660
|
+
mcpServers: (opts.mcpServers ?? []).map((s) => ({
|
|
15661
|
+
name: s.name,
|
|
15662
|
+
command: s.command,
|
|
15663
|
+
args: s.args ?? [],
|
|
15664
|
+
env: s.env ?? []
|
|
15665
|
+
}))
|
|
15666
|
+
};
|
|
15667
|
+
if (opts.systemPrompt)
|
|
15668
|
+
params.systemPrompt = opts.systemPrompt;
|
|
15669
|
+
const meta = {
|
|
15670
|
+
...sessionToolScopeMeta(this.agentLabel) ?? {}
|
|
15671
|
+
};
|
|
15672
|
+
if (opts.systemPrompt && harnessReadsMetaSystemPrompt(this.agentLabel)) {
|
|
15673
|
+
meta.systemPrompt = { append: opts.systemPrompt };
|
|
15674
|
+
}
|
|
15675
|
+
if (Object.keys(meta).length > 0)
|
|
15676
|
+
params._meta = meta;
|
|
15677
|
+
const raw = await this.request("session/new", params, opts.timeoutMs ?? 6e4);
|
|
15678
|
+
const sessionId = raw.sessionId;
|
|
15679
|
+
if (!sessionId)
|
|
15680
|
+
throw new Error("session/new missing sessionId");
|
|
15681
|
+
await this.applySessionMode(sessionId, raw, opts.mode);
|
|
15682
|
+
return { sessionId, raw };
|
|
15701
15683
|
}
|
|
15702
15684
|
/**
|
|
15703
|
-
*
|
|
15704
|
-
*
|
|
15705
|
-
*
|
|
15706
|
-
*
|
|
15685
|
+
* Reopen a provider-persisted logical conversation in this new ACP process.
|
|
15686
|
+
* Unlike `session/new`, load accepts no system prompt: the harness restores
|
|
15687
|
+
* its own conversation and instructions instead of receiving transcript
|
|
15688
|
+
* text from Body again.
|
|
15707
15689
|
*/
|
|
15708
|
-
|
|
15709
|
-
if (!this.
|
|
15710
|
-
|
|
15690
|
+
async sessionLoad(opts) {
|
|
15691
|
+
if (!this.supportsSessionLoading) {
|
|
15692
|
+
throw new Error("ACP agent does not advertise session/load");
|
|
15711
15693
|
}
|
|
15712
|
-
const
|
|
15713
|
-
|
|
15714
|
-
|
|
15715
|
-
|
|
15716
|
-
|
|
15717
|
-
|
|
15718
|
-
|
|
15719
|
-
|
|
15720
|
-
|
|
15721
|
-
|
|
15722
|
-
|
|
15723
|
-
|
|
15724
|
-
|
|
15725
|
-
|
|
15726
|
-
|
|
15727
|
-
|
|
15728
|
-
|
|
15729
|
-
});
|
|
15730
|
-
}
|
|
15731
|
-
notify(method, params) {
|
|
15732
|
-
this.write({ jsonrpc: "2.0", method, params });
|
|
15694
|
+
const params = {
|
|
15695
|
+
sessionId: opts.sessionId,
|
|
15696
|
+
cwd: opts.cwd,
|
|
15697
|
+
mcpServers: (opts.mcpServers ?? []).map((server) => ({
|
|
15698
|
+
name: server.name,
|
|
15699
|
+
command: server.command,
|
|
15700
|
+
args: server.args ?? [],
|
|
15701
|
+
env: server.env ?? []
|
|
15702
|
+
}))
|
|
15703
|
+
};
|
|
15704
|
+
const meta = sessionToolScopeMeta(this.agentLabel);
|
|
15705
|
+
if (meta)
|
|
15706
|
+
params._meta = meta;
|
|
15707
|
+
const result = await this.request("session/load", params);
|
|
15708
|
+
const raw = result && typeof result === "object" ? result : {};
|
|
15709
|
+
await this.applySessionMode(opts.sessionId, raw, opts.mode);
|
|
15710
|
+
return { sessionId: opts.sessionId, raw };
|
|
15733
15711
|
}
|
|
15734
|
-
|
|
15735
|
-
if (!
|
|
15712
|
+
async applySessionMode(sessionId, raw, mode) {
|
|
15713
|
+
if (!mode)
|
|
15736
15714
|
return;
|
|
15737
|
-
|
|
15738
|
-
|
|
15739
|
-
|
|
15740
|
-
|
|
15741
|
-
|
|
15742
|
-
|
|
15743
|
-
function isAllowedAgentModelConfigCategory(category) {
|
|
15744
|
-
return ALLOWED.has(category);
|
|
15745
|
-
}
|
|
15746
|
-
|
|
15747
|
-
// apps/body/dist/model-config.js
|
|
15748
|
-
import { lstatSync as lstatSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
15749
|
-
import { basename as basename3, resolve as resolve4 } from "node:path";
|
|
15750
|
-
var GROK_SESSION_MODEL_AXIS_ID = "beeline:grok-session-model";
|
|
15751
|
-
var GROK_LAUNCH_EFFORT_AXIS_ID = "beeline:grok-launch-reasoning-effort";
|
|
15752
|
-
function isGrokAgentCommand(agent) {
|
|
15753
|
-
if (agent.kind === "grok")
|
|
15754
|
-
return true;
|
|
15755
|
-
if (agent.kind && agent.kind !== "custom")
|
|
15756
|
-
return false;
|
|
15757
|
-
return /^grok(?:\.[a-z0-9]+)?$/i.test(basename3(agent.command));
|
|
15758
|
-
}
|
|
15759
|
-
function agentArgsWithModelSelection(agent, selection) {
|
|
15760
|
-
const original = [...agent.args];
|
|
15761
|
-
if (!selection || !isGrokAgentCommand(agent) || !original.includes("agent") || !original.includes("stdio")) {
|
|
15762
|
-
return original;
|
|
15715
|
+
const modes = raw.modes;
|
|
15716
|
+
const candidates = mode === "readonly" ? roomModeCandidates(this.agentLabel, { osSandbox: this.osSandbox }) : cornerAutonomyModeCandidates(this.agentLabel);
|
|
15717
|
+
const target = modes?.availableModes?.find((candidate) => candidate.id && candidates.includes(candidate.id))?.id;
|
|
15718
|
+
if (!target || modes?.currentModeId === target)
|
|
15719
|
+
return;
|
|
15720
|
+
await this.request("session/set_mode", { sessionId, modeId: target });
|
|
15763
15721
|
}
|
|
15764
|
-
|
|
15765
|
-
|
|
15766
|
-
|
|
15767
|
-
|
|
15768
|
-
|
|
15769
|
-
|
|
15770
|
-
|
|
15771
|
-
|
|
15772
|
-
|
|
15722
|
+
/**
|
|
15723
|
+
* Run one prompt turn. `timeoutMs` is an idle window, not a hard cap on
|
|
15724
|
+
* turn length: every `session/update` for this session (message chunk,
|
|
15725
|
+
* tool call, or otherwise) re-arms it, so an actively-working agent can
|
|
15726
|
+
* run indefinitely while a genuinely wedged one (zero activity for
|
|
15727
|
+
* `timeoutMs`) still gets cancelled. `onChunk` is the ACP-boundary
|
|
15728
|
+
* streaming hook: called for every incremental `agent_message_chunk`
|
|
15729
|
+
* delta as it arrives, so a caller can project live text without waiting
|
|
15730
|
+
* for the turn to finish. A harness that only emits a final message
|
|
15731
|
+
* (never chunks) simply never invokes it — `agentText` is unaffected
|
|
15732
|
+
* either way. `onActivity`, when given, fires on every `session/update`
|
|
15733
|
+
* regardless of kind (the same trigger that re-arms the idle timer above),
|
|
15734
|
+
* so a caller can drive its own shorter "still working" notice off the
|
|
15735
|
+
* identical genuine-activity signal without duplicating the timeout logic.
|
|
15736
|
+
* It receives the current lane snapshot; existing callbacks that ignore
|
|
15737
|
+
* arguments remain valid.
|
|
15738
|
+
*/
|
|
15739
|
+
async sessionPrompt(sessionId, text2, timeoutMs = 12e4, onChunk, onActivity, onToolCalls) {
|
|
15740
|
+
const updates = [];
|
|
15741
|
+
let promptRunId;
|
|
15742
|
+
let requestId;
|
|
15743
|
+
const onUpdate = (u4) => {
|
|
15744
|
+
if (u4.sessionId !== sessionId)
|
|
15745
|
+
return;
|
|
15746
|
+
updates.push(u4);
|
|
15747
|
+
promptRunId ??= this.activeRunIdFromUpdate(u4.update);
|
|
15748
|
+
if (requestId !== void 0)
|
|
15749
|
+
this.resetPendingIdleTimeout(requestId);
|
|
15750
|
+
onActivity?.(agentStreamSnapshot(updates, this.agentLabel));
|
|
15751
|
+
onToolCalls?.(toolCallEntries(updates));
|
|
15752
|
+
if (onChunk) {
|
|
15753
|
+
const delta = agentMessageChunkText(u4.update);
|
|
15754
|
+
if (delta) {
|
|
15755
|
+
const runs = agentMessageRuns(updates, this.agentLabel);
|
|
15756
|
+
onChunk(delta, runs.join("\n\n"), runs.at(-1), runs);
|
|
15757
|
+
}
|
|
15758
|
+
}
|
|
15759
|
+
};
|
|
15760
|
+
this.on("session/update", onUpdate);
|
|
15761
|
+
this.activePromptSessions.add(sessionId);
|
|
15762
|
+
try {
|
|
15763
|
+
const result = await this.request("session/prompt", {
|
|
15764
|
+
sessionId,
|
|
15765
|
+
prompt: typeof text2 === "string" ? [{ type: "text", text: text2 }] : [...text2]
|
|
15766
|
+
}, timeoutMs, (id) => {
|
|
15767
|
+
requestId = id;
|
|
15768
|
+
}, promptPayloadNote(text2));
|
|
15769
|
+
const agentText = finalAgentMessageText(updates, this.agentLabel);
|
|
15770
|
+
const toolCalls = toolCallEntries(updates);
|
|
15771
|
+
return {
|
|
15772
|
+
stopReason: result?.stopReason ?? "end_turn",
|
|
15773
|
+
updates,
|
|
15774
|
+
agentText,
|
|
15775
|
+
toolCalls
|
|
15776
|
+
};
|
|
15777
|
+
} finally {
|
|
15778
|
+
this.activePromptSessions.delete(sessionId);
|
|
15779
|
+
if (promptRunId && this.activeRunIds.get(sessionId) === promptRunId) {
|
|
15780
|
+
this.activeRunIds.delete(sessionId);
|
|
15781
|
+
}
|
|
15782
|
+
this.off("session/update", onUpdate);
|
|
15773
15783
|
}
|
|
15774
|
-
stripped.push(arg);
|
|
15775
15784
|
}
|
|
15776
|
-
|
|
15777
|
-
|
|
15778
|
-
|
|
15779
|
-
|
|
15780
|
-
|
|
15781
|
-
|
|
15782
|
-
|
|
15783
|
-
|
|
15784
|
-
|
|
15785
|
-
|
|
15786
|
-
|
|
15787
|
-
|
|
15788
|
-
|
|
15789
|
-
|
|
15790
|
-
|
|
15791
|
-
|
|
15792
|
-
|
|
15793
|
-
|
|
15794
|
-
const rightParts = right.choice.id.match(/\d+/g)?.map(Number) ?? [];
|
|
15795
|
-
for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) {
|
|
15796
|
-
const delta = (rightParts[index] ?? -1) - (leftParts[index] ?? -1);
|
|
15797
|
-
if (delta !== 0)
|
|
15798
|
-
return delta;
|
|
15785
|
+
/**
|
|
15786
|
+
* Inject follow-up input into the prompt currently running for this session.
|
|
15787
|
+
* buzz-agent advertises the target run through ACP session metadata; binding
|
|
15788
|
+
* the request to that id prevents a late message from steering a newer turn.
|
|
15789
|
+
*/
|
|
15790
|
+
async sessionSteer(sessionId, text2, timeoutMs = 6e4) {
|
|
15791
|
+
if (this.supportsStandardSteering) {
|
|
15792
|
+
const raw2 = await this.request("_session/steering", {
|
|
15793
|
+
sessionId,
|
|
15794
|
+
prompt: [{ type: "text", text: text2 }]
|
|
15795
|
+
}, timeoutMs);
|
|
15796
|
+
if (!raw2.outcome || raw2.outcome === "failed") {
|
|
15797
|
+
throw new Error("ACP session steering failed");
|
|
15798
|
+
}
|
|
15799
|
+
return {
|
|
15800
|
+
runId: this.activeRunId(sessionId) ?? `session:${sessionId}`,
|
|
15801
|
+
messageId: raw2.outcome
|
|
15802
|
+
};
|
|
15799
15803
|
}
|
|
15800
|
-
|
|
15801
|
-
|
|
15802
|
-
|
|
15803
|
-
|
|
15804
|
-
|
|
15805
|
-
|
|
15806
|
-
|
|
15807
|
-
|
|
15808
|
-
continue;
|
|
15809
|
-
const record3 = entry;
|
|
15810
|
-
const id = record3.id;
|
|
15811
|
-
const category = record3.category;
|
|
15812
|
-
if (typeof id !== "string" || typeof category !== "string")
|
|
15813
|
-
continue;
|
|
15814
|
-
const rawChoices = Array.isArray(record3.options) ? record3.options : [];
|
|
15815
|
-
const choices = [];
|
|
15816
|
-
for (const rawChoice of rawChoices) {
|
|
15817
|
-
if (!rawChoice || typeof rawChoice !== "object")
|
|
15818
|
-
continue;
|
|
15819
|
-
const choice = rawChoice;
|
|
15820
|
-
const choiceId = advertisedChoiceId(choice);
|
|
15821
|
-
if (!choiceId)
|
|
15822
|
-
continue;
|
|
15823
|
-
choices.push({
|
|
15824
|
-
id: choiceId,
|
|
15825
|
-
...typeof choice.name === "string" ? { name: choice.name } : {}
|
|
15826
|
-
});
|
|
15804
|
+
const expectedRunId = await this.waitForActiveRun(sessionId, Math.min(timeoutMs, 5e3));
|
|
15805
|
+
const raw = await this.request("_goose/unstable/session/steer", {
|
|
15806
|
+
sessionId,
|
|
15807
|
+
prompt: [{ type: "text", text: text2 }],
|
|
15808
|
+
expectedRunId
|
|
15809
|
+
}, timeoutMs);
|
|
15810
|
+
if (!raw.runId || !raw.messageId) {
|
|
15811
|
+
throw new Error("ACP session steer response missing runId or messageId");
|
|
15827
15812
|
}
|
|
15828
|
-
|
|
15829
|
-
id,
|
|
15830
|
-
category,
|
|
15831
|
-
...typeof record3.currentValue === "string" ? { currentValue: record3.currentValue } : {},
|
|
15832
|
-
options: category === "model" ? sortModelChoicesNewestFirst(choices) : choices
|
|
15833
|
-
});
|
|
15813
|
+
return { runId: raw.runId, messageId: raw.messageId };
|
|
15834
15814
|
}
|
|
15835
|
-
|
|
15836
|
-
|
|
15837
|
-
return
|
|
15838
|
-
const modelState = models;
|
|
15839
|
-
const availableModels = Array.isArray(modelState.availableModels) ? modelState.availableModels : [];
|
|
15840
|
-
const modelChoices = [];
|
|
15841
|
-
for (const entry of availableModels) {
|
|
15842
|
-
if (!entry || typeof entry !== "object")
|
|
15843
|
-
continue;
|
|
15844
|
-
const model = entry;
|
|
15845
|
-
if (typeof model.modelId !== "string" || !model.modelId)
|
|
15846
|
-
continue;
|
|
15847
|
-
modelChoices.push({
|
|
15848
|
-
id: model.modelId,
|
|
15849
|
-
...typeof model.name === "string" ? { name: model.name } : {}
|
|
15850
|
-
});
|
|
15815
|
+
/** Active ACP run for a session, if the agent has advertised one. */
|
|
15816
|
+
activeRunId(sessionId) {
|
|
15817
|
+
return this.activeRunIds.get(sessionId) ?? (this.activePromptSessions.has(sessionId) ? `session:${sessionId}` : void 0);
|
|
15851
15818
|
}
|
|
15852
|
-
|
|
15853
|
-
|
|
15854
|
-
result.push({
|
|
15855
|
-
id: GROK_SESSION_MODEL_AXIS_ID,
|
|
15856
|
-
category: "model",
|
|
15857
|
-
...currentModelId ? { currentValue: currentModelId } : {},
|
|
15858
|
-
options: sortModelChoicesNewestFirst(modelChoices)
|
|
15859
|
-
});
|
|
15819
|
+
sessionCancel(sessionId) {
|
|
15820
|
+
this.notify("session/cancel", { sessionId });
|
|
15860
15821
|
}
|
|
15861
|
-
|
|
15862
|
-
|
|
15863
|
-
|
|
15864
|
-
|
|
15865
|
-
|
|
15866
|
-
|
|
15867
|
-
|
|
15868
|
-
|
|
15869
|
-
|
|
15870
|
-
|
|
15871
|
-
|
|
15872
|
-
|
|
15873
|
-
|
|
15874
|
-
effortChoices.push({
|
|
15875
|
-
id,
|
|
15876
|
-
...typeof effort.label === "string" ? { name: effort.label } : typeof effort.name === "string" ? { name: effort.name } : {}
|
|
15877
|
-
});
|
|
15822
|
+
/**
|
|
15823
|
+
* The portable model/effort setter, confirmed round-tripping on pi/codex/
|
|
15824
|
+
* claude (report `data/buzzy-multiagent-runtimes/report.md` §3.2). The
|
|
15825
|
+
* param is `configId`, not `optionId`. Harnesses that advertise this shape
|
|
15826
|
+
* must use it; `session/set_model` is reserved for the distinct standard
|
|
15827
|
+
* `session/new.models` shape. Returns the runtime's updated `configOptions`.
|
|
15828
|
+
* Callers must validate `configId`/`value` against the
|
|
15829
|
+
* allow-listed catalog before calling this — see
|
|
15830
|
+
* `apps/body/src/model-config.ts`'s `assertModelConfigOptionAllowed`; this
|
|
15831
|
+
* method performs no policy checks of its own.
|
|
15832
|
+
*/
|
|
15833
|
+
async setConfigOption(sessionId, configId, value) {
|
|
15834
|
+
return this.request("session/set_config_option", { sessionId, configId, value });
|
|
15878
15835
|
}
|
|
15879
|
-
|
|
15880
|
-
|
|
15881
|
-
|
|
15882
|
-
|
|
15883
|
-
|
|
15884
|
-
|
|
15885
|
-
|
|
15886
|
-
options: effortChoices
|
|
15887
|
-
});
|
|
15836
|
+
/**
|
|
15837
|
+
* Standard ACP model setter used only when `session/new.models` supplied the
|
|
15838
|
+
* corresponding safe model axis. Most Beeline harnesses use configOptions;
|
|
15839
|
+
* Grok currently exposes models through this standard method instead.
|
|
15840
|
+
*/
|
|
15841
|
+
async setModel(sessionId, modelId) {
|
|
15842
|
+
return this.request("session/set_model", { sessionId, modelId });
|
|
15888
15843
|
}
|
|
15889
|
-
|
|
15890
|
-
|
|
15891
|
-
|
|
15892
|
-
|
|
15893
|
-
}
|
|
15894
|
-
var PROVIDER_CREDENTIAL_ENV_HINTS = {
|
|
15895
|
-
openai: ["OPENAI_API_KEY", "OPENAI_COMPAT_API_KEY"],
|
|
15896
|
-
openrouter: ["OPENROUTER_API_KEY"],
|
|
15897
|
-
anthropic: ["ANTHROPIC_API_KEY"],
|
|
15898
|
-
google: ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
|
|
15899
|
-
groq: ["GROQ_API_KEY"],
|
|
15900
|
-
mistral: ["MISTRAL_API_KEY"],
|
|
15901
|
-
deepseek: ["DEEPSEEK_API_KEY"],
|
|
15902
|
-
xai: ["XAI_API_KEY"],
|
|
15903
|
-
cohere: ["COHERE_API_KEY"],
|
|
15904
|
-
together: ["TOGETHER_API_KEY"],
|
|
15905
|
-
fireworks: ["FIREWORKS_API_KEY"]
|
|
15906
|
-
};
|
|
15907
|
-
function credentialedProviders(env) {
|
|
15908
|
-
const held = /* @__PURE__ */ new Set();
|
|
15909
|
-
for (const [provider, names] of Object.entries(PROVIDER_CREDENTIAL_ENV_HINTS)) {
|
|
15910
|
-
if (names.some((name) => Boolean(env[name]?.trim())))
|
|
15911
|
-
held.add(provider);
|
|
15844
|
+
clearTimer(p) {
|
|
15845
|
+
if (p.timer) {
|
|
15846
|
+
clearTimeout(p.timer);
|
|
15847
|
+
p.timer = void 0;
|
|
15848
|
+
}
|
|
15912
15849
|
}
|
|
15913
|
-
|
|
15914
|
-
|
|
15915
|
-
|
|
15916
|
-
|
|
15917
|
-
|
|
15918
|
-
|
|
15919
|
-
|
|
15920
|
-
|
|
15921
|
-
|
|
15922
|
-
|
|
15923
|
-
|
|
15924
|
-
|
|
15925
|
-
|
|
15926
|
-
|
|
15850
|
+
/**
|
|
15851
|
+
* Re-arm a pending request's idle timer from its full `idleTimeoutMs`,
|
|
15852
|
+
* called on every ACP activity signal for that request (streamed text
|
|
15853
|
+
* delta, session/update, tool call). A request only times out after this
|
|
15854
|
+
* much wall-clock time with zero activity, not after a fixed deadline —
|
|
15855
|
+
* so an actively-working turn can run indefinitely while a genuinely
|
|
15856
|
+
* wedged one still gets caught.
|
|
15857
|
+
*/
|
|
15858
|
+
resetPendingIdleTimeout(id) {
|
|
15859
|
+
const p = this.pending.get(id);
|
|
15860
|
+
if (!p || p.idleTimeoutMs === void 0)
|
|
15861
|
+
return;
|
|
15862
|
+
this.clearTimer(p);
|
|
15863
|
+
const { idleTimeoutMs, method, reject, detail } = p;
|
|
15864
|
+
p.timer = setTimeout(() => {
|
|
15865
|
+
this.pending.delete(id);
|
|
15866
|
+
reject(new AcpRequestTimeoutError(method ?? "request", idleTimeoutMs, this.stderrTail, true, detail ?? ""));
|
|
15867
|
+
}, idleTimeoutMs);
|
|
15868
|
+
}
|
|
15869
|
+
onData(chunk) {
|
|
15870
|
+
this.buf += chunk;
|
|
15871
|
+
let nl;
|
|
15872
|
+
while ((nl = this.buf.indexOf("\n")) >= 0) {
|
|
15873
|
+
const line = this.buf.slice(0, nl).trim();
|
|
15874
|
+
this.buf = this.buf.slice(nl + 1);
|
|
15875
|
+
if (!line)
|
|
15876
|
+
continue;
|
|
15877
|
+
let msg;
|
|
15878
|
+
try {
|
|
15879
|
+
msg = JSON.parse(line);
|
|
15880
|
+
} catch {
|
|
15881
|
+
continue;
|
|
15882
|
+
}
|
|
15883
|
+
this.handleMessage(msg);
|
|
15884
|
+
}
|
|
15885
|
+
}
|
|
15886
|
+
handleMessage(msg) {
|
|
15887
|
+
const id = msg.id;
|
|
15888
|
+
const method = msg.method;
|
|
15889
|
+
if (id !== void 0 && !method) {
|
|
15890
|
+
const n3 = Number(id);
|
|
15891
|
+
const p = this.pending.get(n3);
|
|
15892
|
+
if (!p)
|
|
15893
|
+
return;
|
|
15894
|
+
this.pending.delete(n3);
|
|
15895
|
+
this.clearTimer(p);
|
|
15896
|
+
if (msg.error) {
|
|
15897
|
+
const err = msg.error;
|
|
15898
|
+
const details = typeof err.data?.details === "string" && err.data.details.trim() ? err.data.details.replace(/\s+/g, " ").trim() : "";
|
|
15899
|
+
const meaningful = flattenHarnessCause(this.stderrTail);
|
|
15900
|
+
const extra = [details, meaningful ? `harness stderr: ${meaningful}` : ""].filter(Boolean).join("; ");
|
|
15901
|
+
p.reject(new Error(`ACP error ${err.code}: ${err.message}${extra ? `; ${extra}` : ""}`));
|
|
15902
|
+
} else {
|
|
15903
|
+
p.resolve(msg.result);
|
|
15904
|
+
}
|
|
15905
|
+
return;
|
|
15927
15906
|
}
|
|
15928
|
-
|
|
15929
|
-
|
|
15930
|
-
|
|
15931
|
-
if (typeof name !== "string" || !name.trim() || !config || typeof config !== "object")
|
|
15907
|
+
if (method && id !== void 0) {
|
|
15908
|
+
if (method === "session/request_permission") {
|
|
15909
|
+
void this.handlePermission(id, msg.params);
|
|
15932
15910
|
return;
|
|
15933
|
-
const apiKey = config.apiKey;
|
|
15934
|
-
if (typeof apiKey === "string" && apiKey.trim())
|
|
15935
|
-
held.add(name.trim());
|
|
15936
|
-
};
|
|
15937
|
-
if (Array.isArray(providers)) {
|
|
15938
|
-
for (const provider of providers) {
|
|
15939
|
-
const record3 = provider;
|
|
15940
|
-
remember(record3?.name ?? record3?.id, record3);
|
|
15941
15911
|
}
|
|
15942
|
-
|
|
15943
|
-
|
|
15944
|
-
|
|
15912
|
+
this.write({ jsonrpc: "2.0", id, result: {} });
|
|
15913
|
+
return;
|
|
15914
|
+
}
|
|
15915
|
+
if (method === "session/update") {
|
|
15916
|
+
const params = msg.params;
|
|
15917
|
+
if (params?.sessionId && params.update) {
|
|
15918
|
+
if (params.update.sessionUpdate === "available_commands_update") {
|
|
15919
|
+
const commands = parseAvailableCommands(params.update.availableCommands);
|
|
15920
|
+
this.sessionCommands.set(params.sessionId, commands);
|
|
15921
|
+
this.commandsHandler?.(commands);
|
|
15922
|
+
this.emit("commands", { sessionId: params.sessionId, commands });
|
|
15923
|
+
}
|
|
15924
|
+
const toolCallId = params.update.toolCallId;
|
|
15925
|
+
const sessionUpdate = params.update.sessionUpdate;
|
|
15926
|
+
if (typeof toolCallId === "string" && (sessionUpdate === "tool_call" || sessionUpdate === "tool_call_update")) {
|
|
15927
|
+
const metadataKey = `${params.sessionId}\0${toolCallId}`;
|
|
15928
|
+
const status = params.update.status;
|
|
15929
|
+
if (status === "completed" || status === "failed") {
|
|
15930
|
+
this.toolCallMetadata.delete(metadataKey);
|
|
15931
|
+
} else {
|
|
15932
|
+
const existing = this.toolCallMetadata.get(metadataKey);
|
|
15933
|
+
this.toolCallMetadata.set(metadataKey, {
|
|
15934
|
+
...existing,
|
|
15935
|
+
toolCallId,
|
|
15936
|
+
...typeof params.update.title === "string" ? { title: params.update.title } : {},
|
|
15937
|
+
...typeof params.update.kind === "string" ? { kind: params.update.kind } : {},
|
|
15938
|
+
..."rawInput" in params.update ? { rawInput: params.update.rawInput } : {},
|
|
15939
|
+
// `content`/`locations` carry the touched file paths for adapters
|
|
15940
|
+
// whose permission request omits them (codex-acp's file-change
|
|
15941
|
+
// request has no rawInput at all) — the corner worktree guard in
|
|
15942
|
+
// `session-sandbox.ts` reads them.
|
|
15943
|
+
..."content" in params.update ? { content: params.update.content } : {},
|
|
15944
|
+
..."locations" in params.update ? { locations: params.update.locations } : {}
|
|
15945
|
+
});
|
|
15946
|
+
}
|
|
15947
|
+
}
|
|
15948
|
+
const u4 = {
|
|
15949
|
+
sessionId: params.sessionId,
|
|
15950
|
+
update: params.update
|
|
15951
|
+
};
|
|
15952
|
+
const activeRunId = this.activeRunIdFromUpdate(params.update);
|
|
15953
|
+
if (activeRunId)
|
|
15954
|
+
this.activeRunIds.set(params.sessionId, activeRunId);
|
|
15955
|
+
this.emit("session/update", u4);
|
|
15945
15956
|
}
|
|
15957
|
+
return;
|
|
15946
15958
|
}
|
|
15947
|
-
} catch {
|
|
15948
15959
|
}
|
|
15949
|
-
|
|
15950
|
-
|
|
15951
|
-
|
|
15952
|
-
|
|
15953
|
-
return options.map((option) => {
|
|
15954
|
-
if (option.category !== "model")
|
|
15955
|
-
return option;
|
|
15956
|
-
return {
|
|
15957
|
-
...option,
|
|
15958
|
-
options: option.options.filter((choice) => {
|
|
15959
|
-
const slash = choice.id.indexOf("/");
|
|
15960
|
-
if (slash < 0)
|
|
15961
|
-
return true;
|
|
15962
|
-
return held.has(choice.id.slice(0, slash));
|
|
15963
|
-
})
|
|
15964
|
-
};
|
|
15965
|
-
});
|
|
15966
|
-
}
|
|
15967
|
-
var DisallowedModelConfigOptionError = class extends Error {
|
|
15968
|
-
constructor(configId) {
|
|
15969
|
-
super(`model config option "${configId}" is not in the allow-listed catalog`);
|
|
15970
|
-
this.name = "DisallowedModelConfigOptionError";
|
|
15960
|
+
activeRunIdFromUpdate(update) {
|
|
15961
|
+
const meta = update._meta;
|
|
15962
|
+
const goose = meta?.goose;
|
|
15963
|
+
return typeof goose?.activeRunId === "string" && goose.activeRunId ? goose.activeRunId : void 0;
|
|
15971
15964
|
}
|
|
15972
|
-
|
|
15973
|
-
|
|
15974
|
-
|
|
15975
|
-
|
|
15976
|
-
|
|
15977
|
-
|
|
15978
|
-
|
|
15979
|
-
|
|
15980
|
-
|
|
15981
|
-
|
|
15982
|
-
|
|
15983
|
-
|
|
15984
|
-
|
|
15985
|
-
|
|
15965
|
+
waitForActiveRun(sessionId, timeoutMs) {
|
|
15966
|
+
const current = this.activeRunIds.get(sessionId);
|
|
15967
|
+
if (current)
|
|
15968
|
+
return Promise.resolve(current);
|
|
15969
|
+
return new Promise((resolve36, reject) => {
|
|
15970
|
+
const onUpdate = (update) => {
|
|
15971
|
+
if (update.sessionId !== sessionId)
|
|
15972
|
+
return;
|
|
15973
|
+
const runId = this.activeRunIdFromUpdate(update.update);
|
|
15974
|
+
if (!runId)
|
|
15975
|
+
return;
|
|
15976
|
+
cleanup();
|
|
15977
|
+
resolve36(runId);
|
|
15978
|
+
};
|
|
15979
|
+
const timer = setTimeout(() => {
|
|
15980
|
+
cleanup();
|
|
15981
|
+
reject(new Error(`ACP session ${sessionId} has no active run to steer`));
|
|
15982
|
+
}, timeoutMs);
|
|
15983
|
+
const cleanup = () => {
|
|
15984
|
+
clearTimeout(timer);
|
|
15985
|
+
this.off("session/update", onUpdate);
|
|
15986
|
+
};
|
|
15987
|
+
this.on("session/update", onUpdate);
|
|
15988
|
+
});
|
|
15986
15989
|
}
|
|
15987
|
-
|
|
15988
|
-
|
|
15989
|
-
|
|
15990
|
-
|
|
15991
|
-
|
|
15992
|
-
|
|
15993
|
-
|
|
15994
|
-
|
|
15995
|
-
|
|
15996
|
-
|
|
15997
|
-
|
|
15998
|
-
|
|
15999
|
-
|
|
16000
|
-
|
|
16001
|
-
|
|
16002
|
-
|
|
16003
|
-
|
|
16004
|
-
|
|
15990
|
+
async handlePermission(id, params) {
|
|
15991
|
+
const p = params ?? {};
|
|
15992
|
+
const toolCallId = p.toolCall?.toolCallId;
|
|
15993
|
+
const metadataKey = p.sessionId && toolCallId ? `${p.sessionId}\0${toolCallId}` : void 0;
|
|
15994
|
+
const tracked = metadataKey ? this.toolCallMetadata.get(metadataKey) : void 0;
|
|
15995
|
+
if (tracked)
|
|
15996
|
+
p.toolCall = { ...tracked, ...p.toolCall };
|
|
15997
|
+
let decision2 = this.autoApprove ? "allow" : "reject";
|
|
15998
|
+
if (!this.autoApprove && this.permissionAllowlist) {
|
|
15999
|
+
try {
|
|
16000
|
+
decision2 = this.permissionAllowlist(p) ? "allow" : "reject";
|
|
16001
|
+
} catch (error) {
|
|
16002
|
+
this.emit("permission/error", error);
|
|
16003
|
+
decision2 = "reject";
|
|
16004
|
+
}
|
|
16005
|
+
} else if (this.permissionHandler) {
|
|
16006
|
+
try {
|
|
16007
|
+
decision2 = await this.permissionHandler(p);
|
|
16008
|
+
} catch (error) {
|
|
16009
|
+
this.emit("permission/error", error);
|
|
16010
|
+
decision2 = "reject";
|
|
16011
|
+
}
|
|
16005
16012
|
}
|
|
16006
|
-
|
|
16007
|
-
|
|
16008
|
-
|
|
16009
|
-
|
|
16010
|
-
|
|
16011
|
-
|
|
16012
|
-
|
|
16013
|
-
|
|
16014
|
-
|
|
16015
|
-
|
|
16016
|
-
|
|
16017
|
-
|
|
16018
|
-
|
|
16019
|
-
|
|
16020
|
-
if (!axis)
|
|
16021
|
-
continue;
|
|
16022
|
-
assertModelConfigAxisAllowed(axis.id, currentOptions);
|
|
16023
|
-
if (!axis.options.some((choice) => choice.id === target.value)) {
|
|
16024
|
-
throw new ModelSelectionUnavailableError({
|
|
16025
|
-
label: target.label,
|
|
16026
|
-
value: target.value,
|
|
16027
|
-
reason: "not-advertised"
|
|
16028
|
-
});
|
|
16013
|
+
if (decision2 === "allow") {
|
|
16014
|
+
const allow = p?.options?.find((o) => o.kind === "allow_once" || o.kind === "allow_always") ?? p?.options?.[0];
|
|
16015
|
+
if (allow?.optionId) {
|
|
16016
|
+
if (metadataKey)
|
|
16017
|
+
this.toolCallMetadata.delete(metadataKey);
|
|
16018
|
+
this.write({
|
|
16019
|
+
jsonrpc: "2.0",
|
|
16020
|
+
id,
|
|
16021
|
+
result: {
|
|
16022
|
+
outcome: { outcome: "selected", optionId: allow.optionId }
|
|
16023
|
+
}
|
|
16024
|
+
});
|
|
16025
|
+
return;
|
|
16026
|
+
}
|
|
16029
16027
|
}
|
|
16030
|
-
|
|
16031
|
-
|
|
16032
|
-
|
|
16033
|
-
|
|
16034
|
-
|
|
16035
|
-
|
|
16036
|
-
|
|
16037
|
-
|
|
16028
|
+
const reject = p?.options?.find((o) => o.kind === "reject_once" || o.kind === "reject_always");
|
|
16029
|
+
if (reject?.optionId) {
|
|
16030
|
+
if (metadataKey)
|
|
16031
|
+
this.toolCallMetadata.delete(metadataKey);
|
|
16032
|
+
this.write({
|
|
16033
|
+
jsonrpc: "2.0",
|
|
16034
|
+
id,
|
|
16035
|
+
result: {
|
|
16036
|
+
outcome: { outcome: "selected", optionId: reject.optionId }
|
|
16038
16037
|
}
|
|
16039
|
-
} else {
|
|
16040
|
-
const updated = await client.setConfigOption(sessionId, axis.id, target.value);
|
|
16041
|
-
const refreshed = filterAllowedModelConfigOptions(parseAdvertisedConfigOptions(updated, selection.model));
|
|
16042
|
-
if (refreshed.length > 0)
|
|
16043
|
-
currentOptions = refreshed;
|
|
16044
|
-
}
|
|
16045
|
-
} catch (error) {
|
|
16046
|
-
throw new ModelSelectionUnavailableError({
|
|
16047
|
-
label: target.label,
|
|
16048
|
-
value: target.value,
|
|
16049
|
-
reason: "provider-refused",
|
|
16050
|
-
guidance: error instanceof Error ? error.message : String(error)
|
|
16051
16038
|
});
|
|
16039
|
+
return;
|
|
16040
|
+
}
|
|
16041
|
+
if (metadataKey)
|
|
16042
|
+
this.toolCallMetadata.delete(metadataKey);
|
|
16043
|
+
this.write({
|
|
16044
|
+
jsonrpc: "2.0",
|
|
16045
|
+
id,
|
|
16046
|
+
result: { outcome: { outcome: "cancelled" } }
|
|
16047
|
+
});
|
|
16048
|
+
}
|
|
16049
|
+
/**
|
|
16050
|
+
* `onStart`, when given, receives the assigned request id synchronously
|
|
16051
|
+
* and marks the request as idle-resettable at `timeoutMs`: callers can
|
|
16052
|
+
* then call `resetPendingIdleTimeout(id)` on activity to defer the
|
|
16053
|
+
* timeout instead of it firing at a fixed deadline.
|
|
16054
|
+
*/
|
|
16055
|
+
request(method, params, timeoutMs = 6e4, onStart, detail = "") {
|
|
16056
|
+
if (!this.child || !this.alive) {
|
|
16057
|
+
return Promise.reject(new Error("AcpClient not started"));
|
|
16052
16058
|
}
|
|
16059
|
+
const id = this.nextId++;
|
|
16060
|
+
const payload = { jsonrpc: "2.0", id, method, params };
|
|
16061
|
+
return new Promise((resolve36, reject) => {
|
|
16062
|
+
const timer = setTimeout(() => {
|
|
16063
|
+
this.pending.delete(id);
|
|
16064
|
+
reject(new AcpRequestTimeoutError(method, timeoutMs, this.stderrTail, Boolean(onStart), detail));
|
|
16065
|
+
}, timeoutMs);
|
|
16066
|
+
this.pending.set(id, {
|
|
16067
|
+
resolve: resolve36,
|
|
16068
|
+
reject,
|
|
16069
|
+
timer,
|
|
16070
|
+
method,
|
|
16071
|
+
...detail ? { detail } : {},
|
|
16072
|
+
...onStart ? { idleTimeoutMs: timeoutMs } : {}
|
|
16073
|
+
});
|
|
16074
|
+
onStart?.(id);
|
|
16075
|
+
this.write(payload);
|
|
16076
|
+
});
|
|
16053
16077
|
}
|
|
16054
|
-
|
|
16055
|
-
}
|
|
16056
|
-
|
|
16057
|
-
|
|
16058
|
-
|
|
16078
|
+
notify(method, params) {
|
|
16079
|
+
this.write({ jsonrpc: "2.0", method, params });
|
|
16080
|
+
}
|
|
16081
|
+
write(obj) {
|
|
16082
|
+
if (!this.child?.stdin.writable)
|
|
16083
|
+
return;
|
|
16084
|
+
this.child.stdin.write(JSON.stringify(obj) + "\n");
|
|
16085
|
+
}
|
|
16086
|
+
};
|
|
16059
16087
|
|
|
16060
16088
|
// apps/body/dist/model-catalog.js
|
|
16061
16089
|
function modelCatalogProbeEnvironment(agent, agentEnv, scratchCwd) {
|
|
@@ -16162,19 +16190,33 @@ function modelUnavailableState(selection, error) {
|
|
|
16162
16190
|
}
|
|
16163
16191
|
|
|
16164
16192
|
// apps/body/dist/runtime-model-validation.js
|
|
16165
|
-
async function revalidateRuntimeModelSelection(agent, agentEnv, selection, validate = validateAgentModelSelection) {
|
|
16193
|
+
async function revalidateRuntimeModelSelection(agent, agentEnv, selection, validate = validateAgentModelSelection, refreshAdapter) {
|
|
16166
16194
|
if (!selection.model && !selection.effort)
|
|
16167
16195
|
return void 0;
|
|
16168
16196
|
try {
|
|
16169
16197
|
await validate(agent, agentEnv, selection);
|
|
16170
16198
|
return void 0;
|
|
16171
16199
|
} catch (error) {
|
|
16200
|
+
const selectedModelMissing = error instanceof ModelSelectionUnavailableError && error.label === "model" && (error.reason === "axis-missing" || error.reason === "not-advertised");
|
|
16201
|
+
if (selectedModelMissing && refreshAdapter) {
|
|
16202
|
+
try {
|
|
16203
|
+
await refreshAdapter();
|
|
16204
|
+
} catch {
|
|
16205
|
+
return modelUnavailableState(selection, error);
|
|
16206
|
+
}
|
|
16207
|
+
try {
|
|
16208
|
+
await validate(agent, agentEnv, selection);
|
|
16209
|
+
return void 0;
|
|
16210
|
+
} catch (retryError) {
|
|
16211
|
+
return modelUnavailableState(selection, retryError);
|
|
16212
|
+
}
|
|
16213
|
+
}
|
|
16172
16214
|
return modelUnavailableState(selection, error);
|
|
16173
16215
|
}
|
|
16174
16216
|
}
|
|
16175
|
-
async function applyRuntimeModelPreflight(config, agent, selection, validate = validateAgentModelSelection) {
|
|
16217
|
+
async function applyRuntimeModelPreflight(config, agent, selection, validate = validateAgentModelSelection, refreshAdapter) {
|
|
16176
16218
|
config.modelSelection = selection;
|
|
16177
|
-
config.modelUnavailable = await revalidateRuntimeModelSelection(agent, config.agentEnv, selection, validate);
|
|
16219
|
+
config.modelUnavailable = await revalidateRuntimeModelSelection(agent, config.agentEnv, selection, validate, refreshAdapter);
|
|
16178
16220
|
}
|
|
16179
16221
|
|
|
16180
16222
|
// apps/body/dist/model-catalog-sync.js
|
|
@@ -16770,7 +16812,7 @@ function describe(error) {
|
|
|
16770
16812
|
}
|
|
16771
16813
|
|
|
16772
16814
|
// apps/body/dist/connector-squire.js
|
|
16773
|
-
import { execFile, spawn as
|
|
16815
|
+
import { execFile, spawn as spawn6 } from "node:child_process";
|
|
16774
16816
|
import { createHash as createHash2 } from "node:crypto";
|
|
16775
16817
|
import { lstatSync as lstatSync3, readFileSync as readFileSync4, realpathSync, rmSync } from "node:fs";
|
|
16776
16818
|
import { homedir as homedir3, hostname, tmpdir as tmpdir2 } from "node:os";
|
|
@@ -16919,7 +16961,7 @@ function killConnectTree(child) {
|
|
|
16919
16961
|
child.kill();
|
|
16920
16962
|
}
|
|
16921
16963
|
var defaultStreamedRunner = (command, args, env) => new Promise((resolve36) => {
|
|
16922
|
-
const child =
|
|
16964
|
+
const child = spawn6(command, args, {
|
|
16923
16965
|
stdio: ["ignore", "pipe", "pipe"],
|
|
16924
16966
|
env: env ?? squireConnectProcessEnv(),
|
|
16925
16967
|
detached: true
|
|
@@ -17319,8 +17361,173 @@ async function revokeGrants(mcp, ref) {
|
|
|
17319
17361
|
return { revoked, failed };
|
|
17320
17362
|
}
|
|
17321
17363
|
|
|
17364
|
+
// apps/body/dist/connector-tailscale.js
|
|
17365
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
17366
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
17367
|
+
import { rm as rm2 } from "node:fs/promises";
|
|
17368
|
+
import { tmpdir as tmpdir3, userInfo } from "node:os";
|
|
17369
|
+
import { join as join5 } from "node:path";
|
|
17370
|
+
var COMMAND_TIMEOUT_MS = 2 * 6e4;
|
|
17371
|
+
var OUTPUT_LIMIT = 32 * 1024;
|
|
17372
|
+
var LOGIN_URL = /https:\/\/login\.tailscale\.com\/[A-Za-z0-9_/-]+/u;
|
|
17373
|
+
function clipped(value) {
|
|
17374
|
+
return value.length <= OUTPUT_LIMIT ? value : value.slice(value.length - OUTPUT_LIMIT);
|
|
17375
|
+
}
|
|
17376
|
+
var runTailscaleCommand = (command, args) => new Promise((resolve36) => {
|
|
17377
|
+
execFile2(command, [...args], { timeout: COMMAND_TIMEOUT_MS, maxBuffer: OUTPUT_LIMIT * 2 }, (error, stdout6, stderr) => {
|
|
17378
|
+
const code = error && "code" in error && typeof error.code === "number" ? error.code : error ? null : 0;
|
|
17379
|
+
resolve36({ code, stdout: clipped(stdout6), stderr: clipped(stderr) });
|
|
17380
|
+
});
|
|
17381
|
+
});
|
|
17382
|
+
function parseStatus(output) {
|
|
17383
|
+
try {
|
|
17384
|
+
const parsed = JSON.parse(output);
|
|
17385
|
+
return parsed && typeof parsed === "object" ? parsed : void 0;
|
|
17386
|
+
} catch {
|
|
17387
|
+
return void 0;
|
|
17388
|
+
}
|
|
17389
|
+
}
|
|
17390
|
+
function accountName(status) {
|
|
17391
|
+
const userId = status.Self?.UserID;
|
|
17392
|
+
const user = userId === void 0 ? void 0 : status.User?.[String(userId)];
|
|
17393
|
+
return user?.LoginName ?? user?.DisplayName ?? status.CurrentTailnet?.Name;
|
|
17394
|
+
}
|
|
17395
|
+
function step3(label, status, details = {}) {
|
|
17396
|
+
return { label, status, ...details };
|
|
17397
|
+
}
|
|
17398
|
+
async function readStatus(run2) {
|
|
17399
|
+
const result = await run2("tailscale", ["status", "--json"]);
|
|
17400
|
+
return parseStatus(result.stdout || result.stderr);
|
|
17401
|
+
}
|
|
17402
|
+
async function ensureInstalled(run2, onProgress, platform) {
|
|
17403
|
+
const version = await run2("tailscale", ["version"]);
|
|
17404
|
+
if (version.code === 0) {
|
|
17405
|
+
return {
|
|
17406
|
+
ok: true,
|
|
17407
|
+
steps: [step3("Tailscale installed", "done", { output: version.stdout.trim() })]
|
|
17408
|
+
};
|
|
17409
|
+
}
|
|
17410
|
+
if (platform !== "linux") {
|
|
17411
|
+
const reason = "Tailscale is not installed; install the Tailscale app on this helper and retry";
|
|
17412
|
+
return {
|
|
17413
|
+
ok: false,
|
|
17414
|
+
result: {
|
|
17415
|
+
status: "error",
|
|
17416
|
+
steps: [step3("Tailscale installed", "failed", { reason })],
|
|
17417
|
+
errorMessage: reason
|
|
17418
|
+
}
|
|
17419
|
+
};
|
|
17420
|
+
}
|
|
17421
|
+
const script = join5(tmpdir3(), `beeline-tailscale-install-${process.pid}-${randomUUID2()}.sh`);
|
|
17422
|
+
const steps = [
|
|
17423
|
+
step3("Tailscale installed", "running", {
|
|
17424
|
+
command: "curl -fsSL https://tailscale.com/install.sh"
|
|
17425
|
+
}),
|
|
17426
|
+
step3("Tailnet signed in", "pending")
|
|
17427
|
+
];
|
|
17428
|
+
onProgress(steps);
|
|
17429
|
+
try {
|
|
17430
|
+
const download = await run2("curl", ["-fsSL", "https://tailscale.com/install.sh", "-o", script]);
|
|
17431
|
+
if (download.code !== 0) {
|
|
17432
|
+
const reason = (download.stderr || download.stdout || "Tailscale installer download failed").trim();
|
|
17433
|
+
return {
|
|
17434
|
+
ok: false,
|
|
17435
|
+
result: {
|
|
17436
|
+
status: "error",
|
|
17437
|
+
steps: [step3("Tailscale installed", "failed", { reason })],
|
|
17438
|
+
errorMessage: reason
|
|
17439
|
+
}
|
|
17440
|
+
};
|
|
17441
|
+
}
|
|
17442
|
+
const install = await run2("sh", [script]);
|
|
17443
|
+
if (install.code !== 0) {
|
|
17444
|
+
const reason = (install.stderr || install.stdout || "Tailscale installation failed").trim();
|
|
17445
|
+
return {
|
|
17446
|
+
ok: false,
|
|
17447
|
+
result: {
|
|
17448
|
+
status: "error",
|
|
17449
|
+
steps: [step3("Tailscale installed", "failed", { reason })],
|
|
17450
|
+
errorMessage: reason
|
|
17451
|
+
}
|
|
17452
|
+
};
|
|
17453
|
+
}
|
|
17454
|
+
return {
|
|
17455
|
+
ok: true,
|
|
17456
|
+
steps: [step3("Tailscale installed", "done", { output: install.stdout.trim() })]
|
|
17457
|
+
};
|
|
17458
|
+
} finally {
|
|
17459
|
+
await rm2(script, { force: true }).catch(() => {
|
|
17460
|
+
});
|
|
17461
|
+
}
|
|
17462
|
+
}
|
|
17463
|
+
async function installTailscale(options = {}) {
|
|
17464
|
+
const run2 = options.run ?? runTailscaleCommand;
|
|
17465
|
+
const onProgress = options.onProgress ?? (() => {
|
|
17466
|
+
});
|
|
17467
|
+
const platform = options.platform ?? process.platform;
|
|
17468
|
+
const installed = await ensureInstalled(run2, onProgress, platform);
|
|
17469
|
+
if (!installed.ok)
|
|
17470
|
+
return installed.result;
|
|
17471
|
+
const current = await readStatus(run2);
|
|
17472
|
+
if (current?.BackendState === "Running") {
|
|
17473
|
+
return {
|
|
17474
|
+
status: "connected",
|
|
17475
|
+
steps: [...installed.steps, step3("Tailnet signed in", "done")],
|
|
17476
|
+
...accountName(current) ? { signedInAs: accountName(current) } : {}
|
|
17477
|
+
};
|
|
17478
|
+
}
|
|
17479
|
+
if (options.signIn) {
|
|
17480
|
+
const steps2 = [
|
|
17481
|
+
...installed.steps,
|
|
17482
|
+
step3("Tailnet signed in", "running", { command: "tailscale up" })
|
|
17483
|
+
];
|
|
17484
|
+
return { status: "installing", steps: steps2, signIn: options.signIn };
|
|
17485
|
+
}
|
|
17486
|
+
const steps = [
|
|
17487
|
+
...installed.steps,
|
|
17488
|
+
step3("Tailnet signed in", "running", { command: "tailscale up" })
|
|
17489
|
+
];
|
|
17490
|
+
onProgress(steps);
|
|
17491
|
+
let accountUsername;
|
|
17492
|
+
try {
|
|
17493
|
+
accountUsername = userInfo().username;
|
|
17494
|
+
} catch {
|
|
17495
|
+
}
|
|
17496
|
+
const operator = [options.operator, process.env.USER, accountUsername].find((candidate) => typeof candidate === "string" && /^[A-Za-z_][A-Za-z0-9_-]*$/u.test(candidate));
|
|
17497
|
+
const upArgs = ["tailscale", "up", "--timeout=10s"];
|
|
17498
|
+
if (operator)
|
|
17499
|
+
upArgs.push(`--operator=${operator}`);
|
|
17500
|
+
const getuid = options.getuid ?? process.getuid;
|
|
17501
|
+
const root = getuid?.() === 0;
|
|
17502
|
+
const up = platform === "linux" && !root ? await run2("sudo", ["-n", ...upArgs]) : await run2("tailscale", upArgs.slice(1));
|
|
17503
|
+
const after = await readStatus(run2);
|
|
17504
|
+
if (after?.BackendState === "Running") {
|
|
17505
|
+
return {
|
|
17506
|
+
status: "connected",
|
|
17507
|
+
steps: [...installed.steps, step3("Tailnet signed in", "done")],
|
|
17508
|
+
...accountName(after) ? { signedInAs: accountName(after) } : {}
|
|
17509
|
+
};
|
|
17510
|
+
}
|
|
17511
|
+
const output = `${up.stdout}
|
|
17512
|
+
${up.stderr}`;
|
|
17513
|
+
const url = LOGIN_URL.exec(output)?.[0];
|
|
17514
|
+
if (url) {
|
|
17515
|
+
return {
|
|
17516
|
+
status: "installing",
|
|
17517
|
+
steps,
|
|
17518
|
+
signIn: { method: "oauth", url, browserLocation: { kind: "none" } }
|
|
17519
|
+
};
|
|
17520
|
+
}
|
|
17521
|
+
const reason = (up.stderr || up.stdout || "Tailscale could not start on this helper").trim();
|
|
17522
|
+
return {
|
|
17523
|
+
status: "error",
|
|
17524
|
+
steps: [...installed.steps, step3("Tailnet signed in", "failed", { reason })],
|
|
17525
|
+
errorMessage: reason
|
|
17526
|
+
};
|
|
17527
|
+
}
|
|
17528
|
+
|
|
17322
17529
|
// apps/body/dist/squire-mcp-client.js
|
|
17323
|
-
import { spawn as
|
|
17530
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
17324
17531
|
import { homedir as homedir4 } from "node:os";
|
|
17325
17532
|
var INITIALIZE_TIMEOUT_MS = 3e4;
|
|
17326
17533
|
var CALL_TIMEOUT_MS = 12e4;
|
|
@@ -17378,7 +17585,7 @@ var StdioSquireMcpClient = class {
|
|
|
17378
17585
|
}
|
|
17379
17586
|
initialize() {
|
|
17380
17587
|
const launch = squireFacadeLaunch(homedir4());
|
|
17381
|
-
const child = (this.options.spawn ??
|
|
17588
|
+
const child = (this.options.spawn ?? spawn7)(this.options.command ?? launch.command, [...this.options.args ?? launch.args], { env: this.options.env ?? { ...squireConnectProcessEnv(), ...launch.env } });
|
|
17382
17589
|
this.child = child;
|
|
17383
17590
|
this.buffer = "";
|
|
17384
17591
|
child.stdout.setEncoding("utf8");
|
|
@@ -17470,6 +17677,7 @@ var ConnectorAssignmentLoop = class {
|
|
|
17470
17677
|
log;
|
|
17471
17678
|
install;
|
|
17472
17679
|
installGoogle;
|
|
17680
|
+
installTailscale;
|
|
17473
17681
|
googleHomeDir;
|
|
17474
17682
|
readVaultFn;
|
|
17475
17683
|
revokeGrantsFn;
|
|
@@ -17478,6 +17686,8 @@ var ConnectorAssignmentLoop = class {
|
|
|
17478
17686
|
timer;
|
|
17479
17687
|
/** Armed only while this helper owns a live sign-in ceremony. */
|
|
17480
17688
|
connectWatch;
|
|
17689
|
+
/** Armed only while a Tailscale browser login is waiting for its callback. */
|
|
17690
|
+
tailscaleWatch;
|
|
17481
17691
|
started = false;
|
|
17482
17692
|
stopped = false;
|
|
17483
17693
|
/** One install at a time per connector; other polls skip it. */
|
|
@@ -17496,6 +17706,7 @@ var ConnectorAssignmentLoop = class {
|
|
|
17496
17706
|
onProgress,
|
|
17497
17707
|
resolvedCredentials: sharedCredentials ? sharedCredentials() : this.resolveGoogleCredentials()
|
|
17498
17708
|
}));
|
|
17709
|
+
this.installTailscale = options.installTailscale ?? installTailscale;
|
|
17499
17710
|
this.googleHomeDir = options.googleHome ?? process.env.BEELINE_AGENT_HOME ?? process.cwd();
|
|
17500
17711
|
this.readVaultFn = options.readVault ?? readVault;
|
|
17501
17712
|
this.revokeGrantsFn = options.revokeGrants ?? revokeGrants;
|
|
@@ -17529,6 +17740,10 @@ var ConnectorAssignmentLoop = class {
|
|
|
17529
17740
|
this.cancel(this.connectWatch);
|
|
17530
17741
|
this.connectWatch = void 0;
|
|
17531
17742
|
}
|
|
17743
|
+
if (this.tailscaleWatch !== void 0) {
|
|
17744
|
+
this.cancel(this.tailscaleWatch);
|
|
17745
|
+
this.tailscaleWatch = void 0;
|
|
17746
|
+
}
|
|
17532
17747
|
}
|
|
17533
17748
|
/**
|
|
17534
17749
|
* Watch the sign-in this helper is holding open. The phone paints
|
|
@@ -17641,12 +17856,57 @@ var ConnectorAssignmentLoop = class {
|
|
|
17641
17856
|
}
|
|
17642
17857
|
async handle(assignment) {
|
|
17643
17858
|
if (assignment.kind === "install") {
|
|
17644
|
-
|
|
17645
|
-
|
|
17859
|
+
if (assignment.connectorType === "tailscale") {
|
|
17860
|
+
await this.runTailscaleInstall(assignment.connectorId);
|
|
17861
|
+
} else {
|
|
17862
|
+
await this.runInstall(assignment.connectorId);
|
|
17863
|
+
}
|
|
17864
|
+
} else if (assignment.kind === "sync" && assignment.connectorType !== "tailscale") {
|
|
17646
17865
|
await this.runSync();
|
|
17647
|
-
else if (assignment.kind === "revoke-grants")
|
|
17866
|
+
} else if (assignment.kind === "revoke-grants")
|
|
17648
17867
|
await this.runRevoke(assignment.connectorId, assignment.reference);
|
|
17649
17868
|
}
|
|
17869
|
+
/** Install Tailscale and publish its browser login URL until the tailnet is connected. */
|
|
17870
|
+
async runTailscaleInstall(connectorId) {
|
|
17871
|
+
const report = async (steps) => {
|
|
17872
|
+
try {
|
|
17873
|
+
await this.api.execute("postConnectorStatus", { agentId: this.agentId, connectorId, steps });
|
|
17874
|
+
} catch (error) {
|
|
17875
|
+
this.log(`step report failed: ${describe2(error)}`);
|
|
17876
|
+
}
|
|
17877
|
+
};
|
|
17878
|
+
const existing = await this.connectorRow(connectorId);
|
|
17879
|
+
const result = await this.installTailscale({
|
|
17880
|
+
onProgress: report,
|
|
17881
|
+
...existing?.signIn ? { signIn: existing.signIn } : {}
|
|
17882
|
+
});
|
|
17883
|
+
if (result.status === "connected") {
|
|
17884
|
+
if (this.tailscaleWatch !== void 0) {
|
|
17885
|
+
this.cancel(this.tailscaleWatch);
|
|
17886
|
+
this.tailscaleWatch = void 0;
|
|
17887
|
+
}
|
|
17888
|
+
await this.api.execute("installConnector", {
|
|
17889
|
+
agentId: this.agentId,
|
|
17890
|
+
connectorId,
|
|
17891
|
+
...result.signedInAs ? { signedInAs: result.signedInAs } : {}
|
|
17892
|
+
});
|
|
17893
|
+
return;
|
|
17894
|
+
}
|
|
17895
|
+
await this.api.execute("postConnectorStatus", {
|
|
17896
|
+
agentId: this.agentId,
|
|
17897
|
+
connectorId,
|
|
17898
|
+
steps: result.steps,
|
|
17899
|
+
signIn: result.status === "installing" ? result.signIn : null,
|
|
17900
|
+
...result.status === "error" ? { errorMessage: result.errorMessage } : {}
|
|
17901
|
+
});
|
|
17902
|
+
if (result.status === "installing" && this.tailscaleWatch === void 0) {
|
|
17903
|
+
this.tailscaleWatch = this.schedule(() => {
|
|
17904
|
+
this.tailscaleWatch = void 0;
|
|
17905
|
+
if (!this.stopped)
|
|
17906
|
+
void this.runOnce();
|
|
17907
|
+
}, CONNECT_WATCH_INTERVAL_MS);
|
|
17908
|
+
}
|
|
17909
|
+
}
|
|
17650
17910
|
/** Google tool connectors keep their manual credentials next to the runtime. */
|
|
17651
17911
|
googleHome() {
|
|
17652
17912
|
return this.googleHomeDir;
|
|
@@ -17764,7 +18024,7 @@ var ConnectorAssignmentLoop = class {
|
|
|
17764
18024
|
const status = await this.connectorRow(connectorId);
|
|
17765
18025
|
if (!status)
|
|
17766
18026
|
return false;
|
|
17767
|
-
return status.steps.length > 0 && status.steps.every((
|
|
18027
|
+
return status.steps.length > 0 && status.steps.every((step4) => step4.status === "pending");
|
|
17768
18028
|
}
|
|
17769
18029
|
/** One vault report covers every live trusty-squire connector on this helper. */
|
|
17770
18030
|
async runSync() {
|
|
@@ -17800,7 +18060,7 @@ function isAgentCommand(value) {
|
|
|
17800
18060
|
"reason"
|
|
17801
18061
|
].every((key) => typeof c2[key] === "string" && c2[key].length > 0))
|
|
17802
18062
|
return false;
|
|
17803
|
-
if (!["input", "resume", "stop"].includes(String(c2.action)) || !Number.isInteger(c2.agentDepth) || Number(c2.agentDepth) < 0 || Number(c2.agentDepth) > 3)
|
|
18063
|
+
if (!["input", "resume", "stop", "restart"].includes(String(c2.action)) || !Number.isInteger(c2.agentDepth) || Number(c2.agentDepth) < 0 || Number(c2.agentDepth) > 3)
|
|
17804
18064
|
return false;
|
|
17805
18065
|
const source = c2.source;
|
|
17806
18066
|
return Boolean(source && typeof source.id === "string" && typeof source.authorId === "string" && typeof source.body === "string" && typeof source.createdAt === "number" && Array.isArray(source.attachments));
|
|
@@ -17932,7 +18192,7 @@ function hiccupBackoffMs(attempt) {
|
|
|
17932
18192
|
|
|
17933
18193
|
// apps/body/dist/daemon-api-client.js
|
|
17934
18194
|
import { resolve as resolve9 } from "node:path";
|
|
17935
|
-
import { randomUUID as
|
|
18195
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
17936
18196
|
|
|
17937
18197
|
// node_modules/ws/wrapper.mjs
|
|
17938
18198
|
var import_stream = __toESM(require_stream(), 1);
|
|
@@ -17947,12 +18207,12 @@ var wrapper_default = import_websocket.default;
|
|
|
17947
18207
|
|
|
17948
18208
|
// apps/body/dist/runtime.js
|
|
17949
18209
|
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
17950
|
-
import { execFile as
|
|
18210
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
17951
18211
|
import { closeSync, openSync, readFileSync as readFileSync5 } from "node:fs";
|
|
17952
18212
|
import { mkdir as mkdir2, readFile as readFile2, readdir, rename as rename2, stat, unlink as unlink2, writeFile as writeFile3 } from "node:fs/promises";
|
|
17953
18213
|
import { homedir as homedir5 } from "node:os";
|
|
17954
18214
|
import { dirname as dirname6, resolve as resolve8 } from "node:path";
|
|
17955
|
-
import { spawn as
|
|
18215
|
+
import { spawn as spawn8 } from "node:child_process";
|
|
17956
18216
|
import { promisify } from "node:util";
|
|
17957
18217
|
|
|
17958
18218
|
// node_modules/@noble/hashes/_u64.js
|
|
@@ -22339,7 +22599,7 @@ function alphabet(letters) {
|
|
|
22339
22599
|
};
|
|
22340
22600
|
}
|
|
22341
22601
|
// @__NO_SIDE_EFFECTS__
|
|
22342
|
-
function
|
|
22602
|
+
function join6(separator = "") {
|
|
22343
22603
|
astr("join", separator);
|
|
22344
22604
|
return {
|
|
22345
22605
|
encode: (from) => {
|
|
@@ -22469,8 +22729,8 @@ var base64 = hasBase64Builtin ? {
|
|
|
22469
22729
|
decode(s) {
|
|
22470
22730
|
return decodeBase64Builtin(s, false);
|
|
22471
22731
|
}
|
|
22472
|
-
} : /* @__PURE__ */ chain(/* @__PURE__ */ radix2(6), /* @__PURE__ */ alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), /* @__PURE__ */ padding(6), /* @__PURE__ */
|
|
22473
|
-
var BECH_ALPHABET = /* @__PURE__ */ chain(/* @__PURE__ */ alphabet("qpzry9x8gf2tvdw0s3jn54khce6mua7l"), /* @__PURE__ */
|
|
22732
|
+
} : /* @__PURE__ */ chain(/* @__PURE__ */ radix2(6), /* @__PURE__ */ alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), /* @__PURE__ */ padding(6), /* @__PURE__ */ join6(""));
|
|
22733
|
+
var BECH_ALPHABET = /* @__PURE__ */ chain(/* @__PURE__ */ alphabet("qpzry9x8gf2tvdw0s3jn54khce6mua7l"), /* @__PURE__ */ join6(""));
|
|
22474
22734
|
var POLYMOD_GENERATORS = [996825010, 642813549, 513874426, 1027748829, 705979059];
|
|
22475
22735
|
function bech32Polymod(pre) {
|
|
22476
22736
|
const b = pre >> 25;
|
|
@@ -26075,13 +26335,13 @@ var NegentropyStorageVector = class {
|
|
|
26075
26335
|
let count = last - first;
|
|
26076
26336
|
while (count > 0) {
|
|
26077
26337
|
let it = first;
|
|
26078
|
-
let
|
|
26079
|
-
it +=
|
|
26338
|
+
let step4 = Math.floor(count / 2);
|
|
26339
|
+
it += step4;
|
|
26080
26340
|
if (cmp(arr[it])) {
|
|
26081
26341
|
first = ++it;
|
|
26082
|
-
count -=
|
|
26342
|
+
count -= step4 + 1;
|
|
26083
26343
|
} else {
|
|
26084
|
-
count =
|
|
26344
|
+
count = step4;
|
|
26085
26345
|
}
|
|
26086
26346
|
}
|
|
26087
26347
|
return first;
|
|
@@ -26483,7 +26743,7 @@ function decodeNsec(nsec) {
|
|
|
26483
26743
|
}
|
|
26484
26744
|
|
|
26485
26745
|
// apps/body/dist/runtime.js
|
|
26486
|
-
var execFileAsync = promisify(
|
|
26746
|
+
var execFileAsync = promisify(execFile3);
|
|
26487
26747
|
var DEFAULT_AGENT_IDENTITY_NAME = "beeline-agent";
|
|
26488
26748
|
var DEFAULT_BODY_IDENTITY_NAME = "beeline-body";
|
|
26489
26749
|
var DEFAULT_DAEMON_MONOLITH_BASE_URL = "https://server.usebeeline.app";
|
|
@@ -26731,7 +26991,7 @@ async function launchRuntimeDaemon(configPath, opts = {}) {
|
|
|
26731
26991
|
const entrypoint = opts.entrypoint ?? process.argv[1];
|
|
26732
26992
|
if (!entrypoint)
|
|
26733
26993
|
throw new Error("cannot resolve daemon CLI entrypoint");
|
|
26734
|
-
const child =
|
|
26994
|
+
const child = spawn8(process.execPath, [...opts.execArgv ?? [], entrypoint, "daemon", "--config", resolve8(configPath)], {
|
|
26735
26995
|
cwd: directory,
|
|
26736
26996
|
env: opts.env ?? process.env,
|
|
26737
26997
|
detached: !foreground,
|
|
@@ -26792,7 +27052,7 @@ var DaemonApiClient = class {
|
|
|
26792
27052
|
agentId;
|
|
26793
27053
|
fetchImpl;
|
|
26794
27054
|
webSocketFactory;
|
|
26795
|
-
presenceLifecycleId =
|
|
27055
|
+
presenceLifecycleId = randomUUID3();
|
|
26796
27056
|
liveSocket;
|
|
26797
27057
|
liveReconnect;
|
|
26798
27058
|
liveReconnectDelayMs = 1e3;
|
|
@@ -27061,15 +27321,15 @@ async function activateDaemonTransport(path, fetchImpl = fetch) {
|
|
|
27061
27321
|
}
|
|
27062
27322
|
|
|
27063
27323
|
// apps/body/dist/room-runtime.js
|
|
27064
|
-
import { execFile as
|
|
27324
|
+
import { execFile as execFile8 } from "node:child_process";
|
|
27065
27325
|
import { createHash as createHash7 } from "node:crypto";
|
|
27066
27326
|
import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "node:fs";
|
|
27067
|
-
import { mkdir as mkdir15, rm as
|
|
27327
|
+
import { mkdir as mkdir15, rm as rm6 } from "node:fs/promises";
|
|
27068
27328
|
import { dirname as dirname13, resolve as resolve24 } from "node:path";
|
|
27069
27329
|
import { promisify as promisify5 } from "node:util";
|
|
27070
27330
|
|
|
27071
27331
|
// apps/body/dist/grant-runner.js
|
|
27072
|
-
import { execFile as
|
|
27332
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
27073
27333
|
import { createHash as createHash3, randomBytes as randomBytes6 } from "node:crypto";
|
|
27074
27334
|
import { readFile as readFile4 } from "node:fs/promises";
|
|
27075
27335
|
import { createServer } from "node:http";
|
|
@@ -27118,6 +27378,18 @@ function commandGrantMatches(rule, requested) {
|
|
|
27118
27378
|
return rule.argv.every((word, index) => requested[index] === word);
|
|
27119
27379
|
}
|
|
27120
27380
|
var DECISION_LINE = new RegExp(`^(.+?) (approved once|approved|declined) (${AGENT_GRANT_KINDS.join("|")}) (.+)$`, "s");
|
|
27381
|
+
function parseGrantDecisionLine(body) {
|
|
27382
|
+
const match = DECISION_LINE.exec(body);
|
|
27383
|
+
if (!match)
|
|
27384
|
+
return void 0;
|
|
27385
|
+
const decision2 = match[2] === "approved once" ? "once" : match[2] === "approved" ? "always" : "deny";
|
|
27386
|
+
return {
|
|
27387
|
+
deciderName: match[1],
|
|
27388
|
+
decision: decision2,
|
|
27389
|
+
kind: match[3],
|
|
27390
|
+
target: match[4]
|
|
27391
|
+
};
|
|
27392
|
+
}
|
|
27121
27393
|
var AGENT_GRANT_ESCALATION_REASONS = {
|
|
27122
27394
|
"unseen-script": "it runs a script whose contents nobody has read",
|
|
27123
27395
|
credential: "it names a credential or environment file"
|
|
@@ -27737,7 +28009,7 @@ var GrantCommandRunner = class {
|
|
|
27737
28009
|
const sandboxMarker = runsOnHost ? void 0 : `[beeline-sandbox-started:${randomBytes6(16).toString("hex")}]`;
|
|
27738
28010
|
const spawn13 = runsOnHost ? { command: argv[0], args: argv.slice(1) } : roomSandboxCommand(policy, room.cwd, argv, sandboxMarker);
|
|
27739
28011
|
const outcome = await new Promise((resolveRun) => {
|
|
27740
|
-
const child =
|
|
28012
|
+
const child = execFile4(spawn13.command, spawn13.args, {
|
|
27741
28013
|
cwd: room.cwd,
|
|
27742
28014
|
env,
|
|
27743
28015
|
timeout: this.options.timeoutMs ?? GRANT_COMMAND_TIMEOUT_MS,
|
|
@@ -28033,16 +28305,16 @@ function captureConnectionUsage(recorder, turn, calls) {
|
|
|
28033
28305
|
}
|
|
28034
28306
|
|
|
28035
28307
|
// apps/body/dist/server-command-intake.js
|
|
28036
|
-
import { randomUUID as
|
|
28308
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
28037
28309
|
import { mkdir as mkdir4, writeFile as writeFile5 } from "node:fs/promises";
|
|
28038
|
-
import { tmpdir as
|
|
28039
|
-
import { dirname as dirname9, join as
|
|
28310
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
28311
|
+
import { dirname as dirname9, join as join7 } from "node:path";
|
|
28040
28312
|
var CommandExecutionContext = class {
|
|
28041
|
-
generationId =
|
|
28313
|
+
generationId = randomUUID4();
|
|
28042
28314
|
path;
|
|
28043
28315
|
current;
|
|
28044
28316
|
constructor(root) {
|
|
28045
|
-
this.path =
|
|
28317
|
+
this.path = join7(root ?? tmpdir4(), `beeline-command-${this.generationId}.json`);
|
|
28046
28318
|
}
|
|
28047
28319
|
async enter(command) {
|
|
28048
28320
|
this.current = command;
|
|
@@ -28133,7 +28405,9 @@ async function runServerCommandIntake(options) {
|
|
|
28133
28405
|
return;
|
|
28134
28406
|
for (const command of [...pending.values()]) {
|
|
28135
28407
|
validateServerCommand(command, roomId, agentId);
|
|
28136
|
-
if (busy && command.action !== "stop")
|
|
28408
|
+
if (busy && command.action !== "stop" && command.action !== "restart")
|
|
28409
|
+
continue;
|
|
28410
|
+
if ((command.action === "input" || command.action === "resume") && options.canStartTurn?.() === false)
|
|
28137
28411
|
continue;
|
|
28138
28412
|
pending.delete(command.id);
|
|
28139
28413
|
claimed.add(command.id);
|
|
@@ -28148,7 +28422,11 @@ async function runServerCommandIntake(options) {
|
|
|
28148
28422
|
options.onError?.(error);
|
|
28149
28423
|
continue;
|
|
28150
28424
|
}
|
|
28151
|
-
if (command.action === "
|
|
28425
|
+
if (command.action === "restart") {
|
|
28426
|
+
if (!options.restart)
|
|
28427
|
+
throw new Error("restart command is not supported by this helper");
|
|
28428
|
+
options.restart(command);
|
|
28429
|
+
} else if (command.action === "stop") {
|
|
28152
28430
|
options.stop(command.turnRequestId);
|
|
28153
28431
|
await api.execute("acknowledgeAgentCommand", {
|
|
28154
28432
|
roomId,
|
|
@@ -28180,7 +28458,7 @@ async function runServerCommandIntake(options) {
|
|
|
28180
28458
|
wake = done;
|
|
28181
28459
|
const timer = setTimeout(() => done(true), pushIntakeAcknowledged ? 6e4 : options.pollMs ?? 1e3);
|
|
28182
28460
|
signal?.addEventListener("abort", aborted, { once: true });
|
|
28183
|
-
if (pending.
|
|
28461
|
+
if (!busy && [...pending.values()].some((command) => command.action === "stop" || command.action === "restart" || options.canStartTurn?.() !== false))
|
|
28184
28462
|
done(false);
|
|
28185
28463
|
});
|
|
28186
28464
|
if (signal?.aborted)
|
|
@@ -28199,11 +28477,11 @@ async function runServerCommandIntake(options) {
|
|
|
28199
28477
|
}
|
|
28200
28478
|
|
|
28201
28479
|
// apps/body/dist/monolith-corner-turn.js
|
|
28202
|
-
import { execFile as
|
|
28480
|
+
import { execFile as execFile7 } from "node:child_process";
|
|
28203
28481
|
import { createHash as createHash6 } from "node:crypto";
|
|
28204
28482
|
import { mkdir as mkdir14 } from "node:fs/promises";
|
|
28205
28483
|
import { homedir as homedir10 } from "node:os";
|
|
28206
|
-
import { join as
|
|
28484
|
+
import { join as join13 } from "node:path";
|
|
28207
28485
|
import { promisify as promisify4 } from "node:util";
|
|
28208
28486
|
|
|
28209
28487
|
// apps/body/dist/agent-home.js
|
|
@@ -29005,10 +29283,10 @@ function stringify(obj, { maxDepth = 1e3, numbersAsFloat = false } = {}) {
|
|
|
29005
29283
|
}
|
|
29006
29284
|
|
|
29007
29285
|
// apps/body/dist/agent-home.js
|
|
29008
|
-
import { createHash as createHash4, randomUUID as
|
|
29009
|
-
import { chmod as chmod3, copyFile, lstat as lstat2, mkdir as mkdir6, readFile as readFile6, readdir as readdir2, realpath, rename as rename3, rm as
|
|
29286
|
+
import { createHash as createHash4, randomUUID as randomUUID5 } from "node:crypto";
|
|
29287
|
+
import { chmod as chmod3, copyFile, lstat as lstat2, mkdir as mkdir6, readFile as readFile6, readdir as readdir2, realpath, rename as rename3, rm as rm3, symlink, unlink as unlink3, writeFile as writeFile7 } from "node:fs/promises";
|
|
29010
29288
|
import { homedir as homedir8 } from "node:os";
|
|
29011
|
-
import { basename as basename5, dirname as dirname10, join as
|
|
29289
|
+
import { basename as basename5, dirname as dirname10, join as join9, relative as relative2, resolve as resolve16, sep } from "node:path";
|
|
29012
29290
|
|
|
29013
29291
|
// apps/body/dist/beeline-skill.js
|
|
29014
29292
|
import { readFileSync as readFileSync6 } from "node:fs";
|
|
@@ -29020,6 +29298,7 @@ var MESSAGE_REACTION_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F3
|
|
|
29020
29298
|
// packages/api-contract/dist/workbench.js
|
|
29021
29299
|
var CONNECTABLE_CONNECTOR_KINDS = [
|
|
29022
29300
|
"trusty-squire",
|
|
29301
|
+
"tailscale",
|
|
29023
29302
|
"google-gmail",
|
|
29024
29303
|
"google-calendar",
|
|
29025
29304
|
"google-drive",
|
|
@@ -29141,7 +29420,7 @@ When your corner's pull request is ready, merging is your step: once the configu
|
|
|
29141
29420
|
|
|
29142
29421
|
A **tool** is something you can use once a human adds it; a **key** is the credential that tool holds for that human. You spend a key through the mounted connector and never see the credential itself.
|
|
29143
29422
|
|
|
29144
|
-
The tools this build knows, what each is for, and which the person you are answering already has are one call away: beeline-agent workbench_status. Trusty Squire is vaulted credentials plus a browser that signs up and signs in for you; the Google tools (Gmail, Calendar, Drive, YouTube) work through the person's own Google sign-in \u2014 the same Workbench overlay, never a second Google window. YouTube Analytics answers only the channel owner account, not a manager. The wallet is created only from the Workbench page
|
|
29423
|
+
The tools this build knows, what each is for, and which the person you are answering already has are one call away: beeline-agent workbench_status. Trusty Squire is vaulted credentials plus a browser that signs up and signs in for you; the Google tools (Gmail, Calendar, Drive, YouTube) work through the person's own Google sign-in \u2014 the same Workbench overlay, never a second Google window. YouTube Analytics answers only the channel owner account, not a manager. Tailscale installs its CLI on the selected helper and opens Tailscale's own browser sign-in; once connected, use the CLI for tailnet resources and tailscale file cp or tailscale file get for Taildrop. The wallet is created only from the Workbench page.
|
|
29145
29424
|
|
|
29146
29425
|
**Offer the tool at the moment you need it.** When the work in front of you needs a tool the person does not have, do not send them to a settings page and do not stop at naming it. Call workbench_status first - a tool they already have is used, not offered. Then call offer_connector with the connectorType and one short reason: a card appears in this Room, spoken by you, addressed to the person you are answering, with one action. Only that person or a Workspace admin can accept it; accepting adds the tool on your machine, and the sign-in or keys stay theirs. Your turn pauses on the card - say in prose what you are waiting for and end the turn; you are woken when it is added, and then you carry on.
|
|
29147
29426
|
|
|
@@ -29398,7 +29677,7 @@ function stringArray(value) {
|
|
|
29398
29677
|
}
|
|
29399
29678
|
|
|
29400
29679
|
// apps/body/dist/host-mcp-route.js
|
|
29401
|
-
import { join as
|
|
29680
|
+
import { join as join8, resolve as resolve14 } from "node:path";
|
|
29402
29681
|
var import_yaml = __toESM(require_dist(), 1);
|
|
29403
29682
|
function grantedMcpServerNames(grants) {
|
|
29404
29683
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -29415,18 +29694,33 @@ function ungatedHostServers(hostServers, granted) {
|
|
|
29415
29694
|
const allowed = new Set(granted);
|
|
29416
29695
|
return hostServers.filter((name) => !allowed.has(name));
|
|
29417
29696
|
}
|
|
29418
|
-
function
|
|
29697
|
+
async function claimGrantedHostRoutes(result, consumeOnce) {
|
|
29419
29698
|
if (!result || typeof result !== "object" || !("grants" in result))
|
|
29420
29699
|
return [];
|
|
29421
29700
|
const grants = result.grants;
|
|
29422
29701
|
if (!Array.isArray(grants))
|
|
29423
29702
|
return [];
|
|
29424
|
-
|
|
29703
|
+
const mounted = [];
|
|
29704
|
+
for (const entry of grants) {
|
|
29425
29705
|
if (!entry || typeof entry !== "object")
|
|
29426
|
-
|
|
29427
|
-
const
|
|
29428
|
-
|
|
29429
|
-
|
|
29706
|
+
continue;
|
|
29707
|
+
const grant = entry;
|
|
29708
|
+
if (grant.kind !== "mcp" || typeof grant.target !== "string")
|
|
29709
|
+
continue;
|
|
29710
|
+
if (grant.status === "once") {
|
|
29711
|
+
if (typeof grant.grantId !== "string" || !grant.grantId)
|
|
29712
|
+
continue;
|
|
29713
|
+
try {
|
|
29714
|
+
await consumeOnce(grant.grantId);
|
|
29715
|
+
} catch {
|
|
29716
|
+
continue;
|
|
29717
|
+
}
|
|
29718
|
+
} else if (grant.status !== "approved") {
|
|
29719
|
+
continue;
|
|
29720
|
+
}
|
|
29721
|
+
mounted.push({ kind: "mcp", target: grant.target });
|
|
29722
|
+
}
|
|
29723
|
+
return grantedMcpServerNames(mounted);
|
|
29430
29724
|
}
|
|
29431
29725
|
function recordValue(value) {
|
|
29432
29726
|
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
@@ -29449,7 +29743,7 @@ function grantedSquireHostRoute(granted, declarations = {}) {
|
|
|
29449
29743
|
});
|
|
29450
29744
|
}
|
|
29451
29745
|
function hostRouteEnv(hostHome) {
|
|
29452
|
-
return { XDG_CONFIG_HOME:
|
|
29746
|
+
return { XDG_CONFIG_HOME: join8(resolve14(hostHome), ".config") };
|
|
29453
29747
|
}
|
|
29454
29748
|
function rewriteHostMcpDeclaration(name, declaration, hostHome) {
|
|
29455
29749
|
const next = { ...declaration };
|
|
@@ -30633,7 +30927,7 @@ async function provisionManagedSkillsDir(target, managedSkills, sharedSkills, op
|
|
|
30633
30927
|
const plan = await planManagedSkills(managedSkills, sharedSkills, optionalShares);
|
|
30634
30928
|
if (await materializedSkillManifest(target) === plan.manifest)
|
|
30635
30929
|
return;
|
|
30636
|
-
const staged = resolve16(parent, `.skills.${process.pid}.${
|
|
30930
|
+
const staged = resolve16(parent, `.skills.${process.pid}.${randomUUID5()}.tmp`);
|
|
30637
30931
|
await mkdir6(staged, { mode: 448 });
|
|
30638
30932
|
try {
|
|
30639
30933
|
for (const entry of plan.entries) {
|
|
@@ -30647,10 +30941,10 @@ async function provisionManagedSkillsDir(target, managedSkills, sharedSkills, op
|
|
|
30647
30941
|
}
|
|
30648
30942
|
const existing = await lstat2(target).catch(() => void 0);
|
|
30649
30943
|
if (existing)
|
|
30650
|
-
await
|
|
30944
|
+
await rm3(target, { recursive: existing.isDirectory(), force: true });
|
|
30651
30945
|
await rename3(staged, target);
|
|
30652
30946
|
} finally {
|
|
30653
|
-
await
|
|
30947
|
+
await rm3(staged, { recursive: true, force: true });
|
|
30654
30948
|
}
|
|
30655
30949
|
}
|
|
30656
30950
|
async function planManagedSkills(managedSkills, sharedSkills, optionalShares) {
|
|
@@ -30669,8 +30963,8 @@ async function planManagedSkills(managedSkills, sharedSkills, optionalShares) {
|
|
|
30669
30963
|
try {
|
|
30670
30964
|
const tree = [];
|
|
30671
30965
|
await walkSafeSkillTree(shared.source, shared.source, {
|
|
30672
|
-
directory: async (rel) => void tree.push(`d ${
|
|
30673
|
-
file: async (rel, realPath) => void tree.push(`f ${
|
|
30966
|
+
directory: async (rel) => void tree.push(`d ${join9(shared.name, rel)}`),
|
|
30967
|
+
file: async (rel, realPath) => void tree.push(`f ${join9(shared.name, rel)} ${sha2563(await readFile6(realPath))}`)
|
|
30674
30968
|
});
|
|
30675
30969
|
entries.push({ kind: "shared", name: shared.name, source: shared.source });
|
|
30676
30970
|
lines.push(...tree);
|
|
@@ -30690,7 +30984,7 @@ async function materializedSkillManifest(target) {
|
|
|
30690
30984
|
const visit = async (directory, prefix) => {
|
|
30691
30985
|
for (const entry of await readdir2(directory)) {
|
|
30692
30986
|
const path = resolve16(directory, entry);
|
|
30693
|
-
const rel = prefix ?
|
|
30987
|
+
const rel = prefix ? join9(prefix, entry) : entry;
|
|
30694
30988
|
const entryStats = await lstat2(path);
|
|
30695
30989
|
if (entryStats.isSymbolicLink())
|
|
30696
30990
|
return false;
|
|
@@ -30858,7 +31152,7 @@ async function walkSafeSkillTree(source, sourceRoot, visitor, rel = "") {
|
|
|
30858
31152
|
for (const entry of await readdir2(resolvedSource)) {
|
|
30859
31153
|
if (entry === "." || entry === "..")
|
|
30860
31154
|
throw new Error("invalid shared skill entry");
|
|
30861
|
-
await walkSafeSkillTree(resolve16(source, entry), sourceRoot, visitor, rel ?
|
|
31155
|
+
await walkSafeSkillTree(resolve16(source, entry), sourceRoot, visitor, rel ? join9(rel, entry) : entry);
|
|
30862
31156
|
}
|
|
30863
31157
|
return;
|
|
30864
31158
|
}
|
|
@@ -30885,7 +31179,7 @@ async function writeIsolatedHarnessFile(path, content) {
|
|
|
30885
31179
|
if (!parentStats.isDirectory() || parentStats.isSymbolicLink()) {
|
|
30886
31180
|
throw new Error(`isolated harness parent is not a real directory: ${parent}`);
|
|
30887
31181
|
}
|
|
30888
|
-
const temporary = resolve16(parent, `.${basename5(path)}.${process.pid}.${
|
|
31182
|
+
const temporary = resolve16(parent, `.${basename5(path)}.${process.pid}.${randomUUID5()}.tmp`);
|
|
30889
31183
|
try {
|
|
30890
31184
|
await writeFile7(temporary, content, { mode: 384, flag: "wx" });
|
|
30891
31185
|
await chmod3(temporary, 384);
|
|
@@ -30944,7 +31238,7 @@ function agentCommandCatalogPublisher(input) {
|
|
|
30944
31238
|
|
|
30945
31239
|
// apps/body/dist/attachment-delivery.js
|
|
30946
31240
|
import { mkdir as mkdir7, writeFile as writeFile8 } from "node:fs/promises";
|
|
30947
|
-
import { basename as basename6, extname, join as
|
|
31241
|
+
import { basename as basename6, extname, join as join10 } from "node:path";
|
|
30948
31242
|
var MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
|
|
30949
31243
|
var EXPIRED_REASON = "expired: these attachment bytes are past their retention window";
|
|
30950
31244
|
var FETCH_TIMEOUT_MS = 3e4;
|
|
@@ -31003,7 +31297,7 @@ async function deliverAttachments(attachments, dir, fetchImpl = fetch) {
|
|
|
31003
31297
|
return { attachment, reason: EXPIRED_REASON };
|
|
31004
31298
|
if (!fetched.ok)
|
|
31005
31299
|
throw new Error(`HTTP ${fetched.status}`);
|
|
31006
|
-
const path =
|
|
31300
|
+
const path = join10(dir, safeFileName(attachment, index, taken));
|
|
31007
31301
|
await writeFile8(path, fetched.bytes);
|
|
31008
31302
|
const bytes = fetched.bytes;
|
|
31009
31303
|
const mimeType = attachment.mimeType ?? fetched.mimeType;
|
|
@@ -31611,9 +31905,9 @@ export default async function (pi) {
|
|
|
31611
31905
|
`;
|
|
31612
31906
|
|
|
31613
31907
|
// apps/body/dist/corner-branch-sync.js
|
|
31614
|
-
import { execFile as
|
|
31908
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
31615
31909
|
import { promisify as promisify2 } from "node:util";
|
|
31616
|
-
var execFileAsync2 = promisify2(
|
|
31910
|
+
var execFileAsync2 = promisify2(execFile5);
|
|
31617
31911
|
async function syncCornerBranch(input) {
|
|
31618
31912
|
const git = input.git ?? (async (args) => (await execFileAsync2("git", ["-C", input.worktreePath, ...args], {
|
|
31619
31913
|
...input.env ? { env: input.env } : {},
|
|
@@ -32136,11 +32430,11 @@ function youtubeMcpServer(config, accessToken) {
|
|
|
32136
32430
|
}
|
|
32137
32431
|
|
|
32138
32432
|
// apps/body/dist/codegraph.js
|
|
32139
|
-
import { execFile as
|
|
32433
|
+
import { execFile as execFile6 } from "node:child_process";
|
|
32140
32434
|
import { access, appendFile, mkdir as mkdir9, readFile as readFile7 } from "node:fs/promises";
|
|
32141
32435
|
import { dirname as dirname11, isAbsolute as isAbsolute3, resolve as resolve19 } from "node:path";
|
|
32142
32436
|
import { promisify as promisify3 } from "node:util";
|
|
32143
|
-
var execFileAsync3 = promisify3(
|
|
32437
|
+
var execFileAsync3 = promisify3(execFile6);
|
|
32144
32438
|
var CODEGRAPH_MCP_SERVER_NAME = "codegraph";
|
|
32145
32439
|
var CODEGRAPH_INDEX_PATH = ".codegraph/codegraph.db";
|
|
32146
32440
|
var CODEGRAPH_PREPARE_TIMEOUT_MS = 12e4;
|
|
@@ -32441,7 +32735,7 @@ async function withTurnReceiptHeartbeat(api, receipt, task, onHeartbeatError) {
|
|
|
32441
32735
|
}
|
|
32442
32736
|
|
|
32443
32737
|
// apps/body/dist/turn-trace.js
|
|
32444
|
-
import { appendFile as appendFile2, mkdir as mkdir10, readdir as readdir4, rm as
|
|
32738
|
+
import { appendFile as appendFile2, mkdir as mkdir10, readdir as readdir4, rm as rm4 } from "node:fs/promises";
|
|
32445
32739
|
import { resolve as resolve21 } from "node:path";
|
|
32446
32740
|
import { performance as performance2 } from "node:perf_hooks";
|
|
32447
32741
|
var TURN_PHASES = [
|
|
@@ -32713,7 +33007,7 @@ var TurnTraceFile = class {
|
|
|
32713
33007
|
this.prunedDay = day;
|
|
32714
33008
|
const cutoff = traceFileName(new Date(now2.getTime() - TURN_TRACE_RETENTION_DAYS * 864e5));
|
|
32715
33009
|
const names = await readdir4(this.directory).catch(() => []);
|
|
32716
|
-
await Promise.all(names.filter((name) => /^turns-\d{4}-\d{2}-\d{2}\.jsonl$/.test(name) && name < cutoff).map((name) =>
|
|
33010
|
+
await Promise.all(names.filter((name) => /^turns-\d{4}-\d{2}-\d{2}\.jsonl$/.test(name) && name < cutoff).map((name) => rm4(resolve21(this.directory, name), { force: true })));
|
|
32717
33011
|
}
|
|
32718
33012
|
};
|
|
32719
33013
|
|
|
@@ -32872,10 +33166,10 @@ process.exit(result.status ?? 1);
|
|
|
32872
33166
|
}
|
|
32873
33167
|
|
|
32874
33168
|
// apps/body/dist/warm-node-modules.js
|
|
32875
|
-
import { createHash as createHash5, randomUUID as
|
|
33169
|
+
import { createHash as createHash5, randomUUID as randomUUID6 } from "node:crypto";
|
|
32876
33170
|
import { constants as fsConstants } from "node:fs";
|
|
32877
|
-
import { chmod as chmod5, copyFile as copyFile2, link, lstat as lstat3, mkdir as mkdir12, readdir as readdir5, readFile as readFile9, readlink, rename as rename4, rm as
|
|
32878
|
-
import { dirname as dirname12, join as
|
|
33171
|
+
import { chmod as chmod5, copyFile as copyFile2, link, lstat as lstat3, mkdir as mkdir12, readdir as readdir5, readFile as readFile9, readlink, rename as rename4, rm as rm5, stat as stat2, symlink as symlink2, utimes } from "node:fs/promises";
|
|
33172
|
+
import { dirname as dirname12, join as join11, resolve as resolve23 } from "node:path";
|
|
32879
33173
|
var STORE_FORMAT = "v1";
|
|
32880
33174
|
var STAGING_PREFIX = ".beeline-warm-";
|
|
32881
33175
|
var STAGING_SWEEP_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -32943,7 +33237,7 @@ async function seedWarmNodeModules(input) {
|
|
|
32943
33237
|
if (storeDevice === void 0 || storeDevice !== checkoutDevice) {
|
|
32944
33238
|
return { reason: "cross-device", key: plan.key };
|
|
32945
33239
|
}
|
|
32946
|
-
const staging = resolve23(input.storeRoot, `${STAGING_PREFIX}${process.pid}.${
|
|
33240
|
+
const staging = resolve23(input.storeRoot, `${STAGING_PREFIX}${process.pid}.${randomUUID6()}`);
|
|
32947
33241
|
const placed = [];
|
|
32948
33242
|
const now2 = (input.now ?? Date.now)();
|
|
32949
33243
|
try {
|
|
@@ -32961,11 +33255,11 @@ async function seedWarmNodeModules(input) {
|
|
|
32961
33255
|
return { reason: "seeded", key: plan.key };
|
|
32962
33256
|
} catch (error) {
|
|
32963
33257
|
for (const target of placed) {
|
|
32964
|
-
await
|
|
33258
|
+
await rm5(target, { recursive: true, force: true }).catch(() => void 0);
|
|
32965
33259
|
}
|
|
32966
33260
|
return { reason: "failed", key: plan.key, detail: describe3(error) };
|
|
32967
33261
|
} finally {
|
|
32968
|
-
await
|
|
33262
|
+
await rm5(staging, { recursive: true, force: true }).catch(() => void 0);
|
|
32969
33263
|
}
|
|
32970
33264
|
}
|
|
32971
33265
|
async function harvestWarmNodeModules(input) {
|
|
@@ -32989,7 +33283,7 @@ async function harvestWarmNodeModules(input) {
|
|
|
32989
33283
|
detail: `${missing.length} absent, e.g. ${missing.slice(0, 3).join(", ")}`
|
|
32990
33284
|
};
|
|
32991
33285
|
}
|
|
32992
|
-
const staging = resolve23(input.storeRoot, `${STAGING_PREFIX}${process.pid}.${
|
|
33286
|
+
const staging = resolve23(input.storeRoot, `${STAGING_PREFIX}${process.pid}.${randomUUID6()}`);
|
|
32993
33287
|
try {
|
|
32994
33288
|
await mkdir12(input.storeRoot, { recursive: true, mode: 493 });
|
|
32995
33289
|
await sweepStaleStaging(input.storeRoot, (input.now ?? Date.now)());
|
|
@@ -33008,14 +33302,14 @@ async function harvestWarmNodeModules(input) {
|
|
|
33008
33302
|
return { reason: "already-warm", key: plan.key };
|
|
33009
33303
|
return { reason: "failed", key: plan.key, detail: describe3(error) };
|
|
33010
33304
|
} finally {
|
|
33011
|
-
await
|
|
33305
|
+
await rm5(staging, { recursive: true, force: true }).catch(() => void 0);
|
|
33012
33306
|
}
|
|
33013
33307
|
}
|
|
33014
33308
|
async function stagedTreeIsPublishable(worktreePath, staging, key) {
|
|
33015
33309
|
const settled = await readWarmPlan(worktreePath);
|
|
33016
33310
|
if (isPlanRefusal(settled) || settled.key !== key)
|
|
33017
33311
|
return false;
|
|
33018
|
-
const hidden =
|
|
33312
|
+
const hidden = join11("node_modules", ".package-lock.json");
|
|
33019
33313
|
const [copied, current] = await Promise.all([
|
|
33020
33314
|
readFile9(resolve23(staging, hidden)).catch(() => void 0),
|
|
33021
33315
|
readFile9(resolve23(worktreePath, hidden)).catch(() => void 0)
|
|
@@ -33053,7 +33347,7 @@ async function missingInstalledPackages(worktreePath, treeRoot = worktreePath) {
|
|
|
33053
33347
|
return missing.sort();
|
|
33054
33348
|
}
|
|
33055
33349
|
async function isInstalledPackage(path) {
|
|
33056
|
-
return stat2(
|
|
33350
|
+
return stat2(join11(path, "package.json")).then((info) => info.isFile(), () => false);
|
|
33057
33351
|
}
|
|
33058
33352
|
var INTEGRITY_READ_CONCURRENCY = 64;
|
|
33059
33353
|
async function mapWithLimit(values, limit, visit) {
|
|
@@ -33087,8 +33381,8 @@ function isContainedTreePath(value) {
|
|
|
33087
33381
|
async function cloneTree(source, target, file, topLevel = true) {
|
|
33088
33382
|
await mkdir12(target, { recursive: true, mode: 493 });
|
|
33089
33383
|
for (const entry of await readdir5(source, { withFileTypes: true })) {
|
|
33090
|
-
const from =
|
|
33091
|
-
const to =
|
|
33384
|
+
const from = join11(source, entry.name);
|
|
33385
|
+
const to = join11(target, entry.name);
|
|
33092
33386
|
if (entry.isSymbolicLink()) {
|
|
33093
33387
|
await symlink2(await readlink(from), to);
|
|
33094
33388
|
continue;
|
|
@@ -33118,13 +33412,13 @@ async function pruneWarmStore(storeRoot, keep) {
|
|
|
33118
33412
|
const names = (await readdir5(storeRoot).catch(() => [])).filter((name) => !name.startsWith(STAGING_PREFIX));
|
|
33119
33413
|
const entries = [];
|
|
33120
33414
|
for (const name of names) {
|
|
33121
|
-
const info = await lstat3(
|
|
33415
|
+
const info = await lstat3(join11(storeRoot, name)).catch(() => void 0);
|
|
33122
33416
|
if (info?.isDirectory())
|
|
33123
33417
|
entries.push({ name, usedAt: info.mtimeMs });
|
|
33124
33418
|
}
|
|
33125
33419
|
const dropped = entries.sort((a2, b) => b.usedAt - a2.usedAt || a2.name.localeCompare(b.name)).slice(keep);
|
|
33126
33420
|
for (const entry of dropped) {
|
|
33127
|
-
await
|
|
33421
|
+
await rm5(join11(storeRoot, entry.name), { recursive: true, force: true }).catch(() => void 0);
|
|
33128
33422
|
}
|
|
33129
33423
|
return dropped.map((entry) => entry.name);
|
|
33130
33424
|
}
|
|
@@ -33133,11 +33427,11 @@ async function sweepStaleStaging(storeRoot, now2) {
|
|
|
33133
33427
|
for (const name of entries) {
|
|
33134
33428
|
if (!name.startsWith(STAGING_PREFIX))
|
|
33135
33429
|
continue;
|
|
33136
|
-
const path =
|
|
33430
|
+
const path = join11(storeRoot, name);
|
|
33137
33431
|
const info = await lstat3(path).catch(() => void 0);
|
|
33138
33432
|
if (!info || now2 - info.mtimeMs < STAGING_SWEEP_MS)
|
|
33139
33433
|
continue;
|
|
33140
|
-
await
|
|
33434
|
+
await rm5(path, { recursive: true, force: true }).catch(() => void 0);
|
|
33141
33435
|
}
|
|
33142
33436
|
}
|
|
33143
33437
|
async function pathExists(path) {
|
|
@@ -33156,7 +33450,7 @@ function describe3(error) {
|
|
|
33156
33450
|
// apps/body/dist/monolith-room-turn.js
|
|
33157
33451
|
import { mkdir as mkdir13 } from "node:fs/promises";
|
|
33158
33452
|
import { homedir as homedir9 } from "node:os";
|
|
33159
|
-
import { join as
|
|
33453
|
+
import { join as join12 } from "node:path";
|
|
33160
33454
|
|
|
33161
33455
|
// packages/api-contract/dist/scheduled-prompts.js
|
|
33162
33456
|
var SCHEDULE_SCHEDULER_NAME = "Beeline Scheduler";
|
|
@@ -33200,6 +33494,14 @@ function resumePrompt(item) {
|
|
|
33200
33494
|
'Acknowledge in one short line (for example "Adding Trusty Squire now.") and continue the work that needed it, or say plainly what still has to happen before you can.'
|
|
33201
33495
|
].join(" ");
|
|
33202
33496
|
}
|
|
33497
|
+
const grant = parseGrantDecisionLine(item.body);
|
|
33498
|
+
if (grant?.kind === "mcp" && grant.decision !== "deny") {
|
|
33499
|
+
return [
|
|
33500
|
+
"This is the answer to your grant request; your paused work resumes now.",
|
|
33501
|
+
`The approved ${grant.target} route is mounted in this session.`,
|
|
33502
|
+
"Continue the paused work with that tool now; do not restart, schedule another turn, or request the route again."
|
|
33503
|
+
].join(" ");
|
|
33504
|
+
}
|
|
33203
33505
|
return [
|
|
33204
33506
|
"This is the answer to your grant request; your paused work resumes now.",
|
|
33205
33507
|
"If it was approved and it is a command grant, run it with run_granted_command and the exact argv.",
|
|
@@ -33375,7 +33677,7 @@ var MonolithRoomTurnLoop = class {
|
|
|
33375
33677
|
const cached = this.deliveredAttachments.get(item.id);
|
|
33376
33678
|
if (cached)
|
|
33377
33679
|
return cached;
|
|
33378
|
-
const delivered = await deliverAttachments(item.attachments,
|
|
33680
|
+
const delivered = await deliverAttachments(item.attachments, join12(this.attachmentDir, item.id.replace(/[^\w-]/g, "_")), this.options.fetchImpl);
|
|
33379
33681
|
this.deliveredAttachments.set(item.id, withoutImageData(delivered));
|
|
33380
33682
|
return delivered;
|
|
33381
33683
|
}
|
|
@@ -33432,9 +33734,10 @@ var MonolithRoomTurnLoop = class {
|
|
|
33432
33734
|
}
|
|
33433
33735
|
async grantedHostRoutes() {
|
|
33434
33736
|
try {
|
|
33435
|
-
return
|
|
33436
|
-
agentId: this.agent.publicKey
|
|
33437
|
-
|
|
33737
|
+
return claimGrantedHostRoutes(await this.options.api.execute("listAgentGrants", {
|
|
33738
|
+
agentId: this.agent.publicKey,
|
|
33739
|
+
roomId: this.options.roomId
|
|
33740
|
+
}), (grantId) => this.options.api.execute("consumeAgentGrant", { grantId }));
|
|
33438
33741
|
} catch {
|
|
33439
33742
|
return [];
|
|
33440
33743
|
}
|
|
@@ -33495,7 +33798,7 @@ var MonolithRoomTurnLoop = class {
|
|
|
33495
33798
|
args: this.options.config.agentArgs ?? []
|
|
33496
33799
|
}, selection);
|
|
33497
33800
|
const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
|
|
33498
|
-
this.attachmentDir = tmpDir ?
|
|
33801
|
+
this.attachmentDir = tmpDir ? join12(tmpDir, "beeline-attachments") : void 0;
|
|
33499
33802
|
this.sessionScratchDir = tmpDir;
|
|
33500
33803
|
this.sessionStateDirs = stateDirs;
|
|
33501
33804
|
const homeStateDirs = harnessHomeStateDirs(harnessLabel, agentEnv.HOME ?? operatorHome);
|
|
@@ -33984,6 +34287,8 @@ ${JSON.stringify((corners.corners ?? []).filter((corner) => !corner.archived))}`
|
|
|
33984
34287
|
onPoll: () => this.options.health.poll(),
|
|
33985
34288
|
onError: (error) => console.error("[thin-core] Room command failed", error),
|
|
33986
34289
|
stop: (requestId) => this.stopTurn(requestId),
|
|
34290
|
+
restart: () => this.options.onRestartRequested?.(),
|
|
34291
|
+
canStartTurn: this.options.canStartTurn,
|
|
33987
34292
|
run: async (command) => {
|
|
33988
34293
|
const item = {
|
|
33989
34294
|
...command.source,
|
|
@@ -34017,7 +34322,7 @@ function roomMessagePrompt(author, body, attachments, delivered, harnessAcceptsI
|
|
|
34017
34322
|
}
|
|
34018
34323
|
|
|
34019
34324
|
// apps/body/dist/monolith-corner-turn.js
|
|
34020
|
-
var execFileAsync4 = promisify4(
|
|
34325
|
+
var execFileAsync4 = promisify4(execFile7);
|
|
34021
34326
|
var TOOL_ARGUMENT_MAX_BYTES = 1200;
|
|
34022
34327
|
var TOOL_OUTPUT_MAX_BYTES = 3200;
|
|
34023
34328
|
var TOOL_PATH_LIMIT = 12;
|
|
@@ -34404,9 +34709,10 @@ var MonolithCornerTurnLoop = class {
|
|
|
34404
34709
|
}
|
|
34405
34710
|
async grantedHostRoutes() {
|
|
34406
34711
|
try {
|
|
34407
|
-
return
|
|
34408
|
-
agentId: this.agent.publicKey
|
|
34409
|
-
|
|
34712
|
+
return claimGrantedHostRoutes(await this.options.api.execute("listAgentGrants", {
|
|
34713
|
+
agentId: this.agent.publicKey,
|
|
34714
|
+
roomId: this.options.cornerId
|
|
34715
|
+
}), (grantId) => this.options.api.execute("consumeAgentGrant", { grantId }));
|
|
34410
34716
|
} catch {
|
|
34411
34717
|
return [];
|
|
34412
34718
|
}
|
|
@@ -34493,7 +34799,7 @@ var MonolithCornerTurnLoop = class {
|
|
|
34493
34799
|
args: this.options.config.agentArgs ?? []
|
|
34494
34800
|
}, selection);
|
|
34495
34801
|
const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
|
|
34496
|
-
this.attachmentDir = tmpDir ?
|
|
34802
|
+
this.attachmentDir = tmpDir ? join13(tmpDir, "beeline-attachments") : void 0;
|
|
34497
34803
|
this.sessionScratchDir = tmpDir;
|
|
34498
34804
|
const homeStateDirs = harnessHomeStateDirs(harnessLabel, agentEnv.HOME ?? operatorHome);
|
|
34499
34805
|
await Promise.all(homeStateDirs.map((dir) => mkdir14(dir, { recursive: true })));
|
|
@@ -34798,7 +35104,7 @@ var MonolithCornerTurnLoop = class {
|
|
|
34798
35104
|
const [conversation, roster, delivered, activeReviewerInstruction] = await trace.measure("context-fetch", () => Promise.all([
|
|
34799
35105
|
api.execute("getRoomConversation", { roomId: cornerId, limit: 200 }),
|
|
34800
35106
|
this.roster(),
|
|
34801
|
-
this.attachmentDir && attachments.length ? deliverAttachments(attachments,
|
|
35107
|
+
this.attachmentDir && attachments.length ? deliverAttachments(attachments, join13(this.attachmentDir, requestId.replace(/[^\w-]/g, "_")), this.options.fetchImpl) : Promise.resolve([]),
|
|
34802
35108
|
this.activeReviewerInstruction()
|
|
34803
35109
|
]));
|
|
34804
35110
|
const names = new Map(roster.members.map((member) => [member.identityId, member.name]));
|
|
@@ -35105,6 +35411,8 @@ ${trigger}`,
|
|
|
35105
35411
|
onPoll: () => this.options.onPoll(),
|
|
35106
35412
|
onError: (error) => console.error("[thin-core] corner command failed", error),
|
|
35107
35413
|
stop: (requestId) => this.stopTurn(requestId),
|
|
35414
|
+
restart: () => this.options.onRestartRequested?.(),
|
|
35415
|
+
canStartTurn: this.options.canStartTurn,
|
|
35108
35416
|
closed: async () => {
|
|
35109
35417
|
if (this.closePushed) {
|
|
35110
35418
|
this.closePushed = false;
|
|
@@ -35585,6 +35893,10 @@ var DiscoveryWakes = class {
|
|
|
35585
35893
|
function shouldPostInitialCornerWorkingState(restore, isOpener = true) {
|
|
35586
35894
|
return isOpener && !restore.featureBranch && !restore.lifecycle?.branch && !restore.lifecycle?.pr;
|
|
35587
35895
|
}
|
|
35896
|
+
function cornerBranchIsSafeToDelete(lifecycle) {
|
|
35897
|
+
const pr = lifecycle?.pr;
|
|
35898
|
+
return !pr || Boolean(pr.mergedAt);
|
|
35899
|
+
}
|
|
35588
35900
|
function cornerStartConfigKey(repository, objective) {
|
|
35589
35901
|
return JSON.stringify({
|
|
35590
35902
|
resolution: repository.resolution,
|
|
@@ -35625,7 +35937,7 @@ async function materializeCornerWorktree(input) {
|
|
|
35625
35937
|
`+refs/heads/${input.featureBranch}:refs/remotes/origin/${input.featureBranch}`
|
|
35626
35938
|
], { env: authEnv, maxBuffer: 4 * 1024 * 1024 }).then(() => true, () => false);
|
|
35627
35939
|
if (!existsSync6(resolve24(path, ".git"))) {
|
|
35628
|
-
await
|
|
35940
|
+
await rm6(path, { recursive: true, force: true });
|
|
35629
35941
|
await execFileAsync5("git", [
|
|
35630
35942
|
`--git-dir=${gitCommonDir}`,
|
|
35631
35943
|
"worktree",
|
|
@@ -35693,7 +36005,8 @@ async function mapWithConcurrency(values, limit, visit) {
|
|
|
35693
36005
|
}
|
|
35694
36006
|
}));
|
|
35695
36007
|
}
|
|
35696
|
-
async function removeCornerWorktreeAndBranches(worktree) {
|
|
36008
|
+
async function removeCornerWorktreeAndBranches(worktree, options = {}) {
|
|
36009
|
+
const preserveRemoteBranch = options.preserveRemoteBranch === true;
|
|
35697
36010
|
const localRef = `refs/heads/${worktree.branch}`;
|
|
35698
36011
|
const remoteRef = `refs/heads/${worktree.branch}`;
|
|
35699
36012
|
const worktreeExists = existsSync6(worktree.path);
|
|
@@ -35717,6 +36030,8 @@ async function removeCornerWorktreeAndBranches(worktree) {
|
|
|
35717
36030
|
worktree.path
|
|
35718
36031
|
]);
|
|
35719
36032
|
} else if (!localExists) {
|
|
36033
|
+
if (preserveRemoteBranch)
|
|
36034
|
+
return;
|
|
35720
36035
|
await deleteExactRemoteBranch(worktree.gitCommonDir, remoteRef, worktree.token, {
|
|
35721
36036
|
requireAbsent: true
|
|
35722
36037
|
});
|
|
@@ -35727,7 +36042,9 @@ async function removeCornerWorktreeAndBranches(worktree) {
|
|
|
35727
36042
|
throw new Error(`refusing to delete repository HEAD ${localRef}`);
|
|
35728
36043
|
}
|
|
35729
36044
|
}
|
|
35730
|
-
|
|
36045
|
+
if (!preserveRemoteBranch) {
|
|
36046
|
+
await deleteExactRemoteBranch(worktree.gitCommonDir, remoteRef, worktree.token);
|
|
36047
|
+
}
|
|
35731
36048
|
if (await gitRefExists(worktree.gitCommonDir, localRef)) {
|
|
35732
36049
|
await execFileAsync5("git", [
|
|
35733
36050
|
`--git-dir=${worktree.gitCommonDir}`,
|
|
@@ -35739,13 +36056,13 @@ async function removeCornerWorktreeAndBranches(worktree) {
|
|
|
35739
36056
|
]);
|
|
35740
36057
|
}
|
|
35741
36058
|
}
|
|
35742
|
-
var execFileAsync5 = promisify5(
|
|
36059
|
+
var execFileAsync5 = promisify5(execFile8);
|
|
35743
36060
|
async function removeCornerScratchWorkspace(input) {
|
|
35744
36061
|
const expected = resolve24(input.roomRoot, "scratch");
|
|
35745
36062
|
if (resolve24(input.scratchPath) !== expected) {
|
|
35746
36063
|
throw new Error(`refusing to remove scratch outside corner ${input.cornerId}`);
|
|
35747
36064
|
}
|
|
35748
|
-
await
|
|
36065
|
+
await rm6(expected, { recursive: true, force: true });
|
|
35749
36066
|
}
|
|
35750
36067
|
var RoomRuntimeCoordinator = class {
|
|
35751
36068
|
configPath;
|
|
@@ -35785,6 +36102,7 @@ var RoomRuntimeCoordinator = class {
|
|
|
35785
36102
|
workspaceRemovalConfirmations = 0;
|
|
35786
36103
|
roomRemovalConfirmations = /* @__PURE__ */ new Map();
|
|
35787
36104
|
confirmationPending = false;
|
|
36105
|
+
restartRequested = false;
|
|
35788
36106
|
/** Unscoped agent-directed discovery wakes (#1369), counted instead of
|
|
35789
36107
|
* flagged: a wake that arrives while a reconcile is already running must
|
|
35790
36108
|
* survive its start-pass clearing, or a reconnect mid-reconcile waits a
|
|
@@ -35877,6 +36195,12 @@ var RoomRuntimeCoordinator = class {
|
|
|
35877
36195
|
count += 1;
|
|
35878
36196
|
return count;
|
|
35879
36197
|
}
|
|
36198
|
+
requestLifecycleRestart() {
|
|
36199
|
+
if (this.restartRequested)
|
|
36200
|
+
return;
|
|
36201
|
+
this.restartRequested = true;
|
|
36202
|
+
this.options.onRestartRequested?.();
|
|
36203
|
+
}
|
|
35880
36204
|
quiesceForUpdateIfIdle() {
|
|
35881
36205
|
if (!this.isWorkspaceIdle())
|
|
35882
36206
|
return false;
|
|
@@ -36136,7 +36460,9 @@ var RoomRuntimeCoordinator = class {
|
|
|
36136
36460
|
},
|
|
36137
36461
|
onCornerOpened: () => {
|
|
36138
36462
|
this.confirmationPending = true;
|
|
36139
|
-
}
|
|
36463
|
+
},
|
|
36464
|
+
onRestartRequested: () => this.requestLifecycleRestart(),
|
|
36465
|
+
canStartTurn: () => !this.restartRequested
|
|
36140
36466
|
});
|
|
36141
36467
|
const promise = loop.run().catch((error) => {
|
|
36142
36468
|
if (!controller.signal.aborted)
|
|
@@ -36288,7 +36614,9 @@ var RoomRuntimeCoordinator = class {
|
|
|
36288
36614
|
branch: featureBranch,
|
|
36289
36615
|
parentRoomId: corner.parentRoomId,
|
|
36290
36616
|
token: ""
|
|
36291
|
-
}) : this.reapCornerScratch({ path: workspacePath, cornerId: corner.cornerId })
|
|
36617
|
+
}) : this.reapCornerScratch({ path: workspacePath, cornerId: corner.cornerId }),
|
|
36618
|
+
onRestartRequested: () => this.requestLifecycleRestart(),
|
|
36619
|
+
canStartTurn: () => !this.restartRequested
|
|
36292
36620
|
});
|
|
36293
36621
|
const promise = loop.run().catch((error) => {
|
|
36294
36622
|
if (!controller.signal.aborted) {
|
|
@@ -36394,25 +36722,44 @@ var RoomRuntimeCoordinator = class {
|
|
|
36394
36722
|
}
|
|
36395
36723
|
async reapCornerWorktree(worktree) {
|
|
36396
36724
|
try {
|
|
36725
|
+
const preserveRemoteBranch = !await this.cornerBranchMayBeDeleted(worktree.cornerId);
|
|
36397
36726
|
const parentRoomId = worktree.parentRoomId;
|
|
36398
|
-
if (!parentRoomId) {
|
|
36727
|
+
if (!parentRoomId && !preserveRemoteBranch) {
|
|
36399
36728
|
throw new Error(`corner ${worktree.cornerId} has no parent Room for GitHub token`);
|
|
36400
36729
|
}
|
|
36401
|
-
const token = (await this.options.daemonApi.execute("getRoomGitHubToken", {
|
|
36402
|
-
|
|
36403
|
-
|
|
36404
|
-
|
|
36405
|
-
branch: worktree.branch,
|
|
36406
|
-
state: "gone",
|
|
36407
|
-
checks: "unknown"
|
|
36408
|
-
});
|
|
36730
|
+
const token = preserveRemoteBranch ? "" : (await this.options.daemonApi.execute("getRoomGitHubToken", {
|
|
36731
|
+
roomId: parentRoomId
|
|
36732
|
+
})).token;
|
|
36733
|
+
await removeCornerWorktreeAndBranches({ ...worktree, token }, { preserveRemoteBranch });
|
|
36409
36734
|
this.pendingCornerReaps.delete(worktree.cornerId);
|
|
36735
|
+
if (!preserveRemoteBranch) {
|
|
36736
|
+
await this.options.daemonApi.execute("postCornerRemoteState", {
|
|
36737
|
+
cornerId: worktree.cornerId,
|
|
36738
|
+
branch: worktree.branch,
|
|
36739
|
+
state: "gone",
|
|
36740
|
+
checks: "unknown"
|
|
36741
|
+
});
|
|
36742
|
+
}
|
|
36410
36743
|
} catch (error) {
|
|
36411
36744
|
this.pendingCornerReaps.set(worktree.cornerId, { ...worktree, token: "" });
|
|
36412
36745
|
this.confirmationPending = true;
|
|
36413
36746
|
throw error;
|
|
36414
36747
|
}
|
|
36415
36748
|
}
|
|
36749
|
+
/**
|
|
36750
|
+
* Whether a corner's feature branch is safe to delete on teardown. A read
|
|
36751
|
+
* that fails — an agent removed from the corner, or a corner a helper can no
|
|
36752
|
+
* longer reach — is not proof its pull request is gone, so the branch is
|
|
36753
|
+
* kept.
|
|
36754
|
+
*/
|
|
36755
|
+
async cornerBranchMayBeDeleted(cornerId) {
|
|
36756
|
+
try {
|
|
36757
|
+
const state = await this.options.daemonApi.execute("getCornerRestoreState", { cornerId });
|
|
36758
|
+
return cornerBranchIsSafeToDelete(state.lifecycle);
|
|
36759
|
+
} catch {
|
|
36760
|
+
return false;
|
|
36761
|
+
}
|
|
36762
|
+
}
|
|
36416
36763
|
async reapCornerScratch(scratch) {
|
|
36417
36764
|
await removeCornerScratchWorkspace({
|
|
36418
36765
|
cornerId: scratch.cornerId,
|
|
@@ -36651,13 +36998,13 @@ import { mkdir as mkdir17, rename as rename5 } from "node:fs/promises";
|
|
|
36651
36998
|
import { dirname as dirname15, resolve as resolve26 } from "node:path";
|
|
36652
36999
|
|
|
36653
37000
|
// apps/body/dist/systemd.js
|
|
36654
|
-
import { execFile as
|
|
37001
|
+
import { execFile as execFile9 } from "node:child_process";
|
|
36655
37002
|
import { mkdir as mkdir16, readFile as readFile10, stat as stat3, writeFile as writeFile10 } from "node:fs/promises";
|
|
36656
37003
|
import { homedir as homedir11 } from "node:os";
|
|
36657
37004
|
import { dirname as dirname14, resolve as resolve25 } from "node:path";
|
|
36658
37005
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
36659
37006
|
import { promisify as promisify6 } from "node:util";
|
|
36660
|
-
var execFileAsync6 = promisify6(
|
|
37007
|
+
var execFileAsync6 = promisify6(execFile9);
|
|
36661
37008
|
var DELIBERATE_REMOVAL_EXIT_STATUS = 78;
|
|
36662
37009
|
var DAEMON_DISTRESS_EXIT_STATUS = 77;
|
|
36663
37010
|
var UNKNOWN_AGENT_EXIT_STATUS = 79;
|
|
@@ -36926,8 +37273,8 @@ init_self_update_manifest();
|
|
|
36926
37273
|
|
|
36927
37274
|
// apps/body/dist/managed-update.js
|
|
36928
37275
|
init_self_update();
|
|
36929
|
-
import { spawn as
|
|
36930
|
-
import { mkdir as mkdir20, rm as
|
|
37276
|
+
import { spawn as spawn10 } from "node:child_process";
|
|
37277
|
+
import { mkdir as mkdir20, rm as rm8, stat as stat4, writeFile as writeFile13 } from "node:fs/promises";
|
|
36931
37278
|
import { dirname as dirname18, resolve as resolve29 } from "node:path";
|
|
36932
37279
|
|
|
36933
37280
|
// apps/body/dist/update-rollback-alert.js
|
|
@@ -37020,7 +37367,7 @@ ${now2()}
|
|
|
37020
37367
|
throw error;
|
|
37021
37368
|
const age = now2() - await stat4(lock).then((value) => value.mtimeMs).catch(() => now2());
|
|
37022
37369
|
if (age > LOCK_STALE_MS) {
|
|
37023
|
-
await
|
|
37370
|
+
await rm8(lock, { recursive: true, force: true });
|
|
37024
37371
|
continue;
|
|
37025
37372
|
}
|
|
37026
37373
|
if (now2() >= deadline)
|
|
@@ -37031,7 +37378,7 @@ ${now2()}
|
|
|
37031
37378
|
try {
|
|
37032
37379
|
return await work();
|
|
37033
37380
|
} finally {
|
|
37034
|
-
await
|
|
37381
|
+
await rm8(lock, { recursive: true, force: true });
|
|
37035
37382
|
}
|
|
37036
37383
|
}
|
|
37037
37384
|
var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
|
|
@@ -37341,7 +37688,7 @@ async function runManagedUpdateWorkerProcess() {
|
|
|
37341
37688
|
if (!entrypoint)
|
|
37342
37689
|
throw new Error("cannot resolve the current Beeline entrypoint");
|
|
37343
37690
|
await new Promise((resolveWorker, rejectWorker) => {
|
|
37344
|
-
const child =
|
|
37691
|
+
const child = spawn10(process.execPath, [entrypoint, "managed-update-worker"], {
|
|
37345
37692
|
detached: true,
|
|
37346
37693
|
env: { ...process.env, BEELINE_INTERNAL_UPDATE_WORKER: "1" },
|
|
37347
37694
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -37500,6 +37847,32 @@ async function rollbackFailedSuccessor(layout, runtimeDir, cause = {}) {
|
|
|
37500
37847
|
}
|
|
37501
37848
|
|
|
37502
37849
|
// apps/body/dist/self-update-cli.js
|
|
37850
|
+
async function refreshRuntimeHarnessAdapters(options = {}) {
|
|
37851
|
+
const log2 = options.log ?? console.log;
|
|
37852
|
+
const readRuntime = options.readRuntime ?? readRuntimeRecord;
|
|
37853
|
+
const install = options.install ?? ((command) => runAdapterInstall(command));
|
|
37854
|
+
const configPaths = options.configPaths ?? [
|
|
37855
|
+
...await findRuntimeConfigPaths(process.cwd()).catch(() => []),
|
|
37856
|
+
...await findAgentRuntimeConfigPaths(process.env, process.cwd()).catch(() => [])
|
|
37857
|
+
];
|
|
37858
|
+
const kinds = /* @__PURE__ */ new Set();
|
|
37859
|
+
for (const path of new Set(configPaths)) {
|
|
37860
|
+
const runtime = await readRuntime(path).catch(() => void 0);
|
|
37861
|
+
if (runtime?.agentKind)
|
|
37862
|
+
kinds.add(runtime.agentKind);
|
|
37863
|
+
}
|
|
37864
|
+
for (const kind of kinds) {
|
|
37865
|
+
const command = latestAdapterInstallCommand(kind);
|
|
37866
|
+
if (!command)
|
|
37867
|
+
continue;
|
|
37868
|
+
log2(`[beeline] refreshing ${kind} adapter: ${formatAdapterInstallCommand(command)}`);
|
|
37869
|
+
try {
|
|
37870
|
+
await install(command);
|
|
37871
|
+
} catch (error) {
|
|
37872
|
+
log2(`[beeline] ${kind} adapter refresh failed; keeping the installed copy (${error instanceof Error ? error.message : String(error)})`);
|
|
37873
|
+
}
|
|
37874
|
+
}
|
|
37875
|
+
}
|
|
37503
37876
|
function updateUsage() {
|
|
37504
37877
|
console.log(`
|
|
37505
37878
|
${import_picocolors.default.bold("Update the installed Beeline bundle from the published manifest.")}
|
|
@@ -37601,6 +37974,8 @@ async function runUpdateCommand(args) {
|
|
|
37601
37974
|
await printStatus(layout);
|
|
37602
37975
|
return;
|
|
37603
37976
|
}
|
|
37977
|
+
if (!checkOnly)
|
|
37978
|
+
await refreshRuntimeHarnessAdapters();
|
|
37604
37979
|
const installed = await readInstalledBundleIdentity(layout);
|
|
37605
37980
|
console.log(`[beeline] installed bundle: ${describeIdentity(installed)}`);
|
|
37606
37981
|
const raw = await fetch(manifestUrl, { signal: AbortSignal.timeout(3e4) }).then((response) => {
|
|
@@ -37767,7 +38142,7 @@ async function runStartCommand(args, interactiveUi, dependencyOverrides = {}) {
|
|
|
37767
38142
|
|
|
37768
38143
|
// apps/body/dist/connect-command.js
|
|
37769
38144
|
import { spawn as spawn11 } from "node:child_process";
|
|
37770
|
-
import { createHash as createHash9, randomUUID as
|
|
38145
|
+
import { createHash as createHash9, randomUUID as randomUUID7 } from "node:crypto";
|
|
37771
38146
|
import { chmod as chmod7, mkdir as mkdir21, readFile as readFile13, unlink as unlink5, writeFile as writeFile14 } from "node:fs/promises";
|
|
37772
38147
|
import { homedir as homedir13, hostname as hostname2 } from "node:os";
|
|
37773
38148
|
import { dirname as dirname20, resolve as resolve30 } from "node:path";
|
|
@@ -37854,14 +38229,13 @@ async function verifyProviderKey(input) {
|
|
|
37854
38229
|
}
|
|
37855
38230
|
|
|
37856
38231
|
// apps/body/dist/pair-agent-selection.js
|
|
37857
|
-
import { spawn as spawn10 } from "node:child_process";
|
|
37858
38232
|
import { stdin as stdin2, stdout as stdout3 } from "node:process";
|
|
37859
38233
|
var NO_AGENT_MESSAGE = `No supported ACP-capable coding agent was detected.
|
|
37860
38234
|
Install one of these supported agents:
|
|
37861
|
-
codex npm install -g @openai/codex @agentclientprotocol/codex-acp
|
|
37862
|
-
claude Install Claude Code and npm install -g @agentclientprotocol/claude-agent-acp
|
|
38235
|
+
codex npm install -g @openai/codex@latest @agentclientprotocol/codex-acp@latest
|
|
38236
|
+
claude Install Claude Code and npm install -g @agentclientprotocol/claude-agent-acp@latest
|
|
37863
38237
|
goose https://block.github.io/goose/docs/getting-started/installation/
|
|
37864
|
-
pi npm install -g @mariozechner/pi-coding-agent pi-acp
|
|
38238
|
+
pi npm install -g @mariozechner/pi-coding-agent@latest pi-acp@latest
|
|
37865
38239
|
grok curl -fsSL https://x.ai/cli/install.sh | bash
|
|
37866
38240
|
cursor Install Cursor Agent CLI from https://cursor.com/docs/cli
|
|
37867
38241
|
Then retry, or explicitly use \`--agent reference\` with an LLM key.
|
|
@@ -37882,21 +38256,7 @@ async function clackSelectAgent(candidates) {
|
|
|
37882
38256
|
return unwrapPrompt(picked, "Pairing cancelled.");
|
|
37883
38257
|
}
|
|
37884
38258
|
async function installAdapter(install, opts) {
|
|
37885
|
-
await
|
|
37886
|
-
const child = spawn10(install.command, install.args, {
|
|
37887
|
-
cwd: opts.cwd,
|
|
37888
|
-
env: opts.env ?? process.env,
|
|
37889
|
-
stdio: "inherit"
|
|
37890
|
-
});
|
|
37891
|
-
child.once("error", rejectInstall);
|
|
37892
|
-
child.once("exit", (code, signal) => {
|
|
37893
|
-
if (code === 0)
|
|
37894
|
-
resolveInstall();
|
|
37895
|
-
else {
|
|
37896
|
-
rejectInstall(new Error(signal ? `${install.command} was terminated by ${signal}` : `${install.command} exited with status ${code ?? "unknown"}`));
|
|
37897
|
-
}
|
|
37898
|
-
});
|
|
37899
|
-
});
|
|
38259
|
+
await runAdapterInstall(install, opts);
|
|
37900
38260
|
}
|
|
37901
38261
|
function errorMessage(error) {
|
|
37902
38262
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -37910,14 +38270,38 @@ async function selectPairAgentCommand(opts) {
|
|
|
37910
38270
|
const selectAgent = opts.selectAgent ?? clackSelectAgent;
|
|
37911
38271
|
const confirmInstall = opts.confirmInstall ?? clackConfirmInstall;
|
|
37912
38272
|
const runInstall = opts.install ?? installAdapter;
|
|
38273
|
+
const useReadyAgent = async (agent, label) => {
|
|
38274
|
+
const refresh = latestAdapterInstallCommand(agent.kind);
|
|
38275
|
+
if (!refresh) {
|
|
38276
|
+
output.write(`[beeline] using ${agent.kind} (${label})
|
|
38277
|
+
`);
|
|
38278
|
+
return agent;
|
|
38279
|
+
}
|
|
38280
|
+
output.write(`[beeline] refreshing ${agent.kind} adapter: ${formatAdapterInstallCommand(refresh)}
|
|
38281
|
+
`);
|
|
38282
|
+
try {
|
|
38283
|
+
await runInstall(refresh, { cwd: opts.cwd, env: opts.env });
|
|
38284
|
+
const refreshed = resolveAgentCommand({
|
|
38285
|
+
kind: agent.kind,
|
|
38286
|
+
env: opts.env,
|
|
38287
|
+
cwd: opts.cwd
|
|
38288
|
+
});
|
|
38289
|
+
output.write(`[beeline] using ${refreshed.kind} (${label}; adapter current)
|
|
38290
|
+
`);
|
|
38291
|
+
return refreshed;
|
|
38292
|
+
} catch (installError) {
|
|
38293
|
+
throw new Error(`Could not refresh the ${agent.kind} adapter: ${errorMessage(installError)}. Install it with: ${formatAdapterInstallCommand(refresh)}`);
|
|
38294
|
+
}
|
|
38295
|
+
};
|
|
37913
38296
|
if (opts.explicitKind !== void 0) {
|
|
37914
38297
|
try {
|
|
37915
|
-
|
|
38298
|
+
const selected = resolveAgentCommand({
|
|
37916
38299
|
kind: opts.explicitKind,
|
|
37917
38300
|
customCommand: opts.customCommand,
|
|
37918
38301
|
env: opts.env,
|
|
37919
38302
|
cwd: opts.cwd
|
|
37920
38303
|
});
|
|
38304
|
+
return await useReadyAgent(selected, "selected");
|
|
37921
38305
|
} catch (resolutionError) {
|
|
37922
38306
|
const candidate = detectInstalledAgentCommands({ env: opts.env, cwd: opts.cwd }).find((detected2) => detected2.kind === opts.explicitKind && detected2.status === "missing-adapter");
|
|
37923
38307
|
if (!candidate || candidate.status !== "missing-adapter")
|
|
@@ -37963,25 +38347,19 @@ async function selectPairAgentCommand(opts) {
|
|
|
37963
38347
|
throw new Error("Installed coding agents need ACP adapter setup; install an adapter above, then retry.");
|
|
37964
38348
|
}
|
|
37965
38349
|
if (ready.length === 1) {
|
|
37966
|
-
|
|
37967
|
-
`);
|
|
37968
|
-
return ready[0].agent;
|
|
38350
|
+
return useReadyAgent(ready[0].agent, "auto-detected");
|
|
37969
38351
|
}
|
|
37970
38352
|
const readyKinds = ready.map((candidate) => candidate.kind).join(", ");
|
|
37971
38353
|
throw new Error(`Detected multiple ACP-capable coding agents: ${readyKinds}. This session is non-interactive; pass \`--agent <name>\` explicitly.`);
|
|
37972
38354
|
}
|
|
37973
38355
|
if (detected.length === 1 && detected[0].status === "ready") {
|
|
37974
|
-
|
|
37975
|
-
`);
|
|
37976
|
-
return detected[0].agent;
|
|
38356
|
+
return useReadyAgent(detected[0].agent, "auto-detected");
|
|
37977
38357
|
}
|
|
37978
38358
|
while (true) {
|
|
37979
38359
|
const pickedKind = await selectAgent(detected);
|
|
37980
38360
|
const selected = detected.find((candidate) => candidate.kind === pickedKind);
|
|
37981
38361
|
if (selected.status === "ready") {
|
|
37982
|
-
|
|
37983
|
-
`);
|
|
37984
|
-
return selected.agent;
|
|
38362
|
+
return useReadyAgent(selected.agent, "selected");
|
|
37985
38363
|
}
|
|
37986
38364
|
const manual = missingAdapterMessage(selected);
|
|
37987
38365
|
output.write(`[beeline] installing ${selected.kind} adapter: ${formatAdapterInstallCommand(selected.install)}
|
|
@@ -38200,10 +38578,23 @@ function connectModelPickerFromAxes(axes, fallbackModel, harness, rawAxes = axes
|
|
|
38200
38578
|
note: `${harness} did not enumerate models; offering the provider default`
|
|
38201
38579
|
};
|
|
38202
38580
|
}
|
|
38203
|
-
|
|
38204
|
-
|
|
38581
|
+
var refreshedConnectAdapters = /* @__PURE__ */ new Map();
|
|
38582
|
+
function refreshConnectAdapter(kind) {
|
|
38583
|
+
const existing = refreshedConnectAdapters.get(kind);
|
|
38584
|
+
if (existing)
|
|
38585
|
+
return existing;
|
|
38586
|
+
const refresh = installLatestAgentAdapter(kind).catch((error) => {
|
|
38587
|
+
refreshedConnectAdapters.delete(kind);
|
|
38588
|
+
throw error;
|
|
38589
|
+
});
|
|
38590
|
+
refreshedConnectAdapters.set(kind, refresh);
|
|
38591
|
+
return refresh;
|
|
38592
|
+
}
|
|
38593
|
+
async function loadConnectModelCatalog(input, dependencies = {}) {
|
|
38594
|
+
await (dependencies.refreshAdapter ?? refreshConnectAdapter)(input.harness);
|
|
38595
|
+
const agent = (dependencies.resolveAgent ?? ((kind) => resolveAgentCommand({ kind })))(input.harness);
|
|
38205
38596
|
const model = input.model ?? defaultConnectModel(input.harness, input.provider);
|
|
38206
|
-
const catalog = await fetchAgentModelCatalog(agent, providerEnvironment({
|
|
38597
|
+
const catalog = await (dependencies.fetchCatalog ?? fetchAgentModelCatalog)(agent, providerEnvironment({
|
|
38207
38598
|
harness: input.harness,
|
|
38208
38599
|
...input.provider ? { provider: input.provider } : {},
|
|
38209
38600
|
...input.apiKey ? { apiKey: input.apiKey } : {},
|
|
@@ -38409,7 +38800,7 @@ async function readMachineId(env = process.env) {
|
|
|
38409
38800
|
if (!/^[0-9a-f-]{32,}$/.test(machineId))
|
|
38410
38801
|
throw new Error("invalid persisted machine id");
|
|
38411
38802
|
} catch {
|
|
38412
|
-
machineId =
|
|
38803
|
+
machineId = randomUUID7();
|
|
38413
38804
|
try {
|
|
38414
38805
|
await mkdir21(configDir, { recursive: true, mode: 448 });
|
|
38415
38806
|
await writeFile14(machineIdPath, `${machineId}
|
|
@@ -38689,7 +39080,7 @@ async function runConnectFinishCommand(path) {
|
|
|
38689
39080
|
init_self_update();
|
|
38690
39081
|
|
|
38691
39082
|
// apps/body/dist/daemon-failure.js
|
|
38692
|
-
import { mkdir as mkdir22, readFile as readFile14, rename as rename8, rm as
|
|
39083
|
+
import { mkdir as mkdir22, readFile as readFile14, rename as rename8, rm as rm9, writeFile as writeFile15 } from "node:fs/promises";
|
|
38693
39084
|
import { dirname as dirname21, resolve as resolve31 } from "node:path";
|
|
38694
39085
|
var DAEMON_FAILURE_LIMIT = 3;
|
|
38695
39086
|
var DAEMON_FAILURE_WINDOW_MS = 5 * 6e4;
|
|
@@ -38729,11 +39120,11 @@ async function recordDaemonStartFailure(runtimeDir, error, now2 = Date.now()) {
|
|
|
38729
39120
|
return { distressed, count: failures.length, path: daemonFailurePath(runtimeDir) };
|
|
38730
39121
|
}
|
|
38731
39122
|
async function clearDaemonStartFailures(runtimeDir) {
|
|
38732
|
-
await
|
|
39123
|
+
await rm9(daemonFailurePath(runtimeDir), { force: true });
|
|
38733
39124
|
}
|
|
38734
39125
|
|
|
38735
39126
|
// apps/body/dist/update-functional-probe.js
|
|
38736
|
-
import { mkdir as mkdir23, rm as
|
|
39127
|
+
import { mkdir as mkdir23, rm as rm10 } from "node:fs/promises";
|
|
38737
39128
|
import { homedir as homedir14 } from "node:os";
|
|
38738
39129
|
import { resolve as resolve32 } from "node:path";
|
|
38739
39130
|
var UPDATE_PROBE_SESSION_TIMEOUT_MS = 1e4;
|
|
@@ -38858,7 +39249,7 @@ async function runUpdateFunctionalProbe(input) {
|
|
|
38858
39249
|
const root = input.probeRoot ?? resolve32(input.runtimeDir, "update-functional-probe");
|
|
38859
39250
|
const cwd = resolve32(root, "checkout");
|
|
38860
39251
|
const homeRoot = resolve32(root, "agent-home");
|
|
38861
|
-
await
|
|
39252
|
+
await rm10(root, { recursive: true, force: true });
|
|
38862
39253
|
await mkdir23(cwd, { recursive: true, mode: 448 });
|
|
38863
39254
|
let client;
|
|
38864
39255
|
try {
|
|
@@ -39033,13 +39424,13 @@ async function runUpdateFunctionalProbe(input) {
|
|
|
39033
39424
|
};
|
|
39034
39425
|
} finally {
|
|
39035
39426
|
await client?.stop().catch(() => void 0);
|
|
39036
|
-
await
|
|
39427
|
+
await rm10(root, { recursive: true, force: true }).catch(() => void 0);
|
|
39037
39428
|
}
|
|
39038
39429
|
}
|
|
39039
39430
|
|
|
39040
39431
|
// apps/body/dist/current-release-probe.js
|
|
39041
39432
|
import { spawn as spawn12 } from "node:child_process";
|
|
39042
|
-
import { dirname as dirname22, join as
|
|
39433
|
+
import { dirname as dirname22, join as join15 } from "node:path";
|
|
39043
39434
|
init_self_update();
|
|
39044
39435
|
var CURRENT_RELEASE_PROBE_TIMEOUT_MS = 12e4;
|
|
39045
39436
|
var UPDATE_PROBE_COMMAND = "update-probe";
|
|
@@ -39082,7 +39473,7 @@ function outcomeFromReport(report) {
|
|
|
39082
39473
|
}
|
|
39083
39474
|
}
|
|
39084
39475
|
async function probeReleaseInSubprocess(input) {
|
|
39085
|
-
const bundleDir =
|
|
39476
|
+
const bundleDir = join15(input.layout.releasesRoot, input.releaseId);
|
|
39086
39477
|
const entrypoint = await resolveBundleEntrypoint(bundleDir);
|
|
39087
39478
|
if (!entrypoint) {
|
|
39088
39479
|
return { kind: "unavailable", reason: `release ${input.releaseId} has no runnable CLI entrypoint` };
|
|
@@ -39145,7 +39536,7 @@ async function runUpdateProbeCommand(args, options = {}) {
|
|
|
39145
39536
|
const runtime = await readRuntimeRecord(configPath);
|
|
39146
39537
|
const agent = runtimeAgentCommand(runtime);
|
|
39147
39538
|
const config = loadBodyConfig({
|
|
39148
|
-
workspaceRoot:
|
|
39539
|
+
workspaceRoot: join15(dirname22(configPath), "workspace"),
|
|
39149
39540
|
llmEnvFile: runtime.llmEnvFile,
|
|
39150
39541
|
env: { ...env, BUZZ_AGENT_BIN: agent.command, BUZZ_DEV_MCP_BIN: runtime.mcpBinary },
|
|
39151
39542
|
agent
|
|
@@ -39171,7 +39562,7 @@ async function runUpdateProbeCommand(args, options = {}) {
|
|
|
39171
39562
|
sandboxRequired: runtime.sandbox !== "off",
|
|
39172
39563
|
sandboxUnavailableDetail: sandbox.advisory,
|
|
39173
39564
|
// The successor's probe still holds `<runtimeDir>/update-functional-probe`.
|
|
39174
|
-
probeRoot:
|
|
39565
|
+
probeRoot: join15(runtimeDir, "current-release-probe")
|
|
39175
39566
|
}));
|
|
39176
39567
|
const report = outcome.kind === "served" ? { probe: "served" } : outcome.kind === "refused" ? { probe: "refused", status: outcome.status, reason: outcome.reason } : outcome.kind === "sandbox-unavailable" ? { probe: "sandbox-unavailable", reason: outcome.reason } : { probe: "failed", reason: outcome.reason };
|
|
39177
39568
|
write(JSON.stringify(report));
|
|
@@ -39344,6 +39735,21 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
39344
39735
|
throw new Error("monolith daemon transport activation failed");
|
|
39345
39736
|
runtime = activated.runtime;
|
|
39346
39737
|
const daemonApi = activated.client;
|
|
39738
|
+
const refreshRuntimeAdapter = async () => {
|
|
39739
|
+
const kind = runtime.agentKind;
|
|
39740
|
+
if (!kind)
|
|
39741
|
+
return;
|
|
39742
|
+
const install = latestAdapterInstallCommand(kind);
|
|
39743
|
+
if (!install)
|
|
39744
|
+
return;
|
|
39745
|
+
console.log(`[body] refreshing ${kind} adapter: ${formatAdapterInstallCommand(install)}`);
|
|
39746
|
+
await installLatestAgentAdapter(kind);
|
|
39747
|
+
};
|
|
39748
|
+
try {
|
|
39749
|
+
await refreshRuntimeAdapter();
|
|
39750
|
+
} catch (error) {
|
|
39751
|
+
console.error(`[body] adapter refresh failed; validating the installed copy (${error instanceof Error ? error.message : String(error)})`);
|
|
39752
|
+
}
|
|
39347
39753
|
const agent = runtimeAgentCommand(runtime);
|
|
39348
39754
|
await writeDaemonPidRecord(configPath, process.pid);
|
|
39349
39755
|
const env = {
|
|
@@ -39369,7 +39775,7 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
39369
39775
|
if (runtime.sharedSkills)
|
|
39370
39776
|
config.sharedSkills = [...runtime.sharedSkills];
|
|
39371
39777
|
if (runtime.modelSelection) {
|
|
39372
|
-
await applyRuntimeModelPreflight(config, agent, runtime.modelSelection);
|
|
39778
|
+
await applyRuntimeModelPreflight(config, agent, runtime.modelSelection, void 0, refreshRuntimeAdapter);
|
|
39373
39779
|
if (!config.modelUnavailable) {
|
|
39374
39780
|
console.log("[body] persisted model/effort selection passed live startup validation");
|
|
39375
39781
|
} else {
|
|
@@ -39453,6 +39859,7 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
39453
39859
|
};
|
|
39454
39860
|
let stoppingStatus = "daemon stopped";
|
|
39455
39861
|
try {
|
|
39862
|
+
let lifecycleRestartDrain;
|
|
39456
39863
|
const core = new ThinDaemonCore(runtime, configPath, config, {
|
|
39457
39864
|
daemonApi,
|
|
39458
39865
|
onConfigChanged: refreshCatalog,
|
|
@@ -39465,6 +39872,30 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
39465
39872
|
}
|
|
39466
39873
|
const timer = setTimeout(() => process.exit(0), delay);
|
|
39467
39874
|
timer.unref?.();
|
|
39875
|
+
},
|
|
39876
|
+
onRestartRequested: () => {
|
|
39877
|
+
if (lifecycleRestartDrain)
|
|
39878
|
+
return;
|
|
39879
|
+
const deadlineAt = Date.now() + DEFAULT_DRAIN_DEADLINE_MS;
|
|
39880
|
+
stoppingStatus = `restart requested; active work draining; exit_deadline=${new Date(deadlineAt).toISOString()}`;
|
|
39881
|
+
void notifier.progress(stoppingStatus);
|
|
39882
|
+
core.setDrainDeadlineAt(deadlineAt);
|
|
39883
|
+
lifecycleRestartDrain = (async () => {
|
|
39884
|
+
while (core.activeTurnCount() > 0 && Date.now() < deadlineAt) {
|
|
39885
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 250));
|
|
39886
|
+
}
|
|
39887
|
+
const forced = core.activeTurnCount() > 0;
|
|
39888
|
+
if (forced)
|
|
39889
|
+
await core.prepareForForcedUpdateRestart();
|
|
39890
|
+
else
|
|
39891
|
+
core.quiesceForUpdateIfIdle();
|
|
39892
|
+
stoppingStatus = forced ? "restart requested; drain deadline reached; active work cancelled" : "restart requested; active work drained";
|
|
39893
|
+
await notifier.stopping(stoppingStatus);
|
|
39894
|
+
controller.abort();
|
|
39895
|
+
})().catch((error) => {
|
|
39896
|
+
console.error("[thin-core] requested restart drain failed:", error);
|
|
39897
|
+
controller.abort();
|
|
39898
|
+
});
|
|
39468
39899
|
}
|
|
39469
39900
|
});
|
|
39470
39901
|
const updateDrain = update ? new ManagedUpdateDrain({
|