usebeeline 0.0.122 → 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 +1936 -1447
- 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,1321 +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
|
-
|
|
15327
|
-
|
|
15328
|
-
|
|
15329
|
-
|
|
15330
|
-
|
|
15331
|
-
|
|
15332
|
-
|
|
15333
|
-
|
|
15334
|
-
|
|
15335
|
-
|
|
15336
|
-
|
|
15337
|
-
|
|
15338
|
-
|
|
15339
|
-
|
|
15340
|
-
|
|
15341
|
-
|
|
15342
|
-
|
|
15343
|
-
|
|
15344
|
-
if (!this.supportsSessionLoading) {
|
|
15345
|
-
throw new Error("ACP agent does not advertise session/load");
|
|
15346
|
-
}
|
|
15347
|
-
const params = {
|
|
15348
|
-
sessionId: opts.sessionId,
|
|
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 };
|
|
15364
|
-
}
|
|
15365
|
-
async applySessionMode(sessionId, raw, mode) {
|
|
15366
|
-
if (!mode)
|
|
15367
|
-
return;
|
|
15368
|
-
const modes = raw.modes;
|
|
15369
|
-
const candidates = mode === "readonly" ? roomModeCandidates(this.agentLabel, { osSandbox: this.osSandbox }) : cornerAutonomyModeCandidates(this.agentLabel);
|
|
15370
|
-
const target = modes?.availableModes?.find((candidate) => candidate.id && candidates.includes(candidate.id))?.id;
|
|
15371
|
-
if (!target || modes?.currentModeId === target)
|
|
15372
|
-
return;
|
|
15373
|
-
await this.request("session/set_mode", { sessionId, modeId: target });
|
|
15374
|
-
}
|
|
15375
|
-
/**
|
|
15376
|
-
* Run one prompt turn. `timeoutMs` is an idle window, not a hard cap on
|
|
15377
|
-
* turn length: every `session/update` for this session (message chunk,
|
|
15378
|
-
* tool call, or otherwise) re-arms it, so an actively-working agent can
|
|
15379
|
-
* run indefinitely while a genuinely wedged one (zero activity for
|
|
15380
|
-
* `timeoutMs`) still gets cancelled. `onChunk` is the ACP-boundary
|
|
15381
|
-
* streaming hook: called for every incremental `agent_message_chunk`
|
|
15382
|
-
* delta as it arrives, so a caller can project live text without waiting
|
|
15383
|
-
* for the turn to finish. A harness that only emits a final message
|
|
15384
|
-
* (never chunks) simply never invokes it — `agentText` is unaffected
|
|
15385
|
-
* either way. `onActivity`, when given, fires on every `session/update`
|
|
15386
|
-
* regardless of kind (the same trigger that re-arms the idle timer above),
|
|
15387
|
-
* so a caller can drive its own shorter "still working" notice off the
|
|
15388
|
-
* identical genuine-activity signal without duplicating the timeout logic.
|
|
15389
|
-
* It receives the current lane snapshot; existing callbacks that ignore
|
|
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
|
-
}
|
|
15437
|
-
}
|
|
15438
|
-
/**
|
|
15439
|
-
* Inject follow-up input into the prompt currently running for this session.
|
|
15440
|
-
* buzz-agent advertises the target run through ACP session metadata; binding
|
|
15441
|
-
* the request to that id prevents a late message from steering a newer turn.
|
|
15442
|
-
*/
|
|
15443
|
-
async sessionSteer(sessionId, text2, timeoutMs = 6e4) {
|
|
15444
|
-
if (this.supportsStandardSteering) {
|
|
15445
|
-
const raw2 = await this.request("_session/steering", {
|
|
15446
|
-
sessionId,
|
|
15447
|
-
prompt: [{ type: "text", text: text2 }]
|
|
15448
|
-
}, timeoutMs);
|
|
15449
|
-
if (!raw2.outcome || raw2.outcome === "failed") {
|
|
15450
|
-
throw new Error("ACP session steering failed");
|
|
15451
|
-
}
|
|
15452
|
-
return {
|
|
15453
|
-
runId: this.activeRunId(sessionId) ?? `session:${sessionId}`,
|
|
15454
|
-
messageId: raw2.outcome
|
|
15455
|
-
};
|
|
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;
|
|
15456
15343
|
}
|
|
15457
|
-
|
|
15458
|
-
|
|
15459
|
-
|
|
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");
|
|
15344
|
+
if (!lastWasText && current && !continuesPreviousWord(current, delta)) {
|
|
15345
|
+
runs.push(current);
|
|
15346
|
+
current = "";
|
|
15465
15347
|
}
|
|
15466
|
-
|
|
15467
|
-
|
|
15468
|
-
/** Active ACP run for a session, if the agent has advertised one. */
|
|
15469
|
-
activeRunId(sessionId) {
|
|
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 });
|
|
15348
|
+
current += delta;
|
|
15349
|
+
lastWasText = true;
|
|
15496
15350
|
}
|
|
15497
|
-
|
|
15498
|
-
|
|
15499
|
-
|
|
15500
|
-
|
|
15501
|
-
|
|
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, "");
|
|
15502
15381
|
}
|
|
15503
|
-
|
|
15504
|
-
|
|
15505
|
-
|
|
15506
|
-
|
|
15507
|
-
|
|
15508
|
-
|
|
15509
|
-
|
|
15510
|
-
|
|
15511
|
-
|
|
15512
|
-
|
|
15513
|
-
|
|
15514
|
-
|
|
15515
|
-
|
|
15516
|
-
|
|
15517
|
-
|
|
15518
|
-
this.pending.delete(id);
|
|
15519
|
-
reject(new AcpRequestTimeoutError(method ?? "request", idleTimeoutMs, this.stderrTail, true, detail ?? ""));
|
|
15520
|
-
}, idleTimeoutMs);
|
|
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);
|
|
15521
15397
|
}
|
|
15522
|
-
|
|
15523
|
-
|
|
15524
|
-
|
|
15525
|
-
|
|
15526
|
-
|
|
15527
|
-
|
|
15528
|
-
|
|
15529
|
-
|
|
15530
|
-
|
|
15531
|
-
|
|
15532
|
-
|
|
15533
|
-
|
|
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)
|
|
15534
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 = "";
|
|
15535
15437
|
}
|
|
15536
|
-
|
|
15438
|
+
messageText2 += delta;
|
|
15439
|
+
lastWasMessage = true;
|
|
15440
|
+
thoughtRunOpen = false;
|
|
15441
|
+
continue;
|
|
15537
15442
|
}
|
|
15538
|
-
|
|
15539
|
-
|
|
15540
|
-
|
|
15541
|
-
|
|
15542
|
-
|
|
15543
|
-
|
|
15544
|
-
|
|
15545
|
-
|
|
15546
|
-
|
|
15547
|
-
|
|
15548
|
-
|
|
15549
|
-
|
|
15550
|
-
|
|
15551
|
-
|
|
15552
|
-
|
|
15553
|
-
|
|
15554
|
-
p.reject(new Error(`ACP error ${err.code}: ${err.message}${extra ? `; ${extra}` : ""}`));
|
|
15555
|
-
} else {
|
|
15556
|
-
p.resolve(msg.result);
|
|
15557
|
-
}
|
|
15558
|
-
return;
|
|
15443
|
+
if (lastWasMessage && messageText2) {
|
|
15444
|
+
if (!completedMessages.includes(messageText2))
|
|
15445
|
+
completedMessages.push(messageText2);
|
|
15446
|
+
messageText2 = "";
|
|
15447
|
+
}
|
|
15448
|
+
lastWasMessage = false;
|
|
15449
|
+
if (THOUGHT_UPDATE_TYPES.has(kind)) {
|
|
15450
|
+
const delta = normalizeStreamDelta(updateText(update), agentLabel);
|
|
15451
|
+
if (!delta)
|
|
15452
|
+
continue;
|
|
15453
|
+
if (!thoughtRunOpen)
|
|
15454
|
+
thoughtText2 = "";
|
|
15455
|
+
thoughtText2 += delta;
|
|
15456
|
+
thoughtRunOpen = true;
|
|
15457
|
+
} else {
|
|
15458
|
+
thoughtRunOpen = false;
|
|
15559
15459
|
}
|
|
15560
|
-
|
|
15561
|
-
|
|
15562
|
-
|
|
15563
|
-
|
|
15564
|
-
|
|
15565
|
-
|
|
15566
|
-
|
|
15460
|
+
}
|
|
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
|
-
|
|
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 });
|
|
15738
15721
|
}
|
|
15739
|
-
|
|
15740
|
-
|
|
15741
|
-
|
|
15742
|
-
|
|
15743
|
-
|
|
15744
|
-
|
|
15745
|
-
|
|
15746
|
-
|
|
15747
|
-
|
|
15748
|
-
|
|
15749
|
-
|
|
15750
|
-
|
|
15751
|
-
|
|
15752
|
-
|
|
15753
|
-
|
|
15754
|
-
|
|
15755
|
-
|
|
15756
|
-
|
|
15757
|
-
|
|
15758
|
-
|
|
15759
|
-
|
|
15760
|
-
|
|
15761
|
-
|
|
15762
|
-
|
|
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);
|
|
15783
|
+
}
|
|
15763
15784
|
}
|
|
15764
|
-
|
|
15765
|
-
|
|
15766
|
-
|
|
15767
|
-
|
|
15768
|
-
|
|
15769
|
-
|
|
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
|
+
};
|
|
15770
15803
|
}
|
|
15771
|
-
|
|
15772
|
-
|
|
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");
|
|
15773
15812
|
}
|
|
15774
|
-
|
|
15813
|
+
return { runId: raw.runId, messageId: raw.messageId };
|
|
15775
15814
|
}
|
|
15776
|
-
|
|
15777
|
-
|
|
15778
|
-
|
|
15779
|
-
...selection.effort ? ["--reasoning-effort", selection.effort] : []
|
|
15780
|
-
];
|
|
15781
|
-
stripped.splice(stdioIndex, 0, ...configured);
|
|
15782
|
-
return stripped;
|
|
15783
|
-
}
|
|
15784
|
-
function advertisedChoiceId(choice) {
|
|
15785
|
-
if (typeof choice.id === "string" && choice.id.length > 0)
|
|
15786
|
-
return choice.id;
|
|
15787
|
-
if (typeof choice.value === "string" && choice.value.length > 0)
|
|
15788
|
-
return choice.value;
|
|
15789
|
-
return void 0;
|
|
15790
|
-
}
|
|
15791
|
-
function sortModelChoicesNewestFirst(choices) {
|
|
15792
|
-
return choices.map((choice, index) => ({ choice, index })).sort((left, right) => {
|
|
15793
|
-
const leftParts = left.choice.id.match(/\d+/g)?.map(Number) ?? [];
|
|
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;
|
|
15799
|
-
}
|
|
15800
|
-
return left.index - right.index;
|
|
15801
|
-
}).map(({ choice }) => choice);
|
|
15802
|
-
}
|
|
15803
|
-
function parseAdvertisedConfigOptions(raw, preferredModelId, includeLaunchEffort = false) {
|
|
15804
|
-
const configOptions = raw?.configOptions;
|
|
15805
|
-
const result = [];
|
|
15806
|
-
for (const entry of Array.isArray(configOptions) ? configOptions : []) {
|
|
15807
|
-
if (!entry || typeof entry !== "object")
|
|
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
|
-
});
|
|
15827
|
-
}
|
|
15828
|
-
result.push({
|
|
15829
|
-
id,
|
|
15830
|
-
category,
|
|
15831
|
-
...typeof record3.currentValue === "string" ? { currentValue: record3.currentValue } : {},
|
|
15832
|
-
options: category === "model" ? sortModelChoicesNewestFirst(choices) : choices
|
|
15833
|
-
});
|
|
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);
|
|
15834
15818
|
}
|
|
15835
|
-
|
|
15836
|
-
|
|
15837
|
-
return result;
|
|
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
|
-
});
|
|
15819
|
+
sessionCancel(sessionId) {
|
|
15820
|
+
this.notify("session/cancel", { sessionId });
|
|
15851
15821
|
}
|
|
15852
|
-
|
|
15853
|
-
|
|
15854
|
-
|
|
15855
|
-
|
|
15856
|
-
|
|
15857
|
-
|
|
15858
|
-
|
|
15859
|
-
|
|
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 });
|
|
15860
15835
|
}
|
|
15861
|
-
|
|
15862
|
-
|
|
15863
|
-
|
|
15864
|
-
|
|
15865
|
-
|
|
15866
|
-
|
|
15867
|
-
|
|
15868
|
-
if (!entry || typeof entry !== "object")
|
|
15869
|
-
continue;
|
|
15870
|
-
const effort = entry;
|
|
15871
|
-
const id = advertisedChoiceId(effort);
|
|
15872
|
-
if (!id)
|
|
15873
|
-
continue;
|
|
15874
|
-
effortChoices.push({
|
|
15875
|
-
id,
|
|
15876
|
-
...typeof effort.label === "string" ? { name: effort.label } : typeof effort.name === "string" ? { name: effort.name } : {}
|
|
15877
|
-
});
|
|
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 });
|
|
15878
15843
|
}
|
|
15879
|
-
|
|
15880
|
-
|
|
15881
|
-
|
|
15882
|
-
|
|
15883
|
-
|
|
15884
|
-
category: "reasoning_effort",
|
|
15885
|
-
...typeof currentEffort === "string" ? { currentValue: currentEffort } : {},
|
|
15886
|
-
options: effortChoices
|
|
15887
|
-
});
|
|
15844
|
+
clearTimer(p) {
|
|
15845
|
+
if (p.timer) {
|
|
15846
|
+
clearTimeout(p.timer);
|
|
15847
|
+
p.timer = void 0;
|
|
15848
|
+
}
|
|
15888
15849
|
}
|
|
15889
|
-
|
|
15890
|
-
|
|
15891
|
-
|
|
15892
|
-
|
|
15893
|
-
|
|
15894
|
-
|
|
15895
|
-
|
|
15896
|
-
|
|
15897
|
-
|
|
15898
|
-
|
|
15899
|
-
|
|
15900
|
-
|
|
15901
|
-
|
|
15902
|
-
|
|
15903
|
-
|
|
15904
|
-
|
|
15905
|
-
|
|
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);
|
|
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);
|
|
15912
15868
|
}
|
|
15913
|
-
|
|
15914
|
-
|
|
15915
|
-
|
|
15916
|
-
|
|
15917
|
-
|
|
15918
|
-
|
|
15919
|
-
|
|
15920
|
-
|
|
15921
|
-
|
|
15922
|
-
|
|
15923
|
-
|
|
15924
|
-
|
|
15925
|
-
|
|
15926
|
-
|
|
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);
|
|
15927
15884
|
}
|
|
15928
|
-
|
|
15929
|
-
|
|
15930
|
-
const
|
|
15931
|
-
|
|
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;
|
|
15906
|
+
}
|
|
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
|
-
|
|
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
|
+
}
|
|
16015
16027
|
}
|
|
16016
|
-
|
|
16017
|
-
if (
|
|
16018
|
-
|
|
16019
|
-
|
|
16020
|
-
|
|
16021
|
-
|
|
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 }
|
|
16037
|
+
}
|
|
16022
16038
|
});
|
|
16039
|
+
return;
|
|
16023
16040
|
}
|
|
16041
|
+
if (metadataKey)
|
|
16042
|
+
this.toolCallMetadata.delete(metadataKey);
|
|
16043
|
+
this.write({
|
|
16044
|
+
jsonrpc: "2.0",
|
|
16045
|
+
id,
|
|
16046
|
+
result: { outcome: { outcome: "cancelled" } }
|
|
16047
|
+
});
|
|
16024
16048
|
}
|
|
16025
|
-
|
|
16026
|
-
|
|
16027
|
-
|
|
16028
|
-
|
|
16029
|
-
|
|
16030
|
-
|
|
16031
|
-
|
|
16032
|
-
|
|
16033
|
-
|
|
16034
|
-
|
|
16035
|
-
|
|
16036
|
-
|
|
16037
|
-
|
|
16038
|
-
|
|
16039
|
-
|
|
16040
|
-
|
|
16041
|
-
|
|
16042
|
-
|
|
16043
|
-
|
|
16044
|
-
|
|
16045
|
-
|
|
16046
|
-
|
|
16047
|
-
|
|
16048
|
-
|
|
16049
|
-
}
|
|
16050
|
-
} else {
|
|
16051
|
-
await client.setConfigOption(sessionId, axis.id, target.value);
|
|
16052
|
-
}
|
|
16053
|
-
} catch (error) {
|
|
16054
|
-
throw new ModelSelectionUnavailableError({
|
|
16055
|
-
label: target.label,
|
|
16056
|
-
value: target.value,
|
|
16057
|
-
reason: "provider-refused",
|
|
16058
|
-
guidance: error instanceof Error ? error.message : String(error)
|
|
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"));
|
|
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 } : {}
|
|
16059
16073
|
});
|
|
16060
|
-
|
|
16074
|
+
onStart?.(id);
|
|
16075
|
+
this.write(payload);
|
|
16076
|
+
});
|
|
16061
16077
|
}
|
|
16062
|
-
|
|
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
|
+
};
|
|
16063
16087
|
|
|
16064
16088
|
// apps/body/dist/model-catalog.js
|
|
16065
16089
|
function modelCatalogProbeEnvironment(agent, agentEnv, scratchCwd) {
|
|
@@ -16109,27 +16133,34 @@ async function withAgentModelCatalog(agent, agentEnv, selection, inspect, limits
|
|
|
16109
16133
|
async function fetchAgentModelCatalog(agent, agentEnv, selection, limits = {}) {
|
|
16110
16134
|
return withAgentModelCatalog(agent, agentEnv, selection, async ({ client, sessionId, raw, catalog }) => ({
|
|
16111
16135
|
raw,
|
|
16112
|
-
catalog: await filterModelChoicesByLiveValidation(client, sessionId, catalog)
|
|
16136
|
+
catalog: await filterModelChoicesByLiveValidation(client, sessionId, catalog, selection?.model)
|
|
16113
16137
|
}), limits);
|
|
16114
16138
|
}
|
|
16115
|
-
async function filterModelChoicesByLiveValidation(client, sessionId, catalog) {
|
|
16139
|
+
async function filterModelChoicesByLiveValidation(client, sessionId, catalog, preferredModelId) {
|
|
16116
16140
|
const modelAxis = catalog.find((axis) => axis.category === "model");
|
|
16117
16141
|
if (!modelAxis)
|
|
16118
16142
|
return catalog;
|
|
16119
16143
|
const available = [];
|
|
16144
|
+
const preferred = preferredModelId ?? modelAxis.currentValue;
|
|
16145
|
+
let preferredCatalog;
|
|
16120
16146
|
for (const choice of modelAxis.options) {
|
|
16121
16147
|
try {
|
|
16122
|
-
await
|
|
16148
|
+
const updated = await applyAgentModelSelectionWithUpdatedCatalog(client, sessionId, catalog, {
|
|
16149
|
+
model: choice.id
|
|
16150
|
+
});
|
|
16123
16151
|
available.push(choice);
|
|
16152
|
+
if (choice.id === preferred)
|
|
16153
|
+
preferredCatalog = updated;
|
|
16124
16154
|
} catch {
|
|
16125
16155
|
}
|
|
16126
16156
|
}
|
|
16127
|
-
|
|
16157
|
+
const effective = preferredCatalog ?? catalog;
|
|
16158
|
+
return effective.map((axis) => axis.category === "model" ? { ...axis, options: available } : axis);
|
|
16128
16159
|
}
|
|
16129
16160
|
async function validateAgentModelSelection(agent, agentEnv, selection) {
|
|
16130
16161
|
return withAgentModelCatalog(agent, agentEnv, selection, async ({ client, sessionId, raw, catalog }) => {
|
|
16131
|
-
await
|
|
16132
|
-
return { raw, catalog };
|
|
16162
|
+
const applied = await applyAgentModelSelectionWithUpdatedCatalog(client, sessionId, catalog, selection);
|
|
16163
|
+
return { raw, catalog: applied };
|
|
16133
16164
|
});
|
|
16134
16165
|
}
|
|
16135
16166
|
|
|
@@ -16159,19 +16190,33 @@ function modelUnavailableState(selection, error) {
|
|
|
16159
16190
|
}
|
|
16160
16191
|
|
|
16161
16192
|
// apps/body/dist/runtime-model-validation.js
|
|
16162
|
-
async function revalidateRuntimeModelSelection(agent, agentEnv, selection, validate = validateAgentModelSelection) {
|
|
16193
|
+
async function revalidateRuntimeModelSelection(agent, agentEnv, selection, validate = validateAgentModelSelection, refreshAdapter) {
|
|
16163
16194
|
if (!selection.model && !selection.effort)
|
|
16164
16195
|
return void 0;
|
|
16165
16196
|
try {
|
|
16166
16197
|
await validate(agent, agentEnv, selection);
|
|
16167
16198
|
return void 0;
|
|
16168
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
|
+
}
|
|
16169
16214
|
return modelUnavailableState(selection, error);
|
|
16170
16215
|
}
|
|
16171
16216
|
}
|
|
16172
|
-
async function applyRuntimeModelPreflight(config, agent, selection, validate = validateAgentModelSelection) {
|
|
16217
|
+
async function applyRuntimeModelPreflight(config, agent, selection, validate = validateAgentModelSelection, refreshAdapter) {
|
|
16173
16218
|
config.modelSelection = selection;
|
|
16174
|
-
config.modelUnavailable = await revalidateRuntimeModelSelection(agent, config.agentEnv, selection, validate);
|
|
16219
|
+
config.modelUnavailable = await revalidateRuntimeModelSelection(agent, config.agentEnv, selection, validate, refreshAdapter);
|
|
16175
16220
|
}
|
|
16176
16221
|
|
|
16177
16222
|
// apps/body/dist/model-catalog-sync.js
|
|
@@ -16215,7 +16260,7 @@ async function syncAgentModelCatalog(input) {
|
|
|
16215
16260
|
const effectiveCatalog = input.startupUnavailable ? catalog : withEffectiveCurrentValues(catalog, selection);
|
|
16216
16261
|
const hash = modelCatalogHash(effectiveCatalog, selection, input.startupUnavailable);
|
|
16217
16262
|
const previous = await readFile(hashPath, "utf8").catch(() => "");
|
|
16218
|
-
if (previous.trim() === hash)
|
|
16263
|
+
if (!input.force && previous.trim() === hash)
|
|
16219
16264
|
return "unchanged";
|
|
16220
16265
|
await input.api.execute("postAgentModelCatalog", {
|
|
16221
16266
|
agentId: input.agentId,
|
|
@@ -16767,7 +16812,7 @@ function describe(error) {
|
|
|
16767
16812
|
}
|
|
16768
16813
|
|
|
16769
16814
|
// apps/body/dist/connector-squire.js
|
|
16770
|
-
import { execFile, spawn as
|
|
16815
|
+
import { execFile, spawn as spawn6 } from "node:child_process";
|
|
16771
16816
|
import { createHash as createHash2 } from "node:crypto";
|
|
16772
16817
|
import { lstatSync as lstatSync3, readFileSync as readFileSync4, realpathSync, rmSync } from "node:fs";
|
|
16773
16818
|
import { homedir as homedir3, hostname, tmpdir as tmpdir2 } from "node:os";
|
|
@@ -16916,7 +16961,7 @@ function killConnectTree(child) {
|
|
|
16916
16961
|
child.kill();
|
|
16917
16962
|
}
|
|
16918
16963
|
var defaultStreamedRunner = (command, args, env) => new Promise((resolve36) => {
|
|
16919
|
-
const child =
|
|
16964
|
+
const child = spawn6(command, args, {
|
|
16920
16965
|
stdio: ["ignore", "pipe", "pipe"],
|
|
16921
16966
|
env: env ?? squireConnectProcessEnv(),
|
|
16922
16967
|
detached: true
|
|
@@ -17316,8 +17361,173 @@ async function revokeGrants(mcp, ref) {
|
|
|
17316
17361
|
return { revoked, failed };
|
|
17317
17362
|
}
|
|
17318
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
|
+
|
|
17319
17529
|
// apps/body/dist/squire-mcp-client.js
|
|
17320
|
-
import { spawn as
|
|
17530
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
17321
17531
|
import { homedir as homedir4 } from "node:os";
|
|
17322
17532
|
var INITIALIZE_TIMEOUT_MS = 3e4;
|
|
17323
17533
|
var CALL_TIMEOUT_MS = 12e4;
|
|
@@ -17375,7 +17585,7 @@ var StdioSquireMcpClient = class {
|
|
|
17375
17585
|
}
|
|
17376
17586
|
initialize() {
|
|
17377
17587
|
const launch = squireFacadeLaunch(homedir4());
|
|
17378
|
-
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 } });
|
|
17379
17589
|
this.child = child;
|
|
17380
17590
|
this.buffer = "";
|
|
17381
17591
|
child.stdout.setEncoding("utf8");
|
|
@@ -17467,6 +17677,7 @@ var ConnectorAssignmentLoop = class {
|
|
|
17467
17677
|
log;
|
|
17468
17678
|
install;
|
|
17469
17679
|
installGoogle;
|
|
17680
|
+
installTailscale;
|
|
17470
17681
|
googleHomeDir;
|
|
17471
17682
|
readVaultFn;
|
|
17472
17683
|
revokeGrantsFn;
|
|
@@ -17475,6 +17686,8 @@ var ConnectorAssignmentLoop = class {
|
|
|
17475
17686
|
timer;
|
|
17476
17687
|
/** Armed only while this helper owns a live sign-in ceremony. */
|
|
17477
17688
|
connectWatch;
|
|
17689
|
+
/** Armed only while a Tailscale browser login is waiting for its callback. */
|
|
17690
|
+
tailscaleWatch;
|
|
17478
17691
|
started = false;
|
|
17479
17692
|
stopped = false;
|
|
17480
17693
|
/** One install at a time per connector; other polls skip it. */
|
|
@@ -17493,6 +17706,7 @@ var ConnectorAssignmentLoop = class {
|
|
|
17493
17706
|
onProgress,
|
|
17494
17707
|
resolvedCredentials: sharedCredentials ? sharedCredentials() : this.resolveGoogleCredentials()
|
|
17495
17708
|
}));
|
|
17709
|
+
this.installTailscale = options.installTailscale ?? installTailscale;
|
|
17496
17710
|
this.googleHomeDir = options.googleHome ?? process.env.BEELINE_AGENT_HOME ?? process.cwd();
|
|
17497
17711
|
this.readVaultFn = options.readVault ?? readVault;
|
|
17498
17712
|
this.revokeGrantsFn = options.revokeGrants ?? revokeGrants;
|
|
@@ -17526,6 +17740,10 @@ var ConnectorAssignmentLoop = class {
|
|
|
17526
17740
|
this.cancel(this.connectWatch);
|
|
17527
17741
|
this.connectWatch = void 0;
|
|
17528
17742
|
}
|
|
17743
|
+
if (this.tailscaleWatch !== void 0) {
|
|
17744
|
+
this.cancel(this.tailscaleWatch);
|
|
17745
|
+
this.tailscaleWatch = void 0;
|
|
17746
|
+
}
|
|
17529
17747
|
}
|
|
17530
17748
|
/**
|
|
17531
17749
|
* Watch the sign-in this helper is holding open. The phone paints
|
|
@@ -17638,12 +17856,57 @@ var ConnectorAssignmentLoop = class {
|
|
|
17638
17856
|
}
|
|
17639
17857
|
async handle(assignment) {
|
|
17640
17858
|
if (assignment.kind === "install") {
|
|
17641
|
-
|
|
17642
|
-
|
|
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") {
|
|
17643
17865
|
await this.runSync();
|
|
17644
|
-
else if (assignment.kind === "revoke-grants")
|
|
17866
|
+
} else if (assignment.kind === "revoke-grants")
|
|
17645
17867
|
await this.runRevoke(assignment.connectorId, assignment.reference);
|
|
17646
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
|
+
}
|
|
17647
17910
|
/** Google tool connectors keep their manual credentials next to the runtime. */
|
|
17648
17911
|
googleHome() {
|
|
17649
17912
|
return this.googleHomeDir;
|
|
@@ -17761,7 +18024,7 @@ var ConnectorAssignmentLoop = class {
|
|
|
17761
18024
|
const status = await this.connectorRow(connectorId);
|
|
17762
18025
|
if (!status)
|
|
17763
18026
|
return false;
|
|
17764
|
-
return status.steps.length > 0 && status.steps.every((
|
|
18027
|
+
return status.steps.length > 0 && status.steps.every((step4) => step4.status === "pending");
|
|
17765
18028
|
}
|
|
17766
18029
|
/** One vault report covers every live trusty-squire connector on this helper. */
|
|
17767
18030
|
async runSync() {
|
|
@@ -17797,7 +18060,7 @@ function isAgentCommand(value) {
|
|
|
17797
18060
|
"reason"
|
|
17798
18061
|
].every((key) => typeof c2[key] === "string" && c2[key].length > 0))
|
|
17799
18062
|
return false;
|
|
17800
|
-
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)
|
|
17801
18064
|
return false;
|
|
17802
18065
|
const source = c2.source;
|
|
17803
18066
|
return Boolean(source && typeof source.id === "string" && typeof source.authorId === "string" && typeof source.body === "string" && typeof source.createdAt === "number" && Array.isArray(source.attachments));
|
|
@@ -17929,7 +18192,7 @@ function hiccupBackoffMs(attempt) {
|
|
|
17929
18192
|
|
|
17930
18193
|
// apps/body/dist/daemon-api-client.js
|
|
17931
18194
|
import { resolve as resolve9 } from "node:path";
|
|
17932
|
-
import { randomUUID as
|
|
18195
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
17933
18196
|
|
|
17934
18197
|
// node_modules/ws/wrapper.mjs
|
|
17935
18198
|
var import_stream = __toESM(require_stream(), 1);
|
|
@@ -17944,12 +18207,12 @@ var wrapper_default = import_websocket.default;
|
|
|
17944
18207
|
|
|
17945
18208
|
// apps/body/dist/runtime.js
|
|
17946
18209
|
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
17947
|
-
import { execFile as
|
|
18210
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
17948
18211
|
import { closeSync, openSync, readFileSync as readFileSync5 } from "node:fs";
|
|
17949
18212
|
import { mkdir as mkdir2, readFile as readFile2, readdir, rename as rename2, stat, unlink as unlink2, writeFile as writeFile3 } from "node:fs/promises";
|
|
17950
18213
|
import { homedir as homedir5 } from "node:os";
|
|
17951
18214
|
import { dirname as dirname6, resolve as resolve8 } from "node:path";
|
|
17952
|
-
import { spawn as
|
|
18215
|
+
import { spawn as spawn8 } from "node:child_process";
|
|
17953
18216
|
import { promisify } from "node:util";
|
|
17954
18217
|
|
|
17955
18218
|
// node_modules/@noble/hashes/_u64.js
|
|
@@ -22336,7 +22599,7 @@ function alphabet(letters) {
|
|
|
22336
22599
|
};
|
|
22337
22600
|
}
|
|
22338
22601
|
// @__NO_SIDE_EFFECTS__
|
|
22339
|
-
function
|
|
22602
|
+
function join6(separator = "") {
|
|
22340
22603
|
astr("join", separator);
|
|
22341
22604
|
return {
|
|
22342
22605
|
encode: (from) => {
|
|
@@ -22466,8 +22729,8 @@ var base64 = hasBase64Builtin ? {
|
|
|
22466
22729
|
decode(s) {
|
|
22467
22730
|
return decodeBase64Builtin(s, false);
|
|
22468
22731
|
}
|
|
22469
|
-
} : /* @__PURE__ */ chain(/* @__PURE__ */ radix2(6), /* @__PURE__ */ alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), /* @__PURE__ */ padding(6), /* @__PURE__ */
|
|
22470
|
-
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(""));
|
|
22471
22734
|
var POLYMOD_GENERATORS = [996825010, 642813549, 513874426, 1027748829, 705979059];
|
|
22472
22735
|
function bech32Polymod(pre) {
|
|
22473
22736
|
const b = pre >> 25;
|
|
@@ -26072,13 +26335,13 @@ var NegentropyStorageVector = class {
|
|
|
26072
26335
|
let count = last - first;
|
|
26073
26336
|
while (count > 0) {
|
|
26074
26337
|
let it = first;
|
|
26075
|
-
let
|
|
26076
|
-
it +=
|
|
26338
|
+
let step4 = Math.floor(count / 2);
|
|
26339
|
+
it += step4;
|
|
26077
26340
|
if (cmp(arr[it])) {
|
|
26078
26341
|
first = ++it;
|
|
26079
|
-
count -=
|
|
26342
|
+
count -= step4 + 1;
|
|
26080
26343
|
} else {
|
|
26081
|
-
count =
|
|
26344
|
+
count = step4;
|
|
26082
26345
|
}
|
|
26083
26346
|
}
|
|
26084
26347
|
return first;
|
|
@@ -26480,7 +26743,7 @@ function decodeNsec(nsec) {
|
|
|
26480
26743
|
}
|
|
26481
26744
|
|
|
26482
26745
|
// apps/body/dist/runtime.js
|
|
26483
|
-
var execFileAsync = promisify(
|
|
26746
|
+
var execFileAsync = promisify(execFile3);
|
|
26484
26747
|
var DEFAULT_AGENT_IDENTITY_NAME = "beeline-agent";
|
|
26485
26748
|
var DEFAULT_BODY_IDENTITY_NAME = "beeline-body";
|
|
26486
26749
|
var DEFAULT_DAEMON_MONOLITH_BASE_URL = "https://server.usebeeline.app";
|
|
@@ -26728,7 +26991,7 @@ async function launchRuntimeDaemon(configPath, opts = {}) {
|
|
|
26728
26991
|
const entrypoint = opts.entrypoint ?? process.argv[1];
|
|
26729
26992
|
if (!entrypoint)
|
|
26730
26993
|
throw new Error("cannot resolve daemon CLI entrypoint");
|
|
26731
|
-
const child =
|
|
26994
|
+
const child = spawn8(process.execPath, [...opts.execArgv ?? [], entrypoint, "daemon", "--config", resolve8(configPath)], {
|
|
26732
26995
|
cwd: directory,
|
|
26733
26996
|
env: opts.env ?? process.env,
|
|
26734
26997
|
detached: !foreground,
|
|
@@ -26789,7 +27052,7 @@ var DaemonApiClient = class {
|
|
|
26789
27052
|
agentId;
|
|
26790
27053
|
fetchImpl;
|
|
26791
27054
|
webSocketFactory;
|
|
26792
|
-
presenceLifecycleId =
|
|
27055
|
+
presenceLifecycleId = randomUUID3();
|
|
26793
27056
|
liveSocket;
|
|
26794
27057
|
liveReconnect;
|
|
26795
27058
|
liveReconnectDelayMs = 1e3;
|
|
@@ -27058,15 +27321,15 @@ async function activateDaemonTransport(path, fetchImpl = fetch) {
|
|
|
27058
27321
|
}
|
|
27059
27322
|
|
|
27060
27323
|
// apps/body/dist/room-runtime.js
|
|
27061
|
-
import { execFile as
|
|
27324
|
+
import { execFile as execFile8 } from "node:child_process";
|
|
27062
27325
|
import { createHash as createHash7 } from "node:crypto";
|
|
27063
27326
|
import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "node:fs";
|
|
27064
|
-
import { mkdir as mkdir15, rm as
|
|
27327
|
+
import { mkdir as mkdir15, rm as rm6 } from "node:fs/promises";
|
|
27065
27328
|
import { dirname as dirname13, resolve as resolve24 } from "node:path";
|
|
27066
27329
|
import { promisify as promisify5 } from "node:util";
|
|
27067
27330
|
|
|
27068
27331
|
// apps/body/dist/grant-runner.js
|
|
27069
|
-
import { execFile as
|
|
27332
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
27070
27333
|
import { createHash as createHash3, randomBytes as randomBytes6 } from "node:crypto";
|
|
27071
27334
|
import { readFile as readFile4 } from "node:fs/promises";
|
|
27072
27335
|
import { createServer } from "node:http";
|
|
@@ -27115,6 +27378,18 @@ function commandGrantMatches(rule, requested) {
|
|
|
27115
27378
|
return rule.argv.every((word, index) => requested[index] === word);
|
|
27116
27379
|
}
|
|
27117
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
|
+
}
|
|
27118
27393
|
var AGENT_GRANT_ESCALATION_REASONS = {
|
|
27119
27394
|
"unseen-script": "it runs a script whose contents nobody has read",
|
|
27120
27395
|
credential: "it names a credential or environment file"
|
|
@@ -27734,7 +28009,7 @@ var GrantCommandRunner = class {
|
|
|
27734
28009
|
const sandboxMarker = runsOnHost ? void 0 : `[beeline-sandbox-started:${randomBytes6(16).toString("hex")}]`;
|
|
27735
28010
|
const spawn13 = runsOnHost ? { command: argv[0], args: argv.slice(1) } : roomSandboxCommand(policy, room.cwd, argv, sandboxMarker);
|
|
27736
28011
|
const outcome = await new Promise((resolveRun) => {
|
|
27737
|
-
const child =
|
|
28012
|
+
const child = execFile4(spawn13.command, spawn13.args, {
|
|
27738
28013
|
cwd: room.cwd,
|
|
27739
28014
|
env,
|
|
27740
28015
|
timeout: this.options.timeoutMs ?? GRANT_COMMAND_TIMEOUT_MS,
|
|
@@ -28030,16 +28305,16 @@ function captureConnectionUsage(recorder, turn, calls) {
|
|
|
28030
28305
|
}
|
|
28031
28306
|
|
|
28032
28307
|
// apps/body/dist/server-command-intake.js
|
|
28033
|
-
import { randomUUID as
|
|
28308
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
28034
28309
|
import { mkdir as mkdir4, writeFile as writeFile5 } from "node:fs/promises";
|
|
28035
|
-
import { tmpdir as
|
|
28036
|
-
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";
|
|
28037
28312
|
var CommandExecutionContext = class {
|
|
28038
|
-
generationId =
|
|
28313
|
+
generationId = randomUUID4();
|
|
28039
28314
|
path;
|
|
28040
28315
|
current;
|
|
28041
28316
|
constructor(root) {
|
|
28042
|
-
this.path =
|
|
28317
|
+
this.path = join7(root ?? tmpdir4(), `beeline-command-${this.generationId}.json`);
|
|
28043
28318
|
}
|
|
28044
28319
|
async enter(command) {
|
|
28045
28320
|
this.current = command;
|
|
@@ -28130,7 +28405,9 @@ async function runServerCommandIntake(options) {
|
|
|
28130
28405
|
return;
|
|
28131
28406
|
for (const command of [...pending.values()]) {
|
|
28132
28407
|
validateServerCommand(command, roomId, agentId);
|
|
28133
|
-
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)
|
|
28134
28411
|
continue;
|
|
28135
28412
|
pending.delete(command.id);
|
|
28136
28413
|
claimed.add(command.id);
|
|
@@ -28145,7 +28422,11 @@ async function runServerCommandIntake(options) {
|
|
|
28145
28422
|
options.onError?.(error);
|
|
28146
28423
|
continue;
|
|
28147
28424
|
}
|
|
28148
|
-
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") {
|
|
28149
28430
|
options.stop(command.turnRequestId);
|
|
28150
28431
|
await api.execute("acknowledgeAgentCommand", {
|
|
28151
28432
|
roomId,
|
|
@@ -28177,7 +28458,7 @@ async function runServerCommandIntake(options) {
|
|
|
28177
28458
|
wake = done;
|
|
28178
28459
|
const timer = setTimeout(() => done(true), pushIntakeAcknowledged ? 6e4 : options.pollMs ?? 1e3);
|
|
28179
28460
|
signal?.addEventListener("abort", aborted, { once: true });
|
|
28180
|
-
if (pending.
|
|
28461
|
+
if (!busy && [...pending.values()].some((command) => command.action === "stop" || command.action === "restart" || options.canStartTurn?.() !== false))
|
|
28181
28462
|
done(false);
|
|
28182
28463
|
});
|
|
28183
28464
|
if (signal?.aborted)
|
|
@@ -28196,11 +28477,11 @@ async function runServerCommandIntake(options) {
|
|
|
28196
28477
|
}
|
|
28197
28478
|
|
|
28198
28479
|
// apps/body/dist/monolith-corner-turn.js
|
|
28199
|
-
import { execFile as
|
|
28480
|
+
import { execFile as execFile7 } from "node:child_process";
|
|
28200
28481
|
import { createHash as createHash6 } from "node:crypto";
|
|
28201
28482
|
import { mkdir as mkdir14 } from "node:fs/promises";
|
|
28202
28483
|
import { homedir as homedir10 } from "node:os";
|
|
28203
|
-
import { join as
|
|
28484
|
+
import { join as join13 } from "node:path";
|
|
28204
28485
|
import { promisify as promisify4 } from "node:util";
|
|
28205
28486
|
|
|
28206
28487
|
// apps/body/dist/agent-home.js
|
|
@@ -29002,10 +29283,10 @@ function stringify(obj, { maxDepth = 1e3, numbersAsFloat = false } = {}) {
|
|
|
29002
29283
|
}
|
|
29003
29284
|
|
|
29004
29285
|
// apps/body/dist/agent-home.js
|
|
29005
|
-
import { createHash as createHash4, randomUUID as
|
|
29006
|
-
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";
|
|
29007
29288
|
import { homedir as homedir8 } from "node:os";
|
|
29008
|
-
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";
|
|
29009
29290
|
|
|
29010
29291
|
// apps/body/dist/beeline-skill.js
|
|
29011
29292
|
import { readFileSync as readFileSync6 } from "node:fs";
|
|
@@ -29017,6 +29298,7 @@ var MESSAGE_REACTION_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F3
|
|
|
29017
29298
|
// packages/api-contract/dist/workbench.js
|
|
29018
29299
|
var CONNECTABLE_CONNECTOR_KINDS = [
|
|
29019
29300
|
"trusty-squire",
|
|
29301
|
+
"tailscale",
|
|
29020
29302
|
"google-gmail",
|
|
29021
29303
|
"google-calendar",
|
|
29022
29304
|
"google-drive",
|
|
@@ -29138,7 +29420,7 @@ When your corner's pull request is ready, merging is your step: once the configu
|
|
|
29138
29420
|
|
|
29139
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.
|
|
29140
29422
|
|
|
29141
|
-
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.
|
|
29142
29424
|
|
|
29143
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.
|
|
29144
29426
|
|
|
@@ -29395,7 +29677,7 @@ function stringArray(value) {
|
|
|
29395
29677
|
}
|
|
29396
29678
|
|
|
29397
29679
|
// apps/body/dist/host-mcp-route.js
|
|
29398
|
-
import { join as
|
|
29680
|
+
import { join as join8, resolve as resolve14 } from "node:path";
|
|
29399
29681
|
var import_yaml = __toESM(require_dist(), 1);
|
|
29400
29682
|
function grantedMcpServerNames(grants) {
|
|
29401
29683
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -29412,18 +29694,33 @@ function ungatedHostServers(hostServers, granted) {
|
|
|
29412
29694
|
const allowed = new Set(granted);
|
|
29413
29695
|
return hostServers.filter((name) => !allowed.has(name));
|
|
29414
29696
|
}
|
|
29415
|
-
function
|
|
29697
|
+
async function claimGrantedHostRoutes(result, consumeOnce) {
|
|
29416
29698
|
if (!result || typeof result !== "object" || !("grants" in result))
|
|
29417
29699
|
return [];
|
|
29418
29700
|
const grants = result.grants;
|
|
29419
29701
|
if (!Array.isArray(grants))
|
|
29420
29702
|
return [];
|
|
29421
|
-
|
|
29703
|
+
const mounted = [];
|
|
29704
|
+
for (const entry of grants) {
|
|
29422
29705
|
if (!entry || typeof entry !== "object")
|
|
29423
|
-
|
|
29424
|
-
const
|
|
29425
|
-
|
|
29426
|
-
|
|
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);
|
|
29427
29724
|
}
|
|
29428
29725
|
function recordValue(value) {
|
|
29429
29726
|
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
@@ -29446,7 +29743,7 @@ function grantedSquireHostRoute(granted, declarations = {}) {
|
|
|
29446
29743
|
});
|
|
29447
29744
|
}
|
|
29448
29745
|
function hostRouteEnv(hostHome) {
|
|
29449
|
-
return { XDG_CONFIG_HOME:
|
|
29746
|
+
return { XDG_CONFIG_HOME: join8(resolve14(hostHome), ".config") };
|
|
29450
29747
|
}
|
|
29451
29748
|
function rewriteHostMcpDeclaration(name, declaration, hostHome) {
|
|
29452
29749
|
const next = { ...declaration };
|
|
@@ -30630,7 +30927,7 @@ async function provisionManagedSkillsDir(target, managedSkills, sharedSkills, op
|
|
|
30630
30927
|
const plan = await planManagedSkills(managedSkills, sharedSkills, optionalShares);
|
|
30631
30928
|
if (await materializedSkillManifest(target) === plan.manifest)
|
|
30632
30929
|
return;
|
|
30633
|
-
const staged = resolve16(parent, `.skills.${process.pid}.${
|
|
30930
|
+
const staged = resolve16(parent, `.skills.${process.pid}.${randomUUID5()}.tmp`);
|
|
30634
30931
|
await mkdir6(staged, { mode: 448 });
|
|
30635
30932
|
try {
|
|
30636
30933
|
for (const entry of plan.entries) {
|
|
@@ -30644,10 +30941,10 @@ async function provisionManagedSkillsDir(target, managedSkills, sharedSkills, op
|
|
|
30644
30941
|
}
|
|
30645
30942
|
const existing = await lstat2(target).catch(() => void 0);
|
|
30646
30943
|
if (existing)
|
|
30647
|
-
await
|
|
30944
|
+
await rm3(target, { recursive: existing.isDirectory(), force: true });
|
|
30648
30945
|
await rename3(staged, target);
|
|
30649
30946
|
} finally {
|
|
30650
|
-
await
|
|
30947
|
+
await rm3(staged, { recursive: true, force: true });
|
|
30651
30948
|
}
|
|
30652
30949
|
}
|
|
30653
30950
|
async function planManagedSkills(managedSkills, sharedSkills, optionalShares) {
|
|
@@ -30666,8 +30963,8 @@ async function planManagedSkills(managedSkills, sharedSkills, optionalShares) {
|
|
|
30666
30963
|
try {
|
|
30667
30964
|
const tree = [];
|
|
30668
30965
|
await walkSafeSkillTree(shared.source, shared.source, {
|
|
30669
|
-
directory: async (rel) => void tree.push(`d ${
|
|
30670
|
-
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))}`)
|
|
30671
30968
|
});
|
|
30672
30969
|
entries.push({ kind: "shared", name: shared.name, source: shared.source });
|
|
30673
30970
|
lines.push(...tree);
|
|
@@ -30687,7 +30984,7 @@ async function materializedSkillManifest(target) {
|
|
|
30687
30984
|
const visit = async (directory, prefix) => {
|
|
30688
30985
|
for (const entry of await readdir2(directory)) {
|
|
30689
30986
|
const path = resolve16(directory, entry);
|
|
30690
|
-
const rel = prefix ?
|
|
30987
|
+
const rel = prefix ? join9(prefix, entry) : entry;
|
|
30691
30988
|
const entryStats = await lstat2(path);
|
|
30692
30989
|
if (entryStats.isSymbolicLink())
|
|
30693
30990
|
return false;
|
|
@@ -30855,7 +31152,7 @@ async function walkSafeSkillTree(source, sourceRoot, visitor, rel = "") {
|
|
|
30855
31152
|
for (const entry of await readdir2(resolvedSource)) {
|
|
30856
31153
|
if (entry === "." || entry === "..")
|
|
30857
31154
|
throw new Error("invalid shared skill entry");
|
|
30858
|
-
await walkSafeSkillTree(resolve16(source, entry), sourceRoot, visitor, rel ?
|
|
31155
|
+
await walkSafeSkillTree(resolve16(source, entry), sourceRoot, visitor, rel ? join9(rel, entry) : entry);
|
|
30859
31156
|
}
|
|
30860
31157
|
return;
|
|
30861
31158
|
}
|
|
@@ -30882,7 +31179,7 @@ async function writeIsolatedHarnessFile(path, content) {
|
|
|
30882
31179
|
if (!parentStats.isDirectory() || parentStats.isSymbolicLink()) {
|
|
30883
31180
|
throw new Error(`isolated harness parent is not a real directory: ${parent}`);
|
|
30884
31181
|
}
|
|
30885
|
-
const temporary = resolve16(parent, `.${basename5(path)}.${process.pid}.${
|
|
31182
|
+
const temporary = resolve16(parent, `.${basename5(path)}.${process.pid}.${randomUUID5()}.tmp`);
|
|
30886
31183
|
try {
|
|
30887
31184
|
await writeFile7(temporary, content, { mode: 384, flag: "wx" });
|
|
30888
31185
|
await chmod3(temporary, 384);
|
|
@@ -30941,7 +31238,7 @@ function agentCommandCatalogPublisher(input) {
|
|
|
30941
31238
|
|
|
30942
31239
|
// apps/body/dist/attachment-delivery.js
|
|
30943
31240
|
import { mkdir as mkdir7, writeFile as writeFile8 } from "node:fs/promises";
|
|
30944
|
-
import { basename as basename6, extname, join as
|
|
31241
|
+
import { basename as basename6, extname, join as join10 } from "node:path";
|
|
30945
31242
|
var MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
|
|
30946
31243
|
var EXPIRED_REASON = "expired: these attachment bytes are past their retention window";
|
|
30947
31244
|
var FETCH_TIMEOUT_MS = 3e4;
|
|
@@ -31000,7 +31297,7 @@ async function deliverAttachments(attachments, dir, fetchImpl = fetch) {
|
|
|
31000
31297
|
return { attachment, reason: EXPIRED_REASON };
|
|
31001
31298
|
if (!fetched.ok)
|
|
31002
31299
|
throw new Error(`HTTP ${fetched.status}`);
|
|
31003
|
-
const path =
|
|
31300
|
+
const path = join10(dir, safeFileName(attachment, index, taken));
|
|
31004
31301
|
await writeFile8(path, fetched.bytes);
|
|
31005
31302
|
const bytes = fetched.bytes;
|
|
31006
31303
|
const mimeType = attachment.mimeType ?? fetched.mimeType;
|
|
@@ -31580,14 +31877,18 @@ export default async function (pi) {
|
|
|
31580
31877
|
label: labelOf(tool.name),
|
|
31581
31878
|
description: typeof tool.description === 'string' ? tool.description : tool.name,
|
|
31582
31879
|
parameters: schema,
|
|
31583
|
-
async execute(
|
|
31880
|
+
async execute(toolCallId, params, signal) {
|
|
31584
31881
|
const result = await callServer(
|
|
31585
31882
|
server,
|
|
31586
31883
|
{
|
|
31587
31884
|
jsonrpc: '2.0',
|
|
31588
31885
|
id: 1,
|
|
31589
31886
|
method: 'tools/call',
|
|
31590
|
-
params: {
|
|
31887
|
+
params: {
|
|
31888
|
+
name: tool.name,
|
|
31889
|
+
arguments: params ?? {},
|
|
31890
|
+
_meta: { beelineToolCallId: toolCallId },
|
|
31891
|
+
},
|
|
31591
31892
|
},
|
|
31592
31893
|
signal,
|
|
31593
31894
|
);
|
|
@@ -31604,9 +31905,9 @@ export default async function (pi) {
|
|
|
31604
31905
|
`;
|
|
31605
31906
|
|
|
31606
31907
|
// apps/body/dist/corner-branch-sync.js
|
|
31607
|
-
import { execFile as
|
|
31908
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
31608
31909
|
import { promisify as promisify2 } from "node:util";
|
|
31609
|
-
var execFileAsync2 = promisify2(
|
|
31910
|
+
var execFileAsync2 = promisify2(execFile5);
|
|
31610
31911
|
async function syncCornerBranch(input) {
|
|
31611
31912
|
const git = input.git ?? (async (args) => (await execFileAsync2("git", ["-C", input.worktreePath, ...args], {
|
|
31612
31913
|
...input.env ? { env: input.env } : {},
|
|
@@ -32082,6 +32383,7 @@ function beelineAgentMcpServer(config, api, context) {
|
|
|
32082
32383
|
{ name: "BEELINE_DAEMON_ROOM_ID", value: context.roomId },
|
|
32083
32384
|
{ name: "BEELINE_DAEMON_WORKSPACE_ID", value: context.workspaceId },
|
|
32084
32385
|
...context.cornerId ? [{ name: "BEELINE_DAEMON_CORNER_ID", value: context.cornerId }] : [],
|
|
32386
|
+
...context.agentMayCloseCorner ? [{ name: "BEELINE_CORNER_AGENT_CLOSE", value: "1" }] : [],
|
|
32085
32387
|
...context.reviewer ? [{ name: "BEELINE_CORNER_REVIEWER", value: "1" }] : [],
|
|
32086
32388
|
...context.attachRoot ? [{ name: "BEELINE_ATTACH_ROOT", value: context.attachRoot }] : [],
|
|
32087
32389
|
...context.attachScratchRoot ? [{ name: "BEELINE_ATTACH_SCRATCH_ROOT", value: context.attachScratchRoot }] : [],
|
|
@@ -32128,11 +32430,11 @@ function youtubeMcpServer(config, accessToken) {
|
|
|
32128
32430
|
}
|
|
32129
32431
|
|
|
32130
32432
|
// apps/body/dist/codegraph.js
|
|
32131
|
-
import { execFile as
|
|
32433
|
+
import { execFile as execFile6 } from "node:child_process";
|
|
32132
32434
|
import { access, appendFile, mkdir as mkdir9, readFile as readFile7 } from "node:fs/promises";
|
|
32133
32435
|
import { dirname as dirname11, isAbsolute as isAbsolute3, resolve as resolve19 } from "node:path";
|
|
32134
32436
|
import { promisify as promisify3 } from "node:util";
|
|
32135
|
-
var execFileAsync3 = promisify3(
|
|
32437
|
+
var execFileAsync3 = promisify3(execFile6);
|
|
32136
32438
|
var CODEGRAPH_MCP_SERVER_NAME = "codegraph";
|
|
32137
32439
|
var CODEGRAPH_INDEX_PATH = ".codegraph/codegraph.db";
|
|
32138
32440
|
var CODEGRAPH_PREPARE_TIMEOUT_MS = 12e4;
|
|
@@ -32433,7 +32735,7 @@ async function withTurnReceiptHeartbeat(api, receipt, task, onHeartbeatError) {
|
|
|
32433
32735
|
}
|
|
32434
32736
|
|
|
32435
32737
|
// apps/body/dist/turn-trace.js
|
|
32436
|
-
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";
|
|
32437
32739
|
import { resolve as resolve21 } from "node:path";
|
|
32438
32740
|
import { performance as performance2 } from "node:perf_hooks";
|
|
32439
32741
|
var TURN_PHASES = [
|
|
@@ -32705,7 +33007,7 @@ var TurnTraceFile = class {
|
|
|
32705
33007
|
this.prunedDay = day;
|
|
32706
33008
|
const cutoff = traceFileName(new Date(now2.getTime() - TURN_TRACE_RETENTION_DAYS * 864e5));
|
|
32707
33009
|
const names = await readdir4(this.directory).catch(() => []);
|
|
32708
|
-
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 })));
|
|
32709
33011
|
}
|
|
32710
33012
|
};
|
|
32711
33013
|
|
|
@@ -32864,10 +33166,10 @@ process.exit(result.status ?? 1);
|
|
|
32864
33166
|
}
|
|
32865
33167
|
|
|
32866
33168
|
// apps/body/dist/warm-node-modules.js
|
|
32867
|
-
import { createHash as createHash5, randomUUID as
|
|
33169
|
+
import { createHash as createHash5, randomUUID as randomUUID6 } from "node:crypto";
|
|
32868
33170
|
import { constants as fsConstants } from "node:fs";
|
|
32869
|
-
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
|
|
32870
|
-
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";
|
|
32871
33173
|
var STORE_FORMAT = "v1";
|
|
32872
33174
|
var STAGING_PREFIX = ".beeline-warm-";
|
|
32873
33175
|
var STAGING_SWEEP_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -32935,7 +33237,7 @@ async function seedWarmNodeModules(input) {
|
|
|
32935
33237
|
if (storeDevice === void 0 || storeDevice !== checkoutDevice) {
|
|
32936
33238
|
return { reason: "cross-device", key: plan.key };
|
|
32937
33239
|
}
|
|
32938
|
-
const staging = resolve23(input.storeRoot, `${STAGING_PREFIX}${process.pid}.${
|
|
33240
|
+
const staging = resolve23(input.storeRoot, `${STAGING_PREFIX}${process.pid}.${randomUUID6()}`);
|
|
32939
33241
|
const placed = [];
|
|
32940
33242
|
const now2 = (input.now ?? Date.now)();
|
|
32941
33243
|
try {
|
|
@@ -32953,11 +33255,11 @@ async function seedWarmNodeModules(input) {
|
|
|
32953
33255
|
return { reason: "seeded", key: plan.key };
|
|
32954
33256
|
} catch (error) {
|
|
32955
33257
|
for (const target of placed) {
|
|
32956
|
-
await
|
|
33258
|
+
await rm5(target, { recursive: true, force: true }).catch(() => void 0);
|
|
32957
33259
|
}
|
|
32958
33260
|
return { reason: "failed", key: plan.key, detail: describe3(error) };
|
|
32959
33261
|
} finally {
|
|
32960
|
-
await
|
|
33262
|
+
await rm5(staging, { recursive: true, force: true }).catch(() => void 0);
|
|
32961
33263
|
}
|
|
32962
33264
|
}
|
|
32963
33265
|
async function harvestWarmNodeModules(input) {
|
|
@@ -32981,7 +33283,7 @@ async function harvestWarmNodeModules(input) {
|
|
|
32981
33283
|
detail: `${missing.length} absent, e.g. ${missing.slice(0, 3).join(", ")}`
|
|
32982
33284
|
};
|
|
32983
33285
|
}
|
|
32984
|
-
const staging = resolve23(input.storeRoot, `${STAGING_PREFIX}${process.pid}.${
|
|
33286
|
+
const staging = resolve23(input.storeRoot, `${STAGING_PREFIX}${process.pid}.${randomUUID6()}`);
|
|
32985
33287
|
try {
|
|
32986
33288
|
await mkdir12(input.storeRoot, { recursive: true, mode: 493 });
|
|
32987
33289
|
await sweepStaleStaging(input.storeRoot, (input.now ?? Date.now)());
|
|
@@ -33000,14 +33302,14 @@ async function harvestWarmNodeModules(input) {
|
|
|
33000
33302
|
return { reason: "already-warm", key: plan.key };
|
|
33001
33303
|
return { reason: "failed", key: plan.key, detail: describe3(error) };
|
|
33002
33304
|
} finally {
|
|
33003
|
-
await
|
|
33305
|
+
await rm5(staging, { recursive: true, force: true }).catch(() => void 0);
|
|
33004
33306
|
}
|
|
33005
33307
|
}
|
|
33006
33308
|
async function stagedTreeIsPublishable(worktreePath, staging, key) {
|
|
33007
33309
|
const settled = await readWarmPlan(worktreePath);
|
|
33008
33310
|
if (isPlanRefusal(settled) || settled.key !== key)
|
|
33009
33311
|
return false;
|
|
33010
|
-
const hidden =
|
|
33312
|
+
const hidden = join11("node_modules", ".package-lock.json");
|
|
33011
33313
|
const [copied, current] = await Promise.all([
|
|
33012
33314
|
readFile9(resolve23(staging, hidden)).catch(() => void 0),
|
|
33013
33315
|
readFile9(resolve23(worktreePath, hidden)).catch(() => void 0)
|
|
@@ -33045,7 +33347,7 @@ async function missingInstalledPackages(worktreePath, treeRoot = worktreePath) {
|
|
|
33045
33347
|
return missing.sort();
|
|
33046
33348
|
}
|
|
33047
33349
|
async function isInstalledPackage(path) {
|
|
33048
|
-
return stat2(
|
|
33350
|
+
return stat2(join11(path, "package.json")).then((info) => info.isFile(), () => false);
|
|
33049
33351
|
}
|
|
33050
33352
|
var INTEGRITY_READ_CONCURRENCY = 64;
|
|
33051
33353
|
async function mapWithLimit(values, limit, visit) {
|
|
@@ -33079,8 +33381,8 @@ function isContainedTreePath(value) {
|
|
|
33079
33381
|
async function cloneTree(source, target, file, topLevel = true) {
|
|
33080
33382
|
await mkdir12(target, { recursive: true, mode: 493 });
|
|
33081
33383
|
for (const entry of await readdir5(source, { withFileTypes: true })) {
|
|
33082
|
-
const from =
|
|
33083
|
-
const to =
|
|
33384
|
+
const from = join11(source, entry.name);
|
|
33385
|
+
const to = join11(target, entry.name);
|
|
33084
33386
|
if (entry.isSymbolicLink()) {
|
|
33085
33387
|
await symlink2(await readlink(from), to);
|
|
33086
33388
|
continue;
|
|
@@ -33110,13 +33412,13 @@ async function pruneWarmStore(storeRoot, keep) {
|
|
|
33110
33412
|
const names = (await readdir5(storeRoot).catch(() => [])).filter((name) => !name.startsWith(STAGING_PREFIX));
|
|
33111
33413
|
const entries = [];
|
|
33112
33414
|
for (const name of names) {
|
|
33113
|
-
const info = await lstat3(
|
|
33415
|
+
const info = await lstat3(join11(storeRoot, name)).catch(() => void 0);
|
|
33114
33416
|
if (info?.isDirectory())
|
|
33115
33417
|
entries.push({ name, usedAt: info.mtimeMs });
|
|
33116
33418
|
}
|
|
33117
33419
|
const dropped = entries.sort((a2, b) => b.usedAt - a2.usedAt || a2.name.localeCompare(b.name)).slice(keep);
|
|
33118
33420
|
for (const entry of dropped) {
|
|
33119
|
-
await
|
|
33421
|
+
await rm5(join11(storeRoot, entry.name), { recursive: true, force: true }).catch(() => void 0);
|
|
33120
33422
|
}
|
|
33121
33423
|
return dropped.map((entry) => entry.name);
|
|
33122
33424
|
}
|
|
@@ -33125,11 +33427,11 @@ async function sweepStaleStaging(storeRoot, now2) {
|
|
|
33125
33427
|
for (const name of entries) {
|
|
33126
33428
|
if (!name.startsWith(STAGING_PREFIX))
|
|
33127
33429
|
continue;
|
|
33128
|
-
const path =
|
|
33430
|
+
const path = join11(storeRoot, name);
|
|
33129
33431
|
const info = await lstat3(path).catch(() => void 0);
|
|
33130
33432
|
if (!info || now2 - info.mtimeMs < STAGING_SWEEP_MS)
|
|
33131
33433
|
continue;
|
|
33132
|
-
await
|
|
33434
|
+
await rm5(path, { recursive: true, force: true }).catch(() => void 0);
|
|
33133
33435
|
}
|
|
33134
33436
|
}
|
|
33135
33437
|
async function pathExists(path) {
|
|
@@ -33148,7 +33450,7 @@ function describe3(error) {
|
|
|
33148
33450
|
// apps/body/dist/monolith-room-turn.js
|
|
33149
33451
|
import { mkdir as mkdir13 } from "node:fs/promises";
|
|
33150
33452
|
import { homedir as homedir9 } from "node:os";
|
|
33151
|
-
import { join as
|
|
33453
|
+
import { join as join12 } from "node:path";
|
|
33152
33454
|
|
|
33153
33455
|
// packages/api-contract/dist/scheduled-prompts.js
|
|
33154
33456
|
var SCHEDULE_SCHEDULER_NAME = "Beeline Scheduler";
|
|
@@ -33192,6 +33494,14 @@ function resumePrompt(item) {
|
|
|
33192
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.'
|
|
33193
33495
|
].join(" ");
|
|
33194
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
|
+
}
|
|
33195
33505
|
return [
|
|
33196
33506
|
"This is the answer to your grant request; your paused work resumes now.",
|
|
33197
33507
|
"If it was approved and it is a command grant, run it with run_granted_command and the exact argv.",
|
|
@@ -33367,7 +33677,7 @@ var MonolithRoomTurnLoop = class {
|
|
|
33367
33677
|
const cached = this.deliveredAttachments.get(item.id);
|
|
33368
33678
|
if (cached)
|
|
33369
33679
|
return cached;
|
|
33370
|
-
const delivered = await deliverAttachments(item.attachments,
|
|
33680
|
+
const delivered = await deliverAttachments(item.attachments, join12(this.attachmentDir, item.id.replace(/[^\w-]/g, "_")), this.options.fetchImpl);
|
|
33371
33681
|
this.deliveredAttachments.set(item.id, withoutImageData(delivered));
|
|
33372
33682
|
return delivered;
|
|
33373
33683
|
}
|
|
@@ -33424,9 +33734,10 @@ var MonolithRoomTurnLoop = class {
|
|
|
33424
33734
|
}
|
|
33425
33735
|
async grantedHostRoutes() {
|
|
33426
33736
|
try {
|
|
33427
|
-
return
|
|
33428
|
-
agentId: this.agent.publicKey
|
|
33429
|
-
|
|
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 }));
|
|
33430
33741
|
} catch {
|
|
33431
33742
|
return [];
|
|
33432
33743
|
}
|
|
@@ -33487,7 +33798,7 @@ var MonolithRoomTurnLoop = class {
|
|
|
33487
33798
|
args: this.options.config.agentArgs ?? []
|
|
33488
33799
|
}, selection);
|
|
33489
33800
|
const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
|
|
33490
|
-
this.attachmentDir = tmpDir ?
|
|
33801
|
+
this.attachmentDir = tmpDir ? join12(tmpDir, "beeline-attachments") : void 0;
|
|
33491
33802
|
this.sessionScratchDir = tmpDir;
|
|
33492
33803
|
this.sessionStateDirs = stateDirs;
|
|
33493
33804
|
const homeStateDirs = harnessHomeStateDirs(harnessLabel, agentEnv.HOME ?? operatorHome);
|
|
@@ -33976,6 +34287,8 @@ ${JSON.stringify((corners.corners ?? []).filter((corner) => !corner.archived))}`
|
|
|
33976
34287
|
onPoll: () => this.options.health.poll(),
|
|
33977
34288
|
onError: (error) => console.error("[thin-core] Room command failed", error),
|
|
33978
34289
|
stop: (requestId) => this.stopTurn(requestId),
|
|
34290
|
+
restart: () => this.options.onRestartRequested?.(),
|
|
34291
|
+
canStartTurn: this.options.canStartTurn,
|
|
33979
34292
|
run: async (command) => {
|
|
33980
34293
|
const item = {
|
|
33981
34294
|
...command.source,
|
|
@@ -34009,7 +34322,7 @@ function roomMessagePrompt(author, body, attachments, delivered, harnessAcceptsI
|
|
|
34009
34322
|
}
|
|
34010
34323
|
|
|
34011
34324
|
// apps/body/dist/monolith-corner-turn.js
|
|
34012
|
-
var execFileAsync4 = promisify4(
|
|
34325
|
+
var execFileAsync4 = promisify4(execFile7);
|
|
34013
34326
|
var TOOL_ARGUMENT_MAX_BYTES = 1200;
|
|
34014
34327
|
var TOOL_OUTPUT_MAX_BYTES = 3200;
|
|
34015
34328
|
var TOOL_PATH_LIMIT = 12;
|
|
@@ -34026,6 +34339,8 @@ function cornerReviewerInstruction(input) {
|
|
|
34026
34339
|
const headSha = input.headSha ?? "<head sha>";
|
|
34027
34340
|
return `Checks are green on PR #${number} at ${headSha}. Review it now with the beeline-review skill against that exact head. FAIL: reply \`@${author}\` with the confirmed findings to fix. PASS: call the approve_merge tool for ${headSha}, then reply \`@${author} approved ${headSha}, merge\`. Never merge yourself. Never say you are holding or waiting for checks.`;
|
|
34028
34341
|
}
|
|
34342
|
+
var CORNER_REVIEWER_SESSION_INSTRUCTION = "You are this Room's configured reviewer. The active turn prompt names the latest stable green PR head. Review and approve only that exact head; if no stable green head is named, end the turn without a verdict. Never merge yourself.";
|
|
34343
|
+
var CORNER_REVIEWER_UNSTABLE_HEAD_INSTRUCTION = "There is no stable green PR head for this active reviewer turn. Do not review or call approve_merge. End this turn without a verdict; the next green transition will wake you.";
|
|
34029
34344
|
function cornerSelfReviewerInstruction(input) {
|
|
34030
34345
|
if (!input.reviewerHandle || !input.agentHandle || !input.openedByAgent || input.agentHandle.replace(/^@/, "") !== input.reviewerHandle.replace(/^@/, ""))
|
|
34031
34346
|
return void 0;
|
|
@@ -34247,6 +34562,8 @@ var MonolithCornerTurnLoop = class {
|
|
|
34247
34562
|
yoloMode = false;
|
|
34248
34563
|
/** The live parent-Room reviewer baked into the current session. */
|
|
34249
34564
|
reviewerHandle;
|
|
34565
|
+
/** Identity-only reviewer context; the exact PR head is refreshed inside each active turn. */
|
|
34566
|
+
reviewerInstructionInput;
|
|
34250
34567
|
/** The role-specific second-chance instruction for this session. */
|
|
34251
34568
|
cornerTurnEndNudge = CORNER_DELIVERY_NUDGE;
|
|
34252
34569
|
/** Repository state already given a delivery reminder, until that state changes. */
|
|
@@ -34392,9 +34709,10 @@ var MonolithCornerTurnLoop = class {
|
|
|
34392
34709
|
}
|
|
34393
34710
|
async grantedHostRoutes() {
|
|
34394
34711
|
try {
|
|
34395
|
-
return
|
|
34396
|
-
agentId: this.agent.publicKey
|
|
34397
|
-
|
|
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 }));
|
|
34398
34716
|
} catch {
|
|
34399
34717
|
return [];
|
|
34400
34718
|
}
|
|
@@ -34421,17 +34739,8 @@ var MonolithCornerTurnLoop = class {
|
|
|
34421
34739
|
authorHandle: opener?.handle,
|
|
34422
34740
|
openedByAgent: !this.options.openedBy || this.options.openedBy === this.agent.publicKey
|
|
34423
34741
|
};
|
|
34424
|
-
|
|
34425
|
-
|
|
34426
|
-
const restore = await this.options.api.execute("getCornerRestoreState", {
|
|
34427
|
-
cornerId: this.options.cornerId
|
|
34428
|
-
});
|
|
34429
|
-
reviewerInstruction = cornerReviewerInstruction({
|
|
34430
|
-
...reviewerInput,
|
|
34431
|
-
pullRequestNumber: restore.lifecycle?.pr?.number,
|
|
34432
|
-
headSha: restore.lifecycle?.pr?.headSha
|
|
34433
|
-
});
|
|
34434
|
-
}
|
|
34742
|
+
const reviewerInstruction = cornerReviewerInstruction(reviewerInput) ? CORNER_REVIEWER_SESSION_INSTRUCTION : void 0;
|
|
34743
|
+
this.reviewerInstructionInput = reviewerInstruction ? reviewerInput : void 0;
|
|
34435
34744
|
const selfReviewerInstruction = cornerSelfReviewerInstruction(reviewerInput);
|
|
34436
34745
|
this.cornerTurnEndNudge = reviewerInstruction ?? selfReviewerInstruction ?? cornerMergeInstruction(configuration.yoloMode, configuration.reviewerHandle);
|
|
34437
34746
|
await mkdir14(this.options.worktreePath, { recursive: true });
|
|
@@ -34490,7 +34799,7 @@ var MonolithCornerTurnLoop = class {
|
|
|
34490
34799
|
args: this.options.config.agentArgs ?? []
|
|
34491
34800
|
}, selection);
|
|
34492
34801
|
const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
|
|
34493
|
-
this.attachmentDir = tmpDir ?
|
|
34802
|
+
this.attachmentDir = tmpDir ? join13(tmpDir, "beeline-attachments") : void 0;
|
|
34494
34803
|
this.sessionScratchDir = tmpDir;
|
|
34495
34804
|
const homeStateDirs = harnessHomeStateDirs(harnessLabel, agentEnv.HOME ?? operatorHome);
|
|
34496
34805
|
await Promise.all(homeStateDirs.map((dir) => mkdir14(dir, { recursive: true })));
|
|
@@ -34579,6 +34888,7 @@ var MonolithCornerTurnLoop = class {
|
|
|
34579
34888
|
roomId: this.options.parentRoomId,
|
|
34580
34889
|
workspaceId: this.options.workspaceId,
|
|
34581
34890
|
cornerId: this.options.cornerId,
|
|
34891
|
+
agentMayCloseCorner: Boolean(repository),
|
|
34582
34892
|
reviewer: Boolean(reviewerInstruction),
|
|
34583
34893
|
attachRoot: this.options.worktreePath,
|
|
34584
34894
|
// The whole per-session overlay, not an enumerated subset: see
|
|
@@ -34636,10 +34946,9 @@ var MonolithCornerTurnLoop = class {
|
|
|
34636
34946
|
"Work in this corner's writable workspace. Use write_scratch_file or ordinary tools to create files, then post_artifact with the path to send them back to the corner.",
|
|
34637
34947
|
"Do not initialize a repository, create a branch, commit, push, open a pull request, or wait for GitHub checks.",
|
|
34638
34948
|
// This lane has no pull request URL and no merge card, so its
|
|
34639
|
-
// attached final reply
|
|
34640
|
-
//
|
|
34641
|
-
|
|
34642
|
-
this.options.requesterHandle ? `Deliver the result as artifacts: post_artifact everything the objective asked for, then call close_corner. Finish the turn by replying with @${this.options.requesterHandle} and one line on what you posted; that attached reply is the requester-facing completion signal.` : `Deliver the result as artifacts: post_artifact everything the objective asked for, then call close_corner. Finish the turn by replying with one line on what you posted; that attached reply is the requester-facing completion signal.`
|
|
34949
|
+
// attached final reply reports delivery to the requester. The
|
|
34950
|
+
// corner remains open until a human explicitly closes it.
|
|
34951
|
+
this.options.requesterHandle ? `Deliver the result as artifacts: post_artifact everything the objective asked for. Finish the turn by replying with @${this.options.requesterHandle} and one line on what you posted. The corner stays open until a human explicitly closes it.` : `Deliver the result as artifacts: post_artifact everything the objective asked for. Finish the turn by replying with one line on what you posted. The corner stays open until a human explicitly closes it.`
|
|
34643
34952
|
]
|
|
34644
34953
|
].filter(Boolean).join("\n\n")
|
|
34645
34954
|
});
|
|
@@ -34652,6 +34961,41 @@ var MonolithCornerTurnLoop = class {
|
|
|
34652
34961
|
}
|
|
34653
34962
|
return opened.sessionId;
|
|
34654
34963
|
}
|
|
34964
|
+
/**
|
|
34965
|
+
* Resolve the review target at prompt time, not session activation time.
|
|
34966
|
+
*
|
|
34967
|
+
* A push can land after a reviewer session starts but before its first
|
|
34968
|
+
* prompt, or while that prompt is running. The active prompt and its bounded
|
|
34969
|
+
* second pass therefore each read the current green lifecycle head. The
|
|
34970
|
+
* server still compares approve_merge's SHA with the current head, so a push
|
|
34971
|
+
* after this read fails closed too.
|
|
34972
|
+
*/
|
|
34973
|
+
async activeReviewerInstruction() {
|
|
34974
|
+
const input = this.reviewerInstructionInput;
|
|
34975
|
+
if (!input)
|
|
34976
|
+
return void 0;
|
|
34977
|
+
try {
|
|
34978
|
+
const [restore, localHead] = await Promise.all([
|
|
34979
|
+
this.options.api.execute("getCornerRestoreState", {
|
|
34980
|
+
cornerId: this.options.cornerId
|
|
34981
|
+
}),
|
|
34982
|
+
execFileAsync4("git", ["-C", this.options.worktreePath, "rev-parse", "HEAD"]).then(({ stdout: stdout6 }) => stdout6.trim())
|
|
34983
|
+
]);
|
|
34984
|
+
const pr = restore.lifecycle?.pr;
|
|
34985
|
+
if (restore.lifecycle?.checks !== "passing" || !pr?.number || !pr.headSha || pr.headSha !== localHead)
|
|
34986
|
+
return CORNER_REVIEWER_UNSTABLE_HEAD_INSTRUCTION;
|
|
34987
|
+
return [
|
|
34988
|
+
"Current stable reviewer target for this active turn; it supersedes any older head in the trigger or transcript:",
|
|
34989
|
+
cornerReviewerInstruction({
|
|
34990
|
+
...input,
|
|
34991
|
+
pullRequestNumber: pr.number,
|
|
34992
|
+
headSha: pr.headSha
|
|
34993
|
+
})
|
|
34994
|
+
].join("\n");
|
|
34995
|
+
} catch {
|
|
34996
|
+
return CORNER_REVIEWER_UNSTABLE_HEAD_INSTRUCTION;
|
|
34997
|
+
}
|
|
34998
|
+
}
|
|
34655
34999
|
/** The scheduler seam: `queue-wait` closes when a slot buys a session. */
|
|
34656
35000
|
lifecycle(trace) {
|
|
34657
35001
|
return {
|
|
@@ -34757,10 +35101,11 @@ var MonolithCornerTurnLoop = class {
|
|
|
34757
35101
|
throw new Error("corner turn stopped for daemon handoff");
|
|
34758
35102
|
this.busy = true;
|
|
34759
35103
|
await this.syncBranch();
|
|
34760
|
-
const [conversation, roster, delivered] = await trace.measure("context-fetch", () => Promise.all([
|
|
35104
|
+
const [conversation, roster, delivered, activeReviewerInstruction] = await trace.measure("context-fetch", () => Promise.all([
|
|
34761
35105
|
api.execute("getRoomConversation", { roomId: cornerId, limit: 200 }),
|
|
34762
35106
|
this.roster(),
|
|
34763
|
-
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([]),
|
|
35108
|
+
this.activeReviewerInstruction()
|
|
34764
35109
|
]));
|
|
34765
35110
|
const names = new Map(roster.members.map((member) => [member.identityId, member.name]));
|
|
34766
35111
|
const requestedBy = requester && !requester.name && names.get(requester.pubkey) ? { ...requester, name: names.get(requester.pubkey) } : requester;
|
|
@@ -34779,13 +35124,14 @@ var MonolithCornerTurnLoop = class {
|
|
|
34779
35124
|
${this.options.objective}`,
|
|
34780
35125
|
WarmTranscript.render(this.warmTranscript.select(this.sessionId, transcriptRows), "Corner transcript:", "New in the corner since your last turn (the earlier transcript is already in this session):"),
|
|
34781
35126
|
roomMentionDirectory(roster, this.agent.publicKey),
|
|
35127
|
+
activeReviewerInstruction,
|
|
34782
35128
|
[
|
|
34783
35129
|
...sourceMessageId ? [`Reaction target message id: ${sourceMessageId}`] : [],
|
|
34784
35130
|
`Newest trigger:
|
|
34785
35131
|
${trigger}`,
|
|
34786
35132
|
...attachmentPromptLines(attachments, delivered, this.acceptsImages())
|
|
34787
35133
|
].join("\n"),
|
|
34788
|
-
this.options.repository ? "Continue the objective. Obey the PR checks and human hold rules in your session instructions." : "Continue the objective. Attach completed files
|
|
35134
|
+
this.options.repository ? "Continue the objective. Obey the PR checks and human hold rules in your session instructions." : "Continue the objective. Attach completed files; only a human can close this corner.",
|
|
34789
35135
|
MAINTAIN_ASSIGNED_IDENTITY_DIRECTIVE
|
|
34790
35136
|
].filter(Boolean).join("\n\n");
|
|
34791
35137
|
const stream = new AgentTurnStream({
|
|
@@ -34926,7 +35272,9 @@ ${trigger}`,
|
|
|
34926
35272
|
this.lastDeliveryNudgeState = deliveryState;
|
|
34927
35273
|
replyBeforeNudge = durableReplyText(result.agentText);
|
|
34928
35274
|
await flushToolCalls(result.toolCalls, "");
|
|
34929
|
-
|
|
35275
|
+
if (this.reviewerInstructionInput)
|
|
35276
|
+
await this.syncBranch();
|
|
35277
|
+
result = await runPrompt(this.reviewerHandle ? await this.activeReviewerInstruction() ?? this.cornerTurnEndNudge : checksTurn ? CORNER_YOLO_MERGE_NUDGE : CORNER_DELIVERY_NUDGE);
|
|
34930
35278
|
trace.promptSettled();
|
|
34931
35279
|
explained = await this.explainEmpty(result);
|
|
34932
35280
|
}
|
|
@@ -35063,6 +35411,8 @@ ${trigger}`,
|
|
|
35063
35411
|
onPoll: () => this.options.onPoll(),
|
|
35064
35412
|
onError: (error) => console.error("[thin-core] corner command failed", error),
|
|
35065
35413
|
stop: (requestId) => this.stopTurn(requestId),
|
|
35414
|
+
restart: () => this.options.onRestartRequested?.(),
|
|
35415
|
+
canStartTurn: this.options.canStartTurn,
|
|
35066
35416
|
closed: async () => {
|
|
35067
35417
|
if (this.closePushed) {
|
|
35068
35418
|
this.closePushed = false;
|
|
@@ -35543,6 +35893,10 @@ var DiscoveryWakes = class {
|
|
|
35543
35893
|
function shouldPostInitialCornerWorkingState(restore, isOpener = true) {
|
|
35544
35894
|
return isOpener && !restore.featureBranch && !restore.lifecycle?.branch && !restore.lifecycle?.pr;
|
|
35545
35895
|
}
|
|
35896
|
+
function cornerBranchIsSafeToDelete(lifecycle) {
|
|
35897
|
+
const pr = lifecycle?.pr;
|
|
35898
|
+
return !pr || Boolean(pr.mergedAt);
|
|
35899
|
+
}
|
|
35546
35900
|
function cornerStartConfigKey(repository, objective) {
|
|
35547
35901
|
return JSON.stringify({
|
|
35548
35902
|
resolution: repository.resolution,
|
|
@@ -35583,7 +35937,7 @@ async function materializeCornerWorktree(input) {
|
|
|
35583
35937
|
`+refs/heads/${input.featureBranch}:refs/remotes/origin/${input.featureBranch}`
|
|
35584
35938
|
], { env: authEnv, maxBuffer: 4 * 1024 * 1024 }).then(() => true, () => false);
|
|
35585
35939
|
if (!existsSync6(resolve24(path, ".git"))) {
|
|
35586
|
-
await
|
|
35940
|
+
await rm6(path, { recursive: true, force: true });
|
|
35587
35941
|
await execFileAsync5("git", [
|
|
35588
35942
|
`--git-dir=${gitCommonDir}`,
|
|
35589
35943
|
"worktree",
|
|
@@ -35651,7 +36005,8 @@ async function mapWithConcurrency(values, limit, visit) {
|
|
|
35651
36005
|
}
|
|
35652
36006
|
}));
|
|
35653
36007
|
}
|
|
35654
|
-
async function removeCornerWorktreeAndBranches(worktree) {
|
|
36008
|
+
async function removeCornerWorktreeAndBranches(worktree, options = {}) {
|
|
36009
|
+
const preserveRemoteBranch = options.preserveRemoteBranch === true;
|
|
35655
36010
|
const localRef = `refs/heads/${worktree.branch}`;
|
|
35656
36011
|
const remoteRef = `refs/heads/${worktree.branch}`;
|
|
35657
36012
|
const worktreeExists = existsSync6(worktree.path);
|
|
@@ -35675,6 +36030,8 @@ async function removeCornerWorktreeAndBranches(worktree) {
|
|
|
35675
36030
|
worktree.path
|
|
35676
36031
|
]);
|
|
35677
36032
|
} else if (!localExists) {
|
|
36033
|
+
if (preserveRemoteBranch)
|
|
36034
|
+
return;
|
|
35678
36035
|
await deleteExactRemoteBranch(worktree.gitCommonDir, remoteRef, worktree.token, {
|
|
35679
36036
|
requireAbsent: true
|
|
35680
36037
|
});
|
|
@@ -35685,7 +36042,9 @@ async function removeCornerWorktreeAndBranches(worktree) {
|
|
|
35685
36042
|
throw new Error(`refusing to delete repository HEAD ${localRef}`);
|
|
35686
36043
|
}
|
|
35687
36044
|
}
|
|
35688
|
-
|
|
36045
|
+
if (!preserveRemoteBranch) {
|
|
36046
|
+
await deleteExactRemoteBranch(worktree.gitCommonDir, remoteRef, worktree.token);
|
|
36047
|
+
}
|
|
35689
36048
|
if (await gitRefExists(worktree.gitCommonDir, localRef)) {
|
|
35690
36049
|
await execFileAsync5("git", [
|
|
35691
36050
|
`--git-dir=${worktree.gitCommonDir}`,
|
|
@@ -35697,13 +36056,13 @@ async function removeCornerWorktreeAndBranches(worktree) {
|
|
|
35697
36056
|
]);
|
|
35698
36057
|
}
|
|
35699
36058
|
}
|
|
35700
|
-
var execFileAsync5 = promisify5(
|
|
36059
|
+
var execFileAsync5 = promisify5(execFile8);
|
|
35701
36060
|
async function removeCornerScratchWorkspace(input) {
|
|
35702
36061
|
const expected = resolve24(input.roomRoot, "scratch");
|
|
35703
36062
|
if (resolve24(input.scratchPath) !== expected) {
|
|
35704
36063
|
throw new Error(`refusing to remove scratch outside corner ${input.cornerId}`);
|
|
35705
36064
|
}
|
|
35706
|
-
await
|
|
36065
|
+
await rm6(expected, { recursive: true, force: true });
|
|
35707
36066
|
}
|
|
35708
36067
|
var RoomRuntimeCoordinator = class {
|
|
35709
36068
|
configPath;
|
|
@@ -35743,6 +36102,7 @@ var RoomRuntimeCoordinator = class {
|
|
|
35743
36102
|
workspaceRemovalConfirmations = 0;
|
|
35744
36103
|
roomRemovalConfirmations = /* @__PURE__ */ new Map();
|
|
35745
36104
|
confirmationPending = false;
|
|
36105
|
+
restartRequested = false;
|
|
35746
36106
|
/** Unscoped agent-directed discovery wakes (#1369), counted instead of
|
|
35747
36107
|
* flagged: a wake that arrives while a reconcile is already running must
|
|
35748
36108
|
* survive its start-pass clearing, or a reconnect mid-reconcile waits a
|
|
@@ -35784,6 +36144,7 @@ var RoomRuntimeCoordinator = class {
|
|
|
35784
36144
|
});
|
|
35785
36145
|
this.options.daemonApi.setConfigChangedListener?.(() => {
|
|
35786
36146
|
void this.scheduler.suspendIdle().catch((error) => console.error("[body] config-change session restart failed", error));
|
|
36147
|
+
void Promise.resolve(this.options.onConfigChanged?.()).catch((error) => console.error("[body] config-change catalog refresh failed", error));
|
|
35787
36148
|
});
|
|
35788
36149
|
this.options.daemonApi.setHiccupRestartListener?.((attempt) => {
|
|
35789
36150
|
this.options.onHiccupRestart?.(attempt);
|
|
@@ -35834,6 +36195,12 @@ var RoomRuntimeCoordinator = class {
|
|
|
35834
36195
|
count += 1;
|
|
35835
36196
|
return count;
|
|
35836
36197
|
}
|
|
36198
|
+
requestLifecycleRestart() {
|
|
36199
|
+
if (this.restartRequested)
|
|
36200
|
+
return;
|
|
36201
|
+
this.restartRequested = true;
|
|
36202
|
+
this.options.onRestartRequested?.();
|
|
36203
|
+
}
|
|
35837
36204
|
quiesceForUpdateIfIdle() {
|
|
35838
36205
|
if (!this.isWorkspaceIdle())
|
|
35839
36206
|
return false;
|
|
@@ -36093,7 +36460,9 @@ var RoomRuntimeCoordinator = class {
|
|
|
36093
36460
|
},
|
|
36094
36461
|
onCornerOpened: () => {
|
|
36095
36462
|
this.confirmationPending = true;
|
|
36096
|
-
}
|
|
36463
|
+
},
|
|
36464
|
+
onRestartRequested: () => this.requestLifecycleRestart(),
|
|
36465
|
+
canStartTurn: () => !this.restartRequested
|
|
36097
36466
|
});
|
|
36098
36467
|
const promise = loop.run().catch((error) => {
|
|
36099
36468
|
if (!controller.signal.aborted)
|
|
@@ -36245,7 +36614,9 @@ var RoomRuntimeCoordinator = class {
|
|
|
36245
36614
|
branch: featureBranch,
|
|
36246
36615
|
parentRoomId: corner.parentRoomId,
|
|
36247
36616
|
token: ""
|
|
36248
|
-
}) : this.reapCornerScratch({ path: workspacePath, cornerId: corner.cornerId })
|
|
36617
|
+
}) : this.reapCornerScratch({ path: workspacePath, cornerId: corner.cornerId }),
|
|
36618
|
+
onRestartRequested: () => this.requestLifecycleRestart(),
|
|
36619
|
+
canStartTurn: () => !this.restartRequested
|
|
36249
36620
|
});
|
|
36250
36621
|
const promise = loop.run().catch((error) => {
|
|
36251
36622
|
if (!controller.signal.aborted) {
|
|
@@ -36351,25 +36722,44 @@ var RoomRuntimeCoordinator = class {
|
|
|
36351
36722
|
}
|
|
36352
36723
|
async reapCornerWorktree(worktree) {
|
|
36353
36724
|
try {
|
|
36725
|
+
const preserveRemoteBranch = !await this.cornerBranchMayBeDeleted(worktree.cornerId);
|
|
36354
36726
|
const parentRoomId = worktree.parentRoomId;
|
|
36355
|
-
if (!parentRoomId) {
|
|
36727
|
+
if (!parentRoomId && !preserveRemoteBranch) {
|
|
36356
36728
|
throw new Error(`corner ${worktree.cornerId} has no parent Room for GitHub token`);
|
|
36357
36729
|
}
|
|
36358
|
-
const token = (await this.options.daemonApi.execute("getRoomGitHubToken", {
|
|
36359
|
-
|
|
36360
|
-
|
|
36361
|
-
|
|
36362
|
-
branch: worktree.branch,
|
|
36363
|
-
state: "gone",
|
|
36364
|
-
checks: "unknown"
|
|
36365
|
-
});
|
|
36730
|
+
const token = preserveRemoteBranch ? "" : (await this.options.daemonApi.execute("getRoomGitHubToken", {
|
|
36731
|
+
roomId: parentRoomId
|
|
36732
|
+
})).token;
|
|
36733
|
+
await removeCornerWorktreeAndBranches({ ...worktree, token }, { preserveRemoteBranch });
|
|
36366
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
|
+
}
|
|
36367
36743
|
} catch (error) {
|
|
36368
36744
|
this.pendingCornerReaps.set(worktree.cornerId, { ...worktree, token: "" });
|
|
36369
36745
|
this.confirmationPending = true;
|
|
36370
36746
|
throw error;
|
|
36371
36747
|
}
|
|
36372
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
|
+
}
|
|
36373
36763
|
async reapCornerScratch(scratch) {
|
|
36374
36764
|
await removeCornerScratchWorkspace({
|
|
36375
36765
|
cornerId: scratch.cornerId,
|
|
@@ -36608,13 +36998,13 @@ import { mkdir as mkdir17, rename as rename5 } from "node:fs/promises";
|
|
|
36608
36998
|
import { dirname as dirname15, resolve as resolve26 } from "node:path";
|
|
36609
36999
|
|
|
36610
37000
|
// apps/body/dist/systemd.js
|
|
36611
|
-
import { execFile as
|
|
37001
|
+
import { execFile as execFile9 } from "node:child_process";
|
|
36612
37002
|
import { mkdir as mkdir16, readFile as readFile10, stat as stat3, writeFile as writeFile10 } from "node:fs/promises";
|
|
36613
37003
|
import { homedir as homedir11 } from "node:os";
|
|
36614
37004
|
import { dirname as dirname14, resolve as resolve25 } from "node:path";
|
|
36615
37005
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
36616
37006
|
import { promisify as promisify6 } from "node:util";
|
|
36617
|
-
var execFileAsync6 = promisify6(
|
|
37007
|
+
var execFileAsync6 = promisify6(execFile9);
|
|
36618
37008
|
var DELIBERATE_REMOVAL_EXIT_STATUS = 78;
|
|
36619
37009
|
var DAEMON_DISTRESS_EXIT_STATUS = 77;
|
|
36620
37010
|
var UNKNOWN_AGENT_EXIT_STATUS = 79;
|
|
@@ -36883,8 +37273,8 @@ init_self_update_manifest();
|
|
|
36883
37273
|
|
|
36884
37274
|
// apps/body/dist/managed-update.js
|
|
36885
37275
|
init_self_update();
|
|
36886
|
-
import { spawn as
|
|
36887
|
-
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";
|
|
36888
37278
|
import { dirname as dirname18, resolve as resolve29 } from "node:path";
|
|
36889
37279
|
|
|
36890
37280
|
// apps/body/dist/update-rollback-alert.js
|
|
@@ -36977,7 +37367,7 @@ ${now2()}
|
|
|
36977
37367
|
throw error;
|
|
36978
37368
|
const age = now2() - await stat4(lock).then((value) => value.mtimeMs).catch(() => now2());
|
|
36979
37369
|
if (age > LOCK_STALE_MS) {
|
|
36980
|
-
await
|
|
37370
|
+
await rm8(lock, { recursive: true, force: true });
|
|
36981
37371
|
continue;
|
|
36982
37372
|
}
|
|
36983
37373
|
if (now2() >= deadline)
|
|
@@ -36988,7 +37378,7 @@ ${now2()}
|
|
|
36988
37378
|
try {
|
|
36989
37379
|
return await work();
|
|
36990
37380
|
} finally {
|
|
36991
|
-
await
|
|
37381
|
+
await rm8(lock, { recursive: true, force: true });
|
|
36992
37382
|
}
|
|
36993
37383
|
}
|
|
36994
37384
|
var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
|
|
@@ -37298,7 +37688,7 @@ async function runManagedUpdateWorkerProcess() {
|
|
|
37298
37688
|
if (!entrypoint)
|
|
37299
37689
|
throw new Error("cannot resolve the current Beeline entrypoint");
|
|
37300
37690
|
await new Promise((resolveWorker, rejectWorker) => {
|
|
37301
|
-
const child =
|
|
37691
|
+
const child = spawn10(process.execPath, [entrypoint, "managed-update-worker"], {
|
|
37302
37692
|
detached: true,
|
|
37303
37693
|
env: { ...process.env, BEELINE_INTERNAL_UPDATE_WORKER: "1" },
|
|
37304
37694
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -37457,6 +37847,32 @@ async function rollbackFailedSuccessor(layout, runtimeDir, cause = {}) {
|
|
|
37457
37847
|
}
|
|
37458
37848
|
|
|
37459
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
|
+
}
|
|
37460
37876
|
function updateUsage() {
|
|
37461
37877
|
console.log(`
|
|
37462
37878
|
${import_picocolors.default.bold("Update the installed Beeline bundle from the published manifest.")}
|
|
@@ -37558,6 +37974,8 @@ async function runUpdateCommand(args) {
|
|
|
37558
37974
|
await printStatus(layout);
|
|
37559
37975
|
return;
|
|
37560
37976
|
}
|
|
37977
|
+
if (!checkOnly)
|
|
37978
|
+
await refreshRuntimeHarnessAdapters();
|
|
37561
37979
|
const installed = await readInstalledBundleIdentity(layout);
|
|
37562
37980
|
console.log(`[beeline] installed bundle: ${describeIdentity(installed)}`);
|
|
37563
37981
|
const raw = await fetch(manifestUrl, { signal: AbortSignal.timeout(3e4) }).then((response) => {
|
|
@@ -37724,7 +38142,7 @@ async function runStartCommand(args, interactiveUi, dependencyOverrides = {}) {
|
|
|
37724
38142
|
|
|
37725
38143
|
// apps/body/dist/connect-command.js
|
|
37726
38144
|
import { spawn as spawn11 } from "node:child_process";
|
|
37727
|
-
import { createHash as createHash9, randomUUID as
|
|
38145
|
+
import { createHash as createHash9, randomUUID as randomUUID7 } from "node:crypto";
|
|
37728
38146
|
import { chmod as chmod7, mkdir as mkdir21, readFile as readFile13, unlink as unlink5, writeFile as writeFile14 } from "node:fs/promises";
|
|
37729
38147
|
import { homedir as homedir13, hostname as hostname2 } from "node:os";
|
|
37730
38148
|
import { dirname as dirname20, resolve as resolve30 } from "node:path";
|
|
@@ -37811,14 +38229,13 @@ async function verifyProviderKey(input) {
|
|
|
37811
38229
|
}
|
|
37812
38230
|
|
|
37813
38231
|
// apps/body/dist/pair-agent-selection.js
|
|
37814
|
-
import { spawn as spawn10 } from "node:child_process";
|
|
37815
38232
|
import { stdin as stdin2, stdout as stdout3 } from "node:process";
|
|
37816
38233
|
var NO_AGENT_MESSAGE = `No supported ACP-capable coding agent was detected.
|
|
37817
38234
|
Install one of these supported agents:
|
|
37818
|
-
codex npm install -g @openai/codex @agentclientprotocol/codex-acp
|
|
37819
|
-
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
|
|
37820
38237
|
goose https://block.github.io/goose/docs/getting-started/installation/
|
|
37821
|
-
pi npm install -g @mariozechner/pi-coding-agent pi-acp
|
|
38238
|
+
pi npm install -g @mariozechner/pi-coding-agent@latest pi-acp@latest
|
|
37822
38239
|
grok curl -fsSL https://x.ai/cli/install.sh | bash
|
|
37823
38240
|
cursor Install Cursor Agent CLI from https://cursor.com/docs/cli
|
|
37824
38241
|
Then retry, or explicitly use \`--agent reference\` with an LLM key.
|
|
@@ -37839,21 +38256,7 @@ async function clackSelectAgent(candidates) {
|
|
|
37839
38256
|
return unwrapPrompt(picked, "Pairing cancelled.");
|
|
37840
38257
|
}
|
|
37841
38258
|
async function installAdapter(install, opts) {
|
|
37842
|
-
await
|
|
37843
|
-
const child = spawn10(install.command, install.args, {
|
|
37844
|
-
cwd: opts.cwd,
|
|
37845
|
-
env: opts.env ?? process.env,
|
|
37846
|
-
stdio: "inherit"
|
|
37847
|
-
});
|
|
37848
|
-
child.once("error", rejectInstall);
|
|
37849
|
-
child.once("exit", (code, signal) => {
|
|
37850
|
-
if (code === 0)
|
|
37851
|
-
resolveInstall();
|
|
37852
|
-
else {
|
|
37853
|
-
rejectInstall(new Error(signal ? `${install.command} was terminated by ${signal}` : `${install.command} exited with status ${code ?? "unknown"}`));
|
|
37854
|
-
}
|
|
37855
|
-
});
|
|
37856
|
-
});
|
|
38259
|
+
await runAdapterInstall(install, opts);
|
|
37857
38260
|
}
|
|
37858
38261
|
function errorMessage(error) {
|
|
37859
38262
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -37867,14 +38270,38 @@ async function selectPairAgentCommand(opts) {
|
|
|
37867
38270
|
const selectAgent = opts.selectAgent ?? clackSelectAgent;
|
|
37868
38271
|
const confirmInstall = opts.confirmInstall ?? clackConfirmInstall;
|
|
37869
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
|
+
};
|
|
37870
38296
|
if (opts.explicitKind !== void 0) {
|
|
37871
38297
|
try {
|
|
37872
|
-
|
|
38298
|
+
const selected = resolveAgentCommand({
|
|
37873
38299
|
kind: opts.explicitKind,
|
|
37874
38300
|
customCommand: opts.customCommand,
|
|
37875
38301
|
env: opts.env,
|
|
37876
38302
|
cwd: opts.cwd
|
|
37877
38303
|
});
|
|
38304
|
+
return await useReadyAgent(selected, "selected");
|
|
37878
38305
|
} catch (resolutionError) {
|
|
37879
38306
|
const candidate = detectInstalledAgentCommands({ env: opts.env, cwd: opts.cwd }).find((detected2) => detected2.kind === opts.explicitKind && detected2.status === "missing-adapter");
|
|
37880
38307
|
if (!candidate || candidate.status !== "missing-adapter")
|
|
@@ -37920,25 +38347,19 @@ async function selectPairAgentCommand(opts) {
|
|
|
37920
38347
|
throw new Error("Installed coding agents need ACP adapter setup; install an adapter above, then retry.");
|
|
37921
38348
|
}
|
|
37922
38349
|
if (ready.length === 1) {
|
|
37923
|
-
|
|
37924
|
-
`);
|
|
37925
|
-
return ready[0].agent;
|
|
38350
|
+
return useReadyAgent(ready[0].agent, "auto-detected");
|
|
37926
38351
|
}
|
|
37927
38352
|
const readyKinds = ready.map((candidate) => candidate.kind).join(", ");
|
|
37928
38353
|
throw new Error(`Detected multiple ACP-capable coding agents: ${readyKinds}. This session is non-interactive; pass \`--agent <name>\` explicitly.`);
|
|
37929
38354
|
}
|
|
37930
38355
|
if (detected.length === 1 && detected[0].status === "ready") {
|
|
37931
|
-
|
|
37932
|
-
`);
|
|
37933
|
-
return detected[0].agent;
|
|
38356
|
+
return useReadyAgent(detected[0].agent, "auto-detected");
|
|
37934
38357
|
}
|
|
37935
38358
|
while (true) {
|
|
37936
38359
|
const pickedKind = await selectAgent(detected);
|
|
37937
38360
|
const selected = detected.find((candidate) => candidate.kind === pickedKind);
|
|
37938
38361
|
if (selected.status === "ready") {
|
|
37939
|
-
|
|
37940
|
-
`);
|
|
37941
|
-
return selected.agent;
|
|
38362
|
+
return useReadyAgent(selected.agent, "selected");
|
|
37942
38363
|
}
|
|
37943
38364
|
const manual = missingAdapterMessage(selected);
|
|
37944
38365
|
output.write(`[beeline] installing ${selected.kind} adapter: ${formatAdapterInstallCommand(selected.install)}
|
|
@@ -38157,10 +38578,23 @@ function connectModelPickerFromAxes(axes, fallbackModel, harness, rawAxes = axes
|
|
|
38157
38578
|
note: `${harness} did not enumerate models; offering the provider default`
|
|
38158
38579
|
};
|
|
38159
38580
|
}
|
|
38160
|
-
|
|
38161
|
-
|
|
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);
|
|
38162
38596
|
const model = input.model ?? defaultConnectModel(input.harness, input.provider);
|
|
38163
|
-
const catalog = await fetchAgentModelCatalog(agent, providerEnvironment({
|
|
38597
|
+
const catalog = await (dependencies.fetchCatalog ?? fetchAgentModelCatalog)(agent, providerEnvironment({
|
|
38164
38598
|
harness: input.harness,
|
|
38165
38599
|
...input.provider ? { provider: input.provider } : {},
|
|
38166
38600
|
...input.apiKey ? { apiKey: input.apiKey } : {},
|
|
@@ -38211,8 +38645,6 @@ async function collectConnectWizard(prompts = clackPrompts, loadModels = loadCon
|
|
|
38211
38645
|
return (picked ?? "").trim();
|
|
38212
38646
|
};
|
|
38213
38647
|
const askEffort = async (catalog2, forHarness, model2, forProvider, forKey) => {
|
|
38214
|
-
if (!catalog2.effort)
|
|
38215
|
-
return void 0;
|
|
38216
38648
|
let axis = catalog2.effort;
|
|
38217
38649
|
if (model2 !== catalog2.currentValue) {
|
|
38218
38650
|
const reread = await loadModels({
|
|
@@ -38224,7 +38656,7 @@ async function collectConnectWizard(prompts = clackPrompts, loadModels = loadCon
|
|
|
38224
38656
|
}).catch(() => void 0);
|
|
38225
38657
|
axis = reread?.effort ?? axis;
|
|
38226
38658
|
}
|
|
38227
|
-
if (!axis
|
|
38659
|
+
if (!axis?.options.length)
|
|
38228
38660
|
return void 0;
|
|
38229
38661
|
const picked = await prompts.select({
|
|
38230
38662
|
message: brass("Choose reasoning effort"),
|
|
@@ -38368,7 +38800,7 @@ async function readMachineId(env = process.env) {
|
|
|
38368
38800
|
if (!/^[0-9a-f-]{32,}$/.test(machineId))
|
|
38369
38801
|
throw new Error("invalid persisted machine id");
|
|
38370
38802
|
} catch {
|
|
38371
|
-
machineId =
|
|
38803
|
+
machineId = randomUUID7();
|
|
38372
38804
|
try {
|
|
38373
38805
|
await mkdir21(configDir, { recursive: true, mode: 448 });
|
|
38374
38806
|
await writeFile14(machineIdPath, `${machineId}
|
|
@@ -38648,7 +39080,7 @@ async function runConnectFinishCommand(path) {
|
|
|
38648
39080
|
init_self_update();
|
|
38649
39081
|
|
|
38650
39082
|
// apps/body/dist/daemon-failure.js
|
|
38651
|
-
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";
|
|
38652
39084
|
import { dirname as dirname21, resolve as resolve31 } from "node:path";
|
|
38653
39085
|
var DAEMON_FAILURE_LIMIT = 3;
|
|
38654
39086
|
var DAEMON_FAILURE_WINDOW_MS = 5 * 6e4;
|
|
@@ -38688,11 +39120,11 @@ async function recordDaemonStartFailure(runtimeDir, error, now2 = Date.now()) {
|
|
|
38688
39120
|
return { distressed, count: failures.length, path: daemonFailurePath(runtimeDir) };
|
|
38689
39121
|
}
|
|
38690
39122
|
async function clearDaemonStartFailures(runtimeDir) {
|
|
38691
|
-
await
|
|
39123
|
+
await rm9(daemonFailurePath(runtimeDir), { force: true });
|
|
38692
39124
|
}
|
|
38693
39125
|
|
|
38694
39126
|
// apps/body/dist/update-functional-probe.js
|
|
38695
|
-
import { mkdir as mkdir23, rm as
|
|
39127
|
+
import { mkdir as mkdir23, rm as rm10 } from "node:fs/promises";
|
|
38696
39128
|
import { homedir as homedir14 } from "node:os";
|
|
38697
39129
|
import { resolve as resolve32 } from "node:path";
|
|
38698
39130
|
var UPDATE_PROBE_SESSION_TIMEOUT_MS = 1e4;
|
|
@@ -38817,7 +39249,7 @@ async function runUpdateFunctionalProbe(input) {
|
|
|
38817
39249
|
const root = input.probeRoot ?? resolve32(input.runtimeDir, "update-functional-probe");
|
|
38818
39250
|
const cwd = resolve32(root, "checkout");
|
|
38819
39251
|
const homeRoot = resolve32(root, "agent-home");
|
|
38820
|
-
await
|
|
39252
|
+
await rm10(root, { recursive: true, force: true });
|
|
38821
39253
|
await mkdir23(cwd, { recursive: true, mode: 448 });
|
|
38822
39254
|
let client;
|
|
38823
39255
|
try {
|
|
@@ -38992,13 +39424,13 @@ async function runUpdateFunctionalProbe(input) {
|
|
|
38992
39424
|
};
|
|
38993
39425
|
} finally {
|
|
38994
39426
|
await client?.stop().catch(() => void 0);
|
|
38995
|
-
await
|
|
39427
|
+
await rm10(root, { recursive: true, force: true }).catch(() => void 0);
|
|
38996
39428
|
}
|
|
38997
39429
|
}
|
|
38998
39430
|
|
|
38999
39431
|
// apps/body/dist/current-release-probe.js
|
|
39000
39432
|
import { spawn as spawn12 } from "node:child_process";
|
|
39001
|
-
import { dirname as dirname22, join as
|
|
39433
|
+
import { dirname as dirname22, join as join15 } from "node:path";
|
|
39002
39434
|
init_self_update();
|
|
39003
39435
|
var CURRENT_RELEASE_PROBE_TIMEOUT_MS = 12e4;
|
|
39004
39436
|
var UPDATE_PROBE_COMMAND = "update-probe";
|
|
@@ -39041,7 +39473,7 @@ function outcomeFromReport(report) {
|
|
|
39041
39473
|
}
|
|
39042
39474
|
}
|
|
39043
39475
|
async function probeReleaseInSubprocess(input) {
|
|
39044
|
-
const bundleDir =
|
|
39476
|
+
const bundleDir = join15(input.layout.releasesRoot, input.releaseId);
|
|
39045
39477
|
const entrypoint = await resolveBundleEntrypoint(bundleDir);
|
|
39046
39478
|
if (!entrypoint) {
|
|
39047
39479
|
return { kind: "unavailable", reason: `release ${input.releaseId} has no runnable CLI entrypoint` };
|
|
@@ -39104,7 +39536,7 @@ async function runUpdateProbeCommand(args, options = {}) {
|
|
|
39104
39536
|
const runtime = await readRuntimeRecord(configPath);
|
|
39105
39537
|
const agent = runtimeAgentCommand(runtime);
|
|
39106
39538
|
const config = loadBodyConfig({
|
|
39107
|
-
workspaceRoot:
|
|
39539
|
+
workspaceRoot: join15(dirname22(configPath), "workspace"),
|
|
39108
39540
|
llmEnvFile: runtime.llmEnvFile,
|
|
39109
39541
|
env: { ...env, BUZZ_AGENT_BIN: agent.command, BUZZ_DEV_MCP_BIN: runtime.mcpBinary },
|
|
39110
39542
|
agent
|
|
@@ -39130,7 +39562,7 @@ async function runUpdateProbeCommand(args, options = {}) {
|
|
|
39130
39562
|
sandboxRequired: runtime.sandbox !== "off",
|
|
39131
39563
|
sandboxUnavailableDetail: sandbox.advisory,
|
|
39132
39564
|
// The successor's probe still holds `<runtimeDir>/update-functional-probe`.
|
|
39133
|
-
probeRoot:
|
|
39565
|
+
probeRoot: join15(runtimeDir, "current-release-probe")
|
|
39134
39566
|
}));
|
|
39135
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 };
|
|
39136
39568
|
write(JSON.stringify(report));
|
|
@@ -39303,6 +39735,21 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
39303
39735
|
throw new Error("monolith daemon transport activation failed");
|
|
39304
39736
|
runtime = activated.runtime;
|
|
39305
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
|
+
}
|
|
39306
39753
|
const agent = runtimeAgentCommand(runtime);
|
|
39307
39754
|
await writeDaemonPidRecord(configPath, process.pid);
|
|
39308
39755
|
const env = {
|
|
@@ -39328,7 +39775,7 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
39328
39775
|
if (runtime.sharedSkills)
|
|
39329
39776
|
config.sharedSkills = [...runtime.sharedSkills];
|
|
39330
39777
|
if (runtime.modelSelection) {
|
|
39331
|
-
await applyRuntimeModelPreflight(config, agent, runtime.modelSelection);
|
|
39778
|
+
await applyRuntimeModelPreflight(config, agent, runtime.modelSelection, void 0, refreshRuntimeAdapter);
|
|
39332
39779
|
if (!config.modelUnavailable) {
|
|
39333
39780
|
console.log("[body] persisted model/effort selection passed live startup validation");
|
|
39334
39781
|
} else {
|
|
@@ -39394,10 +39841,28 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
39394
39841
|
scratchSweepTimer.unref();
|
|
39395
39842
|
let ready = false;
|
|
39396
39843
|
let connectorLoop;
|
|
39844
|
+
let catalogRefresh;
|
|
39845
|
+
const refreshCatalog = () => {
|
|
39846
|
+
catalogRefresh ??= syncAgentModelCatalog({
|
|
39847
|
+
api: daemonApi,
|
|
39848
|
+
agent,
|
|
39849
|
+
agentEnv: config.agentEnv,
|
|
39850
|
+
agentId: runtime.agent.publicKey,
|
|
39851
|
+
workspaceId: runtime.communityId,
|
|
39852
|
+
runtimeDir,
|
|
39853
|
+
...runtime.modelSelection ? { runtimeSelection: runtime.modelSelection } : {},
|
|
39854
|
+
force: true
|
|
39855
|
+
}).then(() => void 0).finally(() => {
|
|
39856
|
+
catalogRefresh = void 0;
|
|
39857
|
+
});
|
|
39858
|
+
return catalogRefresh;
|
|
39859
|
+
};
|
|
39397
39860
|
let stoppingStatus = "daemon stopped";
|
|
39398
39861
|
try {
|
|
39862
|
+
let lifecycleRestartDrain;
|
|
39399
39863
|
const core = new ThinDaemonCore(runtime, configPath, config, {
|
|
39400
39864
|
daemonApi,
|
|
39865
|
+
onConfigChanged: refreshCatalog,
|
|
39401
39866
|
onHiccupRestart: (attempt) => {
|
|
39402
39867
|
const delay = hiccupBackoffMs(attempt);
|
|
39403
39868
|
console.warn(`[thin-core] hiccup restart attempt ${attempt}; exiting so systemd can start a fresh helper`);
|
|
@@ -39407,6 +39872,30 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
39407
39872
|
}
|
|
39408
39873
|
const timer = setTimeout(() => process.exit(0), delay);
|
|
39409
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
|
+
});
|
|
39410
39899
|
}
|
|
39411
39900
|
});
|
|
39412
39901
|
const updateDrain = update ? new ManagedUpdateDrain({
|