usebeeline 0.0.63 → 0.0.69
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 +2 -2
- package/dist/usebeeline.mjs +1412 -1395
- package/package.json +1 -1
package/dist/usebeeline.mjs
CHANGED
|
@@ -2415,7 +2415,7 @@ var require_websocket = __commonJS({
|
|
|
2415
2415
|
var http = __require("http");
|
|
2416
2416
|
var net = __require("net");
|
|
2417
2417
|
var tls = __require("tls");
|
|
2418
|
-
var { randomBytes: randomBytes7, createHash:
|
|
2418
|
+
var { randomBytes: randomBytes7, createHash: createHash8 } = __require("crypto");
|
|
2419
2419
|
var { Duplex, Readable } = __require("stream");
|
|
2420
2420
|
var { URL: URL2 } = __require("url");
|
|
2421
2421
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -3083,7 +3083,7 @@ var require_websocket = __commonJS({
|
|
|
3083
3083
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
3084
3084
|
return;
|
|
3085
3085
|
}
|
|
3086
|
-
const digest =
|
|
3086
|
+
const digest = createHash8("sha1").update(key + GUID).digest("base64");
|
|
3087
3087
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
3088
3088
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
3089
3089
|
return;
|
|
@@ -3452,7 +3452,7 @@ var require_websocket_server = __commonJS({
|
|
|
3452
3452
|
var EventEmitter2 = __require("events");
|
|
3453
3453
|
var http = __require("http");
|
|
3454
3454
|
var { Duplex } = __require("stream");
|
|
3455
|
-
var { createHash:
|
|
3455
|
+
var { createHash: createHash8 } = __require("crypto");
|
|
3456
3456
|
var extension2 = require_extension();
|
|
3457
3457
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
3458
3458
|
var subprotocol2 = require_subprotocol();
|
|
@@ -3759,7 +3759,7 @@ var require_websocket_server = __commonJS({
|
|
|
3759
3759
|
);
|
|
3760
3760
|
}
|
|
3761
3761
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
3762
|
-
const digest =
|
|
3762
|
+
const digest = createHash8("sha1").update(key + GUID).digest("base64");
|
|
3763
3763
|
const headers = [
|
|
3764
3764
|
"HTTP/1.1 101 Switching Protocols",
|
|
3765
3765
|
"Upgrade: websocket",
|
|
@@ -3984,12 +3984,12 @@ __export(self_update_exports, {
|
|
|
3984
3984
|
writeUpdateAttemptFixture: () => writeUpdateAttemptFixture,
|
|
3985
3985
|
writeUpdateState: () => writeUpdateState
|
|
3986
3986
|
});
|
|
3987
|
-
import { createHash as
|
|
3987
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
3988
3988
|
import { constants as fsConstants } from "node:fs";
|
|
3989
|
-
import { access, chmod as chmod4, lstat as lstat2, mkdir as
|
|
3989
|
+
import { access, chmod as chmod4, lstat as lstat2, mkdir as mkdir14, open, readFile as readFile9, rename as rename3, rm as rm5, symlink as symlink2, writeFile as writeFile10 } from "node:fs/promises";
|
|
3990
3990
|
import { spawn as spawn4 } from "node:child_process";
|
|
3991
3991
|
import { homedir as homedir9 } from "node:os";
|
|
3992
|
-
import { dirname as
|
|
3992
|
+
import { dirname as dirname10, join as join8, resolve as resolve21 } from "node:path";
|
|
3993
3993
|
function anchorLayout(rawLibDir) {
|
|
3994
3994
|
const libDir = resolve21(rawLibDir);
|
|
3995
3995
|
const segments = libDir.split(/[/\\]/);
|
|
@@ -4028,7 +4028,7 @@ function discoveredBeelineInstallLayout(env = process.env) {
|
|
|
4028
4028
|
return anchorLayout(explicitAnchor);
|
|
4029
4029
|
const explicitBinDir = env.BEELINE_INSTALL_DIR?.trim();
|
|
4030
4030
|
if (explicitBinDir)
|
|
4031
|
-
return anchorLayout(resolve21(
|
|
4031
|
+
return anchorLayout(resolve21(dirname10(resolve21(explicitBinDir)), "lib", "beeline"));
|
|
4032
4032
|
return defaultBeelineInstallLayout(env);
|
|
4033
4033
|
}
|
|
4034
4034
|
function hostPlatformKey() {
|
|
@@ -4039,7 +4039,7 @@ function hostPlatformKey() {
|
|
|
4039
4039
|
return `${os}-${arch}`;
|
|
4040
4040
|
}
|
|
4041
4041
|
function bundleJsonCandidates(bundleDir) {
|
|
4042
|
-
return [
|
|
4042
|
+
return [join8(bundleDir, "lib", "beeline", "bundle.json"), join8(bundleDir, "bundle.json")];
|
|
4043
4043
|
}
|
|
4044
4044
|
async function readBundleJson(bundleDir) {
|
|
4045
4045
|
let raw;
|
|
@@ -4063,7 +4063,7 @@ async function readBundleJson(bundleDir) {
|
|
|
4063
4063
|
}
|
|
4064
4064
|
}
|
|
4065
4065
|
function updateStatePath(layout) {
|
|
4066
|
-
return
|
|
4066
|
+
return join8(layout.releasesRoot, ".state", "update-state.json");
|
|
4067
4067
|
}
|
|
4068
4068
|
async function readUpdateState(layout) {
|
|
4069
4069
|
try {
|
|
@@ -4073,8 +4073,8 @@ async function readUpdateState(layout) {
|
|
|
4073
4073
|
}
|
|
4074
4074
|
}
|
|
4075
4075
|
async function writeUpdateState(layout, state) {
|
|
4076
|
-
await
|
|
4077
|
-
await
|
|
4076
|
+
await mkdir14(join8(layout.releasesRoot, ".state"), { recursive: true });
|
|
4077
|
+
await writeFile10(updateStatePath(layout), `${JSON.stringify(state, null, 2)}
|
|
4078
4078
|
`, "utf8");
|
|
4079
4079
|
}
|
|
4080
4080
|
async function readInstalledBundleIdentity(layout, _state = {}) {
|
|
@@ -4155,7 +4155,7 @@ function run(command, args, timeoutMs) {
|
|
|
4155
4155
|
});
|
|
4156
4156
|
}
|
|
4157
4157
|
function entrypointCandidates(bundleDir) {
|
|
4158
|
-
return [
|
|
4158
|
+
return [join8(bundleDir, BUNDLE_ENTRYPOINT), join8(bundleDir, "beeline-cli.mjs")];
|
|
4159
4159
|
}
|
|
4160
4160
|
async function resolveBundleEntrypoint(bundleDir) {
|
|
4161
4161
|
for (const candidate of entrypointCandidates(bundleDir)) {
|
|
@@ -4177,8 +4177,8 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
|
|
|
4177
4177
|
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
4178
4178
|
const log2 = opts.logger ?? ((line) => console.log(`[body] self-update: ${line}`));
|
|
4179
4179
|
const releaseId = sanitizeReleaseId(published.commit ?? published.version ?? `release-${Date.now()}`);
|
|
4180
|
-
const releaseDir =
|
|
4181
|
-
const okMarker =
|
|
4180
|
+
const releaseDir = join8(layout.releasesRoot, releaseId);
|
|
4181
|
+
const okMarker = join8(releaseDir, ".stage-ok");
|
|
4182
4182
|
let previouslyVerified = false;
|
|
4183
4183
|
try {
|
|
4184
4184
|
const recorded = await readFile9(okMarker, "utf8");
|
|
@@ -4187,8 +4187,8 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
|
|
|
4187
4187
|
previouslyVerified = true;
|
|
4188
4188
|
} catch {
|
|
4189
4189
|
}
|
|
4190
|
-
await
|
|
4191
|
-
const tempArchive =
|
|
4190
|
+
await mkdir14(releaseDir, { recursive: true });
|
|
4191
|
+
const tempArchive = join8(layout.releasesRoot, `.download-${releaseId}-${process.pid}.tar.gz`);
|
|
4192
4192
|
try {
|
|
4193
4193
|
log2(`downloading ${published.file}`);
|
|
4194
4194
|
const response = await fetchImpl(archiveUrlFor(manifestUrl, published.file), {
|
|
@@ -4197,7 +4197,7 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
|
|
|
4197
4197
|
if (!response.ok || !response.body) {
|
|
4198
4198
|
throw new Error(`downloading ${published.file} failed: HTTP ${response.status}`);
|
|
4199
4199
|
}
|
|
4200
|
-
const hash =
|
|
4200
|
+
const hash = createHash6("sha256");
|
|
4201
4201
|
const chunks = [];
|
|
4202
4202
|
for await (const chunk of response.body) {
|
|
4203
4203
|
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
@@ -4208,7 +4208,7 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
|
|
|
4208
4208
|
if (actual !== published.sha256.toLowerCase()) {
|
|
4209
4209
|
throw new Error(`checksum mismatch for ${published.file}: expected ${published.sha256}, got ${actual} \u2014 aborting without touching the installed bundle`);
|
|
4210
4210
|
}
|
|
4211
|
-
await
|
|
4211
|
+
await writeFile10(tempArchive, Buffer.concat(chunks), { mode: 384 });
|
|
4212
4212
|
const entries = (await new Promise((resolveList, rejectList) => {
|
|
4213
4213
|
const child = spawn4("tar", ["-tzf", tempArchive], { stdio: ["ignore", "pipe", "inherit"] });
|
|
4214
4214
|
let out = "";
|
|
@@ -4228,18 +4228,18 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
|
|
|
4228
4228
|
throw new Error(`extracting bundle failed: ${extract2.stderr}`);
|
|
4229
4229
|
for (const relative3 of requiredBundlePaths()) {
|
|
4230
4230
|
try {
|
|
4231
|
-
await access(
|
|
4231
|
+
await access(join8(releaseDir, relative3), fsConstants.F_OK);
|
|
4232
4232
|
} catch {
|
|
4233
4233
|
throw new Error(`staged bundle is missing ${relative3}`);
|
|
4234
4234
|
}
|
|
4235
4235
|
}
|
|
4236
4236
|
if (opts.smokeTestCli !== false) {
|
|
4237
|
-
const probe = await run(process.execPath, [
|
|
4237
|
+
const probe = await run(process.execPath, [join8(releaseDir, BUNDLE_ENTRYPOINT), "--version"], 6e4);
|
|
4238
4238
|
if (probe.status !== 0) {
|
|
4239
4239
|
throw new Error(`staged bundle failed its startup smoke test (--version exited ${probe.status})${probe.stderr ? `: ${probe.stderr.trim()}` : ""}`);
|
|
4240
4240
|
}
|
|
4241
4241
|
}
|
|
4242
|
-
await
|
|
4242
|
+
await writeFile10(okMarker, `${published.sha256}
|
|
4243
4243
|
`, "utf8");
|
|
4244
4244
|
log2(`staged release ${releaseId} (sha256 verified)`);
|
|
4245
4245
|
return releaseId;
|
|
@@ -4267,26 +4267,26 @@ function forwarderScript(tool) {
|
|
|
4267
4267
|
}
|
|
4268
4268
|
async function replaceFile(path, contents, mode) {
|
|
4269
4269
|
const temp = `${path}.new-${process.pid}`;
|
|
4270
|
-
await
|
|
4270
|
+
await writeFile10(temp, contents, { mode });
|
|
4271
4271
|
await chmod4(temp, mode);
|
|
4272
4272
|
await rename3(temp, path);
|
|
4273
4273
|
}
|
|
4274
4274
|
async function activateRelease(layout, releaseId) {
|
|
4275
|
-
const releaseDir =
|
|
4276
|
-
await access(
|
|
4277
|
-
await
|
|
4278
|
-
await
|
|
4275
|
+
const releaseDir = join8(layout.releasesRoot, releaseId);
|
|
4276
|
+
await access(join8(releaseDir, BUNDLE_ENTRYPOINT), fsConstants.F_OK);
|
|
4277
|
+
await mkdir14(layout.releasesRoot, { recursive: true });
|
|
4278
|
+
await mkdir14(layout.binDir, { recursive: true });
|
|
4279
4279
|
let previousReleaseId = await activeReleaseId(layout);
|
|
4280
4280
|
const kind = await pathKind(layout.libDir);
|
|
4281
4281
|
if (kind === "directory") {
|
|
4282
4282
|
const legacyIdentity = await readBundleJson(layout.libDir);
|
|
4283
4283
|
const legacyId = sanitizeReleaseId(legacyIdentity?.commit ?? legacyIdentity?.version ?? `legacy-${Date.now()}`);
|
|
4284
|
-
const legacyDir =
|
|
4284
|
+
const legacyDir = join8(layout.releasesRoot, legacyId);
|
|
4285
4285
|
try {
|
|
4286
4286
|
await access(legacyDir, fsConstants.F_OK);
|
|
4287
4287
|
previousReleaseId = `${legacyId}-${Date.now()}`;
|
|
4288
|
-
await rename3(layout.libDir,
|
|
4289
|
-
await normalizeLegacyBundleShape(
|
|
4288
|
+
await rename3(layout.libDir, join8(layout.releasesRoot, previousReleaseId));
|
|
4289
|
+
await normalizeLegacyBundleShape(join8(layout.releasesRoot, previousReleaseId));
|
|
4290
4290
|
} catch {
|
|
4291
4291
|
await rename3(layout.libDir, legacyDir);
|
|
4292
4292
|
await normalizeLegacyBundleShape(legacyDir);
|
|
@@ -4295,18 +4295,18 @@ async function activateRelease(layout, releaseId) {
|
|
|
4295
4295
|
}
|
|
4296
4296
|
const tempLink = `${layout.libDir}.new-${process.pid}`;
|
|
4297
4297
|
await rm5(tempLink, { force: true });
|
|
4298
|
-
await symlink2(
|
|
4298
|
+
await symlink2(join8("beeline-releases", releaseId), tempLink);
|
|
4299
4299
|
await rename3(tempLink, layout.libDir);
|
|
4300
|
-
await fsyncDir(
|
|
4300
|
+
await fsyncDir(dirname10(layout.libDir));
|
|
4301
4301
|
await writeBinForwarders(layout, releaseDir);
|
|
4302
4302
|
return { previousReleaseId };
|
|
4303
4303
|
}
|
|
4304
4304
|
async function normalizeLegacyBundleShape(bundleDir) {
|
|
4305
|
-
const innerLib =
|
|
4305
|
+
const innerLib = join8(bundleDir, "lib", "beeline");
|
|
4306
4306
|
let anyFlat = false;
|
|
4307
4307
|
for (const name of LEGACY_FLAT_BUNDLE_FILES) {
|
|
4308
4308
|
try {
|
|
4309
|
-
await access(
|
|
4309
|
+
await access(join8(bundleDir, name), fsConstants.F_OK);
|
|
4310
4310
|
anyFlat = true;
|
|
4311
4311
|
break;
|
|
4312
4312
|
} catch {
|
|
@@ -4314,14 +4314,14 @@ async function normalizeLegacyBundleShape(bundleDir) {
|
|
|
4314
4314
|
}
|
|
4315
4315
|
if (!anyFlat)
|
|
4316
4316
|
return;
|
|
4317
|
-
await
|
|
4317
|
+
await mkdir14(innerLib, { recursive: true });
|
|
4318
4318
|
for (const name of LEGACY_FLAT_BUNDLE_FILES) {
|
|
4319
4319
|
try {
|
|
4320
|
-
await access(
|
|
4320
|
+
await access(join8(innerLib, name), fsConstants.F_OK);
|
|
4321
4321
|
continue;
|
|
4322
4322
|
} catch {
|
|
4323
4323
|
}
|
|
4324
|
-
await rename3(
|
|
4324
|
+
await rename3(join8(bundleDir, name), join8(innerLib, name)).catch(() => void 0);
|
|
4325
4325
|
}
|
|
4326
4326
|
}
|
|
4327
4327
|
async function writeBinForwarders(layout, activeBundleRoot) {
|
|
@@ -4330,13 +4330,13 @@ async function writeBinForwarders(layout, activeBundleRoot) {
|
|
|
4330
4330
|
...Object.entries(FORWARDER_ALIASES).map(([alias, tool]) => [alias, tool])
|
|
4331
4331
|
];
|
|
4332
4332
|
for (const [name, tool] of entries) {
|
|
4333
|
-
const target =
|
|
4333
|
+
const target = join8(activeBundleRoot, "bin", tool);
|
|
4334
4334
|
try {
|
|
4335
4335
|
await access(target, fsConstants.X_OK);
|
|
4336
4336
|
} catch {
|
|
4337
4337
|
continue;
|
|
4338
4338
|
}
|
|
4339
|
-
await replaceFile(
|
|
4339
|
+
await replaceFile(join8(layout.binDir, name), forwarderScript(tool), 493);
|
|
4340
4340
|
}
|
|
4341
4341
|
}
|
|
4342
4342
|
async function repairInstallForwarders(layout, opts = {}) {
|
|
@@ -4345,7 +4345,7 @@ async function repairInstallForwarders(layout, opts = {}) {
|
|
|
4345
4345
|
const forwarderHealthy = async (name, tool) => {
|
|
4346
4346
|
let current;
|
|
4347
4347
|
try {
|
|
4348
|
-
current = await readFile9(
|
|
4348
|
+
current = await readFile9(join8(layout.binDir, name), "utf8");
|
|
4349
4349
|
} catch {
|
|
4350
4350
|
current = void 0;
|
|
4351
4351
|
}
|
|
@@ -4358,25 +4358,25 @@ async function repairInstallForwarders(layout, opts = {}) {
|
|
|
4358
4358
|
}
|
|
4359
4359
|
if (healthy)
|
|
4360
4360
|
return false;
|
|
4361
|
-
await
|
|
4361
|
+
await mkdir14(layout.binDir, { recursive: true });
|
|
4362
4362
|
await writeBinForwarders(layout, layout.libDir);
|
|
4363
4363
|
opts.logger?.(`[body] self-update: repaired <prefix>/bin forwarders to follow the active-bundle anchor (${layout.libDir})`);
|
|
4364
4364
|
return true;
|
|
4365
4365
|
}
|
|
4366
4366
|
async function rollbackToPreviousRelease(layout, previousReleaseId) {
|
|
4367
|
-
const releaseDir =
|
|
4367
|
+
const releaseDir = join8(layout.releasesRoot, previousReleaseId);
|
|
4368
4368
|
const entrypoint = await resolveBundleEntrypoint(releaseDir);
|
|
4369
4369
|
if (!entrypoint) {
|
|
4370
4370
|
throw new Error(`release ${previousReleaseId} has no runnable CLI entrypoint`);
|
|
4371
4371
|
}
|
|
4372
4372
|
const tempLink = `${layout.libDir}.rollback-${process.pid}`;
|
|
4373
4373
|
await rm5(tempLink, { force: true });
|
|
4374
|
-
await symlink2(
|
|
4374
|
+
await symlink2(join8("beeline-releases", previousReleaseId), tempLink);
|
|
4375
4375
|
await rename3(tempLink, layout.libDir);
|
|
4376
|
-
await fsyncDir(
|
|
4376
|
+
await fsyncDir(dirname10(layout.libDir));
|
|
4377
4377
|
}
|
|
4378
4378
|
function updateAttemptPath(layout) {
|
|
4379
|
-
return
|
|
4379
|
+
return join8(layout.releasesRoot, ".state", "update-attempt.json");
|
|
4380
4380
|
}
|
|
4381
4381
|
async function readUpdateAttempt(layout) {
|
|
4382
4382
|
try {
|
|
@@ -4390,10 +4390,10 @@ async function readUpdateAttempt(layout) {
|
|
|
4390
4390
|
}
|
|
4391
4391
|
}
|
|
4392
4392
|
async function writeUpdateAttempt(layout, record2) {
|
|
4393
|
-
await
|
|
4393
|
+
await mkdir14(join8(layout.releasesRoot, ".state"), { recursive: true });
|
|
4394
4394
|
const path = updateAttemptPath(layout);
|
|
4395
4395
|
const staged = `${path}.${process.pid}.tmp`;
|
|
4396
|
-
await
|
|
4396
|
+
await writeFile10(staged, `${JSON.stringify(record2, null, 2)}
|
|
4397
4397
|
`, { mode: 384 });
|
|
4398
4398
|
await rename3(staged, path);
|
|
4399
4399
|
}
|
|
@@ -4655,8 +4655,8 @@ var init_self_update = __esm({
|
|
|
4655
4655
|
});
|
|
4656
4656
|
|
|
4657
4657
|
// apps/body/dist/cli.js
|
|
4658
|
-
import { dirname as
|
|
4659
|
-
import { readFile as readFile14, unlink as unlink5, writeFile as
|
|
4658
|
+
import { dirname as dirname16, resolve as resolve29 } from "node:path";
|
|
4659
|
+
import { readFile as readFile14, unlink as unlink5, writeFile as writeFile16 } from "node:fs/promises";
|
|
4660
4660
|
import { stdin as stdin4, stdout as stdout5 } from "node:process";
|
|
4661
4661
|
|
|
4662
4662
|
// node_modules/@clack/core/dist/index.mjs
|
|
@@ -6663,20 +6663,6 @@ var ACCESS_NOTICE_WINDOW_MS = 10 * 6e4;
|
|
|
6663
6663
|
var DEFAULT_ACCESS_POLICY = DEFAULT_AGENT_ACCESS_POLICY;
|
|
6664
6664
|
var LEGACY_ACCESS_POLICY = "everyone";
|
|
6665
6665
|
var ACCESS_REFUSAL_WINDOW_MS = 60 * 60 * 1e3;
|
|
6666
|
-
function isSenderPermitted(policy, senderPubkey, ownerPubkey, allowlist = void 0) {
|
|
6667
|
-
if (!senderPubkey)
|
|
6668
|
-
return false;
|
|
6669
|
-
switch (policy) {
|
|
6670
|
-
case "everyone":
|
|
6671
|
-
return true;
|
|
6672
|
-
case "creator":
|
|
6673
|
-
return Boolean(ownerPubkey) && senderPubkey === ownerPubkey;
|
|
6674
|
-
case "allowlist":
|
|
6675
|
-
return Boolean(allowlist?.includes(senderPubkey));
|
|
6676
|
-
default:
|
|
6677
|
-
return false;
|
|
6678
|
-
}
|
|
6679
|
-
}
|
|
6680
6666
|
|
|
6681
6667
|
// apps/body/dist/model-catalog.js
|
|
6682
6668
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
@@ -6912,8 +6898,13 @@ function agentMessageRuns(updates, agentLabel) {
|
|
|
6912
6898
|
let current = "";
|
|
6913
6899
|
let lastWasText = false;
|
|
6914
6900
|
for (const u4 of updates) {
|
|
6901
|
+
const isToolCall = u4.update.sessionUpdate === "tool_call";
|
|
6915
6902
|
const delta = normalizeStreamDelta(agentMessageChunkText(u4.update), agentLabel);
|
|
6916
6903
|
if (!delta) {
|
|
6904
|
+
if (isToolCall && current) {
|
|
6905
|
+
runs.push(current);
|
|
6906
|
+
current = "";
|
|
6907
|
+
}
|
|
6917
6908
|
lastWasText = false;
|
|
6918
6909
|
continue;
|
|
6919
6910
|
}
|
|
@@ -6934,9 +6925,6 @@ function agentMessageRuns(updates, agentLabel) {
|
|
|
6934
6925
|
return true;
|
|
6935
6926
|
});
|
|
6936
6927
|
}
|
|
6937
|
-
function joinAgentMessageChunks(updates, agentLabel) {
|
|
6938
|
-
return agentMessageRuns(updates, agentLabel).join("\n\n");
|
|
6939
|
-
}
|
|
6940
6928
|
var RETRY_NARRATION_FRAGMENTS = [
|
|
6941
6929
|
// `Retrying (attempt 2/3, waiting 4s)` — the pi/ox-alpha shape.
|
|
6942
6930
|
/retrying\s*\((?:attempt|try)\s*\d+\s*\/\s*\d+(?:,\s*(?:waiting|backoff)\s*\d+(?:\.\d+)?s)?\)/gi,
|
|
@@ -7065,6 +7053,7 @@ function toolCallEntries(updates) {
|
|
|
7065
7053
|
...typeof update.title === "string" ? { title: update.title } : {},
|
|
7066
7054
|
...typeof update.kind === "string" ? { kind: update.kind } : {},
|
|
7067
7055
|
...typeof update.status === "string" ? { status: update.status } : {},
|
|
7056
|
+
...sessionUpdate === "tool_result" ? { resultReceived: true } : {},
|
|
7068
7057
|
..."rawInput" in update ? { rawInput: update.rawInput } : {},
|
|
7069
7058
|
..."content" in update ? { content: update.content } : {},
|
|
7070
7059
|
..."rawOutput" in update ? { rawOutput: update.rawOutput } : {},
|
|
@@ -7327,8 +7316,10 @@ var AcpClient = class extends EventEmitter {
|
|
|
7327
7316
|
onToolCalls?.(toolCallEntries(updates));
|
|
7328
7317
|
if (onChunk) {
|
|
7329
7318
|
const delta = agentMessageChunkText(u4.update);
|
|
7330
|
-
if (delta)
|
|
7331
|
-
|
|
7319
|
+
if (delta) {
|
|
7320
|
+
const runs = agentMessageRuns(updates, this.agentLabel);
|
|
7321
|
+
onChunk(delta, runs.join("\n\n"), runs.at(-1), runs);
|
|
7322
|
+
}
|
|
7332
7323
|
}
|
|
7333
7324
|
};
|
|
7334
7325
|
this.on("session/update", onUpdate);
|
|
@@ -8124,6 +8115,40 @@ async function syncAgentModelCatalog(input) {
|
|
|
8124
8115
|
}
|
|
8125
8116
|
}
|
|
8126
8117
|
|
|
8118
|
+
// packages/api-contract/dist/daemon-operations.js
|
|
8119
|
+
function isAgentCommand(value) {
|
|
8120
|
+
if (!value || typeof value !== "object")
|
|
8121
|
+
return false;
|
|
8122
|
+
const c2 = value;
|
|
8123
|
+
if (![
|
|
8124
|
+
"id",
|
|
8125
|
+
"roomId",
|
|
8126
|
+
"agentId",
|
|
8127
|
+
"sourceMessageId",
|
|
8128
|
+
"turnRequestId",
|
|
8129
|
+
"rootCommandId",
|
|
8130
|
+
"rootSourceMessageId",
|
|
8131
|
+
"reason"
|
|
8132
|
+
].every((key) => typeof c2[key] === "string" && c2[key].length > 0))
|
|
8133
|
+
return false;
|
|
8134
|
+
if (!["input", "resume", "stop"].includes(String(c2.action)) || !Number.isInteger(c2.agentDepth) || Number(c2.agentDepth) < 0 || Number(c2.agentDepth) > 3)
|
|
8135
|
+
return false;
|
|
8136
|
+
const source = c2.source;
|
|
8137
|
+
return Boolean(source && typeof source.id === "string" && typeof source.authorId === "string" && typeof source.body === "string" && typeof source.createdAt === "number" && Array.isArray(source.attachments) && Array.isArray(source.mentionIds));
|
|
8138
|
+
}
|
|
8139
|
+
|
|
8140
|
+
// packages/api-contract/dist/system-events.js
|
|
8141
|
+
var SERVER_EVENT_KINDS = [
|
|
8142
|
+
"joined",
|
|
8143
|
+
"schedule-ran",
|
|
8144
|
+
"corner-opened",
|
|
8145
|
+
"check-passed",
|
|
8146
|
+
"check-failed",
|
|
8147
|
+
"merged",
|
|
8148
|
+
"grant-decided",
|
|
8149
|
+
"turn-cancelled"
|
|
8150
|
+
];
|
|
8151
|
+
|
|
8127
8152
|
// apps/body/dist/daemon-api-client.js
|
|
8128
8153
|
import { resolve as resolve7 } from "node:path";
|
|
8129
8154
|
import { randomUUID } from "node:crypto";
|
|
@@ -16905,20 +16930,6 @@ var DaemonApiError = class extends Error {
|
|
|
16905
16930
|
}
|
|
16906
16931
|
};
|
|
16907
16932
|
var AGENT_REMOVED_CODE = "agent_removed";
|
|
16908
|
-
function laterInboxCursor(left, right) {
|
|
16909
|
-
if (!left)
|
|
16910
|
-
return right;
|
|
16911
|
-
if (!right)
|
|
16912
|
-
return left;
|
|
16913
|
-
const leftMatch = left.match(/^(\d+),([0-9a-f]{64})$/);
|
|
16914
|
-
const rightMatch = right.match(/^(\d+),([0-9a-f]{64})$/);
|
|
16915
|
-
if (!leftMatch || !rightMatch)
|
|
16916
|
-
return right;
|
|
16917
|
-
const timeOrder = BigInt(leftMatch[1]) - BigInt(rightMatch[1]);
|
|
16918
|
-
if (timeOrder !== 0n)
|
|
16919
|
-
return timeOrder > 0n ? left : right;
|
|
16920
|
-
return leftMatch[2] >= rightMatch[2] ? left : right;
|
|
16921
|
-
}
|
|
16922
16933
|
function isAgentRemovedError(error) {
|
|
16923
16934
|
return error instanceof DaemonApiError && error.status === 403 && error.code === AGENT_REMOVED_CODE;
|
|
16924
16935
|
}
|
|
@@ -16958,20 +16969,23 @@ var DaemonApiClient = class {
|
|
|
16958
16969
|
return { baseUrl: this.baseUrl, daemonToken: this.daemonToken, agentId: this.agentId };
|
|
16959
16970
|
}
|
|
16960
16971
|
/** Add one Room to this agent's shared live socket. */
|
|
16961
|
-
liveSubscribe(roomId, cursor3, onItems, onState, presence) {
|
|
16972
|
+
liveSubscribe(roomId, cursor3, onItems, onState, presence, onCommands) {
|
|
16962
16973
|
const existing = this.liveRooms.get(roomId);
|
|
16963
16974
|
if (existing) {
|
|
16964
16975
|
existing.cursor = cursor3 ?? existing.cursor;
|
|
16965
16976
|
existing.onItems = onItems ?? existing.onItems;
|
|
16966
16977
|
existing.onState = onState ?? existing.onState;
|
|
16967
16978
|
existing.presence = presence ?? existing.presence;
|
|
16979
|
+
existing.onCommands = onCommands ?? existing.onCommands;
|
|
16968
16980
|
} else {
|
|
16969
16981
|
this.liveRooms.set(roomId, {
|
|
16970
16982
|
...cursor3 ? { cursor: cursor3 } : {},
|
|
16971
16983
|
pushedIds: /* @__PURE__ */ new Set(),
|
|
16984
|
+
pushedCommandIds: /* @__PURE__ */ new Set(),
|
|
16972
16985
|
...onItems ? { onItems } : {},
|
|
16973
16986
|
...onState ? { onState } : {},
|
|
16974
|
-
...presence ? { presence } : {}
|
|
16987
|
+
...presence ? { presence } : {},
|
|
16988
|
+
...onCommands ? { onCommands } : {}
|
|
16975
16989
|
});
|
|
16976
16990
|
}
|
|
16977
16991
|
this.ensureLiveSocket();
|
|
@@ -17041,6 +17055,22 @@ var DaemonApiClient = class {
|
|
|
17041
17055
|
});
|
|
17042
17056
|
return;
|
|
17043
17057
|
}
|
|
17058
|
+
if (event.type === "commands" && typeof event.roomId === "string" && event.commandProtocol === 1 && Array.isArray(event.commands)) {
|
|
17059
|
+
const room2 = this.liveRooms.get(event.roomId);
|
|
17060
|
+
if (!room2)
|
|
17061
|
+
return;
|
|
17062
|
+
const commands = event.commands.filter((command) => {
|
|
17063
|
+
if (!isAgentCommand(command) || command.roomId !== event.roomId || command.agentId !== this.agentId || room2.pushedCommandIds.has(command.id))
|
|
17064
|
+
return false;
|
|
17065
|
+
room2.pushedCommandIds.add(command.id);
|
|
17066
|
+
return true;
|
|
17067
|
+
});
|
|
17068
|
+
while (room2.pushedCommandIds.size > 1e4)
|
|
17069
|
+
room2.pushedCommandIds.delete(room2.pushedCommandIds.values().next().value);
|
|
17070
|
+
if (commands.length)
|
|
17071
|
+
room2.onCommands?.(commands);
|
|
17072
|
+
return;
|
|
17073
|
+
}
|
|
17044
17074
|
if (event.type !== "inbox" || typeof event.roomId !== "string" || !Array.isArray(event.items))
|
|
17045
17075
|
return;
|
|
17046
17076
|
const room = this.liveRooms.get(event.roomId);
|
|
@@ -17134,10 +17164,10 @@ async function activateDaemonTransport(path, fetchImpl = fetch) {
|
|
|
17134
17164
|
|
|
17135
17165
|
// apps/body/dist/room-runtime.js
|
|
17136
17166
|
import { execFile as execFile5 } from "node:child_process";
|
|
17137
|
-
import { createHash as
|
|
17167
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
17138
17168
|
import { existsSync as existsSync4, mkdirSync } from "node:fs";
|
|
17139
|
-
import { mkdir as
|
|
17140
|
-
import { dirname as
|
|
17169
|
+
import { mkdir as mkdir12, rm as rm4 } from "node:fs/promises";
|
|
17170
|
+
import { dirname as dirname7, resolve as resolve19 } from "node:path";
|
|
17141
17171
|
import { promisify as promisify4 } from "node:util";
|
|
17142
17172
|
|
|
17143
17173
|
// apps/body/dist/grant-runner.js
|
|
@@ -17190,18 +17220,6 @@ function commandGrantMatches(rule, requested) {
|
|
|
17190
17220
|
return rule.argv.every((word, index) => requested[index] === word);
|
|
17191
17221
|
}
|
|
17192
17222
|
var DECISION_LINE = new RegExp(`^(.+?) (approved once|approved|declined) (${AGENT_GRANT_KINDS.join("|")}) (.+)$`, "s");
|
|
17193
|
-
function parseGrantDecisionLine(body) {
|
|
17194
|
-
const match = DECISION_LINE.exec(body);
|
|
17195
|
-
if (!match)
|
|
17196
|
-
return void 0;
|
|
17197
|
-
const decision2 = match[2] === "approved once" ? "once" : match[2] === "approved" ? "always" : "deny";
|
|
17198
|
-
return {
|
|
17199
|
-
deciderName: match[1],
|
|
17200
|
-
decision: decision2,
|
|
17201
|
-
kind: match[3],
|
|
17202
|
-
target: match[4]
|
|
17203
|
-
};
|
|
17204
|
-
}
|
|
17205
17223
|
var AGENT_GRANT_ESCALATION_REASONS = {
|
|
17206
17224
|
"unseen-script": "it runs a script whose contents nobody has read",
|
|
17207
17225
|
credential: "it names a credential or environment file"
|
|
@@ -17781,7 +17799,9 @@ var GrantCommandRunner = class {
|
|
|
17781
17799
|
if (!room)
|
|
17782
17800
|
throw new Error("this daemon is not serving that Room");
|
|
17783
17801
|
const argv = validateGrantArgv(input.argv);
|
|
17784
|
-
const live = await this.options.api.execute("listAgentGrants", {
|
|
17802
|
+
const live = await this.options.api.execute("listAgentGrants", {
|
|
17803
|
+
agentId: this.options.agentId
|
|
17804
|
+
});
|
|
17785
17805
|
const match = matchCommandGrant(live.grants, room.workspaceId, argv);
|
|
17786
17806
|
if (!match) {
|
|
17787
17807
|
throw new Error(`no approved command grant matches: ${argv.join(" ")}. Ask with request_grant kind=command first.`);
|
|
@@ -17844,6 +17864,7 @@ ${ROOM_WRITE_REFUSED_NOTE}` : outcome.output, secrets), cap);
|
|
|
17844
17864
|
agentId: this.options.agentId,
|
|
17845
17865
|
roomId: input.roomId,
|
|
17846
17866
|
requestId: turn?.requestId ?? `grant:${grant.grantId}`,
|
|
17867
|
+
generationId: turn?.generationId,
|
|
17847
17868
|
activity: [
|
|
17848
17869
|
{
|
|
17849
17870
|
kind: "tool",
|
|
@@ -17898,7 +17919,10 @@ function scriptCandidates(cwd, scratch, argv) {
|
|
|
17898
17919
|
const argument = interpreterScriptArgument(argv);
|
|
17899
17920
|
if (!argument)
|
|
17900
17921
|
return [];
|
|
17901
|
-
const paths = [
|
|
17922
|
+
const paths = [
|
|
17923
|
+
resolve10(cwd, argument.path),
|
|
17924
|
+
...scratch ? [resolve10(scratch, argument.path)] : []
|
|
17925
|
+
];
|
|
17902
17926
|
return [...new Set(paths)];
|
|
17903
17927
|
}
|
|
17904
17928
|
function roomSandboxCommand(policy, cwd, argv) {
|
|
@@ -17977,42 +18001,167 @@ var GrantRunnerServer = class {
|
|
|
17977
18001
|
}
|
|
17978
18002
|
};
|
|
17979
18003
|
|
|
18004
|
+
// apps/body/dist/server-command-intake.js
|
|
18005
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
18006
|
+
import { mkdir as mkdir3, writeFile as writeFile4 } from "node:fs/promises";
|
|
18007
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
18008
|
+
import { dirname as dirname5, join as join3 } from "node:path";
|
|
18009
|
+
var CommandExecutionContext = class {
|
|
18010
|
+
generationId = randomUUID2();
|
|
18011
|
+
path;
|
|
18012
|
+
current;
|
|
18013
|
+
constructor(root) {
|
|
18014
|
+
this.path = join3(root ?? tmpdir2(), `beeline-command-${this.generationId}.json`);
|
|
18015
|
+
}
|
|
18016
|
+
async enter(command) {
|
|
18017
|
+
this.current = command;
|
|
18018
|
+
await mkdir3(dirname5(this.path), { recursive: true });
|
|
18019
|
+
await writeFile4(this.path, JSON.stringify({
|
|
18020
|
+
roomId: command.roomId,
|
|
18021
|
+
requestId: command.turnRequestId,
|
|
18022
|
+
generationId: this.generationId
|
|
18023
|
+
}), { mode: 384 });
|
|
18024
|
+
}
|
|
18025
|
+
async leave() {
|
|
18026
|
+
this.current = void 0;
|
|
18027
|
+
await writeFile4(this.path, "{}", { mode: 384 });
|
|
18028
|
+
}
|
|
18029
|
+
bind(api) {
|
|
18030
|
+
return new Proxy(api, {
|
|
18031
|
+
get: (target, key) => {
|
|
18032
|
+
if (key === "execute")
|
|
18033
|
+
return (name, input) => {
|
|
18034
|
+
const turn = this.current;
|
|
18035
|
+
return target.execute(name, {
|
|
18036
|
+
...input,
|
|
18037
|
+
...turn && (input.roomId === turn.roomId || input.cornerId === turn.roomId) ? {
|
|
18038
|
+
generationId: this.generationId,
|
|
18039
|
+
requestId: input.requestId ?? input.turnId ?? turn.turnRequestId
|
|
18040
|
+
} : {}
|
|
18041
|
+
});
|
|
18042
|
+
};
|
|
18043
|
+
const value = Reflect.get(target, key);
|
|
18044
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
18045
|
+
}
|
|
18046
|
+
});
|
|
18047
|
+
}
|
|
18048
|
+
};
|
|
18049
|
+
function validateServerCommand(command, roomId, agentId) {
|
|
18050
|
+
if (!isAgentCommand(command) || command.roomId !== roomId || command.agentId !== agentId)
|
|
18051
|
+
throw new Error("invalid server command");
|
|
18052
|
+
}
|
|
18053
|
+
async function runServerCommandIntake(options) {
|
|
18054
|
+
const { api, roomId, agentId, context, signal } = options;
|
|
18055
|
+
const first = await api.execute("getAgentCommands", { roomId });
|
|
18056
|
+
if (first?.commandProtocol !== 1)
|
|
18057
|
+
throw new Error("server command protocol 1 is required; refusing intake");
|
|
18058
|
+
let busy;
|
|
18059
|
+
let wake;
|
|
18060
|
+
const pending = new Map(first.commands.map((command) => [command.id, command]));
|
|
18061
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
18062
|
+
const notify2 = (commands = []) => {
|
|
18063
|
+
for (const command of commands)
|
|
18064
|
+
if (!claimed.has(command.id))
|
|
18065
|
+
pending.set(command.id, command);
|
|
18066
|
+
wake?.(false);
|
|
18067
|
+
};
|
|
18068
|
+
options.onWake?.(notify2);
|
|
18069
|
+
const off = api.liveSubscribe?.(roomId, void 0, void 0, (connected) => {
|
|
18070
|
+
if (!connected)
|
|
18071
|
+
wake?.(true);
|
|
18072
|
+
}, options.presence, notify2);
|
|
18073
|
+
try {
|
|
18074
|
+
while (!signal?.aborted) {
|
|
18075
|
+
if (options.closed && await options.closed())
|
|
18076
|
+
return;
|
|
18077
|
+
for (const command of [...pending.values()]) {
|
|
18078
|
+
validateServerCommand(command, roomId, agentId);
|
|
18079
|
+
if (busy && command.action !== "stop")
|
|
18080
|
+
continue;
|
|
18081
|
+
pending.delete(command.id);
|
|
18082
|
+
try {
|
|
18083
|
+
await api.execute("claimAgentCommand", {
|
|
18084
|
+
roomId,
|
|
18085
|
+
commandId: command.id,
|
|
18086
|
+
generationId: context.generationId
|
|
18087
|
+
});
|
|
18088
|
+
} catch (error) {
|
|
18089
|
+
options.onError?.(error);
|
|
18090
|
+
continue;
|
|
18091
|
+
}
|
|
18092
|
+
claimed.add(command.id);
|
|
18093
|
+
if (command.action === "stop") {
|
|
18094
|
+
options.stop(command.turnRequestId);
|
|
18095
|
+
await api.execute("acknowledgeAgentCommand", {
|
|
18096
|
+
roomId,
|
|
18097
|
+
commandId: command.id,
|
|
18098
|
+
generationId: context.generationId
|
|
18099
|
+
});
|
|
18100
|
+
} else {
|
|
18101
|
+
await context.enter(command);
|
|
18102
|
+
busy = options.run(command).catch((error) => {
|
|
18103
|
+
claimed.delete(command.id);
|
|
18104
|
+
options.onError?.(error);
|
|
18105
|
+
}).finally(async () => {
|
|
18106
|
+
await context.leave();
|
|
18107
|
+
busy = void 0;
|
|
18108
|
+
notify2();
|
|
18109
|
+
});
|
|
18110
|
+
}
|
|
18111
|
+
}
|
|
18112
|
+
options.onPoll?.();
|
|
18113
|
+
const reconcile = await new Promise((resolve30) => {
|
|
18114
|
+
const done = (needed) => {
|
|
18115
|
+
if (timer)
|
|
18116
|
+
clearTimeout(timer);
|
|
18117
|
+
signal?.removeEventListener("abort", aborted);
|
|
18118
|
+
wake = void 0;
|
|
18119
|
+
resolve30(needed);
|
|
18120
|
+
};
|
|
18121
|
+
const aborted = () => done(false);
|
|
18122
|
+
wake = done;
|
|
18123
|
+
const timer = setTimeout(() => done(true), options.pollMs ?? 1e3);
|
|
18124
|
+
signal?.addEventListener("abort", aborted, { once: true });
|
|
18125
|
+
if (pending.size && !busy)
|
|
18126
|
+
done(false);
|
|
18127
|
+
});
|
|
18128
|
+
if (signal?.aborted)
|
|
18129
|
+
break;
|
|
18130
|
+
if (reconcile) {
|
|
18131
|
+
const page = await api.execute("getAgentCommands", { roomId });
|
|
18132
|
+
if (page.commandProtocol !== 1)
|
|
18133
|
+
throw new Error("server command protocol changed; refusing intake");
|
|
18134
|
+
notify2(page.commands);
|
|
18135
|
+
}
|
|
18136
|
+
}
|
|
18137
|
+
} finally {
|
|
18138
|
+
off?.();
|
|
18139
|
+
options.onWake?.(void 0);
|
|
18140
|
+
if (context.current)
|
|
18141
|
+
options.stop(context.current.turnRequestId);
|
|
18142
|
+
await busy;
|
|
18143
|
+
}
|
|
18144
|
+
}
|
|
18145
|
+
|
|
17980
18146
|
// apps/body/dist/monolith-corner-turn.js
|
|
17981
18147
|
import { execFile as execFile4 } from "node:child_process";
|
|
17982
|
-
import {
|
|
17983
|
-
import {
|
|
17984
|
-
import {
|
|
18148
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
18149
|
+
import { mkdir as mkdir11 } from "node:fs/promises";
|
|
18150
|
+
import { homedir as homedir7 } from "node:os";
|
|
18151
|
+
import { join as join7 } from "node:path";
|
|
17985
18152
|
import { promisify as promisify3 } from "node:util";
|
|
17986
18153
|
|
|
17987
18154
|
// apps/body/dist/agent-home.js
|
|
17988
18155
|
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
|
|
17989
|
-
import { createHash as createHash3, randomUUID as
|
|
17990
|
-
import { chmod as chmod2, copyFile, lstat, mkdir as
|
|
18156
|
+
import { createHash as createHash3, randomUUID as randomUUID3 } from "node:crypto";
|
|
18157
|
+
import { chmod as chmod2, copyFile, lstat, mkdir as mkdir5, readFile as readFile6, readdir as readdir2, realpath, rename as rename2, rm as rm2, symlink, unlink, writeFile as writeFile6 } from "node:fs/promises";
|
|
17991
18158
|
import { homedir as homedir5 } from "node:os";
|
|
17992
|
-
import { basename as basename3, dirname as
|
|
18159
|
+
import { basename as basename3, dirname as dirname6, join as join4, relative as relative2, resolve as resolve13, sep } from "node:path";
|
|
17993
18160
|
|
|
17994
18161
|
// apps/body/dist/beeline-skill.js
|
|
17995
18162
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
17996
18163
|
import { resolve as resolve11 } from "node:path";
|
|
17997
18164
|
|
|
17998
|
-
// packages/api-contract/dist/system-events.js
|
|
17999
|
-
var SERVER_EVENT_KINDS = [
|
|
18000
|
-
"joined",
|
|
18001
|
-
"schedule-ran",
|
|
18002
|
-
"corner-opened",
|
|
18003
|
-
"check-passed",
|
|
18004
|
-
"check-failed",
|
|
18005
|
-
"merged",
|
|
18006
|
-
"grant-decided"
|
|
18007
|
-
];
|
|
18008
|
-
function isServerEventKind(value) {
|
|
18009
|
-
return SERVER_EVENT_KINDS.includes(value);
|
|
18010
|
-
}
|
|
18011
|
-
var RESUME_KINDS = ["grant-decided"];
|
|
18012
|
-
function isResumeKind(value) {
|
|
18013
|
-
return RESUME_KINDS.includes(value);
|
|
18014
|
-
}
|
|
18015
|
-
|
|
18016
18165
|
// packages/api-contract/dist/agent-pairing-code.js
|
|
18017
18166
|
var CURRENT_AGENT_PAIRING_CODE = /^[0-9A-F]{8}-[0-9A-F]{8}$/;
|
|
18018
18167
|
var LEGACY_AGENT_PAIRING_CODE = /^BUZZ-(?:[0-9A-F]{8}-[0-9A-F]{8}|[A-HJ-NP-Z2-9]{4}-[A-HJ-NP-Z2-9]{4})$/;
|
|
@@ -18110,7 +18259,7 @@ var SQUIRE_GOVERNED_TOOLS = [
|
|
|
18110
18259
|
var SQUIRE_GOVERNED_TOOL_SET = new Set(SQUIRE_GOVERNED_TOOLS);
|
|
18111
18260
|
|
|
18112
18261
|
// apps/body/dist/openrouter-routing.js
|
|
18113
|
-
import { mkdir as
|
|
18262
|
+
import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile5 } from "node:fs/promises";
|
|
18114
18263
|
import { resolve as resolve12 } from "node:path";
|
|
18115
18264
|
var OPENROUTER_ENDPOINTS_BASE_URL = "https://openrouter.ai/api/v1/models";
|
|
18116
18265
|
var OPENROUTER_COMPLETIONS_URL = "https://openrouter.ai/api/v1/chat/completions";
|
|
@@ -18240,8 +18389,8 @@ async function readCache(cacheDir, model) {
|
|
|
18240
18389
|
}
|
|
18241
18390
|
}
|
|
18242
18391
|
async function writeCache(cacheDir, value) {
|
|
18243
|
-
await
|
|
18244
|
-
await
|
|
18392
|
+
await mkdir4(cacheDir, { recursive: true, mode: 448 });
|
|
18393
|
+
await writeFile5(cachePath(cacheDir, value.model), `${JSON.stringify(value, null, 2)}
|
|
18245
18394
|
`, {
|
|
18246
18395
|
mode: 384
|
|
18247
18396
|
});
|
|
@@ -18275,8 +18424,8 @@ async function readProbeCache(cacheDir, model) {
|
|
|
18275
18424
|
}
|
|
18276
18425
|
}
|
|
18277
18426
|
async function writeProbeCache(cacheDir, value) {
|
|
18278
|
-
await
|
|
18279
|
-
await
|
|
18427
|
+
await mkdir4(cacheDir, { recursive: true, mode: 448 });
|
|
18428
|
+
await writeFile5(probeCachePath(cacheDir, value.model), `${JSON.stringify(value, null, 2)}
|
|
18280
18429
|
`, {
|
|
18281
18430
|
mode: 384
|
|
18282
18431
|
});
|
|
@@ -18732,14 +18881,14 @@ async function prepareRoomAgentHome(input) {
|
|
|
18732
18881
|
const root = resolve13(input.root);
|
|
18733
18882
|
const operatorHome = input.operatorHome ?? homedir5();
|
|
18734
18883
|
try {
|
|
18735
|
-
await
|
|
18884
|
+
await mkdir5(root, { recursive: true, mode: 448 });
|
|
18736
18885
|
const rootStats = await lstat(root);
|
|
18737
18886
|
if (!rootStats.isDirectory() || rootStats.isSymbolicLink()) {
|
|
18738
18887
|
throw new AgentHomeSecurityError(`agent home root is not an ordinary directory: ${root}`);
|
|
18739
18888
|
}
|
|
18740
18889
|
for (const subdir of HOME_SUBDIRS) {
|
|
18741
18890
|
const path = resolve13(root, subdir);
|
|
18742
|
-
await
|
|
18891
|
+
await mkdir5(path, { recursive: true, mode: 448 });
|
|
18743
18892
|
await assertRealContainedDirectory(path, root);
|
|
18744
18893
|
}
|
|
18745
18894
|
} catch (error) {
|
|
@@ -18791,7 +18940,7 @@ async function provisionAgentSkillsAndMcp(root, operatorHome, skillReleaseId, fa
|
|
|
18791
18940
|
}
|
|
18792
18941
|
try {
|
|
18793
18942
|
const gooseConfigDir = resolve13(root, "goose", "config");
|
|
18794
|
-
await
|
|
18943
|
+
await mkdir5(gooseConfigDir, { recursive: true, mode: 448 });
|
|
18795
18944
|
for (const name of GOOSE_SHARED_CONFIG_FILES) {
|
|
18796
18945
|
const source = resolve13(operatorHome, ".config", "goose", name);
|
|
18797
18946
|
const target = resolve13(gooseConfigDir, name);
|
|
@@ -18903,18 +19052,18 @@ function filteredHarnessMcpToml(source) {
|
|
|
18903
19052
|
return extractTomlSections(source, ["mcp_servers"], excluded);
|
|
18904
19053
|
}
|
|
18905
19054
|
async function provisionManagedSkillsDir(target, managedSkills, sharedSkills, optionalShares) {
|
|
18906
|
-
const parent =
|
|
18907
|
-
await assertRealContainedDirectory(parent,
|
|
19055
|
+
const parent = dirname6(target);
|
|
19056
|
+
await assertRealContainedDirectory(parent, dirname6(parent));
|
|
18908
19057
|
const plan = await planManagedSkills(managedSkills, sharedSkills, optionalShares);
|
|
18909
19058
|
if (await materializedSkillManifest(target) === plan.manifest)
|
|
18910
19059
|
return;
|
|
18911
|
-
const staged = resolve13(parent, `.skills.${process.pid}.${
|
|
18912
|
-
await
|
|
19060
|
+
const staged = resolve13(parent, `.skills.${process.pid}.${randomUUID3()}.tmp`);
|
|
19061
|
+
await mkdir5(staged, { mode: 448 });
|
|
18913
19062
|
try {
|
|
18914
19063
|
for (const entry of plan.entries) {
|
|
18915
19064
|
if (entry.kind === "managed") {
|
|
18916
19065
|
const skillDir = resolve13(staged, entry.name);
|
|
18917
|
-
await
|
|
19066
|
+
await mkdir5(skillDir, { recursive: true });
|
|
18918
19067
|
await writeIsolatedHarnessFile(resolve13(skillDir, "SKILL.md"), entry.content);
|
|
18919
19068
|
} else {
|
|
18920
19069
|
await copySafeSkillTree(entry.source, resolve13(staged, entry.name), entry.source);
|
|
@@ -18944,8 +19093,8 @@ async function planManagedSkills(managedSkills, sharedSkills, optionalShares) {
|
|
|
18944
19093
|
try {
|
|
18945
19094
|
const tree = [];
|
|
18946
19095
|
await walkSafeSkillTree(shared.source, shared.source, {
|
|
18947
|
-
directory: async (rel) => void tree.push(`d ${
|
|
18948
|
-
file: async (rel, realPath) => void tree.push(`f ${
|
|
19096
|
+
directory: async (rel) => void tree.push(`d ${join4(shared.name, rel)}`),
|
|
19097
|
+
file: async (rel, realPath) => void tree.push(`f ${join4(shared.name, rel)} ${sha2563(await readFile6(realPath))}`)
|
|
18949
19098
|
});
|
|
18950
19099
|
entries.push({ kind: "shared", name: shared.name, source: shared.source });
|
|
18951
19100
|
lines.push(...tree);
|
|
@@ -18965,7 +19114,7 @@ async function materializedSkillManifest(target) {
|
|
|
18965
19114
|
const visit = async (directory, prefix) => {
|
|
18966
19115
|
for (const entry of await readdir2(directory)) {
|
|
18967
19116
|
const path = resolve13(directory, entry);
|
|
18968
|
-
const rel = prefix ?
|
|
19117
|
+
const rel = prefix ? join4(prefix, entry) : entry;
|
|
18969
19118
|
const entryStats = await lstat(path);
|
|
18970
19119
|
if (entryStats.isSymbolicLink())
|
|
18971
19120
|
return false;
|
|
@@ -19133,7 +19282,7 @@ async function walkSafeSkillTree(source, sourceRoot, visitor, rel = "") {
|
|
|
19133
19282
|
for (const entry of await readdir2(resolvedSource)) {
|
|
19134
19283
|
if (entry === "." || entry === "..")
|
|
19135
19284
|
throw new Error("invalid shared skill entry");
|
|
19136
|
-
await walkSafeSkillTree(resolve13(source, entry), sourceRoot, visitor, rel ?
|
|
19285
|
+
await walkSafeSkillTree(resolve13(source, entry), sourceRoot, visitor, rel ? join4(rel, entry) : entry);
|
|
19137
19286
|
}
|
|
19138
19287
|
return;
|
|
19139
19288
|
}
|
|
@@ -19145,7 +19294,7 @@ async function walkSafeSkillTree(source, sourceRoot, visitor, rel = "") {
|
|
|
19145
19294
|
async function copySafeSkillTree(source, target, sourceRoot) {
|
|
19146
19295
|
await walkSafeSkillTree(source, sourceRoot, {
|
|
19147
19296
|
directory: async (rel) => {
|
|
19148
|
-
await
|
|
19297
|
+
await mkdir5(resolve13(target, rel), { mode: 448 });
|
|
19149
19298
|
},
|
|
19150
19299
|
file: async (rel, realPath) => {
|
|
19151
19300
|
const destination = resolve13(target, rel);
|
|
@@ -19155,14 +19304,14 @@ async function copySafeSkillTree(source, target, sourceRoot) {
|
|
|
19155
19304
|
});
|
|
19156
19305
|
}
|
|
19157
19306
|
async function writeIsolatedHarnessFile(path, content) {
|
|
19158
|
-
const parent =
|
|
19307
|
+
const parent = dirname6(path);
|
|
19159
19308
|
const parentStats = await lstat(parent);
|
|
19160
19309
|
if (!parentStats.isDirectory() || parentStats.isSymbolicLink()) {
|
|
19161
19310
|
throw new Error(`isolated harness parent is not a real directory: ${parent}`);
|
|
19162
19311
|
}
|
|
19163
|
-
const temporary = resolve13(parent, `.${basename3(path)}.${process.pid}.${
|
|
19312
|
+
const temporary = resolve13(parent, `.${basename3(path)}.${process.pid}.${randomUUID3()}.tmp`);
|
|
19164
19313
|
try {
|
|
19165
|
-
await
|
|
19314
|
+
await writeFile6(temporary, content, { mode: 384, flag: "wx" });
|
|
19166
19315
|
await chmod2(temporary, 384);
|
|
19167
19316
|
await rename2(temporary, path);
|
|
19168
19317
|
} finally {
|
|
@@ -19205,8 +19354,8 @@ function harnessStateDirsFromEnv(env) {
|
|
|
19205
19354
|
}
|
|
19206
19355
|
|
|
19207
19356
|
// apps/body/dist/attachment-delivery.js
|
|
19208
|
-
import { mkdir as
|
|
19209
|
-
import { basename as basename4, extname, join as
|
|
19357
|
+
import { mkdir as mkdir6, writeFile as writeFile7 } from "node:fs/promises";
|
|
19358
|
+
import { basename as basename4, extname, join as join5 } from "node:path";
|
|
19210
19359
|
var MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
|
|
19211
19360
|
var MEDIA_TTL_HOURS = 24;
|
|
19212
19361
|
var EXPIRED_REASON = `expired: attachments are kept for ${MEDIA_TTL_HOURS} hours and these bytes are past that window`;
|
|
@@ -19226,7 +19375,7 @@ async function deliverAttachments(attachments, dir, fetchImpl = fetch) {
|
|
|
19226
19375
|
if (!attachments.length)
|
|
19227
19376
|
return [];
|
|
19228
19377
|
const taken = /* @__PURE__ */ new Set();
|
|
19229
|
-
await
|
|
19378
|
+
await mkdir6(dir, { recursive: true });
|
|
19230
19379
|
return Promise.all(attachments.map(async (attachment, index) => {
|
|
19231
19380
|
const tooLarge = (bytes) => ({
|
|
19232
19381
|
attachment,
|
|
@@ -19250,8 +19399,8 @@ async function deliverAttachments(attachments, dir, fetchImpl = fetch) {
|
|
|
19250
19399
|
const bytes = Buffer.from(await response.arrayBuffer());
|
|
19251
19400
|
if (bytes.length > MAX_ATTACHMENT_BYTES)
|
|
19252
19401
|
return tooLarge(bytes.length);
|
|
19253
|
-
const path =
|
|
19254
|
-
await
|
|
19402
|
+
const path = join5(dir, safeFileName(attachment, index, taken));
|
|
19403
|
+
await writeFile7(path, bytes);
|
|
19255
19404
|
const mimeType = attachment.mimeType ?? response.headers.get("content-type") ?? "";
|
|
19256
19405
|
if (!mimeType.startsWith("image/"))
|
|
19257
19406
|
return { attachment, path };
|
|
@@ -19433,6 +19582,11 @@ function isCornerStatusRestatement(reply, systemLines) {
|
|
|
19433
19582
|
return statusWords(text2).every((word) => !word || admitted.has(word));
|
|
19434
19583
|
}
|
|
19435
19584
|
|
|
19585
|
+
// apps/body/dist/turn-stop.js
|
|
19586
|
+
var TurnStoppedError = class extends Error {
|
|
19587
|
+
name = "TurnStoppedError";
|
|
19588
|
+
};
|
|
19589
|
+
|
|
19436
19590
|
// apps/body/dist/turn-stream.js
|
|
19437
19591
|
function durableReplyText(agentText) {
|
|
19438
19592
|
return sanitizeAgentReply(agentText);
|
|
@@ -19491,7 +19645,8 @@ var AgentTurnStream = class {
|
|
|
19491
19645
|
/**
|
|
19492
19646
|
* Everything the delta hook has seen this turn: every assistant run joined,
|
|
19493
19647
|
* which is a LONGER string than `PromptResult.agentText` whenever the turn
|
|
19494
|
-
* spoke before a tool call.
|
|
19648
|
+
* spoke before a tool call. This final-reply lane derives nothing durable
|
|
19649
|
+
* from it; a corner records its current normalized run independently.
|
|
19495
19650
|
*/
|
|
19496
19651
|
get streamedText() {
|
|
19497
19652
|
return this.latest;
|
|
@@ -19514,17 +19669,18 @@ var AgentTurnStream = class {
|
|
|
19514
19669
|
* An empty reply settles through the turn receipt instead, and the lane is
|
|
19515
19670
|
* retracted either way.
|
|
19516
19671
|
*/
|
|
19517
|
-
async settle(reply, fields = {}) {
|
|
19672
|
+
async settle(reply, fields = {}, onReplyPosted) {
|
|
19518
19673
|
this.close();
|
|
19519
19674
|
const { api, agentId, roomId, requestId } = this.options;
|
|
19520
19675
|
if (reply) {
|
|
19521
|
-
await api.execute("postRoomMessage", {
|
|
19676
|
+
const posted = await api.execute("postRoomMessage", {
|
|
19522
19677
|
roomId,
|
|
19523
19678
|
requestId,
|
|
19524
19679
|
text: reply,
|
|
19525
19680
|
presentation: "message",
|
|
19526
19681
|
...fields
|
|
19527
19682
|
});
|
|
19683
|
+
onReplyPosted?.(posted);
|
|
19528
19684
|
}
|
|
19529
19685
|
await this.inFlight;
|
|
19530
19686
|
await api.execute("retractAgentLiveOutput", {
|
|
@@ -19602,7 +19758,7 @@ function sessionConfigFingerprint(input) {
|
|
|
19602
19758
|
}
|
|
19603
19759
|
|
|
19604
19760
|
// apps/body/dist/pi-mcp-bridge.js
|
|
19605
|
-
import { mkdir as
|
|
19761
|
+
import { mkdir as mkdir7 } from "node:fs/promises";
|
|
19606
19762
|
import { resolve as resolve14 } from "node:path";
|
|
19607
19763
|
var PI_MCP_BRIDGE_FILENAME = "beeline-mcp-bridge.js";
|
|
19608
19764
|
function harnessMountsSessionMcpServers(agentCommand) {
|
|
@@ -19626,7 +19782,7 @@ async function installPiMcpBridge(input) {
|
|
|
19626
19782
|
const directory = resolve14(input.piHome, "extensions");
|
|
19627
19783
|
const path = resolve14(directory, PI_MCP_BRIDGE_FILENAME);
|
|
19628
19784
|
try {
|
|
19629
|
-
await
|
|
19785
|
+
await mkdir7(directory, { recursive: true, mode: 448 });
|
|
19630
19786
|
await writeIsolatedHarnessFile(path, piMcpBridgeSource(input.servers));
|
|
19631
19787
|
return path;
|
|
19632
19788
|
} catch (error) {
|
|
@@ -20031,6 +20187,7 @@ function beelineAgentMcpServer(config, api, context) {
|
|
|
20031
20187
|
args: [...config.readonlyMcpArgs ?? []],
|
|
20032
20188
|
env: [
|
|
20033
20189
|
{ name: "BEELINE_MCP_SURFACE", value: "agent" },
|
|
20190
|
+
...context.turnContextPath ? [{ name: "BEELINE_TURN_CONTEXT_FILE", value: context.turnContextPath }] : [],
|
|
20034
20191
|
...context.directMessage ? [{ name: "BEELINE_AGENT_DM", value: "1" }] : [],
|
|
20035
20192
|
{ name: "BEELINE_DAEMON_BASE_URL", value: connection.baseUrl },
|
|
20036
20193
|
{ name: "BEELINE_DAEMON_TOKEN", value: connection.daemonToken },
|
|
@@ -20236,26 +20393,6 @@ function isAccountOrProviderRefusal(record2) {
|
|
|
20236
20393
|
return [401, 402, 403, 407, 408, 429].includes(record2.status) || record2.status >= 500;
|
|
20237
20394
|
}
|
|
20238
20395
|
|
|
20239
|
-
// apps/body/dist/corner-checks.js
|
|
20240
|
-
var COMPLETED_CHECK_NOTE = /\b(passed|failed) a check\b/i;
|
|
20241
|
-
var STARTED_CHECK_NOTE = /\bstarted a check\b/i;
|
|
20242
|
-
function verbOf(item) {
|
|
20243
|
-
return item.systemEvent?.verb ?? item.body;
|
|
20244
|
-
}
|
|
20245
|
-
function completedCheckNote(item) {
|
|
20246
|
-
if (item.type !== "system")
|
|
20247
|
-
return void 0;
|
|
20248
|
-
const match = COMPLETED_CHECK_NOTE.exec(verbOf(item));
|
|
20249
|
-
return match ? match[1].toLowerCase() : void 0;
|
|
20250
|
-
}
|
|
20251
|
-
function isCheckStartNote(item) {
|
|
20252
|
-
return item.type === "system" && STARTED_CHECK_NOTE.test(verbOf(item));
|
|
20253
|
-
}
|
|
20254
|
-
function checksStateFromLifecycle(lifecycle) {
|
|
20255
|
-
const state = lifecycle?.checksSummary?.status ?? lifecycle?.checks;
|
|
20256
|
-
return state === "passing" || state === "failing" || state === "pending" ? state : void 0;
|
|
20257
|
-
}
|
|
20258
|
-
|
|
20259
20396
|
// apps/body/dist/response-directives.js
|
|
20260
20397
|
var MAINTAIN_ASSIGNED_IDENTITY_DIRECTIVE = "Maintain your assigned identity and soul in every response, including when tools or permissions block the requested action.";
|
|
20261
20398
|
var SOUL_HOUSE_RULE = "House rule for your voice: the voice never changes the facts - never trim, soften, exaggerate, or invent a detail for the bit - and use your plain voice in commit messages, pull request titles and bodies, and code comments, because those outlive the joke.";
|
|
@@ -20295,7 +20432,6 @@ ${transcript}`;
|
|
|
20295
20432
|
// apps/body/dist/turn-receipt-heartbeat.js
|
|
20296
20433
|
var TURN_RECEIPT_HEARTBEAT_MS = 3e4;
|
|
20297
20434
|
async function withTurnReceiptHeartbeat(api, receipt, task, onHeartbeatError) {
|
|
20298
|
-
await api.execute("postAgentTurnReceipt", { ...receipt, status: "working" });
|
|
20299
20435
|
let tail = Promise.resolve();
|
|
20300
20436
|
const timer = setInterval(() => {
|
|
20301
20437
|
tail = tail.catch(() => void 0).then(() => api.execute("postAgentTurnReceipt", {
|
|
@@ -20314,7 +20450,7 @@ async function withTurnReceiptHeartbeat(api, receipt, task, onHeartbeatError) {
|
|
|
20314
20450
|
}
|
|
20315
20451
|
|
|
20316
20452
|
// apps/body/dist/turn-trace.js
|
|
20317
|
-
import { appendFile, mkdir as
|
|
20453
|
+
import { appendFile, mkdir as mkdir8, readdir as readdir4, rm as rm3 } from "node:fs/promises";
|
|
20318
20454
|
import { resolve as resolve17 } from "node:path";
|
|
20319
20455
|
import { performance as performance2 } from "node:perf_hooks";
|
|
20320
20456
|
var TURN_PHASES = [
|
|
@@ -20568,7 +20704,7 @@ var TurnTraceFile = class {
|
|
|
20568
20704
|
this.tail = this.tail.catch(() => void 0).then(async () => {
|
|
20569
20705
|
const now2 = (this.options.clock ?? (() => /* @__PURE__ */ new Date()))();
|
|
20570
20706
|
const path = this.path(now2);
|
|
20571
|
-
await
|
|
20707
|
+
await mkdir8(this.directory, { recursive: true, mode: 448 });
|
|
20572
20708
|
await appendFile(path, `${JSON.stringify(record2)}
|
|
20573
20709
|
`, { mode: 384 });
|
|
20574
20710
|
await this.prune(now2);
|
|
@@ -20591,11 +20727,11 @@ var TurnTraceFile = class {
|
|
|
20591
20727
|
};
|
|
20592
20728
|
|
|
20593
20729
|
// apps/body/dist/corner-github-auth.js
|
|
20594
|
-
import { chmod as chmod3, mkdir as
|
|
20730
|
+
import { chmod as chmod3, mkdir as mkdir9, writeFile as writeFile8 } from "node:fs/promises";
|
|
20595
20731
|
import { delimiter as delimiter2, resolve as resolve18 } from "node:path";
|
|
20596
20732
|
async function installCornerGitHubWrappers(input) {
|
|
20597
20733
|
const bin = resolve18(input.root, "beeline-github-bin");
|
|
20598
|
-
await
|
|
20734
|
+
await mkdir9(bin, { recursive: true, mode: 448 });
|
|
20599
20735
|
const common = {
|
|
20600
20736
|
node: process.execPath,
|
|
20601
20737
|
cli: input.cliEntrypoint,
|
|
@@ -20637,149 +20773,118 @@ if (result.stderr) process.stderr.write(result.stderr);
|
|
|
20637
20773
|
if (result.error) throw result.error;
|
|
20638
20774
|
process.exit(result.status ?? 1);
|
|
20639
20775
|
`;
|
|
20640
|
-
await
|
|
20776
|
+
await writeFile8(path, source, { mode: 448 });
|
|
20641
20777
|
await chmod3(path, 448);
|
|
20642
20778
|
}
|
|
20643
20779
|
|
|
20644
|
-
// apps/body/dist/monolith-
|
|
20645
|
-
|
|
20646
|
-
|
|
20647
|
-
|
|
20648
|
-
|
|
20649
|
-
|
|
20650
|
-
|
|
20780
|
+
// apps/body/dist/monolith-room-turn.js
|
|
20781
|
+
import { mkdir as mkdir10 } from "node:fs/promises";
|
|
20782
|
+
import { homedir as homedir6 } from "node:os";
|
|
20783
|
+
import { join as join6 } from "node:path";
|
|
20784
|
+
|
|
20785
|
+
// packages/api-contract/dist/scheduled-prompts.js
|
|
20786
|
+
var SCHEDULE_SCHEDULER_NAME = "Beeline Scheduler";
|
|
20787
|
+
var SCHEDULE_RAN_VERB = "ran a schedule for";
|
|
20788
|
+
|
|
20789
|
+
// apps/body/dist/monolith-room-turn.js
|
|
20790
|
+
function isRoomMcpPermissionRequest(request, mountedServers = ROOM_MOUNTED_MCP_SERVERS) {
|
|
20791
|
+
if (isSquireMcpPermissionRequest(request))
|
|
20792
|
+
return false;
|
|
20793
|
+
return isMountedMcpToolPermissionRequest(request, mountedServers);
|
|
20651
20794
|
}
|
|
20652
|
-
function
|
|
20653
|
-
if (
|
|
20654
|
-
return
|
|
20655
|
-
|
|
20656
|
-
return
|
|
20657
|
-
|
|
20658
|
-
return "";
|
|
20659
|
-
}
|
|
20795
|
+
function isScheduledPrompt(item, agentId) {
|
|
20796
|
+
if (item.type !== "system" || !item.mentionIds.includes(agentId))
|
|
20797
|
+
return false;
|
|
20798
|
+
if (item.systemEvent?.kind)
|
|
20799
|
+
return item.systemEvent.kind === "schedule-ran";
|
|
20800
|
+
return item.systemEvent?.verb === SCHEDULE_RAN_VERB;
|
|
20660
20801
|
}
|
|
20661
|
-
function
|
|
20662
|
-
|
|
20802
|
+
function inboxItemAuthorName(item, agentId, names) {
|
|
20803
|
+
if (isScheduledPrompt(item, agentId))
|
|
20804
|
+
return SCHEDULE_SCHEDULER_NAME;
|
|
20805
|
+
const subject = item.systemEvent?.subject;
|
|
20806
|
+
if (item.type === "system" && subject?.name)
|
|
20807
|
+
return subject.name;
|
|
20808
|
+
return names.get(item.authorId) ?? item.authorId.slice(0, 12);
|
|
20663
20809
|
}
|
|
20664
|
-
function
|
|
20665
|
-
|
|
20666
|
-
if (Buffer.byteLength(clean4) <= maxBytes)
|
|
20667
|
-
return clean4;
|
|
20668
|
-
const suffix = "\n\u2026[truncated]";
|
|
20669
|
-
const allowed = maxBytes - Buffer.byteLength(suffix);
|
|
20670
|
-
return `${Buffer.from(clean4).subarray(0, Math.max(0, allowed)).toString("utf8")}${suffix}`;
|
|
20810
|
+
function inboxItemPromptBody(item, agentId) {
|
|
20811
|
+
return isScheduledPrompt(item, agentId) ? item.systemEvent?.consequence ?? item.body : item.body;
|
|
20671
20812
|
}
|
|
20672
|
-
function
|
|
20673
|
-
|
|
20674
|
-
|
|
20675
|
-
|
|
20676
|
-
|
|
20677
|
-
|
|
20813
|
+
function pendingGrantToolCall(call) {
|
|
20814
|
+
if (!/(?:^|[._:/-])request_grant$/i.test(call.title ?? ""))
|
|
20815
|
+
return false;
|
|
20816
|
+
return /pending, card posted/i.test(typeof call.content === "string" ? call.content : JSON.stringify(call.content ?? ""));
|
|
20817
|
+
}
|
|
20818
|
+
function roomMentionDirectory(roster, selfId) {
|
|
20819
|
+
const rows = [];
|
|
20820
|
+
for (const member of roster.members) {
|
|
20821
|
+
if (member.identityId === selfId)
|
|
20822
|
+
continue;
|
|
20823
|
+
const handle = member.handle?.trim().replace(/^@/, "");
|
|
20824
|
+
const name = member.name?.trim() ?? "";
|
|
20825
|
+
const alias = handle || name;
|
|
20826
|
+
if (!alias)
|
|
20827
|
+
continue;
|
|
20828
|
+
const kind = member.kind === "agent" ? "agent" : "person";
|
|
20829
|
+
rows.push(`- @${alias}${name && name !== alias ? ` \u2014 ${name}` : ""} (${kind})`);
|
|
20678
20830
|
}
|
|
20679
|
-
|
|
20680
|
-
|
|
20681
|
-
|
|
20682
|
-
|
|
20831
|
+
if (!rows.length)
|
|
20832
|
+
return "";
|
|
20833
|
+
return [
|
|
20834
|
+
"Room members, and the exact spelling that tags each one:",
|
|
20835
|
+
"An exact agent tag assigns that agent work; use it only when you are asking that agent to act.",
|
|
20836
|
+
...rows,
|
|
20837
|
+
"Write a tag exactly as spelled here. An @name spelled any other way is plain text: it reaches nobody, and nobody is told it was meant for them. Never invent a handle, shorten one, or copy an @name out of the conversation \u2014 old messages carry spellings that no longer exist."
|
|
20838
|
+
].join("\n");
|
|
20683
20839
|
}
|
|
20684
|
-
function
|
|
20685
|
-
|
|
20686
|
-
const visit = (candidate, key) => {
|
|
20687
|
-
if (paths.size >= TOOL_PATH_LIMIT || candidate === null || candidate === void 0)
|
|
20688
|
-
return;
|
|
20689
|
-
if (typeof candidate === "string") {
|
|
20690
|
-
if (key && /(?:^|_)(?:path|file|filename|target)$/i.test(key)) {
|
|
20691
|
-
const path = candidate.startsWith(`${worktreePath}/`) ? candidate.slice(worktreePath.length + 1) : candidate;
|
|
20692
|
-
if (path && path.length <= 512)
|
|
20693
|
-
paths.add(redactToolDetail(path));
|
|
20694
|
-
}
|
|
20695
|
-
return;
|
|
20696
|
-
}
|
|
20697
|
-
if (Array.isArray(candidate)) {
|
|
20698
|
-
candidate.forEach((entry) => visit(entry));
|
|
20699
|
-
return;
|
|
20700
|
-
}
|
|
20701
|
-
const object = record(candidate);
|
|
20702
|
-
if (object)
|
|
20703
|
-
Object.entries(object).forEach(([entryKey, entry]) => visit(entry, entryKey));
|
|
20704
|
-
};
|
|
20705
|
-
visit(value);
|
|
20706
|
-
return [...paths];
|
|
20840
|
+
function escapeRegExp(value) {
|
|
20841
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
20707
20842
|
}
|
|
20708
|
-
function
|
|
20709
|
-
const
|
|
20710
|
-
|
|
20711
|
-
|
|
20712
|
-
|
|
20713
|
-
|
|
20714
|
-
|
|
20715
|
-
|
|
20716
|
-
|
|
20717
|
-
|
|
20718
|
-
|
|
20719
|
-
|
|
20720
|
-
|
|
20721
|
-
|
|
20722
|
-
return "error";
|
|
20723
|
-
return /(?:failed|error|denied)/i.test(call.status ?? "") ? "error" : "ok";
|
|
20724
|
-
}
|
|
20725
|
-
function toolArguments(call) {
|
|
20726
|
-
const raw = record(call.rawInput);
|
|
20727
|
-
const command = typeof call.rawInput === "string" ? call.rawInput : typeof raw?.command === "string" ? raw.command : typeof raw?.cmd === "string" ? raw.cmd : void 0;
|
|
20728
|
-
if (command)
|
|
20729
|
-
return { command: clampBytes(redactToolDetail(command), TOOL_ARGUMENT_MAX_BYTES) };
|
|
20730
|
-
const input = serialized(call.rawInput);
|
|
20731
|
-
return input ? { input: clampBytes(redactToolDetail(input), TOOL_ARGUMENT_MAX_BYTES) } : {};
|
|
20732
|
-
}
|
|
20733
|
-
function toolCallKey(call, index) {
|
|
20734
|
-
return call.id ?? `tool-${index}`;
|
|
20735
|
-
}
|
|
20736
|
-
function toolCallSettled(call) {
|
|
20737
|
-
return /^(?:completed|complete|failed|error|succeeded|success|passed|done)$/i.test(call.status ?? "");
|
|
20738
|
-
}
|
|
20739
|
-
function isSuccessfulCommit(call) {
|
|
20740
|
-
if (/failed|error|denied/i.test(call.status ?? ""))
|
|
20741
|
-
return false;
|
|
20742
|
-
return /\bgit\s+commit\b|\bcommit(?:ted)?\s+(?:changes|files?)\b/i.test(`${call.title ?? ""} ${serialized(call.rawInput)}`);
|
|
20743
|
-
}
|
|
20744
|
-
async function cornerToolActivity(call, worktreePath, requestedBy) {
|
|
20745
|
-
const operation = oneLine(call.kind ?? "") || "tool";
|
|
20746
|
-
let title = oneLine(redactToolDetail(call.title ?? "")) || `${operation} tool`;
|
|
20747
|
-
if (isSuccessfulCommit(call)) {
|
|
20748
|
-
try {
|
|
20749
|
-
const shown = await execFileAsync3("git", ["-C", worktreePath, "show", "--format=%s", "--name-only", "--no-renames", "HEAD"], { maxBuffer: 1024 * 1024 });
|
|
20750
|
-
const lines = shown.stdout.split(/\r?\n/);
|
|
20751
|
-
const subject = oneLine(lines.shift() ?? "commit");
|
|
20752
|
-
const files = new Set(lines.map(oneLine).filter(Boolean));
|
|
20753
|
-
title = `committed ${files.size} files: ${subject}`;
|
|
20754
|
-
} catch {
|
|
20843
|
+
function agentReplyMentionIds(text2, roster, authorId) {
|
|
20844
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
20845
|
+
for (const member of roster.members) {
|
|
20846
|
+
if (member.identityId === authorId)
|
|
20847
|
+
continue;
|
|
20848
|
+
const rawAliases = member.kind === "agent" ? [member.handle] : [member.name, member.handle, member.soul?.name];
|
|
20849
|
+
for (const raw of rawAliases) {
|
|
20850
|
+
const display = raw?.trim().replace(/^@/, "");
|
|
20851
|
+
if (!display)
|
|
20852
|
+
continue;
|
|
20853
|
+
const key = display.toLocaleLowerCase();
|
|
20854
|
+
const entry = aliases.get(key) ?? { display, ids: /* @__PURE__ */ new Set() };
|
|
20855
|
+
entry.ids.add(member.identityId);
|
|
20856
|
+
aliases.set(key, entry);
|
|
20755
20857
|
}
|
|
20756
20858
|
}
|
|
20757
|
-
const
|
|
20758
|
-
|
|
20759
|
-
|
|
20760
|
-
|
|
20761
|
-
|
|
20762
|
-
|
|
20763
|
-
|
|
20764
|
-
|
|
20765
|
-
|
|
20766
|
-
|
|
20767
|
-
|
|
20768
|
-
|
|
20769
|
-
|
|
20770
|
-
}
|
|
20771
|
-
|
|
20772
|
-
|
|
20773
|
-
|
|
20859
|
+
for (const member of roster.members) {
|
|
20860
|
+
if (member.identityId === authorId || member.kind === "agent" || !member.handle)
|
|
20861
|
+
continue;
|
|
20862
|
+
const handle = member.handle.trim().replace(/^@/, "").toLocaleLowerCase();
|
|
20863
|
+
const canonical = aliases.get(handle);
|
|
20864
|
+
const legacy = `a_${handle}`;
|
|
20865
|
+
if (canonical && !aliases.has(legacy))
|
|
20866
|
+
aliases.set(legacy, { ...canonical, display: legacy });
|
|
20867
|
+
}
|
|
20868
|
+
const mentioned = [];
|
|
20869
|
+
for (const { display, ids } of [...aliases.values()].sort((left, right) => right.display.length - left.display.length)) {
|
|
20870
|
+
if (ids.size !== 1)
|
|
20871
|
+
continue;
|
|
20872
|
+
const pattern = new RegExp(`(^|[\\s([{])@${escapeRegExp(display)}(?=$|[\\s.,!?;:)\\]}])`, "iu");
|
|
20873
|
+
if (!pattern.test(text2))
|
|
20874
|
+
continue;
|
|
20875
|
+
const identityId = [...ids][0];
|
|
20876
|
+
if (!mentioned.includes(identityId))
|
|
20877
|
+
mentioned.push(identityId);
|
|
20878
|
+
}
|
|
20879
|
+
return mentioned;
|
|
20774
20880
|
}
|
|
20775
|
-
var
|
|
20881
|
+
var MonolithRoomTurnLoop = class {
|
|
20776
20882
|
options;
|
|
20883
|
+
commandContext;
|
|
20777
20884
|
agent;
|
|
20778
|
-
reconciliationRequested = true;
|
|
20779
20885
|
wakeIntake;
|
|
20780
20886
|
/** Called by the daemon's one slow workspace reconciliation sweep. */
|
|
20781
20887
|
requestReconciliation() {
|
|
20782
|
-
this.reconciliationRequested = true;
|
|
20783
20888
|
this.wakeIntake?.();
|
|
20784
20889
|
this.wakeIntake = void 0;
|
|
20785
20890
|
}
|
|
@@ -20787,67 +20892,110 @@ var MonolithCornerTurnLoop = class {
|
|
|
20787
20892
|
sessionId;
|
|
20788
20893
|
/** The configuration the live session baked in; a change invalidates it. */
|
|
20789
20894
|
sessionFingerprint;
|
|
20790
|
-
/** What this exact ACP session has already been prompted with (`warm-transcript.ts`). */
|
|
20791
|
-
warmTranscript = new WarmTranscript();
|
|
20792
20895
|
/** The live session's environment, read back for pi's own turn record. */
|
|
20793
20896
|
agentEnv = {};
|
|
20794
20897
|
/** OpenRouter providers this activation pinned, in order (C92). */
|
|
20795
20898
|
pinnedProviders = [];
|
|
20796
|
-
/** Whether the pinned model takes images; `undefined` when the pin did not say. */
|
|
20797
|
-
modelTakesImages;
|
|
20798
20899
|
/** The one provider re-pinned after an empty completion, until the session ends. */
|
|
20799
20900
|
pinnedProviderOverride;
|
|
20800
|
-
turnIdentityInstructions = "";
|
|
20801
20901
|
busy = false;
|
|
20802
|
-
|
|
20803
|
-
|
|
20902
|
+
turnInstructionPrefix = "";
|
|
20903
|
+
activeTurn;
|
|
20904
|
+
queuedTurns = [];
|
|
20804
20905
|
/** Session scratch directory attachments are downloaded into (`TMPDIR/beeline-attachments`). */
|
|
20805
20906
|
attachmentDir;
|
|
20806
|
-
/**
|
|
20907
|
+
/** Whether the pinned model takes images; `undefined` when the pin did not say. */
|
|
20908
|
+
modelTakesImages;
|
|
20909
|
+
/** The session's TMPDIR: writable to a granted command in a Room, as it is to the harness (C94). */
|
|
20807
20910
|
sessionScratchDir;
|
|
20808
|
-
/** The
|
|
20809
|
-
|
|
20911
|
+
/** The `agent-home.ts` overlay this session writes into; a Room grant keeps it. */
|
|
20912
|
+
sessionStateDirs = [];
|
|
20913
|
+
/** What this exact ACP session has already been prompted with (`warm-transcript.ts`). */
|
|
20914
|
+
warmTranscript = new WarmTranscript();
|
|
20915
|
+
/** Local copies already delivered this session, by message id, so transcript renders reuse them. */
|
|
20916
|
+
deliveredAttachments = /* @__PURE__ */ new Map();
|
|
20917
|
+
/** Names from the latest roster read, for ledger bylines the runner writes. */
|
|
20918
|
+
memberNames = /* @__PURE__ */ new Map();
|
|
20919
|
+
/** The request id of the turn that paused on a grant card, until its decision arrives. */
|
|
20920
|
+
pausedOnGrantRequestId;
|
|
20810
20921
|
/** Operator-local turn traces; built once when the daemon configured a directory. */
|
|
20811
20922
|
turnTraceSink;
|
|
20812
|
-
memberNames = /* @__PURE__ */ new Map();
|
|
20813
|
-
/** Agent identities in this Workspace, so a mention can be told from a human's. */
|
|
20814
|
-
agentMembers = /* @__PURE__ */ new Set();
|
|
20815
|
-
/** The member agent that answered in this corner last (`carriesCorner`). */
|
|
20816
|
-
carrier;
|
|
20817
|
-
/** The last server check state that started a turn; the same state never starts another. */
|
|
20818
|
-
lastChecksState;
|
|
20819
20923
|
constructor(options) {
|
|
20820
20924
|
this.options = options;
|
|
20821
20925
|
this.agent = runtimeIdentity(options.runtime.agent);
|
|
20822
|
-
|
|
20926
|
+
this.commandContext = new CommandExecutionContext(options.config.agentHomeRoot);
|
|
20927
|
+
this.options = { ...options, api: this.commandContext.bind(options.api) };
|
|
20928
|
+
options.grantRunner?.register(options.roomId, {
|
|
20823
20929
|
workspaceId: options.workspaceId,
|
|
20824
|
-
cwd: options.
|
|
20825
|
-
// A
|
|
20826
|
-
//
|
|
20827
|
-
|
|
20828
|
-
|
|
20829
|
-
|
|
20830
|
-
|
|
20831
|
-
}
|
|
20832
|
-
turn: () => this.currentTurn
|
|
20930
|
+
cwd: options.cwd,
|
|
20931
|
+
// A top-level Room keeps its read-only promise for grants too: the runner
|
|
20932
|
+
// wraps the command in this Room's own mount table (C94).
|
|
20933
|
+
writePolicy: () => this.grantWritePolicy(),
|
|
20934
|
+
turn: () => {
|
|
20935
|
+
const turn = this.currentTurnForRunner();
|
|
20936
|
+
return turn ? { ...turn, generationId: this.commandContext.generationId } : void 0;
|
|
20937
|
+
}
|
|
20833
20938
|
});
|
|
20834
20939
|
}
|
|
20835
20940
|
isBusy() {
|
|
20836
20941
|
return this.busy;
|
|
20837
20942
|
}
|
|
20838
|
-
|
|
20839
|
-
|
|
20943
|
+
/** The turn a `request_grant` paused, if any (cleared when its decision resumes it). */
|
|
20944
|
+
pausedGrantRequestId() {
|
|
20945
|
+
return this.pausedOnGrantRequestId;
|
|
20840
20946
|
}
|
|
20841
|
-
|
|
20842
|
-
|
|
20947
|
+
/**
|
|
20948
|
+
* What the grant runner may write here: the session's own scratch and home
|
|
20949
|
+
* overlay and nothing else, enforced by the same read-only mount table the
|
|
20950
|
+
* harness runs under. With no usable bwrap there is no way to keep that
|
|
20951
|
+
* promise, so the policy carries no path and the runner refuses the run
|
|
20952
|
+
* rather than widening the boundary.
|
|
20953
|
+
*/
|
|
20954
|
+
grantWritePolicy() {
|
|
20955
|
+
return {
|
|
20956
|
+
surface: "room",
|
|
20957
|
+
...this.options.config.bwrapPath ? { bwrapPath: this.options.config.bwrapPath } : {},
|
|
20958
|
+
...this.sessionScratchDir ? { scratch: this.sessionScratchDir } : {},
|
|
20959
|
+
...this.sessionStateDirs.length ? { harnessStateDirs: this.sessionStateDirs } : {},
|
|
20960
|
+
maskPaths: credentialMaskPaths(this.options.config.sandboxMaskPaths, this.options.config.operatorHome ?? homedir6())
|
|
20961
|
+
};
|
|
20962
|
+
}
|
|
20963
|
+
/**
|
|
20964
|
+
* One turn's stopwatch. It is created for every turn — measuring is cheap —
|
|
20965
|
+
* and only WRITES when the daemon configured a runtime directory to write
|
|
20966
|
+
* into, so a standalone or test Body stays silent.
|
|
20967
|
+
*/
|
|
20968
|
+
beginTurnTrace(requestId) {
|
|
20969
|
+
const directory = this.options.config.turnTraceDir;
|
|
20970
|
+
if (directory)
|
|
20971
|
+
this.turnTraceSink ??= new TurnTraceFile(directory);
|
|
20972
|
+
return new TurnTrace({
|
|
20973
|
+
surface: "room",
|
|
20974
|
+
agentId: this.agent.publicKey,
|
|
20975
|
+
roomId: this.options.roomId,
|
|
20976
|
+
requestId,
|
|
20977
|
+
...this.turnTraceSink ? { sink: this.turnTraceSink } : {}
|
|
20978
|
+
});
|
|
20979
|
+
}
|
|
20980
|
+
currentTurnForRunner() {
|
|
20981
|
+
const active = this.activeTurn;
|
|
20982
|
+
if (!active)
|
|
20983
|
+
return void 0;
|
|
20984
|
+
return { requestId: active.item.id, requester: this.requesterOf(active.item.authorId) };
|
|
20985
|
+
}
|
|
20986
|
+
requesterOf(authorId) {
|
|
20987
|
+
const name = this.memberNames.get(authorId);
|
|
20988
|
+
return { pubkey: authorId, ...name ? { name } : {} };
|
|
20989
|
+
}
|
|
20990
|
+
async refreshPersonaForSoulUpdate() {
|
|
20991
|
+
await this.options.scheduler.suspend(this.options.roomId);
|
|
20843
20992
|
}
|
|
20844
20993
|
async prepareForForcedUpdateRestart() {
|
|
20845
|
-
this.forcedStop = true;
|
|
20846
20994
|
}
|
|
20847
20995
|
async forceRecoverRoom() {
|
|
20848
20996
|
if (this.client && this.sessionId)
|
|
20849
20997
|
this.client.sessionCancel(this.sessionId);
|
|
20850
|
-
await this.options.scheduler.forceSuspend(this.options.
|
|
20998
|
+
await this.options.scheduler.forceSuspend(this.options.roomId);
|
|
20851
20999
|
}
|
|
20852
21000
|
async roster() {
|
|
20853
21001
|
const roster = await this.options.api.execute("getWorkspaceRoster", {
|
|
@@ -20855,11 +21003,36 @@ var MonolithCornerTurnLoop = class {
|
|
|
20855
21003
|
workspaceId: this.options.workspaceId
|
|
20856
21004
|
});
|
|
20857
21005
|
this.memberNames = new Map(roster.members.map((member) => [member.identityId, member.name]));
|
|
20858
|
-
this.agentMembers = new Set(roster.members.filter((member) => member.kind === "agent").map((member) => member.identityId));
|
|
20859
21006
|
return roster;
|
|
20860
21007
|
}
|
|
20861
21008
|
/**
|
|
20862
|
-
*
|
|
21009
|
+
* Whether a picture actually reaches the model this session. Both halves
|
|
21010
|
+
* have to hold (C87): the harness must advertise `promptCapabilities.image`,
|
|
21011
|
+
* AND — when the pin knows the model's modalities — the model must take
|
|
21012
|
+
* images. `undefined` modalities mean the question was never settled, and
|
|
21013
|
+
* the harness answer stands alone as before.
|
|
21014
|
+
*/
|
|
21015
|
+
acceptsImages() {
|
|
21016
|
+
if (!(this.client?.canPromptWithImages() ?? false))
|
|
21017
|
+
return false;
|
|
21018
|
+
return this.modelTakesImages ?? true;
|
|
21019
|
+
}
|
|
21020
|
+
/** Download a message's attachments into the session scratch directory once. */
|
|
21021
|
+
async deliver(item) {
|
|
21022
|
+
if (!item.attachments.length || !this.attachmentDir)
|
|
21023
|
+
return [];
|
|
21024
|
+
const cached = this.deliveredAttachments.get(item.id);
|
|
21025
|
+
if (cached)
|
|
21026
|
+
return cached;
|
|
21027
|
+
const delivered = await deliverAttachments(item.attachments, join6(this.attachmentDir, item.id.replace(/[^\w-]/g, "_")), this.options.fetchImpl);
|
|
21028
|
+
this.deliveredAttachments.set(item.id, withoutImageData(delivered));
|
|
21029
|
+
return delivered;
|
|
21030
|
+
}
|
|
21031
|
+
repositoryState() {
|
|
21032
|
+
return this.options.api.execute("getRoomRepositoryState", { roomId: this.options.roomId });
|
|
21033
|
+
}
|
|
21034
|
+
/**
|
|
21035
|
+
* Drop this Room's live harness process. The next activation starts cold.
|
|
20863
21036
|
* A rotation is a fact about one live session, so the pin goes with it.
|
|
20864
21037
|
*/
|
|
20865
21038
|
async discardSession() {
|
|
@@ -20871,8 +21044,14 @@ var MonolithCornerTurnLoop = class {
|
|
|
20871
21044
|
if (client?.isAlive)
|
|
20872
21045
|
await client.stop();
|
|
20873
21046
|
}
|
|
20874
|
-
/**
|
|
20875
|
-
*
|
|
21047
|
+
/**
|
|
21048
|
+
* Whether the retained session still matches the agent's server-side
|
|
21049
|
+
* configuration. Retention (C104) is a saving only while what it keeps is
|
|
21050
|
+
* still current, and a session's persona and model pin are fixed when it
|
|
21051
|
+
* opens: they cannot be corrected in place, so a changed one has to cost a
|
|
21052
|
+
* respawn. The check is one round trip — the roster half of which the turn
|
|
21053
|
+
* was going to fetch anyway — against a cold spawn measured in seconds.
|
|
21054
|
+
*/
|
|
20876
21055
|
async sessionIsCurrent() {
|
|
20877
21056
|
return await this.currentSessionFingerprint() === this.sessionFingerprint;
|
|
20878
21057
|
}
|
|
@@ -20880,7 +21059,7 @@ var MonolithCornerTurnLoop = class {
|
|
|
20880
21059
|
const [configuration, roster] = await Promise.all([
|
|
20881
21060
|
this.options.api.execute("getAgentConfiguration", {
|
|
20882
21061
|
agentId: this.agent.publicKey,
|
|
20883
|
-
roomId: this.options.
|
|
21062
|
+
roomId: this.options.roomId
|
|
20884
21063
|
}),
|
|
20885
21064
|
this.roster()
|
|
20886
21065
|
]);
|
|
@@ -20896,12 +21075,13 @@ var MonolithCornerTurnLoop = class {
|
|
|
20896
21075
|
if (this.client?.isAlive && this.sessionId)
|
|
20897
21076
|
return this.sessionId;
|
|
20898
21077
|
trace?.noteActivation("cold");
|
|
20899
|
-
const [configuration, roster] = await Promise.all([
|
|
21078
|
+
const [configuration, roster, repositoryState] = await Promise.all([
|
|
20900
21079
|
this.options.api.execute("getAgentConfiguration", {
|
|
20901
21080
|
agentId: this.agent.publicKey,
|
|
20902
|
-
roomId: this.options.
|
|
21081
|
+
roomId: this.options.roomId
|
|
20903
21082
|
}),
|
|
20904
|
-
this.roster()
|
|
21083
|
+
this.roster(),
|
|
21084
|
+
this.repositoryState()
|
|
20905
21085
|
]);
|
|
20906
21086
|
const self = roster.members.find((member) => member.identityId === this.agent.publicKey);
|
|
20907
21087
|
const fingerprint = sessionConfigFingerprint({
|
|
@@ -20910,7 +21090,8 @@ var MonolithCornerTurnLoop = class {
|
|
|
20910
21090
|
soul: configuration.soul ?? self?.soul,
|
|
20911
21091
|
agentName: self?.name ?? this.agent.name
|
|
20912
21092
|
});
|
|
20913
|
-
|
|
21093
|
+
const directMessage = Array.isArray(repositoryState.directParticipants) && repositoryState.directParticipants.length === 2;
|
|
21094
|
+
await mkdir10(this.options.cwd, { recursive: true });
|
|
20914
21095
|
const selection = configuration.model || configuration.effort ? { model: configuration.model, effort: configuration.effort } : this.options.config.modelSelection;
|
|
20915
21096
|
const homeOverlay = this.options.config.agentHomeRoot ? await prepareRoomAgentHome({
|
|
20916
21097
|
root: this.options.config.agentHomeRoot,
|
|
@@ -20927,26 +21108,7 @@ var MonolithCornerTurnLoop = class {
|
|
|
20927
21108
|
})
|
|
20928
21109
|
}) : {};
|
|
20929
21110
|
const command = this.options.config.agentCommand ?? this.options.config.agentBinary;
|
|
20930
|
-
const
|
|
20931
|
-
let githubEnv = repository ? { GH_TOKEN: repository.githubToken, GITHUB_TOKEN: repository.githubToken } : {};
|
|
20932
|
-
if (repository && this.options.config.runtimeConfigPath && this.options.config.agentHomeRoot) {
|
|
20933
|
-
const gitBinary = (await execFileAsync3("which", ["git"])).stdout.trim();
|
|
20934
|
-
const ghBinary = await execFileAsync3("which", ["gh"]).then((result) => result.stdout.trim()).catch(() => void 0);
|
|
20935
|
-
githubEnv = await installCornerGitHubWrappers({
|
|
20936
|
-
root: this.options.config.agentHomeRoot,
|
|
20937
|
-
runtimeConfigPath: this.options.config.runtimeConfigPath,
|
|
20938
|
-
roomId: this.options.parentRoomId,
|
|
20939
|
-
cliEntrypoint: process.argv[1],
|
|
20940
|
-
gitBinary,
|
|
20941
|
-
...ghBinary ? { ghBinary } : {},
|
|
20942
|
-
inheritedPath: this.options.config.agentEnv.PATH ?? process.env.PATH
|
|
20943
|
-
});
|
|
20944
|
-
}
|
|
20945
|
-
const agentEnv = {
|
|
20946
|
-
...this.options.config.agentEnv,
|
|
20947
|
-
...homeOverlay,
|
|
20948
|
-
...githubEnv
|
|
20949
|
-
};
|
|
21111
|
+
const agentEnv = { ...this.options.config.agentEnv, ...homeOverlay };
|
|
20950
21112
|
this.agentEnv = agentEnv;
|
|
20951
21113
|
const agentArgs = agentArgsWithModelSelection({
|
|
20952
21114
|
kind: this.options.config.agentKind,
|
|
@@ -20955,21 +21117,19 @@ var MonolithCornerTurnLoop = class {
|
|
|
20955
21117
|
}, selection);
|
|
20956
21118
|
const operatorHome = this.options.config.operatorHome ?? homedir6();
|
|
20957
21119
|
const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
|
|
20958
|
-
this.attachmentDir = tmpDir ?
|
|
21120
|
+
this.attachmentDir = tmpDir ? join6(tmpDir, "beeline-attachments") : void 0;
|
|
20959
21121
|
this.sessionScratchDir = tmpDir;
|
|
21122
|
+
this.sessionStateDirs = stateDirs;
|
|
20960
21123
|
const homeStateDirs = harnessHomeStateDirs(command, agentEnv.HOME ?? operatorHome);
|
|
20961
|
-
await Promise.all(homeStateDirs.map((dir) =>
|
|
21124
|
+
await Promise.all(homeStateDirs.map((dir) => mkdir10(dir, { recursive: true })));
|
|
20962
21125
|
const attachScratchRoot = this.options.config.agentHomeRoot ?? tmpDir;
|
|
20963
21126
|
if (attachScratchRoot)
|
|
20964
|
-
await
|
|
21127
|
+
await mkdir10(attachScratchRoot, { recursive: true });
|
|
20965
21128
|
const spawnCommand = wrapAgentCommand({
|
|
20966
21129
|
bwrapPath: this.options.config.bwrapPath,
|
|
20967
21130
|
spec: {
|
|
20968
|
-
mode: "
|
|
20969
|
-
cwd: this.options.
|
|
20970
|
-
worktreePath: this.options.worktreePath,
|
|
20971
|
-
...repository ? { gitCommonDir: repository.gitCommonDir } : {},
|
|
20972
|
-
protectedPaths: [this.options.runtime.supervisorRoot],
|
|
21131
|
+
mode: "readonly",
|
|
21132
|
+
cwd: this.options.cwd,
|
|
20973
21133
|
harnessStateDirs: stateDirs,
|
|
20974
21134
|
harnessHomeStateDirs: homeStateDirs,
|
|
20975
21135
|
...tmpDir ? { tmpDir } : {},
|
|
@@ -20979,81 +21139,64 @@ var MonolithCornerTurnLoop = class {
|
|
|
20979
21139
|
command,
|
|
20980
21140
|
args: agentArgs
|
|
20981
21141
|
});
|
|
21142
|
+
const servers = [
|
|
21143
|
+
readOnlyMcpServer(this.options.config, this.options.cwd),
|
|
21144
|
+
beelineAgentMcpServer(this.options.config, this.options.api, {
|
|
21145
|
+
roomId: this.options.roomId,
|
|
21146
|
+
workspaceId: this.options.workspaceId,
|
|
21147
|
+
attachRoot: this.options.cwd,
|
|
21148
|
+
// The whole per-session overlay, not an enumerated subset: the agent
|
|
21149
|
+
// never picks where a harness writes a file it generates (grok's own
|
|
21150
|
+
// images dir, say), so anything inside the overlay it could possibly
|
|
21151
|
+
// have written must be attachable, whatever subdirectory that is.
|
|
21152
|
+
attachScratchRoot,
|
|
21153
|
+
turnContextPath: this.commandContext.path,
|
|
21154
|
+
directMessage,
|
|
21155
|
+
...this.options.grantRunnerEndpoint ? { grantRunner: this.options.grantRunnerEndpoint } : {}
|
|
21156
|
+
})
|
|
21157
|
+
];
|
|
21158
|
+
await installPiMcpBridge({
|
|
21159
|
+
agentCommand: command,
|
|
21160
|
+
piHome: agentEnv.PI_CODING_AGENT_DIR,
|
|
21161
|
+
servers
|
|
21162
|
+
});
|
|
21163
|
+
const mountedServers = servers.map((server) => server.name);
|
|
20982
21164
|
const clientOptions = {
|
|
20983
21165
|
agentCommand: spawnCommand.command,
|
|
20984
21166
|
agentArgs: spawnCommand.args,
|
|
20985
21167
|
agentEnv,
|
|
20986
|
-
agentCwd: this.options.
|
|
21168
|
+
agentCwd: this.options.cwd,
|
|
20987
21169
|
agentLabel: command,
|
|
20988
|
-
|
|
20989
|
-
|
|
21170
|
+
// `bwrapPath` is set only when `detectBwrapSandbox` passed its self-test
|
|
21171
|
+
// (`config.ts`), which is exactly when `wrapAgentCommand` above wraps.
|
|
21172
|
+
osSandbox: Boolean(this.options.config.bwrapPath),
|
|
21173
|
+
autoApprovePermissions: false,
|
|
21174
|
+
permissionAllowlist: (request) => isRoomMcpPermissionRequest(request, mountedServers)
|
|
20990
21175
|
};
|
|
20991
21176
|
this.client = (this.options.createAcpClient ?? ((value) => new AcpClient(value)))(clientOptions);
|
|
20992
21177
|
await this.client.start();
|
|
20993
|
-
const servers = [
|
|
20994
|
-
...repository ? [
|
|
20995
|
-
{
|
|
20996
|
-
name: "buzz-dev-mcp",
|
|
20997
|
-
command: this.options.config.mcpBinary,
|
|
20998
|
-
args: [],
|
|
20999
|
-
// ACP hosts launch stdio MCP servers with an explicit, sanitized env.
|
|
21000
|
-
// This token is minted for this exact corner and is also the credential
|
|
21001
|
-
// helper's password source, so its shell commands need the same scope as
|
|
21002
|
-
// the corner harness without inheriting any host credentials.
|
|
21003
|
-
env: [
|
|
21004
|
-
{ name: "GH_TOKEN", value: repository.githubToken },
|
|
21005
|
-
{ name: "GITHUB_TOKEN", value: repository.githubToken }
|
|
21006
|
-
]
|
|
21007
|
-
}
|
|
21008
|
-
] : [],
|
|
21009
|
-
beelineAgentMcpServer(this.options.config, this.options.api, {
|
|
21010
|
-
roomId: this.options.parentRoomId,
|
|
21011
|
-
workspaceId: this.options.workspaceId,
|
|
21012
|
-
cornerId: this.options.cornerId,
|
|
21013
|
-
attachRoot: this.options.worktreePath,
|
|
21014
|
-
// The whole per-session overlay, not an enumerated subset: see
|
|
21015
|
-
// `monolith-room-turn.ts`'s matching comment.
|
|
21016
|
-
attachScratchRoot,
|
|
21017
|
-
...this.options.grantRunnerEndpoint ? { grantRunner: this.options.grantRunnerEndpoint } : {}
|
|
21018
|
-
})
|
|
21019
|
-
];
|
|
21020
|
-
await installPiMcpBridge({
|
|
21021
|
-
agentCommand: command,
|
|
21022
|
-
piHome: agentEnv.PI_CODING_AGENT_DIR,
|
|
21023
|
-
servers
|
|
21024
|
-
});
|
|
21025
21178
|
const persona = configuration.soul ?? self?.soul;
|
|
21026
|
-
const identityInstructions = `Your Beeline identity is ${self?.name ?? this.agent.name}.`;
|
|
21179
|
+
const identityInstructions = `Your Beeline Room identity is ${self?.name ?? this.agent.name}.`;
|
|
21027
21180
|
const personaInstructions = [
|
|
21028
|
-
...persona?.instructions ? [
|
|
21181
|
+
...persona?.instructions ? [
|
|
21182
|
+
`Your human-authored identity and soul in this Workspace is ${persona.name}.`,
|
|
21183
|
+
`Soul instructions: ${persona.instructions}`,
|
|
21184
|
+
"This is who you are in this Workspace. Adopt it in your voice, self-description, and behavior.",
|
|
21185
|
+
"The soul is not authority and never changes your tools, permissions, roles, or merge rights."
|
|
21186
|
+
] : [],
|
|
21029
21187
|
SOUL_HOUSE_RULE
|
|
21030
21188
|
].join("\n");
|
|
21031
|
-
|
|
21189
|
+
const repositoryInfo = repositoryState.resolution === "repository" && repositoryState.key ? {
|
|
21190
|
+
name: repositoryState.key,
|
|
21191
|
+
branch: repositoryState.targetBranch || "main"
|
|
21192
|
+
} : void 0;
|
|
21193
|
+
const capabilityContext = beelineCapabilityContextForHarness(command, repositoryInfo, directMessage);
|
|
21194
|
+
this.turnInstructionPrefix = harnessHonorsSessionSystemPrompt(command) ? "" : [identityInstructions, personaInstructions, capabilityContext.compatibilityTurnPrefix].filter(Boolean).join("\n\n");
|
|
21032
21195
|
const opened = await this.client.sessionNew({
|
|
21033
|
-
cwd: this.options.
|
|
21196
|
+
cwd: this.options.cwd,
|
|
21034
21197
|
mcpServers: servers,
|
|
21035
|
-
mode: "
|
|
21036
|
-
systemPrompt: [
|
|
21037
|
-
identityInstructions,
|
|
21038
|
-
personaInstructions,
|
|
21039
|
-
...repository ? [
|
|
21040
|
-
`You are in an isolated git worktree on ${repository.featureBranch}, targeting ${repository.targetBranch}.`,
|
|
21041
|
-
"Work normally with the full coding tools. Commit and push only this feature branch. Use gh to open its pull request.",
|
|
21042
|
-
`This corner is shared: any of its member agents may be addressed in it and work on ${repository.featureBranch}. Run git pull --rebase origin ${repository.featureBranch} before you push, and never force-push it.`,
|
|
21043
|
-
"PR-opening turn rule: as soon as a pull request exists, print its full GitHub URL as your final response and end the turn immediately. Do not call pr_checks_status in that same turn and do not wait for checks inside it. Then stay idle until a later corner fact or human message starts another turn.",
|
|
21044
|
-
'Never merge because local tests pass or because gh reports passing checks. On a later turn triggered by a server-posted checks-passed note, call beeline-agent pr_checks_status. Merge only when it returns checks="passed", held=false, and approvalPending=false.',
|
|
21045
|
-
"Merge the PR yourself only after the checks-passed event shows every check green; if any check failed or is still running, say exactly which and stop - never merge red.",
|
|
21046
|
-
"If any human in this corner says hold or do not merge, do not merge until a later human explicitly resumes it.",
|
|
21047
|
-
"Do not tag the user when a corner turn finishes: the server posts the merge summary card and its push already cover completion. Tag a human only mid-turn, and only when you need a decision or input.",
|
|
21048
|
-
'GitHub check and merge notes are server lines already in the corner: never restate them (no "checks passed", "CI is green", "PR ready for review"). On a checks turn, say nothing unless you act - a merge or a pushed fix - and then one short line about that.',
|
|
21049
|
-
"A human approval in the app asks the server to merge. When approval is pending, wait for the server close request instead of racing it with gh. If checks passed, no hold exists, and no approval is pending, merge the pull request yourself with gh.",
|
|
21050
|
-
"Never push directly to the target branch. Never merge a different pull request."
|
|
21051
|
-
] : [
|
|
21052
|
-
"This is a chat-only corner with no repository or GitHub workflow.",
|
|
21053
|
-
"Work in this corner's writable workspace. Use write_scratch_file or ordinary tools to create files, then attach_file to send them back to the corner.",
|
|
21054
|
-
"Do not initialize a repository, create a branch, push, open a pull request, or wait for GitHub checks."
|
|
21055
|
-
]
|
|
21056
|
-
].filter(Boolean).join("\n\n")
|
|
21198
|
+
mode: "readonly",
|
|
21199
|
+
systemPrompt: [identityInstructions, personaInstructions, capabilityContext.sessionPrompt].filter(Boolean).join("\n\n")
|
|
21057
21200
|
});
|
|
21058
21201
|
this.sessionId = opened.sessionId;
|
|
21059
21202
|
this.sessionFingerprint = fingerprint;
|
|
@@ -21063,7 +21206,12 @@ var MonolithCornerTurnLoop = class {
|
|
|
21063
21206
|
}
|
|
21064
21207
|
return opened.sessionId;
|
|
21065
21208
|
}
|
|
21066
|
-
/**
|
|
21209
|
+
/**
|
|
21210
|
+
* The scheduler seam, and the only place that can see the boundary between
|
|
21211
|
+
* waiting for a slot and spawning a harness: `queue-wait` closes the instant
|
|
21212
|
+
* `activate()` is called, and `cold` vs `warm` is decided by whether this
|
|
21213
|
+
* Room already holds a live ACP client.
|
|
21214
|
+
*/
|
|
21067
21215
|
lifecycle(trace) {
|
|
21068
21216
|
return {
|
|
21069
21217
|
activate: async () => {
|
|
@@ -21082,12 +21230,6 @@ var MonolithCornerTurnLoop = class {
|
|
|
21082
21230
|
suspend: () => this.discardSession()
|
|
21083
21231
|
};
|
|
21084
21232
|
}
|
|
21085
|
-
/** A picture reaches the model only if the harness AND the model take one (C87). */
|
|
21086
|
-
acceptsImages() {
|
|
21087
|
-
if (!(this.client?.canPromptWithImages() ?? false))
|
|
21088
|
-
return false;
|
|
21089
|
-
return this.modelTakesImages ?? true;
|
|
21090
|
-
}
|
|
21091
21233
|
/** The pinned providers a failure reason should name for this session. */
|
|
21092
21234
|
servingProviders() {
|
|
21093
21235
|
return this.pinnedProviderOverride ? [this.pinnedProviderOverride] : this.pinnedProviders;
|
|
@@ -21106,7 +21248,8 @@ var MonolithCornerTurnLoop = class {
|
|
|
21106
21248
|
/**
|
|
21107
21249
|
* Re-pin the session to the next provider in the OpenRouter order and open a
|
|
21108
21250
|
* fresh session on it, so the retry of an empty completion is served — and
|
|
21109
|
-
* named — by exactly one provider
|
|
21251
|
+
* named — by exactly one provider. Undefined when the pin has nowhere left
|
|
21252
|
+
* to go.
|
|
21110
21253
|
*/
|
|
21111
21254
|
async repinNextProvider(trace, reason) {
|
|
21112
21255
|
const next = nextPinnedProvider(this.pinnedProviders, this.pinnedProviderOverride);
|
|
@@ -21123,523 +21266,418 @@ var MonolithCornerTurnLoop = class {
|
|
|
21123
21266
|
await (trace ? trace.measure("activation", () => this.activate(trace)) : this.activate());
|
|
21124
21267
|
return next;
|
|
21125
21268
|
}
|
|
21126
|
-
|
|
21127
|
-
|
|
21128
|
-
|
|
21129
|
-
|
|
21130
|
-
|
|
21131
|
-
|
|
21132
|
-
|
|
21133
|
-
|
|
21134
|
-
|
|
21135
|
-
|
|
21136
|
-
|
|
21269
|
+
startPrompt(item) {
|
|
21270
|
+
const active = {
|
|
21271
|
+
item,
|
|
21272
|
+
steers: [],
|
|
21273
|
+
steerTail: Promise.resolve(),
|
|
21274
|
+
resumeRequested: false,
|
|
21275
|
+
cancelled: false,
|
|
21276
|
+
phase: "prompting",
|
|
21277
|
+
promise: Promise.resolve()
|
|
21278
|
+
};
|
|
21279
|
+
this.activeTurn = active;
|
|
21280
|
+
active.promise = this.prompt(active).catch((error) => {
|
|
21281
|
+
this.options.health.failure(1e3);
|
|
21282
|
+
console.error(`[thin-core] monolith Room ${this.options.roomId} turn failed:`, error);
|
|
21283
|
+
}).finally(() => {
|
|
21284
|
+
if (this.activeTurn === active) {
|
|
21285
|
+
this.activeTurn = void 0;
|
|
21286
|
+
this.wakeIntake?.();
|
|
21287
|
+
this.wakeIntake = void 0;
|
|
21288
|
+
}
|
|
21137
21289
|
});
|
|
21138
21290
|
}
|
|
21139
|
-
|
|
21140
|
-
|
|
21141
|
-
|
|
21142
|
-
|
|
21143
|
-
|
|
21144
|
-
|
|
21145
|
-
|
|
21146
|
-
|
|
21147
|
-
|
|
21148
|
-
|
|
21291
|
+
/**
|
|
21292
|
+
* Obey a stop the requester already made a fact.
|
|
21293
|
+
*
|
|
21294
|
+
* A stop names ONE request id and touches only the turn that answers it. The
|
|
21295
|
+
* turn in flight is cancelled at the harness and marked so its own run
|
|
21296
|
+
* publishes nothing; a turn still queued is simply dropped, since starting
|
|
21297
|
+
* work the person has already withdrawn is worse than never starting it.
|
|
21298
|
+
* A stop for neither is ignored — it belongs to a turn that ended between
|
|
21299
|
+
* the press and the delivery, and the server's own receipt already said so.
|
|
21300
|
+
*/
|
|
21301
|
+
stopTurn(requestId) {
|
|
21302
|
+
for (let index = this.queuedTurns.length - 1; index >= 0; index -= 1)
|
|
21303
|
+
if (this.queuedTurns[index].id === requestId)
|
|
21304
|
+
this.queuedTurns.splice(index, 1);
|
|
21305
|
+
const active = this.activeTurn;
|
|
21306
|
+
if (!active || active.item.id !== requestId)
|
|
21307
|
+
return;
|
|
21308
|
+
active.cancelled = true;
|
|
21309
|
+
if (this.client && this.sessionId)
|
|
21310
|
+
this.client.sessionCancel(this.sessionId);
|
|
21311
|
+
}
|
|
21312
|
+
async prompt(active) {
|
|
21313
|
+
const { item } = active;
|
|
21314
|
+
const api = this.options.api;
|
|
21315
|
+
this.busy = true;
|
|
21316
|
+
const trace = this.beginTurnTrace(item.id);
|
|
21149
21317
|
try {
|
|
21318
|
+
if (!this.memberNames.has(item.authorId))
|
|
21319
|
+
await this.roster().catch(() => void 0);
|
|
21150
21320
|
await withTurnReceiptHeartbeat(api, {
|
|
21151
21321
|
agentId: this.agent.publicKey,
|
|
21152
|
-
roomId:
|
|
21153
|
-
requestId,
|
|
21154
|
-
generationId:
|
|
21155
|
-
}, () => {
|
|
21322
|
+
roomId: this.options.roomId,
|
|
21323
|
+
requestId: item.id,
|
|
21324
|
+
generationId: this.commandContext.generationId
|
|
21325
|
+
}, async () => {
|
|
21326
|
+
await api.execute("postAgentActivity", {
|
|
21327
|
+
agentId: this.agent.publicKey,
|
|
21328
|
+
roomId: this.options.roomId,
|
|
21329
|
+
requestId: item.id,
|
|
21330
|
+
activity: [
|
|
21331
|
+
{
|
|
21332
|
+
kind: "thinking",
|
|
21333
|
+
title: "Working",
|
|
21334
|
+
status: "in_progress",
|
|
21335
|
+
requestedBy: this.requesterOf(item.authorId)
|
|
21336
|
+
}
|
|
21337
|
+
]
|
|
21338
|
+
});
|
|
21156
21339
|
trace.noteScheduler("queue", this.options.scheduler.snapshot());
|
|
21157
21340
|
trace.start("queue-wait");
|
|
21158
|
-
|
|
21341
|
+
await this.options.scheduler.run(this.options.roomId, this.lifecycle(trace), async () => {
|
|
21159
21342
|
trace.end("queue-wait");
|
|
21160
21343
|
trace.noteScheduler("admission", this.options.scheduler.snapshot());
|
|
21161
|
-
if (this.forcedStop)
|
|
21162
|
-
throw new Error("corner turn stopped for daemon handoff");
|
|
21163
|
-
this.busy = true;
|
|
21164
|
-
await this.syncBranch();
|
|
21165
21344
|
const [conversation, roster, delivered] = await trace.measure("context-fetch", () => Promise.all([
|
|
21166
|
-
api.execute("getRoomConversation", { roomId:
|
|
21345
|
+
api.execute("getRoomConversation", { roomId: this.options.roomId, limit: 200 }),
|
|
21167
21346
|
this.roster(),
|
|
21168
|
-
this.
|
|
21347
|
+
this.deliver(item)
|
|
21169
21348
|
]));
|
|
21170
21349
|
const names = new Map(roster.members.map((member) => [member.identityId, member.name]));
|
|
21171
|
-
const
|
|
21172
|
-
if (requestedBy)
|
|
21173
|
-
this.currentTurn = { requestId, requester: requestedBy };
|
|
21174
|
-
const transcriptRows = conversation.items.slice(-120).map((message) => ({
|
|
21350
|
+
const transcriptRows = conversation.items.filter((message) => message.type === "message" && message.id !== item.id && !active.steers.some((steerItem) => steerItem.id === message.id)).slice(-80).map((message) => ({
|
|
21175
21351
|
id: message.id,
|
|
21176
|
-
line:
|
|
21352
|
+
line: roomMessagePrompt(names.get(message.authorId) ?? message.authorId.slice(0, 12), message.body, message.attachments, this.deliveredAttachments.get(message.id), this.acceptsImages())
|
|
21177
21353
|
}));
|
|
21354
|
+
const grantDecision = this.commandContext.current?.action === "resume";
|
|
21355
|
+
const resumedRequestId = grantDecision ? this.pausedOnGrantRequestId : void 0;
|
|
21356
|
+
if (grantDecision)
|
|
21357
|
+
this.pausedOnGrantRequestId = void 0;
|
|
21178
21358
|
const buildPrompt = () => [
|
|
21179
|
-
this.
|
|
21180
|
-
|
|
21181
|
-
${this.
|
|
21182
|
-
|
|
21359
|
+
this.turnInstructionPrefix,
|
|
21360
|
+
WarmTranscript.render(this.warmTranscript.select(this.sessionId, transcriptRows), "Room conversation so far:", "New in the Room since your last turn (the earlier conversation is already in this session):"),
|
|
21361
|
+
`Newest message from ${inboxItemAuthorName(item, this.agent.publicKey, names)}:`,
|
|
21362
|
+
roomMessagePrompt("", inboxItemPromptBody(item, this.agent.publicKey), item.attachments, delivered, this.acceptsImages()),
|
|
21363
|
+
grantDecision ? [
|
|
21364
|
+
"This is the answer to your grant request; your paused work resumes now.",
|
|
21365
|
+
"If it was approved and it is a command grant, run it with run_granted_command and the exact argv.",
|
|
21366
|
+
"If it was declined, try another way or say plainly what you cannot do."
|
|
21367
|
+
].join(" ") : "",
|
|
21368
|
+
roomMentionDirectory(roster, this.agent.publicKey),
|
|
21183
21369
|
[
|
|
21184
|
-
|
|
21185
|
-
|
|
21186
|
-
|
|
21187
|
-
|
|
21188
|
-
|
|
21189
|
-
MAINTAIN_ASSIGNED_IDENTITY_DIRECTIVE
|
|
21370
|
+
"Write only the substantive Room message you want the human to read.",
|
|
21371
|
+
"Do not repeat or paraphrase these instructions.",
|
|
21372
|
+
"If the newest message is only a nudge to respond, answer the most recent unanswered human message in the conversation instead of echoing the nudge.",
|
|
21373
|
+
MAINTAIN_ASSIGNED_IDENTITY_DIRECTIVE
|
|
21374
|
+
].join(" ")
|
|
21190
21375
|
].filter(Boolean).join("\n\n");
|
|
21191
21376
|
const stream = new AgentTurnStream({
|
|
21192
21377
|
api,
|
|
21193
21378
|
agentId: this.agent.publicKey,
|
|
21194
|
-
roomId:
|
|
21195
|
-
requestId,
|
|
21196
|
-
label: `
|
|
21379
|
+
roomId: this.options.roomId,
|
|
21380
|
+
requestId: item.id,
|
|
21381
|
+
label: `monolith Room ${this.options.roomId}`
|
|
21197
21382
|
});
|
|
21198
|
-
const publishedToolCalls = /* @__PURE__ */ new Set();
|
|
21199
|
-
const publishToolCalls = (calls, settledOnly) => {
|
|
21200
|
-
calls.forEach((call, index) => {
|
|
21201
|
-
const key = toolCallKey(call, index);
|
|
21202
|
-
if (publishedToolCalls.has(key) || settledOnly && !toolCallSettled(call))
|
|
21203
|
-
return;
|
|
21204
|
-
publishedToolCalls.add(key);
|
|
21205
|
-
this.activityTail = this.activityTail.catch(() => void 0).then(async () => {
|
|
21206
|
-
const activity = await cornerToolActivity(call, this.options.worktreePath, requestedBy);
|
|
21207
|
-
await api.execute("postAgentActivity", {
|
|
21208
|
-
agentId: this.agent.publicKey,
|
|
21209
|
-
roomId: cornerId,
|
|
21210
|
-
requestId,
|
|
21211
|
-
activity: [activity]
|
|
21212
|
-
});
|
|
21213
|
-
}).then(() => void 0).catch((error) => {
|
|
21214
|
-
publishedToolCalls.delete(key);
|
|
21215
|
-
console.error(`[thin-core] corner ${cornerId} tool activity failed:`, error);
|
|
21216
|
-
});
|
|
21217
|
-
});
|
|
21218
|
-
};
|
|
21219
21383
|
const runPrompt = async () => {
|
|
21220
|
-
|
|
21221
|
-
|
|
21222
|
-
|
|
21223
|
-
|
|
21224
|
-
|
|
21225
|
-
|
|
21226
|
-
|
|
21227
|
-
|
|
21228
|
-
|
|
21229
|
-
|
|
21230
|
-
|
|
21231
|
-
|
|
21232
|
-
|
|
21233
|
-
|
|
21234
|
-
|
|
21235
|
-
|
|
21236
|
-
|
|
21237
|
-
|
|
21238
|
-
|
|
21239
|
-
|
|
21240
|
-
|
|
21241
|
-
|
|
21242
|
-
|
|
21243
|
-
|
|
21244
|
-
|
|
21245
|
-
|
|
21246
|
-
|
|
21247
|
-
|
|
21248
|
-
|
|
21249
|
-
|
|
21250
|
-
|
|
21251
|
-
|
|
21252
|
-
|
|
21384
|
+
let nextPrompt = promptWithImages(buildPrompt(), attachmentImageBlocks(delivered, this.acceptsImages()));
|
|
21385
|
+
let result2;
|
|
21386
|
+
for (; ; ) {
|
|
21387
|
+
let promptError;
|
|
21388
|
+
try {
|
|
21389
|
+
stream.beginRun();
|
|
21390
|
+
trace.promptSent();
|
|
21391
|
+
result2 = await this.client.sessionPrompt(this.sessionId, nextPrompt, 12e4, (delta, full) => {
|
|
21392
|
+
trace.firstModelOutput();
|
|
21393
|
+
stream.onChunk(delta, full);
|
|
21394
|
+
}, void 0, (calls) => trace.toolCalls(calls));
|
|
21395
|
+
} catch (error) {
|
|
21396
|
+
promptError = error;
|
|
21397
|
+
}
|
|
21398
|
+
const settledSteerTail = active.steerTail;
|
|
21399
|
+
await settledSteerTail;
|
|
21400
|
+
if (settledSteerTail !== active.steerTail)
|
|
21401
|
+
continue;
|
|
21402
|
+
if (!active.resumeRequested) {
|
|
21403
|
+
if (promptError)
|
|
21404
|
+
throw promptError;
|
|
21405
|
+
break;
|
|
21406
|
+
}
|
|
21407
|
+
active.resumeRequested = false;
|
|
21408
|
+
nextPrompt = [
|
|
21409
|
+
"The previous run was cancelled because its harness could not accept every live steer.",
|
|
21410
|
+
"Resume the same turn. Keep the original request and everything that happened before it was cancelled.",
|
|
21411
|
+
"Human messages that arrived after the original request, in transcript order:",
|
|
21412
|
+
...active.steers.map((steerItem) => roomMessagePrompt(steerItem.authorId.slice(0, 12), steerItem.body, steerItem.attachments, this.deliveredAttachments.get(steerItem.id), this.acceptsImages())),
|
|
21413
|
+
"Continue now and answer the updated request without erasing the earlier context."
|
|
21414
|
+
].join("\n\n");
|
|
21415
|
+
}
|
|
21416
|
+
return result2;
|
|
21417
|
+
};
|
|
21418
|
+
let result = await runPrompt();
|
|
21419
|
+
trace.promptSettled();
|
|
21420
|
+
let explained = await this.explainEmpty(result);
|
|
21421
|
+
if (explained && shouldRetryEmptyTurn(explained)) {
|
|
21422
|
+
const silent = this.servingProviders();
|
|
21423
|
+
const next = await this.repinNextProvider(trace, explained.reason);
|
|
21424
|
+
if (next) {
|
|
21425
|
+
console.warn(`[thin-core] monolith Room ${this.options.roomId} turn ${item.id}: ${turnFailureReasonWithProvider(explained.reason, silent)}; retrying on ${next}`);
|
|
21426
|
+
result = await runPrompt();
|
|
21427
|
+
trace.promptSettled();
|
|
21428
|
+
explained = await this.explainEmpty(result);
|
|
21429
|
+
}
|
|
21430
|
+
}
|
|
21431
|
+
if (active.cancelled) {
|
|
21432
|
+
stream.close();
|
|
21433
|
+
throw new TurnStoppedError("turn stopped by the requester");
|
|
21434
|
+
}
|
|
21435
|
+
active.phase = "finishing";
|
|
21436
|
+
if (result.toolCalls.some((call) => pendingGrantToolCall(call))) {
|
|
21437
|
+
this.pausedOnGrantRequestId = item.id;
|
|
21438
|
+
console.log(`[thin-core] monolith Room ${this.options.roomId} turn ${item.id} paused on a grant card`);
|
|
21439
|
+
} else if (resumedRequestId) {
|
|
21440
|
+
console.log(`[thin-core] monolith Room ${this.options.roomId} turn ${resumedRequestId} resumed by grant decision ${item.id}`);
|
|
21441
|
+
}
|
|
21442
|
+
const openCornerCall = result.toolCalls.find((call) => /(?:^|[._:/-])open_corner$/i.test(call.title ?? ""));
|
|
21443
|
+
if (openCornerCall) {
|
|
21444
|
+
console.log(`[thin-core] monolith Room ${this.options.roomId} tool call: ${openCornerCall.title} (${openCornerCall.status ?? "no status"})`);
|
|
21445
|
+
if (!isFailedToolCall(openCornerCall))
|
|
21446
|
+
this.options.onCornerOpened?.();
|
|
21447
|
+
}
|
|
21448
|
+
for (const call of result?.toolCalls ?? []) {
|
|
21449
|
+
const failure = toolCallFailureLine(call);
|
|
21450
|
+
if (failure) {
|
|
21451
|
+
console.warn(`[thin-core] monolith Room ${this.options.roomId} ${failure}`);
|
|
21452
|
+
}
|
|
21453
|
+
}
|
|
21454
|
+
stream.close();
|
|
21455
|
+
let reply = durableReplyText(result.agentText);
|
|
21253
21456
|
if (!reply && explained) {
|
|
21254
21457
|
reply = explained.recoveredText ? durableReplyText(explained.recoveredText) : "";
|
|
21255
|
-
if (!reply
|
|
21458
|
+
if (!reply) {
|
|
21256
21459
|
throw new Error(turnFailureReasonWithProvider(explained.reason, this.servingProviders()));
|
|
21257
21460
|
}
|
|
21258
|
-
console.warn(`[thin-core]
|
|
21461
|
+
console.warn(`[thin-core] monolith Room ${this.options.roomId} turn ${item.id}: ${explained.reason}`);
|
|
21259
21462
|
}
|
|
21260
|
-
|
|
21261
|
-
|
|
21262
|
-
|
|
21463
|
+
if (openCornerCall && !isFailedToolCall(openCornerCall)) {
|
|
21464
|
+
reply = stripCornerOpenEcho(reply);
|
|
21465
|
+
}
|
|
21466
|
+
const mentionIds = reply ? agentReplyMentionIds(reply, roster, this.agent.publicKey) : [];
|
|
21467
|
+
await trace.measure("publish", () => stream.settle(reply, reply ? {
|
|
21468
|
+
triggerMessageId: item.id,
|
|
21469
|
+
mentionIds
|
|
21470
|
+
} : {}));
|
|
21471
|
+
}, { priority: "interactive", roomKey: this.options.roomId });
|
|
21472
|
+
}, (error) => console.error(`[thin-core] monolith Room ${this.options.roomId} receipt heartbeat failed:`, error));
|
|
21263
21473
|
await api.execute("postAgentTurnReceipt", {
|
|
21264
21474
|
agentId: this.agent.publicKey,
|
|
21265
|
-
roomId:
|
|
21266
|
-
requestId,
|
|
21475
|
+
roomId: this.options.roomId,
|
|
21476
|
+
requestId: item.id,
|
|
21267
21477
|
status: "complete",
|
|
21268
|
-
generationId:
|
|
21478
|
+
generationId: this.commandContext.generationId
|
|
21269
21479
|
});
|
|
21270
21480
|
await trace.finish("complete");
|
|
21271
21481
|
} catch (error) {
|
|
21482
|
+
if (error instanceof TurnStoppedError || active.cancelled) {
|
|
21483
|
+
console.log(`[thin-core] monolith Room ${this.options.roomId} turn ${item.id} stopped by the requester`);
|
|
21484
|
+
await trace.finish("cancelled");
|
|
21485
|
+
return;
|
|
21486
|
+
}
|
|
21272
21487
|
const reason = distillTurnFailureReason(error);
|
|
21273
21488
|
await api.execute("postAgentTurnReceipt", {
|
|
21274
21489
|
agentId: this.agent.publicKey,
|
|
21275
|
-
roomId:
|
|
21276
|
-
requestId,
|
|
21490
|
+
roomId: this.options.roomId,
|
|
21491
|
+
requestId: item.id,
|
|
21277
21492
|
status: "failed",
|
|
21278
|
-
generationId:
|
|
21493
|
+
generationId: this.commandContext.generationId,
|
|
21279
21494
|
reason
|
|
21280
21495
|
});
|
|
21281
21496
|
await trace.finish("failed", reason);
|
|
21282
21497
|
throw error;
|
|
21283
21498
|
} finally {
|
|
21284
21499
|
this.busy = false;
|
|
21285
|
-
this.currentTurn = void 0;
|
|
21286
21500
|
}
|
|
21287
21501
|
}
|
|
21288
|
-
/** Whether this agent opened the corner. A corner with no recorded opener
|
|
21289
|
-
* behaves exactly as it did before members could carry it. */
|
|
21290
|
-
isOpener() {
|
|
21291
|
-
return !this.options.openedBy || this.options.openedBy === this.agent.publicKey;
|
|
21292
|
-
}
|
|
21293
|
-
/**
|
|
21294
|
-
* Whether this agent is the one carrying the corner right now.
|
|
21295
|
-
*
|
|
21296
|
-
* Every member agent polls the corner, but its lifecycle — a server check
|
|
21297
|
-
* note, a close request answered with work — is ONE fact and must start ONE
|
|
21298
|
-
* turn, not one per member (the "one check turn per changed server state"
|
|
21299
|
-
* rule). The carrier is whoever answered in the corner last, which is the
|
|
21300
|
-
* opener until a human hands the work to someone else.
|
|
21301
|
-
*/
|
|
21302
|
-
carriesCorner() {
|
|
21303
|
-
return (this.carrier ?? this.options.openedBy ?? this.agent.publicKey) === this.agent.publicKey;
|
|
21304
|
-
}
|
|
21305
|
-
/** Notes an agent's durable message as the corner changing hands. */
|
|
21306
|
-
noteCarrier(authorId) {
|
|
21307
|
-
if (this.agentMembers.has(authorId))
|
|
21308
|
-
this.carrier = authorId;
|
|
21309
|
-
}
|
|
21310
|
-
/**
|
|
21311
|
-
* Whether a human message in this corner is addressed to THIS agent.
|
|
21312
|
-
*
|
|
21313
|
-
* A corner now runs like a Room — every member agent polls it — so an
|
|
21314
|
-
* unrouted message would start one turn per member on one branch. A mention
|
|
21315
|
-
* routes: the mentioned agent answers and nobody else. A message that names
|
|
21316
|
-
* no agent at all keeps the old behaviour and falls to the opener, which is
|
|
21317
|
-
* every single-agent corner ever opened.
|
|
21318
|
-
*/
|
|
21319
|
-
async addressesThisAgent(item) {
|
|
21320
|
-
if (item.mentionIds.includes(this.agent.publicKey))
|
|
21321
|
-
return true;
|
|
21322
|
-
if (!item.mentionIds.length)
|
|
21323
|
-
return this.carriesCorner();
|
|
21324
|
-
await this.roster().catch(() => void 0);
|
|
21325
|
-
return item.mentionIds.some((id) => this.agentMembers.has(id)) ? false : this.carriesCorner();
|
|
21326
|
-
}
|
|
21327
|
-
/**
|
|
21328
|
-
* Bring this worktree onto the corner's branch as GitHub currently has it,
|
|
21329
|
-
* before any work is done on top of it.
|
|
21330
|
-
*
|
|
21331
|
-
* The branch is the shared artifact: another member agent may have pushed to
|
|
21332
|
-
* it since this helper last looked, and a first touch of a corner this
|
|
21333
|
-
* helper did not open starts from whatever `room-runtime.ts` restored. A
|
|
21334
|
-
* divergence this cannot rebase away is raised, not pushed over — the turn
|
|
21335
|
-
* fails with that sentence and the server inscribes it in the corner.
|
|
21336
|
-
*/
|
|
21337
|
-
async syncBranch() {
|
|
21338
|
-
const repository = this.options.repository;
|
|
21339
|
-
if (!repository)
|
|
21340
|
-
return;
|
|
21341
|
-
const token = await this.options.api.execute("getRoomGitHubToken", { roomId: this.options.parentRoomId }).then((granted) => granted.token).catch(() => repository.githubToken);
|
|
21342
|
-
await syncCornerBranch({
|
|
21343
|
-
worktreePath: this.options.worktreePath,
|
|
21344
|
-
featureBranch: repository.featureBranch,
|
|
21345
|
-
env: { ...process.env, GH_TOKEN: token, GITHUB_TOKEN: token, GIT_TERMINAL_PROMPT: "0" }
|
|
21346
|
-
});
|
|
21347
|
-
}
|
|
21348
|
-
/** The server's check state for this head, or the notes' own verdict when the server carries none. */
|
|
21349
|
-
async checksState(notes) {
|
|
21350
|
-
try {
|
|
21351
|
-
const restore = await this.options.api.execute("getCornerRestoreState", {
|
|
21352
|
-
cornerId: this.options.cornerId
|
|
21353
|
-
});
|
|
21354
|
-
const fromServer = checksStateFromLifecycle(restore.lifecycle);
|
|
21355
|
-
if (fromServer)
|
|
21356
|
-
return fromServer;
|
|
21357
|
-
} catch (error) {
|
|
21358
|
-
console.error(`[thin-core] corner ${this.options.cornerId} check state read failed:`, error);
|
|
21359
|
-
}
|
|
21360
|
-
return notes.some((note) => completedCheckNote(note) === "failed") ? "failing" : "passing";
|
|
21361
|
-
}
|
|
21362
21502
|
async run() {
|
|
21363
|
-
const { api,
|
|
21364
|
-
const activation = await api.execute("getRoomInbox", {
|
|
21365
|
-
roomId: cornerId,
|
|
21366
|
-
startAtLatest: true
|
|
21367
|
-
});
|
|
21368
|
-
let cursor3 = activation.cursor;
|
|
21369
|
-
const processedInboxIds = /* @__PURE__ */ new Set();
|
|
21370
|
-
const pushedInbox = [];
|
|
21371
|
-
let pendingPushedCursor;
|
|
21372
|
-
let liveConnected = false;
|
|
21373
|
-
const rewindSupported = Array.isArray(activation.rewindIds);
|
|
21374
|
-
for (const id of activation.rewindIds ?? [])
|
|
21375
|
-
processedInboxIds.add(id);
|
|
21376
|
-
const stopLive = api.liveSubscribe?.(cornerId, cursor3, (items, pushedCursor) => {
|
|
21377
|
-
pushedInbox.push(...items);
|
|
21378
|
-
pendingPushedCursor = laterInboxCursor(pendingPushedCursor, pushedCursor);
|
|
21379
|
-
this.wakeIntake?.();
|
|
21380
|
-
this.wakeIntake = void 0;
|
|
21381
|
-
}, (connected, capabilities) => {
|
|
21382
|
-
liveConnected = connected && capabilities?.pushIntake === true;
|
|
21383
|
-
this.wakeIntake?.();
|
|
21384
|
-
this.wakeIntake = void 0;
|
|
21385
|
-
}, {
|
|
21386
|
-
...this.options.config.daemonReleaseVersion ? { releaseVersion: this.options.config.daemonReleaseVersion } : {},
|
|
21387
|
-
...this.options.config.daemonSourceSha ? { sourceSha: this.options.config.daemonSourceSha } : {},
|
|
21388
|
-
available: !this.options.config.modelUnavailable
|
|
21389
|
-
});
|
|
21390
|
-
const history = await api.execute("getRoomConversation", { roomId: cornerId, limit: 200 });
|
|
21391
|
-
await this.roster().catch(() => void 0);
|
|
21392
|
-
for (const item of history.items)
|
|
21393
|
-
if (item.type === "message")
|
|
21394
|
-
this.noteCarrier(item.authorId);
|
|
21395
|
-
const durableAgentReplies = history.items.filter((item) => item.type === "message" && item.authorId === this.agent.publicKey && item.body.trim() !== this.options.objective.trim());
|
|
21396
|
-
if (durableAgentReplies.length === 0 && this.isOpener()) {
|
|
21397
|
-
await this.prompt(history.items.find((item) => item.requestId)?.requestId ?? cornerId.replaceAll("-", ""), this.options.objective);
|
|
21398
|
-
}
|
|
21503
|
+
const { api, roomId, signal } = this.options;
|
|
21399
21504
|
try {
|
|
21400
|
-
|
|
21401
|
-
|
|
21402
|
-
|
|
21403
|
-
|
|
21404
|
-
|
|
21405
|
-
|
|
21406
|
-
|
|
21407
|
-
|
|
21408
|
-
}
|
|
21409
|
-
|
|
21410
|
-
|
|
21411
|
-
|
|
21412
|
-
|
|
21413
|
-
|
|
21414
|
-
|
|
21415
|
-
|
|
21416
|
-
|
|
21417
|
-
|
|
21418
|
-
|
|
21419
|
-
|
|
21420
|
-
|
|
21421
|
-
|
|
21422
|
-
|
|
21423
|
-
|
|
21424
|
-
|
|
21425
|
-
|
|
21426
|
-
if (item.authorId === this.agent.publicKey)
|
|
21427
|
-
continue;
|
|
21428
|
-
if (!await this.addressesThisAgent(item))
|
|
21429
|
-
continue;
|
|
21430
|
-
const authority = await api.execute("getRoomAuthority", {
|
|
21431
|
-
roomId: cornerId,
|
|
21432
|
-
principalId: item.authorId
|
|
21433
|
-
});
|
|
21434
|
-
if (!authority.member || authority.principalKind !== "human")
|
|
21435
|
-
continue;
|
|
21436
|
-
await this.prompt(item.id, item.body, item.attachments, item.authorId);
|
|
21437
|
-
pollWithoutWait = true;
|
|
21438
|
-
continue;
|
|
21439
|
-
}
|
|
21440
|
-
const grantDecision = item.type === "system" && item.mentionIds.includes(this.agent.publicKey) && parseGrantDecisionLine(item.body) !== void 0;
|
|
21441
|
-
if (grantDecision) {
|
|
21442
|
-
await this.prompt(item.id, `${item.body}
|
|
21443
|
-
This answers your grant request; resume the paused work. If approved and it is a command grant, run it with run_granted_command and the exact argv; if declined, try another way or say what you cannot do.`, [], item.authorId);
|
|
21444
|
-
pollWithoutWait = true;
|
|
21445
|
-
continue;
|
|
21446
|
-
}
|
|
21447
|
-
if (isCheckStartNote(item)) {
|
|
21448
|
-
this.lastChecksState = void 0;
|
|
21449
|
-
continue;
|
|
21450
|
-
}
|
|
21451
|
-
if (completedCheckNote(item))
|
|
21452
|
-
checkNotes.push(item);
|
|
21453
|
-
}
|
|
21454
|
-
if (checkNotes.length && this.carriesCorner()) {
|
|
21455
|
-
const state = await this.checksState(checkNotes);
|
|
21456
|
-
if (state && state !== "pending" && state !== this.lastChecksState) {
|
|
21457
|
-
this.lastChecksState = state;
|
|
21458
|
-
const lines = checkNotes.map((note) => note.body);
|
|
21459
|
-
await this.prompt(checkNotes[checkNotes.length - 1].id, lines.join("\n"), [], void 0, lines);
|
|
21460
|
-
pollWithoutWait = true;
|
|
21461
|
-
}
|
|
21462
|
-
}
|
|
21463
|
-
cursor3 = laterInboxCursor(cursor3, laterInboxCursor(inbox.cursor, pendingPushedCursor));
|
|
21464
|
-
pendingPushedCursor = void 0;
|
|
21465
|
-
api.updateLiveCursor?.(cornerId, cursor3);
|
|
21466
|
-
if (pollNow)
|
|
21467
|
-
this.options.onPoll();
|
|
21468
|
-
await Promise.race([
|
|
21469
|
-
wait(pollWithoutWait ? 0 : liveConnected ? 2147483647 : this.options.pollMs ?? cornerClosePollMs(), signal),
|
|
21470
|
-
pushedInbox.length ? Promise.resolve() : new Promise((resolve30) => {
|
|
21471
|
-
this.wakeIntake = resolve30;
|
|
21472
|
-
})
|
|
21473
|
-
]);
|
|
21474
|
-
pollWithoutWait = false;
|
|
21475
|
-
} catch (error) {
|
|
21476
|
-
if (signal?.aborted)
|
|
21477
|
-
break;
|
|
21478
|
-
this.options.onFailure(1e3);
|
|
21479
|
-
console.error(`[thin-core] corner ${cornerId} turn loop failed:`, error);
|
|
21480
|
-
await wait(1e3, signal);
|
|
21505
|
+
await runServerCommandIntake({
|
|
21506
|
+
api,
|
|
21507
|
+
roomId,
|
|
21508
|
+
agentId: this.agent.publicKey,
|
|
21509
|
+
context: this.commandContext,
|
|
21510
|
+
signal,
|
|
21511
|
+
pollMs: this.options.pollMs,
|
|
21512
|
+
presence: {
|
|
21513
|
+
...this.options.config.daemonReleaseVersion ? { releaseVersion: this.options.config.daemonReleaseVersion } : {},
|
|
21514
|
+
...this.options.config.daemonSourceSha ? { sourceSha: this.options.config.daemonSourceSha } : {},
|
|
21515
|
+
available: !this.options.config.modelUnavailable
|
|
21516
|
+
},
|
|
21517
|
+
onWake: (wake) => {
|
|
21518
|
+
this.wakeIntake = wake;
|
|
21519
|
+
},
|
|
21520
|
+
onPoll: () => this.options.health.poll(),
|
|
21521
|
+
onError: (error) => console.error("[thin-core] Room command failed", error),
|
|
21522
|
+
stop: (requestId) => this.stopTurn(requestId),
|
|
21523
|
+
run: async (command) => {
|
|
21524
|
+
const item = {
|
|
21525
|
+
...command.source,
|
|
21526
|
+
id: command.turnRequestId,
|
|
21527
|
+
body: command.action === "resume" ? `Resume the paused turn. The server supplied this grant decision: ${command.source.body}` : command.source.body
|
|
21528
|
+
};
|
|
21529
|
+
this.startPrompt(item);
|
|
21530
|
+
await this.activeTurn?.promise;
|
|
21481
21531
|
}
|
|
21482
|
-
}
|
|
21532
|
+
});
|
|
21483
21533
|
} finally {
|
|
21484
|
-
|
|
21485
|
-
this.
|
|
21486
|
-
this.options.grantRunner?.unregister(cornerId);
|
|
21487
|
-
await this.options.scheduler.suspend(cornerId);
|
|
21534
|
+
this.options.grantRunner?.unregister(roomId);
|
|
21535
|
+
await this.options.scheduler.suspend(roomId);
|
|
21488
21536
|
}
|
|
21489
21537
|
}
|
|
21490
21538
|
};
|
|
21491
|
-
|
|
21492
|
-
|
|
21493
|
-
|
|
21494
|
-
|
|
21495
|
-
const done = () => {
|
|
21496
|
-
clearTimeout(timer);
|
|
21497
|
-
signal?.removeEventListener("abort", done);
|
|
21498
|
-
resolveWait();
|
|
21499
|
-
};
|
|
21500
|
-
const timer = setTimeout(done, ms);
|
|
21501
|
-
signal?.addEventListener("abort", done, { once: true });
|
|
21502
|
-
});
|
|
21539
|
+
function roomMessagePrompt(author, body, attachments, delivered, harnessAcceptsImages = true) {
|
|
21540
|
+
const message = body.trim() || "(shared attachments)";
|
|
21541
|
+
const rendered = author ? `${author}: ${message}` : message;
|
|
21542
|
+
return [rendered, ...attachmentPromptLines(attachments, delivered, harnessAcceptsImages)].join("\n");
|
|
21503
21543
|
}
|
|
21504
21544
|
|
|
21505
|
-
// apps/body/dist/monolith-
|
|
21506
|
-
|
|
21507
|
-
|
|
21508
|
-
|
|
21509
|
-
|
|
21510
|
-
|
|
21511
|
-
|
|
21512
|
-
var SCHEDULE_RAN_VERB = "ran a schedule for";
|
|
21513
|
-
|
|
21514
|
-
// apps/body/dist/monolith-room-turn.js
|
|
21515
|
-
function isRoomMcpPermissionRequest(request, mountedServers = ROOM_MOUNTED_MCP_SERVERS) {
|
|
21516
|
-
if (isSquireMcpPermissionRequest(request))
|
|
21517
|
-
return false;
|
|
21518
|
-
return isMountedMcpToolPermissionRequest(request, mountedServers);
|
|
21519
|
-
}
|
|
21520
|
-
function roomPrincipalMayAddressAgent(authority, humanPermitted) {
|
|
21521
|
-
if (!authority.member)
|
|
21522
|
-
return false;
|
|
21523
|
-
if (authority.principalKind === "agent")
|
|
21524
|
-
return true;
|
|
21525
|
-
if (authority.principalKind !== "human")
|
|
21526
|
-
return false;
|
|
21527
|
-
return authority.mayAddressAgent ?? humanPermitted;
|
|
21545
|
+
// apps/body/dist/monolith-corner-turn.js
|
|
21546
|
+
var execFileAsync3 = promisify3(execFile4);
|
|
21547
|
+
var TOOL_ARGUMENT_MAX_BYTES = 1200;
|
|
21548
|
+
var TOOL_OUTPUT_MAX_BYTES = 3200;
|
|
21549
|
+
var TOOL_PATH_LIMIT = 12;
|
|
21550
|
+
function oneLine(value) {
|
|
21551
|
+
return value.replace(/\s+/g, " ").trim();
|
|
21528
21552
|
}
|
|
21529
|
-
function
|
|
21530
|
-
if (
|
|
21531
|
-
return
|
|
21532
|
-
|
|
21533
|
-
return
|
|
21534
|
-
|
|
21553
|
+
function serialized(value) {
|
|
21554
|
+
if (typeof value === "string")
|
|
21555
|
+
return value;
|
|
21556
|
+
try {
|
|
21557
|
+
return JSON.stringify(value) ?? "";
|
|
21558
|
+
} catch {
|
|
21559
|
+
return "";
|
|
21560
|
+
}
|
|
21535
21561
|
}
|
|
21536
|
-
function
|
|
21537
|
-
|
|
21538
|
-
return SCHEDULE_SCHEDULER_NAME;
|
|
21539
|
-
const subject = item.systemEvent?.subject;
|
|
21540
|
-
if (item.type === "system" && subject?.name)
|
|
21541
|
-
return subject.name;
|
|
21542
|
-
return names.get(item.authorId) ?? item.authorId.slice(0, 12);
|
|
21562
|
+
function record(value) {
|
|
21563
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
21543
21564
|
}
|
|
21544
|
-
function
|
|
21545
|
-
|
|
21565
|
+
function clampBytes(value, maxBytes) {
|
|
21566
|
+
const clean4 = value.trim();
|
|
21567
|
+
if (Buffer.byteLength(clean4) <= maxBytes)
|
|
21568
|
+
return clean4;
|
|
21569
|
+
const suffix = "\n\u2026[truncated]";
|
|
21570
|
+
const allowed = maxBytes - Buffer.byteLength(suffix);
|
|
21571
|
+
return `${Buffer.from(clean4).subarray(0, Math.max(0, allowed)).toString("utf8")}${suffix}`;
|
|
21546
21572
|
}
|
|
21547
|
-
function
|
|
21548
|
-
const
|
|
21549
|
-
|
|
21573
|
+
function outputExcerpt(value) {
|
|
21574
|
+
const redacted = redactToolDetail(serialized(value));
|
|
21575
|
+
if (!redacted.trim())
|
|
21576
|
+
return void 0;
|
|
21577
|
+
if (/\b(?:git[- ]credential|credential[- ]helper)\b/i.test(redacted)) {
|
|
21578
|
+
return "Credential-helper output omitted.";
|
|
21579
|
+
}
|
|
21580
|
+
const lines = redacted.split(/\r?\n/).map((line) => line.trimEnd());
|
|
21581
|
+
if (lines.length <= 8)
|
|
21582
|
+
return clampBytes(lines.join("\n"), TOOL_OUTPUT_MAX_BYTES);
|
|
21583
|
+
return clampBytes([...lines.slice(0, 4), "\u2026[output omitted]\u2026", ...lines.slice(-4)].join("\n"), TOOL_OUTPUT_MAX_BYTES);
|
|
21550
21584
|
}
|
|
21551
|
-
function
|
|
21552
|
-
|
|
21553
|
-
|
|
21554
|
-
|
|
21555
|
-
|
|
21556
|
-
|
|
21557
|
-
|
|
21585
|
+
function filePaths(value, worktreePath) {
|
|
21586
|
+
const paths = /* @__PURE__ */ new Set();
|
|
21587
|
+
const visit = (candidate, key) => {
|
|
21588
|
+
if (paths.size >= TOOL_PATH_LIMIT || candidate === null || candidate === void 0)
|
|
21589
|
+
return;
|
|
21590
|
+
if (typeof candidate === "string") {
|
|
21591
|
+
if (key && /(?:^|_)(?:path|file|filename|target)$/i.test(key)) {
|
|
21592
|
+
const path = candidate.startsWith(`${worktreePath}/`) ? candidate.slice(worktreePath.length + 1) : candidate;
|
|
21593
|
+
if (path && path.length <= 512)
|
|
21594
|
+
paths.add(redactToolDetail(path));
|
|
21595
|
+
}
|
|
21596
|
+
return;
|
|
21597
|
+
}
|
|
21598
|
+
if (Array.isArray(candidate)) {
|
|
21599
|
+
candidate.forEach((entry) => visit(entry));
|
|
21600
|
+
return;
|
|
21601
|
+
}
|
|
21602
|
+
const object = record(candidate);
|
|
21603
|
+
if (object)
|
|
21604
|
+
Object.entries(object).forEach(([entryKey, entry]) => visit(entry, entryKey));
|
|
21605
|
+
};
|
|
21606
|
+
visit(value);
|
|
21607
|
+
return [...paths];
|
|
21558
21608
|
}
|
|
21559
|
-
function
|
|
21560
|
-
|
|
21609
|
+
function resultStatus(call) {
|
|
21610
|
+
const content = record(call.content) ?? (typeof call.content === "string" ? (() => {
|
|
21611
|
+
try {
|
|
21612
|
+
return record(JSON.parse(call.content));
|
|
21613
|
+
} catch {
|
|
21614
|
+
return void 0;
|
|
21615
|
+
}
|
|
21616
|
+
})() : void 0);
|
|
21617
|
+
const exitCode = content?.exitCode ?? content?.exit_code ?? content?.code;
|
|
21618
|
+
if (typeof exitCode === "number" && Number.isFinite(exitCode))
|
|
21619
|
+
return `exit ${exitCode}`;
|
|
21620
|
+
if (content?.ok === true || content?.success === true)
|
|
21621
|
+
return "ok";
|
|
21622
|
+
if (content?.ok === false || content?.success === false)
|
|
21623
|
+
return "error";
|
|
21624
|
+
return /(?:failed|error|denied)/i.test(call.status ?? "") ? "error" : "ok";
|
|
21561
21625
|
}
|
|
21562
|
-
function
|
|
21563
|
-
|
|
21564
|
-
|
|
21565
|
-
if (
|
|
21566
|
-
return
|
|
21567
|
-
|
|
21626
|
+
function toolArguments(call) {
|
|
21627
|
+
const raw = record(call.rawInput);
|
|
21628
|
+
const command = typeof call.rawInput === "string" ? call.rawInput : typeof raw?.command === "string" ? raw.command : typeof raw?.cmd === "string" ? raw.cmd : void 0;
|
|
21629
|
+
if (command)
|
|
21630
|
+
return { command: clampBytes(redactToolDetail(command), TOOL_ARGUMENT_MAX_BYTES) };
|
|
21631
|
+
const input = serialized(call.rawInput);
|
|
21632
|
+
return input ? { input: clampBytes(redactToolDetail(input), TOOL_ARGUMENT_MAX_BYTES) } : {};
|
|
21568
21633
|
}
|
|
21569
|
-
function
|
|
21570
|
-
|
|
21571
|
-
return false;
|
|
21572
|
-
return /pending, card posted/i.test(typeof call.content === "string" ? call.content : JSON.stringify(call.content ?? ""));
|
|
21634
|
+
function toolCallKey(call, index) {
|
|
21635
|
+
return call.id ? `id-${createHash4("sha256").update(call.id).digest("hex")}` : `tool-${index}`;
|
|
21573
21636
|
}
|
|
21574
|
-
function
|
|
21575
|
-
|
|
21576
|
-
|
|
21577
|
-
|
|
21578
|
-
continue;
|
|
21579
|
-
const handle = member.handle?.trim().replace(/^@/, "");
|
|
21580
|
-
const name = member.name?.trim() ?? "";
|
|
21581
|
-
const alias = handle || name;
|
|
21582
|
-
if (!alias)
|
|
21583
|
-
continue;
|
|
21584
|
-
const kind = member.kind === "agent" ? "agent" : "person";
|
|
21585
|
-
rows.push(`- @${alias}${name && name !== alias ? ` \u2014 ${name}` : ""} (${kind})`);
|
|
21586
|
-
}
|
|
21587
|
-
if (!rows.length)
|
|
21588
|
-
return "";
|
|
21589
|
-
return [
|
|
21590
|
-
"Room members, and the exact spelling that tags each one:",
|
|
21591
|
-
...rows,
|
|
21592
|
-
"Write a tag exactly as spelled here. An @name spelled any other way is plain text: it reaches nobody, and nobody is told it was meant for them. Never invent a handle, shorten one, or copy an @name out of the conversation \u2014 old messages carry spellings that no longer exist."
|
|
21593
|
-
].join("\n");
|
|
21637
|
+
function toolCallSettled(call) {
|
|
21638
|
+
if (call.resultReceived)
|
|
21639
|
+
return true;
|
|
21640
|
+
return /^(?:completed|complete|failed|error|succeeded|success|passed|done)$/i.test(call.status ?? "");
|
|
21594
21641
|
}
|
|
21595
|
-
function
|
|
21596
|
-
|
|
21642
|
+
function isSuccessfulCommit(call) {
|
|
21643
|
+
if (/failed|error|denied/i.test(call.status ?? ""))
|
|
21644
|
+
return false;
|
|
21645
|
+
return /\bgit\s+commit\b|\bcommit(?:ted)?\s+(?:changes|files?)\b/i.test(`${call.title ?? ""} ${serialized(call.rawInput)}`);
|
|
21597
21646
|
}
|
|
21598
|
-
function
|
|
21599
|
-
const
|
|
21600
|
-
|
|
21601
|
-
|
|
21602
|
-
|
|
21603
|
-
|
|
21604
|
-
const
|
|
21605
|
-
|
|
21606
|
-
|
|
21607
|
-
|
|
21608
|
-
|
|
21609
|
-
entry.ids.add(member.identityId);
|
|
21610
|
-
aliases.set(key, entry);
|
|
21647
|
+
async function cornerToolActivity(call, worktreePath, requestedBy) {
|
|
21648
|
+
const operation = oneLine(call.kind ?? "") || "tool";
|
|
21649
|
+
let title = oneLine(redactToolDetail(call.title ?? "")) || `${operation} tool`;
|
|
21650
|
+
if (isSuccessfulCommit(call)) {
|
|
21651
|
+
try {
|
|
21652
|
+
const shown = await execFileAsync3("git", ["-C", worktreePath, "show", "--format=%s", "--name-only", "--no-renames", "HEAD"], { maxBuffer: 1024 * 1024 });
|
|
21653
|
+
const lines = shown.stdout.split(/\r?\n/);
|
|
21654
|
+
const subject = oneLine(lines.shift() ?? "commit");
|
|
21655
|
+
const files = new Set(lines.map(oneLine).filter(Boolean));
|
|
21656
|
+
title = `committed ${files.size} files: ${subject}`;
|
|
21657
|
+
} catch {
|
|
21611
21658
|
}
|
|
21612
21659
|
}
|
|
21613
|
-
|
|
21614
|
-
|
|
21615
|
-
|
|
21616
|
-
|
|
21617
|
-
|
|
21618
|
-
|
|
21619
|
-
|
|
21620
|
-
|
|
21621
|
-
|
|
21622
|
-
|
|
21623
|
-
|
|
21624
|
-
|
|
21625
|
-
|
|
21626
|
-
const pattern = new RegExp(`(^|[\\s([{])@${escapeRegExp(display)}(?=$|[\\s.,!?;:)\\]}])`, "iu");
|
|
21627
|
-
if (!pattern.test(text2))
|
|
21628
|
-
continue;
|
|
21629
|
-
const identityId = [...ids][0];
|
|
21630
|
-
if (!mentioned.includes(identityId))
|
|
21631
|
-
mentioned.push(identityId);
|
|
21632
|
-
}
|
|
21633
|
-
return mentioned;
|
|
21660
|
+
const paths = filePaths([call.rawInput, call.content, call.locations], worktreePath);
|
|
21661
|
+
const argumentsSummary = toolArguments(call);
|
|
21662
|
+
const output = outputExcerpt(call.content);
|
|
21663
|
+
return {
|
|
21664
|
+
kind: "tool",
|
|
21665
|
+
title: title.slice(0, 240),
|
|
21666
|
+
operation: operation.slice(0, 80),
|
|
21667
|
+
status: resultStatus(call),
|
|
21668
|
+
...argumentsSummary,
|
|
21669
|
+
...output ? { output } : {},
|
|
21670
|
+
...requestedBy ? { requestedBy } : {},
|
|
21671
|
+
...paths.length ? { files: paths.map((path) => ({ path })) } : {}
|
|
21672
|
+
};
|
|
21634
21673
|
}
|
|
21635
|
-
var
|
|
21674
|
+
var MonolithCornerTurnLoop = class {
|
|
21636
21675
|
options;
|
|
21676
|
+
commandContext;
|
|
21637
21677
|
agent;
|
|
21638
|
-
reconciliationRequested = true;
|
|
21639
21678
|
wakeIntake;
|
|
21640
21679
|
/** Called by the daemon's one slow workspace reconciliation sweep. */
|
|
21641
21680
|
requestReconciliation() {
|
|
21642
|
-
this.reconciliationRequested = true;
|
|
21643
21681
|
this.wakeIntake?.();
|
|
21644
21682
|
this.wakeIntake = void 0;
|
|
21645
21683
|
}
|
|
@@ -21647,108 +21685,88 @@ var MonolithRoomTurnLoop = class {
|
|
|
21647
21685
|
sessionId;
|
|
21648
21686
|
/** The configuration the live session baked in; a change invalidates it. */
|
|
21649
21687
|
sessionFingerprint;
|
|
21688
|
+
/** What this exact ACP session has already been prompted with (`warm-transcript.ts`). */
|
|
21689
|
+
warmTranscript = new WarmTranscript();
|
|
21650
21690
|
/** The live session's environment, read back for pi's own turn record. */
|
|
21651
21691
|
agentEnv = {};
|
|
21652
21692
|
/** OpenRouter providers this activation pinned, in order (C92). */
|
|
21653
21693
|
pinnedProviders = [];
|
|
21694
|
+
/** Whether the pinned model takes images; `undefined` when the pin did not say. */
|
|
21695
|
+
modelTakesImages;
|
|
21654
21696
|
/** The one provider re-pinned after an empty completion, until the session ends. */
|
|
21655
21697
|
pinnedProviderOverride;
|
|
21698
|
+
turnIdentityInstructions = "";
|
|
21656
21699
|
busy = false;
|
|
21657
|
-
|
|
21658
|
-
|
|
21659
|
-
queuedTurns = [];
|
|
21700
|
+
forcedStop = false;
|
|
21701
|
+
activityTail = Promise.resolve();
|
|
21660
21702
|
/** Session scratch directory attachments are downloaded into (`TMPDIR/beeline-attachments`). */
|
|
21661
21703
|
attachmentDir;
|
|
21662
|
-
/**
|
|
21663
|
-
modelTakesImages;
|
|
21664
|
-
/** The session's TMPDIR: writable to a granted command in a Room, as it is to the harness (C94). */
|
|
21704
|
+
/** The session's TMPDIR, where a granted command's script argument may also live. */
|
|
21665
21705
|
sessionScratchDir;
|
|
21666
|
-
/** The
|
|
21667
|
-
|
|
21668
|
-
/** What this exact ACP session has already been prompted with (`warm-transcript.ts`). */
|
|
21669
|
-
warmTranscript = new WarmTranscript();
|
|
21670
|
-
/** Local copies already delivered this session, by message id, so transcript renders reuse them. */
|
|
21671
|
-
deliveredAttachments = /* @__PURE__ */ new Map();
|
|
21672
|
-
/** Names from the latest roster read, for ledger bylines the runner writes. */
|
|
21673
|
-
memberNames = /* @__PURE__ */ new Map();
|
|
21674
|
-
/** The request id of the turn that paused on a grant card, until its decision arrives. */
|
|
21675
|
-
pausedOnGrantRequestId;
|
|
21706
|
+
/** The turn in flight and who asked for it, for ledger rows and the grant runner. */
|
|
21707
|
+
currentTurn;
|
|
21676
21708
|
/** Operator-local turn traces; built once when the daemon configured a directory. */
|
|
21677
21709
|
turnTraceSink;
|
|
21710
|
+
memberNames = /* @__PURE__ */ new Map();
|
|
21711
|
+
/** Agent identities in this Workspace, so a mention can be told from a human's. */
|
|
21712
|
+
/** Per-sender continuity, shared in shape with top-level Room intake. */
|
|
21713
|
+
/** The member agent that owns corner-wide lifecycle facts such as checks. */
|
|
21714
|
+
/** The last server check state that started a turn; the same state never starts another. */
|
|
21715
|
+
/**
|
|
21716
|
+
* Request ids the requester has stopped. A corner's intake is blocked while
|
|
21717
|
+
* its turn runs, so a stop is recorded from the live-push callback and read
|
|
21718
|
+
* back here; ids that never matched a running turn are harmless and bounded
|
|
21719
|
+
* by the same replay window the inbox already de-duplicates over.
|
|
21720
|
+
*/
|
|
21721
|
+
stoppedTurns = /* @__PURE__ */ new Set();
|
|
21678
21722
|
constructor(options) {
|
|
21679
21723
|
this.options = options;
|
|
21680
21724
|
this.agent = runtimeIdentity(options.runtime.agent);
|
|
21681
|
-
|
|
21725
|
+
this.commandContext = new CommandExecutionContext(options.config.agentHomeRoot);
|
|
21726
|
+
this.options = { ...options, api: this.commandContext.bind(options.api) };
|
|
21727
|
+
options.grantRunner?.register(options.cornerId, {
|
|
21682
21728
|
workspaceId: options.workspaceId,
|
|
21683
|
-
cwd: options.
|
|
21684
|
-
// A
|
|
21685
|
-
//
|
|
21686
|
-
|
|
21687
|
-
|
|
21729
|
+
cwd: options.worktreePath,
|
|
21730
|
+
// A corner is the surface with `run-host-command`: its worktree becomes a
|
|
21731
|
+
// branch and a pull request, and host work belongs here, next to the
|
|
21732
|
+
// transcript that explains it. A granted command runs unwrapped (C94).
|
|
21733
|
+
writePolicy: () => ({
|
|
21734
|
+
surface: "corner",
|
|
21735
|
+
...this.sessionScratchDir ? { scratch: this.sessionScratchDir } : {}
|
|
21736
|
+
}),
|
|
21737
|
+
turn: () => this.currentTurn ? { ...this.currentTurn, generationId: this.commandContext.generationId } : void 0
|
|
21688
21738
|
});
|
|
21689
21739
|
}
|
|
21690
21740
|
isBusy() {
|
|
21691
21741
|
return this.busy;
|
|
21692
21742
|
}
|
|
21693
|
-
|
|
21694
|
-
|
|
21695
|
-
return this.pausedOnGrantRequestId;
|
|
21696
|
-
}
|
|
21697
|
-
/**
|
|
21698
|
-
* What the grant runner may write here: the session's own scratch and home
|
|
21699
|
-
* overlay and nothing else, enforced by the same read-only mount table the
|
|
21700
|
-
* harness runs under. With no usable bwrap there is no way to keep that
|
|
21701
|
-
* promise, so the policy carries no path and the runner refuses the run
|
|
21702
|
-
* rather than widening the boundary.
|
|
21703
|
-
*/
|
|
21704
|
-
grantWritePolicy() {
|
|
21705
|
-
return {
|
|
21706
|
-
surface: "room",
|
|
21707
|
-
...this.options.config.bwrapPath ? { bwrapPath: this.options.config.bwrapPath } : {},
|
|
21708
|
-
...this.sessionScratchDir ? { scratch: this.sessionScratchDir } : {},
|
|
21709
|
-
...this.sessionStateDirs.length ? { harnessStateDirs: this.sessionStateDirs } : {},
|
|
21710
|
-
maskPaths: credentialMaskPaths(this.options.config.sandboxMaskPaths, this.options.config.operatorHome ?? homedir7())
|
|
21711
|
-
};
|
|
21712
|
-
}
|
|
21713
|
-
/**
|
|
21714
|
-
* One turn's stopwatch. It is created for every turn — measuring is cheap —
|
|
21715
|
-
* and only WRITES when the daemon configured a runtime directory to write
|
|
21716
|
-
* into, so a standalone or test Body stays silent.
|
|
21717
|
-
*/
|
|
21718
|
-
beginTurnTrace(requestId) {
|
|
21719
|
-
const directory = this.options.config.turnTraceDir;
|
|
21720
|
-
if (directory)
|
|
21721
|
-
this.turnTraceSink ??= new TurnTraceFile(directory);
|
|
21722
|
-
return new TurnTrace({
|
|
21723
|
-
surface: "room",
|
|
21724
|
-
agentId: this.agent.publicKey,
|
|
21725
|
-
roomId: this.options.roomId,
|
|
21726
|
-
requestId,
|
|
21727
|
-
...this.turnTraceSink ? { sink: this.turnTraceSink } : {}
|
|
21728
|
-
});
|
|
21729
|
-
}
|
|
21730
|
-
currentTurnForRunner() {
|
|
21731
|
-
const active = this.activeTurn;
|
|
21732
|
-
if (!active)
|
|
21733
|
-
return void 0;
|
|
21734
|
-
return { requestId: active.item.id, requester: this.requesterOf(active.item.authorId) };
|
|
21735
|
-
}
|
|
21736
|
-
requesterOf(authorId) {
|
|
21737
|
-
const name = this.memberNames.get(authorId);
|
|
21738
|
-
return { pubkey: authorId, ...name ? { name } : {} };
|
|
21739
|
-
}
|
|
21740
|
-
currentPrincipalCanDrive(_workspaceId, principalId) {
|
|
21741
|
-
return Promise.resolve(isSenderPermitted(this.options.config.accessPolicy ?? LEGACY_ACCESS_POLICY, principalId, this.options.config.accessOwnerPubkey, this.options.config.accessAllowlist));
|
|
21742
|
-
}
|
|
21743
|
-
async refreshPersonaForSoulUpdate() {
|
|
21744
|
-
await this.options.scheduler.suspend(this.options.roomId);
|
|
21743
|
+
refreshPersonaForSoulUpdate() {
|
|
21744
|
+
return this.options.scheduler.suspend(this.options.cornerId);
|
|
21745
21745
|
}
|
|
21746
21746
|
async prepareForForcedUpdateRestart() {
|
|
21747
|
+
this.forcedStop = true;
|
|
21747
21748
|
}
|
|
21748
21749
|
async forceRecoverRoom() {
|
|
21749
21750
|
if (this.client && this.sessionId)
|
|
21750
21751
|
this.client.sessionCancel(this.sessionId);
|
|
21751
|
-
await this.options.scheduler.forceSuspend(this.options.
|
|
21752
|
+
await this.options.scheduler.forceSuspend(this.options.cornerId);
|
|
21753
|
+
}
|
|
21754
|
+
/**
|
|
21755
|
+
* Obey a stop the requester already made a fact.
|
|
21756
|
+
*
|
|
21757
|
+
* The id is remembered whether or not it names the turn in flight: a stop for
|
|
21758
|
+
* work not yet started must still keep that work from starting, and one for a
|
|
21759
|
+
* turn that has already ended is simply never read again. Only the session
|
|
21760
|
+
* actually running the stopped turn is cancelled.
|
|
21761
|
+
*/
|
|
21762
|
+
stopTurn(requestId) {
|
|
21763
|
+
this.stoppedTurns.add(requestId);
|
|
21764
|
+
while (this.stoppedTurns.size > 500)
|
|
21765
|
+
this.stoppedTurns.delete(this.stoppedTurns.values().next().value);
|
|
21766
|
+
if (this.currentTurn?.requestId !== requestId)
|
|
21767
|
+
return;
|
|
21768
|
+
if (this.client && this.sessionId)
|
|
21769
|
+
this.client.sessionCancel(this.sessionId);
|
|
21752
21770
|
}
|
|
21753
21771
|
async roster() {
|
|
21754
21772
|
const roster = await this.options.api.execute("getWorkspaceRoster", {
|
|
@@ -21759,33 +21777,7 @@ var MonolithRoomTurnLoop = class {
|
|
|
21759
21777
|
return roster;
|
|
21760
21778
|
}
|
|
21761
21779
|
/**
|
|
21762
|
-
*
|
|
21763
|
-
* have to hold (C87): the harness must advertise `promptCapabilities.image`,
|
|
21764
|
-
* AND — when the pin knows the model's modalities — the model must take
|
|
21765
|
-
* images. `undefined` modalities mean the question was never settled, and
|
|
21766
|
-
* the harness answer stands alone as before.
|
|
21767
|
-
*/
|
|
21768
|
-
acceptsImages() {
|
|
21769
|
-
if (!(this.client?.canPromptWithImages() ?? false))
|
|
21770
|
-
return false;
|
|
21771
|
-
return this.modelTakesImages ?? true;
|
|
21772
|
-
}
|
|
21773
|
-
/** Download a message's attachments into the session scratch directory once. */
|
|
21774
|
-
async deliver(item) {
|
|
21775
|
-
if (!item.attachments.length || !this.attachmentDir)
|
|
21776
|
-
return [];
|
|
21777
|
-
const cached = this.deliveredAttachments.get(item.id);
|
|
21778
|
-
if (cached)
|
|
21779
|
-
return cached;
|
|
21780
|
-
const delivered = await deliverAttachments(item.attachments, join6(this.attachmentDir, item.id.replace(/[^\w-]/g, "_")), this.options.fetchImpl);
|
|
21781
|
-
this.deliveredAttachments.set(item.id, withoutImageData(delivered));
|
|
21782
|
-
return delivered;
|
|
21783
|
-
}
|
|
21784
|
-
repositoryState() {
|
|
21785
|
-
return this.options.api.execute("getRoomRepositoryState", { roomId: this.options.roomId });
|
|
21786
|
-
}
|
|
21787
|
-
/**
|
|
21788
|
-
* Drop this Room's live harness process. The next activation starts cold.
|
|
21780
|
+
* Drop this corner's live harness process. The next activation starts cold.
|
|
21789
21781
|
* A rotation is a fact about one live session, so the pin goes with it.
|
|
21790
21782
|
*/
|
|
21791
21783
|
async discardSession() {
|
|
@@ -21797,14 +21789,8 @@ var MonolithRoomTurnLoop = class {
|
|
|
21797
21789
|
if (client?.isAlive)
|
|
21798
21790
|
await client.stop();
|
|
21799
21791
|
}
|
|
21800
|
-
/**
|
|
21801
|
-
*
|
|
21802
|
-
* configuration. Retention (C104) is a saving only while what it keeps is
|
|
21803
|
-
* still current, and a session's persona and model pin are fixed when it
|
|
21804
|
-
* opens: they cannot be corrected in place, so a changed one has to cost a
|
|
21805
|
-
* respawn. The check is one round trip — the roster half of which the turn
|
|
21806
|
-
* was going to fetch anyway — against a cold spawn measured in seconds.
|
|
21807
|
-
*/
|
|
21792
|
+
/** See `MonolithRoomTurnLoop.sessionIsCurrent`: retention never keeps a
|
|
21793
|
+
* session whose persona or model pin the operator has since changed. */
|
|
21808
21794
|
async sessionIsCurrent() {
|
|
21809
21795
|
return await this.currentSessionFingerprint() === this.sessionFingerprint;
|
|
21810
21796
|
}
|
|
@@ -21812,7 +21798,7 @@ var MonolithRoomTurnLoop = class {
|
|
|
21812
21798
|
const [configuration, roster] = await Promise.all([
|
|
21813
21799
|
this.options.api.execute("getAgentConfiguration", {
|
|
21814
21800
|
agentId: this.agent.publicKey,
|
|
21815
|
-
roomId: this.options.
|
|
21801
|
+
roomId: this.options.cornerId
|
|
21816
21802
|
}),
|
|
21817
21803
|
this.roster()
|
|
21818
21804
|
]);
|
|
@@ -21828,13 +21814,12 @@ var MonolithRoomTurnLoop = class {
|
|
|
21828
21814
|
if (this.client?.isAlive && this.sessionId)
|
|
21829
21815
|
return this.sessionId;
|
|
21830
21816
|
trace?.noteActivation("cold");
|
|
21831
|
-
const [configuration, roster
|
|
21817
|
+
const [configuration, roster] = await Promise.all([
|
|
21832
21818
|
this.options.api.execute("getAgentConfiguration", {
|
|
21833
21819
|
agentId: this.agent.publicKey,
|
|
21834
|
-
roomId: this.options.
|
|
21820
|
+
roomId: this.options.cornerId
|
|
21835
21821
|
}),
|
|
21836
|
-
this.roster()
|
|
21837
|
-
this.repositoryState()
|
|
21822
|
+
this.roster()
|
|
21838
21823
|
]);
|
|
21839
21824
|
const self = roster.members.find((member) => member.identityId === this.agent.publicKey);
|
|
21840
21825
|
const fingerprint = sessionConfigFingerprint({
|
|
@@ -21843,8 +21828,7 @@ var MonolithRoomTurnLoop = class {
|
|
|
21843
21828
|
soul: configuration.soul ?? self?.soul,
|
|
21844
21829
|
agentName: self?.name ?? this.agent.name
|
|
21845
21830
|
});
|
|
21846
|
-
|
|
21847
|
-
await mkdir10(this.options.cwd, { recursive: true });
|
|
21831
|
+
await mkdir11(this.options.worktreePath, { recursive: true });
|
|
21848
21832
|
const selection = configuration.model || configuration.effort ? { model: configuration.model, effort: configuration.effort } : this.options.config.modelSelection;
|
|
21849
21833
|
const homeOverlay = this.options.config.agentHomeRoot ? await prepareRoomAgentHome({
|
|
21850
21834
|
root: this.options.config.agentHomeRoot,
|
|
@@ -21861,7 +21845,26 @@ var MonolithRoomTurnLoop = class {
|
|
|
21861
21845
|
})
|
|
21862
21846
|
}) : {};
|
|
21863
21847
|
const command = this.options.config.agentCommand ?? this.options.config.agentBinary;
|
|
21864
|
-
const
|
|
21848
|
+
const repository = this.options.repository;
|
|
21849
|
+
let githubEnv = repository ? { GH_TOKEN: repository.githubToken, GITHUB_TOKEN: repository.githubToken } : {};
|
|
21850
|
+
if (repository && this.options.config.runtimeConfigPath && this.options.config.agentHomeRoot) {
|
|
21851
|
+
const gitBinary = (await execFileAsync3("which", ["git"])).stdout.trim();
|
|
21852
|
+
const ghBinary = await execFileAsync3("which", ["gh"]).then((result) => result.stdout.trim()).catch(() => void 0);
|
|
21853
|
+
githubEnv = await installCornerGitHubWrappers({
|
|
21854
|
+
root: this.options.config.agentHomeRoot,
|
|
21855
|
+
runtimeConfigPath: this.options.config.runtimeConfigPath,
|
|
21856
|
+
roomId: this.options.parentRoomId,
|
|
21857
|
+
cliEntrypoint: process.argv[1],
|
|
21858
|
+
gitBinary,
|
|
21859
|
+
...ghBinary ? { ghBinary } : {},
|
|
21860
|
+
inheritedPath: this.options.config.agentEnv.PATH ?? process.env.PATH
|
|
21861
|
+
});
|
|
21862
|
+
}
|
|
21863
|
+
const agentEnv = {
|
|
21864
|
+
...this.options.config.agentEnv,
|
|
21865
|
+
...homeOverlay,
|
|
21866
|
+
...githubEnv
|
|
21867
|
+
};
|
|
21865
21868
|
this.agentEnv = agentEnv;
|
|
21866
21869
|
const agentArgs = agentArgsWithModelSelection({
|
|
21867
21870
|
kind: this.options.config.agentKind,
|
|
@@ -21870,19 +21873,21 @@ var MonolithRoomTurnLoop = class {
|
|
|
21870
21873
|
}, selection);
|
|
21871
21874
|
const operatorHome = this.options.config.operatorHome ?? homedir7();
|
|
21872
21875
|
const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
|
|
21873
|
-
this.attachmentDir = tmpDir ?
|
|
21876
|
+
this.attachmentDir = tmpDir ? join7(tmpDir, "beeline-attachments") : void 0;
|
|
21874
21877
|
this.sessionScratchDir = tmpDir;
|
|
21875
|
-
this.sessionStateDirs = stateDirs;
|
|
21876
21878
|
const homeStateDirs = harnessHomeStateDirs(command, agentEnv.HOME ?? operatorHome);
|
|
21877
|
-
await Promise.all(homeStateDirs.map((dir) =>
|
|
21879
|
+
await Promise.all(homeStateDirs.map((dir) => mkdir11(dir, { recursive: true })));
|
|
21878
21880
|
const attachScratchRoot = this.options.config.agentHomeRoot ?? tmpDir;
|
|
21879
21881
|
if (attachScratchRoot)
|
|
21880
|
-
await
|
|
21882
|
+
await mkdir11(attachScratchRoot, { recursive: true });
|
|
21881
21883
|
const spawnCommand = wrapAgentCommand({
|
|
21882
21884
|
bwrapPath: this.options.config.bwrapPath,
|
|
21883
21885
|
spec: {
|
|
21884
|
-
mode: "
|
|
21885
|
-
cwd: this.options.
|
|
21886
|
+
mode: "edit",
|
|
21887
|
+
cwd: this.options.worktreePath,
|
|
21888
|
+
worktreePath: this.options.worktreePath,
|
|
21889
|
+
...repository ? { gitCommonDir: repository.gitCommonDir } : {},
|
|
21890
|
+
protectedPaths: [this.options.runtime.supervisorRoot],
|
|
21886
21891
|
harnessStateDirs: stateDirs,
|
|
21887
21892
|
harnessHomeStateDirs: homeStateDirs,
|
|
21888
21893
|
...tmpDir ? { tmpDir } : {},
|
|
@@ -21892,18 +21897,42 @@ var MonolithRoomTurnLoop = class {
|
|
|
21892
21897
|
command,
|
|
21893
21898
|
args: agentArgs
|
|
21894
21899
|
});
|
|
21900
|
+
const clientOptions = {
|
|
21901
|
+
agentCommand: spawnCommand.command,
|
|
21902
|
+
agentArgs: spawnCommand.args,
|
|
21903
|
+
agentEnv,
|
|
21904
|
+
agentCwd: this.options.worktreePath,
|
|
21905
|
+
agentLabel: command,
|
|
21906
|
+
autoApprovePermissions: true,
|
|
21907
|
+
permissionHandler: () => Promise.resolve("allow")
|
|
21908
|
+
};
|
|
21909
|
+
this.client = (this.options.createAcpClient ?? ((value) => new AcpClient(value)))(clientOptions);
|
|
21910
|
+
await this.client.start();
|
|
21895
21911
|
const servers = [
|
|
21896
|
-
|
|
21912
|
+
...repository ? [
|
|
21913
|
+
{
|
|
21914
|
+
name: "buzz-dev-mcp",
|
|
21915
|
+
command: this.options.config.mcpBinary,
|
|
21916
|
+
args: [],
|
|
21917
|
+
// ACP hosts launch stdio MCP servers with an explicit, sanitized env.
|
|
21918
|
+
// This token is minted for this exact corner and is also the credential
|
|
21919
|
+
// helper's password source, so its shell commands need the same scope as
|
|
21920
|
+
// the corner harness without inheriting any host credentials.
|
|
21921
|
+
env: [
|
|
21922
|
+
{ name: "GH_TOKEN", value: repository.githubToken },
|
|
21923
|
+
{ name: "GITHUB_TOKEN", value: repository.githubToken }
|
|
21924
|
+
]
|
|
21925
|
+
}
|
|
21926
|
+
] : [],
|
|
21897
21927
|
beelineAgentMcpServer(this.options.config, this.options.api, {
|
|
21898
|
-
roomId: this.options.
|
|
21928
|
+
roomId: this.options.parentRoomId,
|
|
21899
21929
|
workspaceId: this.options.workspaceId,
|
|
21900
|
-
|
|
21901
|
-
|
|
21902
|
-
//
|
|
21903
|
-
//
|
|
21904
|
-
// have written must be attachable, whatever subdirectory that is.
|
|
21930
|
+
cornerId: this.options.cornerId,
|
|
21931
|
+
attachRoot: this.options.worktreePath,
|
|
21932
|
+
// The whole per-session overlay, not an enumerated subset: see
|
|
21933
|
+
// `monolith-room-turn.ts`'s matching comment.
|
|
21905
21934
|
attachScratchRoot,
|
|
21906
|
-
|
|
21935
|
+
turnContextPath: this.commandContext.path,
|
|
21907
21936
|
...this.options.grantRunnerEndpoint ? { grantRunner: this.options.grantRunnerEndpoint } : {}
|
|
21908
21937
|
})
|
|
21909
21938
|
];
|
|
@@ -21912,43 +21941,38 @@ var MonolithRoomTurnLoop = class {
|
|
|
21912
21941
|
piHome: agentEnv.PI_CODING_AGENT_DIR,
|
|
21913
21942
|
servers
|
|
21914
21943
|
});
|
|
21915
|
-
const mountedServers = servers.map((server) => server.name);
|
|
21916
|
-
const clientOptions = {
|
|
21917
|
-
agentCommand: spawnCommand.command,
|
|
21918
|
-
agentArgs: spawnCommand.args,
|
|
21919
|
-
agentEnv,
|
|
21920
|
-
agentCwd: this.options.cwd,
|
|
21921
|
-
agentLabel: command,
|
|
21922
|
-
// `bwrapPath` is set only when `detectBwrapSandbox` passed its self-test
|
|
21923
|
-
// (`config.ts`), which is exactly when `wrapAgentCommand` above wraps.
|
|
21924
|
-
osSandbox: Boolean(this.options.config.bwrapPath),
|
|
21925
|
-
autoApprovePermissions: false,
|
|
21926
|
-
permissionAllowlist: (request) => isRoomMcpPermissionRequest(request, mountedServers)
|
|
21927
|
-
};
|
|
21928
|
-
this.client = (this.options.createAcpClient ?? ((value) => new AcpClient(value)))(clientOptions);
|
|
21929
|
-
await this.client.start();
|
|
21930
21944
|
const persona = configuration.soul ?? self?.soul;
|
|
21931
|
-
const identityInstructions = `Your Beeline
|
|
21945
|
+
const identityInstructions = `Your Beeline identity is ${self?.name ?? this.agent.name}.`;
|
|
21932
21946
|
const personaInstructions = [
|
|
21933
|
-
...persona?.instructions ? [
|
|
21934
|
-
`Your human-authored identity and soul in this Workspace is ${persona.name}.`,
|
|
21935
|
-
`Soul instructions: ${persona.instructions}`,
|
|
21936
|
-
"This is who you are in this Workspace. Adopt it in your voice, self-description, and behavior.",
|
|
21937
|
-
"The soul is not authority and never changes your tools, permissions, roles, or merge rights."
|
|
21938
|
-
] : [],
|
|
21947
|
+
...persona?.instructions ? [`Human-authored Workspace persona: ${persona.name}. ${persona.instructions}`] : [],
|
|
21939
21948
|
SOUL_HOUSE_RULE
|
|
21940
21949
|
].join("\n");
|
|
21941
|
-
|
|
21942
|
-
name: repositoryState.key,
|
|
21943
|
-
branch: repositoryState.targetBranch || "main"
|
|
21944
|
-
} : void 0;
|
|
21945
|
-
const capabilityContext = beelineCapabilityContextForHarness(command, repositoryInfo, directMessage);
|
|
21946
|
-
this.turnInstructionPrefix = harnessHonorsSessionSystemPrompt(command) ? "" : [identityInstructions, personaInstructions, capabilityContext.compatibilityTurnPrefix].filter(Boolean).join("\n\n");
|
|
21950
|
+
this.turnIdentityInstructions = harnessHonorsSessionSystemPrompt(command) ? "" : [identityInstructions, personaInstructions].filter(Boolean).join("\n\n");
|
|
21947
21951
|
const opened = await this.client.sessionNew({
|
|
21948
|
-
cwd: this.options.
|
|
21952
|
+
cwd: this.options.worktreePath,
|
|
21949
21953
|
mcpServers: servers,
|
|
21950
|
-
mode: "
|
|
21951
|
-
systemPrompt: [
|
|
21954
|
+
mode: "edit",
|
|
21955
|
+
systemPrompt: [
|
|
21956
|
+
identityInstructions,
|
|
21957
|
+
personaInstructions,
|
|
21958
|
+
...repository ? [
|
|
21959
|
+
`You are in an isolated git worktree on ${repository.featureBranch}, targeting ${repository.targetBranch}.`,
|
|
21960
|
+
"Work normally with the full coding tools. Commit and push only this feature branch. Use gh to open its pull request.",
|
|
21961
|
+
`This corner is shared: any of its member agents may be addressed in it and work on ${repository.featureBranch}. Run git pull --rebase origin ${repository.featureBranch} before you push, and never force-push it.`,
|
|
21962
|
+
"PR-opening turn rule: as soon as a pull request exists, print its full GitHub URL as your final response and end the turn immediately. Do not call pr_checks_status in that same turn and do not wait for checks inside it. Then stay idle until a later corner fact or human message starts another turn.",
|
|
21963
|
+
'Never merge because local tests pass or because gh reports passing checks. On a later turn triggered by a server-posted checks-passed note, call beeline-agent pr_checks_status. Merge only when it returns checks="passed", held=false, and approvalPending=false.',
|
|
21964
|
+
"Merge the PR yourself only after the checks-passed event shows every check green; if any check failed or is still running, say exactly which and stop - never merge red.",
|
|
21965
|
+
"If any human in this corner says hold or do not merge, do not merge until a later human explicitly resumes it.",
|
|
21966
|
+
"Do not tag the user when a corner turn finishes: the server posts the merge summary card and its push already cover completion. Tag a human only mid-turn, and only when you need a decision or input.",
|
|
21967
|
+
'GitHub check and merge notes are server lines already in the corner: never restate them (no "checks passed", "CI is green", "PR ready for review"). On a checks turn, say nothing unless you act - a merge or a pushed fix - and then one short line about that.',
|
|
21968
|
+
"A human approval in the app asks the server to merge. When approval is pending, wait for the server close request instead of racing it with gh. If checks passed, no hold exists, and no approval is pending, merge the pull request yourself with gh.",
|
|
21969
|
+
"Never push directly to the target branch. Never merge a different pull request."
|
|
21970
|
+
] : [
|
|
21971
|
+
"This is a chat-only corner with no repository or GitHub workflow.",
|
|
21972
|
+
"Work in this corner's writable workspace. Use write_scratch_file or ordinary tools to create files, then attach_file to send them back to the corner.",
|
|
21973
|
+
"Do not initialize a repository, create a branch, push, open a pull request, or wait for GitHub checks."
|
|
21974
|
+
]
|
|
21975
|
+
].filter(Boolean).join("\n\n")
|
|
21952
21976
|
});
|
|
21953
21977
|
this.sessionId = opened.sessionId;
|
|
21954
21978
|
this.sessionFingerprint = fingerprint;
|
|
@@ -21958,12 +21982,7 @@ var MonolithRoomTurnLoop = class {
|
|
|
21958
21982
|
}
|
|
21959
21983
|
return opened.sessionId;
|
|
21960
21984
|
}
|
|
21961
|
-
/**
|
|
21962
|
-
* The scheduler seam, and the only place that can see the boundary between
|
|
21963
|
-
* waiting for a slot and spawning a harness: `queue-wait` closes the instant
|
|
21964
|
-
* `activate()` is called, and `cold` vs `warm` is decided by whether this
|
|
21965
|
-
* Room already holds a live ACP client.
|
|
21966
|
-
*/
|
|
21985
|
+
/** The scheduler seam: `queue-wait` closes when a slot buys a session. */
|
|
21967
21986
|
lifecycle(trace) {
|
|
21968
21987
|
return {
|
|
21969
21988
|
activate: async () => {
|
|
@@ -21982,6 +22001,12 @@ var MonolithRoomTurnLoop = class {
|
|
|
21982
22001
|
suspend: () => this.discardSession()
|
|
21983
22002
|
};
|
|
21984
22003
|
}
|
|
22004
|
+
/** A picture reaches the model only if the harness AND the model take one (C87). */
|
|
22005
|
+
acceptsImages() {
|
|
22006
|
+
if (!(this.client?.canPromptWithImages() ?? false))
|
|
22007
|
+
return false;
|
|
22008
|
+
return this.modelTakesImages ?? true;
|
|
22009
|
+
}
|
|
21985
22010
|
/** The pinned providers a failure reason should name for this session. */
|
|
21986
22011
|
servingProviders() {
|
|
21987
22012
|
return this.pinnedProviderOverride ? [this.pinnedProviderOverride] : this.pinnedProviders;
|
|
@@ -22000,8 +22025,7 @@ var MonolithRoomTurnLoop = class {
|
|
|
22000
22025
|
/**
|
|
22001
22026
|
* Re-pin the session to the next provider in the OpenRouter order and open a
|
|
22002
22027
|
* fresh session on it, so the retry of an empty completion is served — and
|
|
22003
|
-
* named — by exactly one provider.
|
|
22004
|
-
* to go.
|
|
22028
|
+
* named — by exactly one provider (C92).
|
|
22005
22029
|
*/
|
|
22006
22030
|
async repinNextProvider(trace, reason) {
|
|
22007
22031
|
const next = nextPinnedProvider(this.pinnedProviders, this.pinnedProviderOverride);
|
|
@@ -22018,343 +22042,310 @@ var MonolithRoomTurnLoop = class {
|
|
|
22018
22042
|
await (trace ? trace.measure("activation", () => this.activate(trace)) : this.activate());
|
|
22019
22043
|
return next;
|
|
22020
22044
|
}
|
|
22021
|
-
|
|
22022
|
-
|
|
22023
|
-
|
|
22024
|
-
|
|
22025
|
-
|
|
22026
|
-
|
|
22027
|
-
|
|
22028
|
-
|
|
22029
|
-
|
|
22030
|
-
|
|
22031
|
-
|
|
22032
|
-
this.options.health.failure(1e3);
|
|
22033
|
-
console.error(`[thin-core] monolith Room ${this.options.roomId} turn failed:`, error);
|
|
22034
|
-
}).finally(() => {
|
|
22035
|
-
if (this.activeTurn === active)
|
|
22036
|
-
this.activeTurn = void 0;
|
|
22037
|
-
});
|
|
22038
|
-
}
|
|
22039
|
-
steer(active, item) {
|
|
22040
|
-
active.steers.push(item);
|
|
22041
|
-
active.steerTail = active.steerTail.catch(() => void 0).then(async () => {
|
|
22042
|
-
try {
|
|
22043
|
-
const [roster, delivered] = await Promise.all([this.roster(), this.deliver(item)]);
|
|
22044
|
-
const author = roster.members.find((member) => member.identityId === item.authorId)?.name ?? item.authorId.slice(0, 12);
|
|
22045
|
-
await this.client.sessionSteer(this.sessionId, [
|
|
22046
|
-
`Human steer received while the current turn is running from ${author}:`,
|
|
22047
|
-
roomMessagePrompt("", item.body, item.attachments, delivered, this.acceptsImages()),
|
|
22048
|
-
"Adjust the current work now. Keep the original request and earlier messages as context."
|
|
22049
|
-
].join("\n\n"));
|
|
22050
|
-
} catch (error) {
|
|
22051
|
-
active.resumeRequested = true;
|
|
22052
|
-
this.client?.sessionCancel(this.sessionId);
|
|
22053
|
-
console.warn(`[thin-core] monolith Room ${this.options.roomId} live steer unavailable; cancelling and resuming:`, error);
|
|
22054
|
-
}
|
|
22045
|
+
/** One turn's stopwatch; writes only when the daemon configured a trace directory. */
|
|
22046
|
+
beginTurnTrace(requestId) {
|
|
22047
|
+
const directory = this.options.config.turnTraceDir;
|
|
22048
|
+
if (directory)
|
|
22049
|
+
this.turnTraceSink ??= new TurnTraceFile(directory);
|
|
22050
|
+
return new TurnTrace({
|
|
22051
|
+
surface: "corner",
|
|
22052
|
+
agentId: this.agent.publicKey,
|
|
22053
|
+
roomId: this.options.cornerId,
|
|
22054
|
+
requestId,
|
|
22055
|
+
...this.turnTraceSink ? { sink: this.turnTraceSink } : {}
|
|
22055
22056
|
});
|
|
22056
22057
|
}
|
|
22057
|
-
async prompt(
|
|
22058
|
-
const {
|
|
22059
|
-
|
|
22060
|
-
|
|
22061
|
-
const
|
|
22062
|
-
|
|
22063
|
-
|
|
22064
|
-
|
|
22065
|
-
|
|
22066
|
-
|
|
22067
|
-
|
|
22068
|
-
|
|
22069
|
-
|
|
22070
|
-
|
|
22071
|
-
|
|
22072
|
-
|
|
22073
|
-
|
|
22074
|
-
|
|
22075
|
-
activity: [
|
|
22076
|
-
{
|
|
22077
|
-
kind: "thinking",
|
|
22078
|
-
title: "Working",
|
|
22079
|
-
status: "in_progress",
|
|
22080
|
-
requestedBy: this.requesterOf(item.authorId)
|
|
22081
|
-
}
|
|
22082
|
-
]
|
|
22083
|
-
});
|
|
22058
|
+
async prompt(requestId, trigger, attachments = [], requestedById, restates) {
|
|
22059
|
+
const { api, cornerId } = this.options;
|
|
22060
|
+
if (this.stoppedTurns.has(requestId))
|
|
22061
|
+
return;
|
|
22062
|
+
const spoken = (text2) => restates && isCornerStatusRestatement(text2, restates) ? "" : text2;
|
|
22063
|
+
const requester = requestedById ? {
|
|
22064
|
+
pubkey: requestedById,
|
|
22065
|
+
...this.memberNames.get(requestedById) ? { name: this.memberNames.get(requestedById) } : {}
|
|
22066
|
+
} : void 0;
|
|
22067
|
+
this.currentTurn = { requestId, ...requester ? { requester } : {} };
|
|
22068
|
+
const trace = this.beginTurnTrace(requestId);
|
|
22069
|
+
try {
|
|
22070
|
+
await withTurnReceiptHeartbeat(api, {
|
|
22071
|
+
agentId: this.agent.publicKey,
|
|
22072
|
+
roomId: cornerId,
|
|
22073
|
+
requestId,
|
|
22074
|
+
generationId: this.commandContext.generationId
|
|
22075
|
+
}, () => {
|
|
22084
22076
|
trace.noteScheduler("queue", this.options.scheduler.snapshot());
|
|
22085
22077
|
trace.start("queue-wait");
|
|
22086
|
-
|
|
22078
|
+
return this.options.scheduler.run(cornerId, this.lifecycle(trace), async () => {
|
|
22087
22079
|
trace.end("queue-wait");
|
|
22088
22080
|
trace.noteScheduler("admission", this.options.scheduler.snapshot());
|
|
22081
|
+
if (this.forcedStop)
|
|
22082
|
+
throw new Error("corner turn stopped for daemon handoff");
|
|
22083
|
+
this.busy = true;
|
|
22084
|
+
await this.syncBranch();
|
|
22089
22085
|
const [conversation, roster, delivered] = await trace.measure("context-fetch", () => Promise.all([
|
|
22090
|
-
api.execute("getRoomConversation", { roomId:
|
|
22086
|
+
api.execute("getRoomConversation", { roomId: cornerId, limit: 200 }),
|
|
22091
22087
|
this.roster(),
|
|
22092
|
-
this.
|
|
22088
|
+
this.attachmentDir && attachments.length ? deliverAttachments(attachments, join7(this.attachmentDir, requestId.replace(/[^\w-]/g, "_")), this.options.fetchImpl) : Promise.resolve([])
|
|
22093
22089
|
]));
|
|
22094
22090
|
const names = new Map(roster.members.map((member) => [member.identityId, member.name]));
|
|
22095
|
-
const
|
|
22091
|
+
const requestedBy = requester && !requester.name && names.get(requester.pubkey) ? { ...requester, name: names.get(requester.pubkey) } : requester;
|
|
22092
|
+
if (requestedBy)
|
|
22093
|
+
this.currentTurn = { requestId, requester: requestedBy };
|
|
22094
|
+
const transcriptRows = conversation.items.slice(-120).map((message) => ({
|
|
22096
22095
|
id: message.id,
|
|
22097
|
-
line:
|
|
22096
|
+
line: `${names.get(message.authorId) ?? "Beeline"} [${message.type}]: ${message.body}`
|
|
22098
22097
|
}));
|
|
22099
|
-
const grantDecision = isGrantDecisionLine(item, this.agent.publicKey);
|
|
22100
|
-
const resumedRequestId = grantDecision ? this.pausedOnGrantRequestId : void 0;
|
|
22101
|
-
if (grantDecision)
|
|
22102
|
-
this.pausedOnGrantRequestId = void 0;
|
|
22103
22098
|
const buildPrompt = () => [
|
|
22104
|
-
this.
|
|
22105
|
-
|
|
22106
|
-
|
|
22107
|
-
|
|
22108
|
-
grantDecision ? [
|
|
22109
|
-
"This is the answer to your grant request; your paused work resumes now.",
|
|
22110
|
-
"If it was approved and it is a command grant, run it with run_granted_command and the exact argv.",
|
|
22111
|
-
"If it was declined, try another way or say plainly what you cannot do."
|
|
22112
|
-
].join(" ") : "",
|
|
22099
|
+
this.turnIdentityInstructions,
|
|
22100
|
+
`Corner objective:
|
|
22101
|
+
${this.options.objective}`,
|
|
22102
|
+
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):"),
|
|
22113
22103
|
roomMentionDirectory(roster, this.agent.publicKey),
|
|
22114
22104
|
[
|
|
22115
|
-
|
|
22116
|
-
|
|
22117
|
-
|
|
22118
|
-
|
|
22119
|
-
|
|
22105
|
+
`Newest trigger:
|
|
22106
|
+
${trigger}`,
|
|
22107
|
+
...attachmentPromptLines(attachments, delivered, this.acceptsImages())
|
|
22108
|
+
].join("\n"),
|
|
22109
|
+
this.options.repository ? "Continue the objective. Obey the PR checks and human hold rules in your session instructions." : "Continue the objective. Attach completed files before calling close_corner.",
|
|
22110
|
+
MAINTAIN_ASSIGNED_IDENTITY_DIRECTIVE
|
|
22120
22111
|
].filter(Boolean).join("\n\n");
|
|
22121
22112
|
const stream = new AgentTurnStream({
|
|
22122
22113
|
api,
|
|
22123
22114
|
agentId: this.agent.publicKey,
|
|
22124
|
-
roomId:
|
|
22125
|
-
requestId
|
|
22126
|
-
label: `
|
|
22115
|
+
roomId: cornerId,
|
|
22116
|
+
requestId,
|
|
22117
|
+
label: `corner ${cornerId}`
|
|
22127
22118
|
});
|
|
22128
|
-
|
|
22129
|
-
|
|
22130
|
-
|
|
22131
|
-
|
|
22132
|
-
|
|
22133
|
-
|
|
22134
|
-
|
|
22135
|
-
|
|
22136
|
-
|
|
22137
|
-
|
|
22138
|
-
|
|
22139
|
-
|
|
22140
|
-
|
|
22141
|
-
|
|
22119
|
+
let currentNarrationRun = "";
|
|
22120
|
+
let completedNarrationRuns = [];
|
|
22121
|
+
let narrationRunBoundary = 0;
|
|
22122
|
+
const takeInterimNarration = () => {
|
|
22123
|
+
const narration = spoken([...completedNarrationRuns, currentNarrationRun].map((run2) => spoken(durableReplyText(run2))).filter((run2) => run2 && !isPureRetryNarration(run2)).join("\n\n"));
|
|
22124
|
+
narrationRunBoundary += completedNarrationRuns.length;
|
|
22125
|
+
completedNarrationRuns = [];
|
|
22126
|
+
currentNarrationRun = "";
|
|
22127
|
+
return narration;
|
|
22128
|
+
};
|
|
22129
|
+
const publishedToolCalls = /* @__PURE__ */ new Set();
|
|
22130
|
+
const observedToolCalls = /* @__PURE__ */ new Set();
|
|
22131
|
+
const pendingToolNarrations = /* @__PURE__ */ new Map();
|
|
22132
|
+
const pendingToolActivities = /* @__PURE__ */ new Map();
|
|
22133
|
+
let lastNarratedToolCall;
|
|
22134
|
+
let activityAttempt = 0;
|
|
22135
|
+
const publishToolCalls = (calls, settledOnly) => {
|
|
22136
|
+
calls.forEach((call, index) => {
|
|
22137
|
+
const key = `${activityAttempt}:${toolCallKey(call, index)}`;
|
|
22138
|
+
if (settledOnly && !observedToolCalls.has(key)) {
|
|
22139
|
+
observedToolCalls.add(key);
|
|
22140
|
+
const narration2 = takeInterimNarration();
|
|
22141
|
+
pendingToolNarrations.set(key, narration2);
|
|
22142
|
+
if (narration2)
|
|
22143
|
+
lastNarratedToolCall = key;
|
|
22142
22144
|
}
|
|
22143
|
-
|
|
22144
|
-
|
|
22145
|
-
|
|
22146
|
-
|
|
22147
|
-
|
|
22148
|
-
|
|
22149
|
-
|
|
22150
|
-
|
|
22145
|
+
if (settledOnly || publishedToolCalls.has(key) || !toolCallSettled(call))
|
|
22146
|
+
return;
|
|
22147
|
+
publishedToolCalls.add(key);
|
|
22148
|
+
const narration = pendingToolNarrations.get(key) ?? "";
|
|
22149
|
+
this.activityTail = this.activityTail.catch(() => void 0).then(async () => {
|
|
22150
|
+
let activity = pendingToolActivities.get(key);
|
|
22151
|
+
if (!activity) {
|
|
22152
|
+
const toolActivity = await cornerToolActivity(call, this.options.worktreePath, requestedBy);
|
|
22153
|
+
activity = [
|
|
22154
|
+
...narration ? [
|
|
22155
|
+
{
|
|
22156
|
+
kind: "output",
|
|
22157
|
+
title: "Update",
|
|
22158
|
+
text: narration,
|
|
22159
|
+
...requestedBy ? { requestedBy } : {}
|
|
22160
|
+
}
|
|
22161
|
+
] : [],
|
|
22162
|
+
toolActivity
|
|
22163
|
+
];
|
|
22164
|
+
pendingToolActivities.set(key, activity);
|
|
22165
|
+
}
|
|
22166
|
+
await api.execute("postAgentActivity", {
|
|
22167
|
+
agentId: this.agent.publicKey,
|
|
22168
|
+
roomId: cornerId,
|
|
22169
|
+
requestId,
|
|
22170
|
+
cornerActivityKey: key,
|
|
22171
|
+
activity
|
|
22172
|
+
});
|
|
22173
|
+
pendingToolNarrations.delete(key);
|
|
22174
|
+
pendingToolActivities.delete(key);
|
|
22175
|
+
}).then(() => void 0).catch((error) => {
|
|
22176
|
+
publishedToolCalls.delete(key);
|
|
22177
|
+
console.error(`[thin-core] corner ${cornerId} tool activity failed:`, error);
|
|
22178
|
+
});
|
|
22179
|
+
});
|
|
22180
|
+
};
|
|
22181
|
+
const flushToolCalls = async (calls, finalReply) => {
|
|
22182
|
+
await this.activityTail;
|
|
22183
|
+
if (lastNarratedToolCall && pendingToolNarrations.get(lastNarratedToolCall) === finalReply)
|
|
22184
|
+
pendingToolNarrations.delete(lastNarratedToolCall);
|
|
22185
|
+
publishToolCalls(calls, false);
|
|
22186
|
+
await this.activityTail;
|
|
22187
|
+
publishToolCalls(calls, false);
|
|
22188
|
+
await this.activityTail;
|
|
22189
|
+
};
|
|
22190
|
+
const runPrompt = async () => {
|
|
22191
|
+
activityAttempt += 1;
|
|
22192
|
+
publishedToolCalls.clear();
|
|
22193
|
+
observedToolCalls.clear();
|
|
22194
|
+
pendingToolNarrations.clear();
|
|
22195
|
+
pendingToolActivities.clear();
|
|
22196
|
+
lastNarratedToolCall = void 0;
|
|
22197
|
+
stream.beginRun();
|
|
22198
|
+
completedNarrationRuns = [];
|
|
22199
|
+
narrationRunBoundary = 0;
|
|
22200
|
+
currentNarrationRun = "";
|
|
22201
|
+
trace.promptSent();
|
|
22202
|
+
return this.client.sessionPrompt(this.sessionId, promptWithImages(buildPrompt(), attachmentImageBlocks(delivered, this.acceptsImages())), 12e4, (delta, full, currentRun, runs) => {
|
|
22203
|
+
trace.firstModelOutput();
|
|
22204
|
+
if (runs) {
|
|
22205
|
+
completedNarrationRuns = runs.slice(narrationRunBoundary);
|
|
22206
|
+
currentNarrationRun = "";
|
|
22207
|
+
} else if (currentRun === void 0)
|
|
22208
|
+
currentNarrationRun += delta;
|
|
22209
|
+
else {
|
|
22210
|
+
if (currentNarrationRun && currentNarrationRun !== currentRun && !currentRun.startsWith(currentNarrationRun))
|
|
22211
|
+
completedNarrationRuns.push(currentNarrationRun);
|
|
22212
|
+
currentNarrationRun = currentRun;
|
|
22151
22213
|
}
|
|
22152
|
-
|
|
22153
|
-
|
|
22154
|
-
|
|
22155
|
-
|
|
22156
|
-
|
|
22157
|
-
...active.steers.map((steerItem) => roomMessagePrompt(steerItem.authorId.slice(0, 12), steerItem.body, steerItem.attachments, this.deliveredAttachments.get(steerItem.id), this.acceptsImages())),
|
|
22158
|
-
"Continue now and answer the updated request without erasing the earlier context."
|
|
22159
|
-
].join("\n\n");
|
|
22160
|
-
}
|
|
22161
|
-
return result2;
|
|
22214
|
+
stream.onChunk(delta, full);
|
|
22215
|
+
}, void 0, (calls) => {
|
|
22216
|
+
trace.toolCalls(calls);
|
|
22217
|
+
publishToolCalls(calls, true);
|
|
22218
|
+
});
|
|
22162
22219
|
};
|
|
22163
22220
|
let result = await runPrompt();
|
|
22164
22221
|
trace.promptSettled();
|
|
22165
22222
|
let explained = await this.explainEmpty(result);
|
|
22166
|
-
if (explained && shouldRetryEmptyTurn(explained)) {
|
|
22223
|
+
if (explained && !restates && shouldRetryEmptyTurn(explained)) {
|
|
22224
|
+
await flushToolCalls(result.toolCalls, "");
|
|
22167
22225
|
const silent = this.servingProviders();
|
|
22168
22226
|
const next = await this.repinNextProvider(trace, explained.reason);
|
|
22169
22227
|
if (next) {
|
|
22170
|
-
console.warn(`[thin-core]
|
|
22228
|
+
console.warn(`[thin-core] corner ${cornerId} turn ${requestId}: ${turnFailureReasonWithProvider(explained.reason, silent)}; retrying on ${next}`);
|
|
22171
22229
|
result = await runPrompt();
|
|
22172
22230
|
trace.promptSettled();
|
|
22173
22231
|
explained = await this.explainEmpty(result);
|
|
22174
22232
|
}
|
|
22175
22233
|
}
|
|
22176
|
-
|
|
22177
|
-
|
|
22178
|
-
|
|
22179
|
-
console.log(`[thin-core] monolith Room ${this.options.roomId} turn ${item.id} paused on a grant card`);
|
|
22180
|
-
} else if (resumedRequestId) {
|
|
22181
|
-
console.log(`[thin-core] monolith Room ${this.options.roomId} turn ${resumedRequestId} resumed by grant decision ${item.id}`);
|
|
22182
|
-
}
|
|
22183
|
-
const openCornerCall = result.toolCalls.find((call) => /(?:^|[._:/-])open_corner$/i.test(call.title ?? ""));
|
|
22184
|
-
if (openCornerCall) {
|
|
22185
|
-
console.log(`[thin-core] monolith Room ${this.options.roomId} tool call: ${openCornerCall.title} (${openCornerCall.status ?? "no status"})`);
|
|
22186
|
-
if (!isFailedToolCall(openCornerCall))
|
|
22187
|
-
this.options.onCornerOpened?.();
|
|
22234
|
+
if (this.stoppedTurns.has(requestId)) {
|
|
22235
|
+
stream.close();
|
|
22236
|
+
throw new TurnStoppedError("turn stopped by the requester");
|
|
22188
22237
|
}
|
|
22189
|
-
|
|
22238
|
+
let reply = durableReplyText(result.agentText);
|
|
22239
|
+
if (!reply && explained?.recoveredText)
|
|
22240
|
+
reply = durableReplyText(explained.recoveredText);
|
|
22241
|
+
await flushToolCalls(result.toolCalls, reply);
|
|
22242
|
+
for (const call of result.toolCalls) {
|
|
22190
22243
|
const failure = toolCallFailureLine(call);
|
|
22191
|
-
if (failure)
|
|
22192
|
-
console.warn(`[thin-core]
|
|
22193
|
-
}
|
|
22244
|
+
if (failure)
|
|
22245
|
+
console.warn(`[thin-core] corner ${cornerId} ${failure}`);
|
|
22194
22246
|
}
|
|
22195
22247
|
stream.close();
|
|
22196
|
-
let reply = durableReplyText(result.agentText);
|
|
22197
22248
|
if (!reply && explained) {
|
|
22198
|
-
reply
|
|
22199
|
-
if (!reply) {
|
|
22249
|
+
if (!reply && !(restates && !isAccountOrProviderRefusal(explained.record))) {
|
|
22200
22250
|
throw new Error(turnFailureReasonWithProvider(explained.reason, this.servingProviders()));
|
|
22201
22251
|
}
|
|
22202
|
-
console.warn(`[thin-core]
|
|
22203
|
-
}
|
|
22204
|
-
if (openCornerCall && !isFailedToolCall(openCornerCall)) {
|
|
22205
|
-
reply = stripCornerOpenEcho(reply);
|
|
22252
|
+
console.warn(`[thin-core] corner ${cornerId} turn ${requestId}: ${explained.reason}`);
|
|
22206
22253
|
}
|
|
22207
|
-
|
|
22208
|
-
|
|
22209
|
-
|
|
22254
|
+
const durableReply = spoken(reply);
|
|
22255
|
+
const mentionIds = durableReply ? agentReplyMentionIds(durableReply, roster, this.agent.publicKey) : [];
|
|
22256
|
+
await trace.measure("publish", () => stream.settle(durableReply, durableReply ? {
|
|
22257
|
+
...requestedById ? { triggerMessageId: requestId } : {},
|
|
22258
|
+
mentionIds
|
|
22210
22259
|
} : {}));
|
|
22211
|
-
}, { priority: "interactive", roomKey:
|
|
22212
|
-
}, (error) => console.error(`[thin-core]
|
|
22260
|
+
}, { priority: "interactive", roomKey: cornerId });
|
|
22261
|
+
}, (error) => console.error(`[thin-core] corner ${cornerId} receipt heartbeat failed:`, error));
|
|
22213
22262
|
await api.execute("postAgentTurnReceipt", {
|
|
22214
22263
|
agentId: this.agent.publicKey,
|
|
22215
|
-
roomId:
|
|
22216
|
-
requestId
|
|
22264
|
+
roomId: cornerId,
|
|
22265
|
+
requestId,
|
|
22217
22266
|
status: "complete",
|
|
22218
|
-
generationId:
|
|
22267
|
+
generationId: this.commandContext.generationId
|
|
22219
22268
|
});
|
|
22220
22269
|
await trace.finish("complete");
|
|
22221
22270
|
} catch (error) {
|
|
22271
|
+
if (error instanceof TurnStoppedError || this.stoppedTurns.has(requestId)) {
|
|
22272
|
+
console.log(`[thin-core] corner ${cornerId} turn ${requestId} stopped by the requester`);
|
|
22273
|
+
await trace.finish("cancelled");
|
|
22274
|
+
return;
|
|
22275
|
+
}
|
|
22222
22276
|
const reason = distillTurnFailureReason(error);
|
|
22223
22277
|
await api.execute("postAgentTurnReceipt", {
|
|
22224
22278
|
agentId: this.agent.publicKey,
|
|
22225
|
-
roomId:
|
|
22226
|
-
requestId
|
|
22279
|
+
roomId: cornerId,
|
|
22280
|
+
requestId,
|
|
22227
22281
|
status: "failed",
|
|
22228
|
-
generationId:
|
|
22282
|
+
generationId: this.commandContext.generationId,
|
|
22229
22283
|
reason
|
|
22230
22284
|
});
|
|
22231
22285
|
await trace.finish("failed", reason);
|
|
22232
22286
|
throw error;
|
|
22233
22287
|
} finally {
|
|
22234
22288
|
this.busy = false;
|
|
22289
|
+
this.currentTurn = void 0;
|
|
22235
22290
|
}
|
|
22236
22291
|
}
|
|
22292
|
+
/**
|
|
22293
|
+
* Bring this worktree onto the corner's branch as GitHub currently has it,
|
|
22294
|
+
* before any work is done on top of it.
|
|
22295
|
+
*
|
|
22296
|
+
* The branch is the shared artifact: another member agent may have pushed to
|
|
22297
|
+
* it since this helper last looked, and a first touch of a corner this
|
|
22298
|
+
* helper did not open starts from whatever `room-runtime.ts` restored. A
|
|
22299
|
+
* divergence this cannot rebase away is raised, not pushed over — the turn
|
|
22300
|
+
* fails with that sentence and the server inscribes it in the corner.
|
|
22301
|
+
*/
|
|
22302
|
+
async syncBranch() {
|
|
22303
|
+
const repository = this.options.repository;
|
|
22304
|
+
if (!repository)
|
|
22305
|
+
return;
|
|
22306
|
+
const token = await this.options.api.execute("getRoomGitHubToken", { roomId: this.options.parentRoomId }).then((granted) => granted.token).catch(() => repository.githubToken);
|
|
22307
|
+
await syncCornerBranch({
|
|
22308
|
+
worktreePath: this.options.worktreePath,
|
|
22309
|
+
featureBranch: repository.featureBranch,
|
|
22310
|
+
env: { ...process.env, GH_TOKEN: token, GITHUB_TOKEN: token, GIT_TERMINAL_PROMPT: "0" }
|
|
22311
|
+
});
|
|
22312
|
+
}
|
|
22237
22313
|
async run() {
|
|
22238
|
-
const { api,
|
|
22239
|
-
let cursor3;
|
|
22240
|
-
const processedInboxIds = /* @__PURE__ */ new Set();
|
|
22241
|
-
const pushedInbox = [];
|
|
22242
|
-
let pendingPushedCursor;
|
|
22243
|
-
let liveConnected = false;
|
|
22244
|
-
let stopLive;
|
|
22314
|
+
const { api, cornerId, signal } = this.options;
|
|
22245
22315
|
try {
|
|
22246
|
-
|
|
22247
|
-
|
|
22248
|
-
|
|
22249
|
-
|
|
22250
|
-
|
|
22251
|
-
|
|
22252
|
-
|
|
22253
|
-
|
|
22254
|
-
|
|
22255
|
-
|
|
22256
|
-
|
|
22257
|
-
|
|
22258
|
-
|
|
22259
|
-
|
|
22260
|
-
|
|
22261
|
-
|
|
22262
|
-
|
|
22263
|
-
|
|
22264
|
-
|
|
22316
|
+
await runServerCommandIntake({
|
|
22317
|
+
api,
|
|
22318
|
+
roomId: cornerId,
|
|
22319
|
+
agentId: this.agent.publicKey,
|
|
22320
|
+
context: this.commandContext,
|
|
22321
|
+
signal,
|
|
22322
|
+
pollMs: this.options.pollMs,
|
|
22323
|
+
presence: {
|
|
22324
|
+
...this.options.config.daemonReleaseVersion ? { releaseVersion: this.options.config.daemonReleaseVersion } : {},
|
|
22325
|
+
...this.options.config.daemonSourceSha ? { sourceSha: this.options.config.daemonSourceSha } : {},
|
|
22326
|
+
available: !this.options.config.modelUnavailable
|
|
22327
|
+
},
|
|
22328
|
+
onWake: (wake) => {
|
|
22329
|
+
this.wakeIntake = wake;
|
|
22330
|
+
},
|
|
22331
|
+
onPoll: () => this.options.onPoll(),
|
|
22332
|
+
onError: (error) => console.error("[thin-core] corner command failed", error),
|
|
22333
|
+
stop: (requestId) => this.stopTurn(requestId),
|
|
22334
|
+
closed: async () => {
|
|
22335
|
+
const state = await api.execute("getCornerRestoreState", { cornerId });
|
|
22336
|
+
if (!state.closeRequested)
|
|
22337
|
+
return false;
|
|
22338
|
+
await this.options.onCloseRequested();
|
|
22339
|
+
return true;
|
|
22340
|
+
},
|
|
22341
|
+
run: (command) => this.prompt(command.turnRequestId, command.source.body, command.source.attachments, command.source.authorId, command.reason === "corner_check" ? [command.source.body] : void 0)
|
|
22265
22342
|
});
|
|
22266
|
-
while (!signal?.aborted) {
|
|
22267
|
-
try {
|
|
22268
|
-
if (!this.activeTurn && this.queuedTurns.length) {
|
|
22269
|
-
this.startPrompt(this.queuedTurns.shift());
|
|
22270
|
-
}
|
|
22271
|
-
const pollNow = pushedInbox.length === 0 && (!liveConnected || this.reconciliationRequested);
|
|
22272
|
-
const inbox = !pollNow ? { items: [], cursor: void 0 } : await api.execute("getRoomInbox", {
|
|
22273
|
-
roomId,
|
|
22274
|
-
...cursor3 ? { after: cursor3 } : {},
|
|
22275
|
-
...rewindSupported ? { rewind: true } : {},
|
|
22276
|
-
limit: 200
|
|
22277
|
-
});
|
|
22278
|
-
if (pollNow) {
|
|
22279
|
-
this.reconciliationRequested = false;
|
|
22280
|
-
}
|
|
22281
|
-
const delivered = [...pushedInbox.splice(0), ...inbox.items];
|
|
22282
|
-
for (const item of delivered) {
|
|
22283
|
-
if (processedInboxIds.has(item.id))
|
|
22284
|
-
continue;
|
|
22285
|
-
processedInboxIds.add(item.id);
|
|
22286
|
-
while (processedInboxIds.size > 1e4)
|
|
22287
|
-
processedInboxIds.delete(processedInboxIds.values().next().value);
|
|
22288
|
-
if (!inboxItemTriggersTurn(item, this.agent.publicKey))
|
|
22289
|
-
continue;
|
|
22290
|
-
if (!inboxItemSkipsSenderPolicy(item, this.agent.publicKey)) {
|
|
22291
|
-
const authority = await api.execute("getRoomAuthority", {
|
|
22292
|
-
roomId,
|
|
22293
|
-
principalId: item.authorId
|
|
22294
|
-
});
|
|
22295
|
-
const humanPermitted = authority.principalKind === "human" ? await this.currentPrincipalCanDrive(this.options.workspaceId, item.authorId) : false;
|
|
22296
|
-
if (!roomPrincipalMayAddressAgent(authority, humanPermitted))
|
|
22297
|
-
continue;
|
|
22298
|
-
}
|
|
22299
|
-
const active = this.activeTurn;
|
|
22300
|
-
if (!active)
|
|
22301
|
-
this.startPrompt(item);
|
|
22302
|
-
else if (active.phase === "prompting")
|
|
22303
|
-
this.steer(active, item);
|
|
22304
|
-
else
|
|
22305
|
-
this.queuedTurns.push(item);
|
|
22306
|
-
}
|
|
22307
|
-
cursor3 = laterInboxCursor(cursor3, laterInboxCursor(inbox.cursor, pendingPushedCursor));
|
|
22308
|
-
pendingPushedCursor = void 0;
|
|
22309
|
-
api.updateLiveCursor?.(roomId, cursor3);
|
|
22310
|
-
if (pollNow)
|
|
22311
|
-
this.options.health.poll();
|
|
22312
|
-
if (!pushedInbox.length) {
|
|
22313
|
-
await Promise.race([
|
|
22314
|
-
wait2(liveConnected ? 2147483647 : this.options.pollMs ?? 1e3, signal),
|
|
22315
|
-
new Promise((resolve30) => {
|
|
22316
|
-
this.wakeIntake = resolve30;
|
|
22317
|
-
})
|
|
22318
|
-
]);
|
|
22319
|
-
}
|
|
22320
|
-
} catch (error) {
|
|
22321
|
-
if (signal?.aborted)
|
|
22322
|
-
break;
|
|
22323
|
-
this.options.health.failure(1e3);
|
|
22324
|
-
console.error(`[thin-core] monolith Room ${roomId} turn loop failed:`, error);
|
|
22325
|
-
await wait2(1e3, signal);
|
|
22326
|
-
}
|
|
22327
|
-
}
|
|
22328
22343
|
} finally {
|
|
22329
|
-
|
|
22330
|
-
this.
|
|
22331
|
-
this.options.grantRunner?.unregister(roomId);
|
|
22332
|
-
if (this.activeTurn?.phase === "prompting" && this.client && this.sessionId) {
|
|
22333
|
-
this.client.sessionCancel(this.sessionId);
|
|
22334
|
-
}
|
|
22335
|
-
await this.activeTurn?.promise;
|
|
22336
|
-
await this.options.scheduler.suspend(roomId);
|
|
22344
|
+
this.options.grantRunner?.unregister(cornerId);
|
|
22345
|
+
await this.options.scheduler.suspend(cornerId);
|
|
22337
22346
|
}
|
|
22338
22347
|
}
|
|
22339
22348
|
};
|
|
22340
|
-
function roomMessagePrompt(author, body, attachments, delivered, harnessAcceptsImages = true) {
|
|
22341
|
-
const message = body.trim() || "(shared attachments)";
|
|
22342
|
-
const rendered = author ? `${author}: ${message}` : message;
|
|
22343
|
-
return [rendered, ...attachmentPromptLines(attachments, delivered, harnessAcceptsImages)].join("\n");
|
|
22344
|
-
}
|
|
22345
|
-
async function wait2(ms, signal) {
|
|
22346
|
-
if (signal?.aborted)
|
|
22347
|
-
return;
|
|
22348
|
-
await new Promise((resolveWait) => {
|
|
22349
|
-
const done = () => {
|
|
22350
|
-
clearTimeout(timer);
|
|
22351
|
-
signal?.removeEventListener("abort", done);
|
|
22352
|
-
resolveWait();
|
|
22353
|
-
};
|
|
22354
|
-
const timer = setTimeout(done, ms);
|
|
22355
|
-
signal?.addEventListener("abort", done, { once: true });
|
|
22356
|
-
});
|
|
22357
|
-
}
|
|
22358
22349
|
|
|
22359
22350
|
// apps/body/dist/session-scheduler.js
|
|
22360
22351
|
var DEFAULT_PER_ROOM_LIVE_SESSIONS = 10;
|
|
@@ -22771,11 +22762,11 @@ function shouldPostInitialCornerWorkingState(restore, isOpener = true) {
|
|
|
22771
22762
|
}
|
|
22772
22763
|
async function materializeCornerWorktree(input) {
|
|
22773
22764
|
const remote = roomCheckoutRemote(input.remote);
|
|
22774
|
-
const repositoryHash =
|
|
22765
|
+
const repositoryHash = createHash5("sha256").update(remote).digest("hex").slice(0, 24);
|
|
22775
22766
|
const gitCommonDir = resolve19(input.supervisorRoot, "beeline", "repositories", `${repositoryHash}.git`);
|
|
22776
22767
|
const path = resolve19(input.supervisorRoot, "beeline", "corners", input.cornerId);
|
|
22777
|
-
await
|
|
22778
|
-
await
|
|
22768
|
+
await mkdir12(dirname7(gitCommonDir), { recursive: true, mode: 448 });
|
|
22769
|
+
await mkdir12(dirname7(path), { recursive: true, mode: 448 });
|
|
22779
22770
|
const authEnv = githubGitEnv(input.token);
|
|
22780
22771
|
if (!existsSync4(resolve19(gitCommonDir, "HEAD"))) {
|
|
22781
22772
|
await execFileAsync4("git", ["clone", "--bare", remote, gitCommonDir], {
|
|
@@ -22962,9 +22953,6 @@ var RoomRuntimeCoordinator = class {
|
|
|
22962
22953
|
this.drainDeadlineAt = Math.min(this.drainDeadlineAt ?? Number.POSITIVE_INFINITY, deadlineAt);
|
|
22963
22954
|
}
|
|
22964
22955
|
}
|
|
22965
|
-
async currentPrincipalCanDrive(roomId, workspaceId, principalId) {
|
|
22966
|
-
return this.running.get(roomId)?.body.currentPrincipalCanDrive(workspaceId, principalId);
|
|
22967
|
-
}
|
|
22968
22956
|
async prepareForForcedUpdateRestart() {
|
|
22969
22957
|
const rooms = [...this.running.values()];
|
|
22970
22958
|
await Promise.allSettled(rooms.filter((room) => room.body.isBusy()).map((room) => room.body.prepareForForcedUpdateRestart()));
|
|
@@ -23049,7 +23037,7 @@ var RoomRuntimeCoordinator = class {
|
|
|
23049
23037
|
return this.runtime.rooms.find((room) => room.channelId === roomId);
|
|
23050
23038
|
}
|
|
23051
23039
|
roomRoot(roomId) {
|
|
23052
|
-
return this.roomRecord(roomId)?.root ?? resolve19(
|
|
23040
|
+
return this.roomRecord(roomId)?.root ?? resolve19(dirname7(this.configPath), "rooms", roomId);
|
|
23053
23041
|
}
|
|
23054
23042
|
roomAgentHomeRoot(workspaceRoot, required = false) {
|
|
23055
23043
|
const flag = process.env.BUZZY_BODY_ROOM_HOME;
|
|
@@ -23072,9 +23060,9 @@ var RoomRuntimeCoordinator = class {
|
|
|
23072
23060
|
...this.baseConfig,
|
|
23073
23061
|
workspaceRoot,
|
|
23074
23062
|
agentPrivateRoot: resolve19(workspaceRoot, "agent-private"),
|
|
23075
|
-
agentMemoryRoot: resolve19(
|
|
23076
|
-
openRouterRoutingCacheDir: openRouterRoutingCacheDir(
|
|
23077
|
-
turnTraceDir: turnTraceDirectory(
|
|
23063
|
+
agentMemoryRoot: resolve19(dirname7(this.configPath), "memory"),
|
|
23064
|
+
openRouterRoutingCacheDir: openRouterRoutingCacheDir(dirname7(this.configPath)),
|
|
23065
|
+
turnTraceDir: turnTraceDirectory(dirname7(this.configPath)),
|
|
23078
23066
|
...agentHomeRoot ? { agentHomeRoot } : {}
|
|
23079
23067
|
};
|
|
23080
23068
|
}
|
|
@@ -23140,9 +23128,9 @@ var RoomRuntimeCoordinator = class {
|
|
|
23140
23128
|
return this.roomRoot(roomId);
|
|
23141
23129
|
const remote = roomCheckoutRemote(repository.remote);
|
|
23142
23130
|
const targetBranch = repository.targetBranch || "main";
|
|
23143
|
-
const checkoutId =
|
|
23131
|
+
const checkoutId = createHash5("sha256").update(`${remote}\0${targetBranch}`).digest("hex").slice(0, 24);
|
|
23144
23132
|
const path = resolve19(this.runtime.supervisorRoot, "beeline", "room-checkouts", checkoutId);
|
|
23145
|
-
await
|
|
23133
|
+
await mkdir12(dirname7(path), { recursive: true, mode: 448 });
|
|
23146
23134
|
const token = remote.startsWith("https://github.com/") ? await this.options.daemonApi.execute("getRoomGitHubToken", { roomId }) : void 0;
|
|
23147
23135
|
const env = token ? githubGitEnv(token.token) : process.env;
|
|
23148
23136
|
if (!existsSync4(resolve19(path, ".git"))) {
|
|
@@ -23208,7 +23196,7 @@ var RoomRuntimeCoordinator = class {
|
|
|
23208
23196
|
}) : void 0;
|
|
23209
23197
|
const workspacePath = worktree?.path ?? resolve19(this.roomRoot(corner.cornerId), "scratch");
|
|
23210
23198
|
if (!worktree)
|
|
23211
|
-
await
|
|
23199
|
+
await mkdir12(workspacePath, { recursive: true, mode: 448 });
|
|
23212
23200
|
const isOpener = !corner.openedBy || corner.openedBy === this.agent.publicKey;
|
|
23213
23201
|
if (worktree && shouldPostInitialCornerWorkingState(restore, isOpener)) {
|
|
23214
23202
|
await this.options.daemonApi.execute("postCornerRemoteState", {
|
|
@@ -23507,9 +23495,9 @@ var ThinDaemonCore = class {
|
|
|
23507
23495
|
|
|
23508
23496
|
// apps/body/dist/systemd.js
|
|
23509
23497
|
import { execFile as execFile6 } from "node:child_process";
|
|
23510
|
-
import { mkdir as
|
|
23498
|
+
import { mkdir as mkdir13, readFile as readFile8, writeFile as writeFile9 } from "node:fs/promises";
|
|
23511
23499
|
import { homedir as homedir8 } from "node:os";
|
|
23512
|
-
import { dirname as
|
|
23500
|
+
import { dirname as dirname8, resolve as resolve20 } from "node:path";
|
|
23513
23501
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
23514
23502
|
import { promisify as promisify5 } from "node:util";
|
|
23515
23503
|
var execFileAsync5 = promisify5(execFile6);
|
|
@@ -23580,8 +23568,8 @@ async function installAgentService(publicKey, options = {}) {
|
|
|
23580
23568
|
const content = agentServiceUnit();
|
|
23581
23569
|
const existing = await readFile8(path, "utf8").catch(() => "");
|
|
23582
23570
|
if (existing !== content) {
|
|
23583
|
-
await
|
|
23584
|
-
await
|
|
23571
|
+
await mkdir13(dirname8(path), { recursive: true, mode: 448 });
|
|
23572
|
+
await writeFile9(path, content, { mode: 384 });
|
|
23585
23573
|
}
|
|
23586
23574
|
const run2 = options.run ?? runSystemctl;
|
|
23587
23575
|
await run2(["daemon-reload"]);
|
|
@@ -23661,7 +23649,7 @@ async function retireRemovedAgent(runtime, options = {}) {
|
|
|
23661
23649
|
}
|
|
23662
23650
|
|
|
23663
23651
|
// apps/body/dist/start-command.js
|
|
23664
|
-
import { dirname as
|
|
23652
|
+
import { dirname as dirname9 } from "node:path";
|
|
23665
23653
|
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
23666
23654
|
var DEFAULT_RESTART_DRAIN_TIMEOUT_MS = 30 * 6e4;
|
|
23667
23655
|
var RESTART_WAIT_REPORT_INTERVAL_MS = 3e4;
|
|
@@ -23736,7 +23724,7 @@ async function runStartCommand(args, interactiveUi) {
|
|
|
23736
23724
|
continue;
|
|
23737
23725
|
}
|
|
23738
23726
|
const spinnerHandle = spinner();
|
|
23739
|
-
spinnerHandle.start(`Starting ${
|
|
23727
|
+
spinnerHandle.start(`Starting ${dirname9(path)}\u2026`);
|
|
23740
23728
|
try {
|
|
23741
23729
|
await startRuntime(path, spinnerHandle);
|
|
23742
23730
|
spinnerHandle.stop(import_picocolors.default.green("Started."));
|
|
@@ -23752,9 +23740,9 @@ async function runStartCommand(args, interactiveUi) {
|
|
|
23752
23740
|
|
|
23753
23741
|
// apps/body/dist/connect-command.js
|
|
23754
23742
|
import { spawn as spawn5 } from "node:child_process";
|
|
23755
|
-
import { createHash as
|
|
23756
|
-
import { chmod as chmod5, mkdir as
|
|
23757
|
-
import { dirname as
|
|
23743
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
23744
|
+
import { chmod as chmod5, mkdir as mkdir15, readFile as readFile10, unlink as unlink2, writeFile as writeFile11 } from "node:fs/promises";
|
|
23745
|
+
import { dirname as dirname11, resolve as resolve22 } from "node:path";
|
|
23758
23746
|
import { stdin as stdin3, stdout as stdout4 } from "node:process";
|
|
23759
23747
|
|
|
23760
23748
|
// apps/body/dist/clack-support.js
|
|
@@ -24328,7 +24316,7 @@ function requestConnectGrant(baseUrl, pairingCode, selection, fetchImpl) {
|
|
|
24328
24316
|
const normalizedPairingCode = normalizeAgentPairingCode(pairingCode);
|
|
24329
24317
|
if (!normalizedPairingCode)
|
|
24330
24318
|
throw new Error("invalid pairing code");
|
|
24331
|
-
const avatarSeed =
|
|
24319
|
+
const avatarSeed = createHash7("sha256").update(normalizedPairingCode.toUpperCase()).digest("hex").slice(0, 32);
|
|
24332
24320
|
return jsonRequest(`${baseUrl}/auth/agent/connect`, {
|
|
24333
24321
|
pairing_code: normalizedPairingCode,
|
|
24334
24322
|
harness: selection.harness,
|
|
@@ -24398,8 +24386,8 @@ function providerEnvironment(selection) {
|
|
|
24398
24386
|
};
|
|
24399
24387
|
}
|
|
24400
24388
|
async function writePrivateJson(path, value) {
|
|
24401
|
-
await
|
|
24402
|
-
await
|
|
24389
|
+
await mkdir15(dirname11(path), { recursive: true, mode: 448 });
|
|
24390
|
+
await writeFile11(path, `${JSON.stringify(value, null, 2)}
|
|
24403
24391
|
`, { mode: 384 });
|
|
24404
24392
|
await chmod5(path, 384);
|
|
24405
24393
|
}
|
|
@@ -24408,9 +24396,9 @@ async function writeProviderEnv(selection, agentPubkey) {
|
|
|
24408
24396
|
if (Object.keys(values).length === 0)
|
|
24409
24397
|
return void 0;
|
|
24410
24398
|
const path = resolve22(defaultSupervisorRoot(process.env), "beeline", "connect", `${agentPubkey}.env`);
|
|
24411
|
-
await
|
|
24399
|
+
await mkdir15(dirname11(path), { recursive: true, mode: 448 });
|
|
24412
24400
|
const contents = Object.entries(values).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join("\n");
|
|
24413
|
-
await
|
|
24401
|
+
await writeFile11(path, `${contents}
|
|
24414
24402
|
`, { mode: 384 });
|
|
24415
24403
|
await chmod5(path, 384);
|
|
24416
24404
|
return path;
|
|
@@ -24566,7 +24554,7 @@ async function runConnectFinishCommand(path) {
|
|
|
24566
24554
|
if (model) {
|
|
24567
24555
|
const decision2 = await resolveOpenRouterRouting({
|
|
24568
24556
|
model,
|
|
24569
|
-
cacheDir: openRouterRoutingCacheDir(
|
|
24557
|
+
cacheDir: openRouterRoutingCacheDir(dirname11(connected.configPath)),
|
|
24570
24558
|
...apiKey ? { apiKey } : {},
|
|
24571
24559
|
probeTimeoutMs: 1e4
|
|
24572
24560
|
});
|
|
@@ -24588,12 +24576,12 @@ init_self_update_manifest();
|
|
|
24588
24576
|
// apps/body/dist/managed-update.js
|
|
24589
24577
|
init_self_update();
|
|
24590
24578
|
import { spawn as spawn6 } from "node:child_process";
|
|
24591
|
-
import { mkdir as
|
|
24592
|
-
import { dirname as
|
|
24579
|
+
import { mkdir as mkdir17, rm as rm6, stat as stat2, writeFile as writeFile13 } from "node:fs/promises";
|
|
24580
|
+
import { dirname as dirname13, resolve as resolve24 } from "node:path";
|
|
24593
24581
|
|
|
24594
24582
|
// apps/body/dist/update-rollback-alert.js
|
|
24595
|
-
import { mkdir as
|
|
24596
|
-
import { dirname as
|
|
24583
|
+
import { mkdir as mkdir16, readFile as readFile11, rename as rename4, unlink as unlink3, writeFile as writeFile12 } from "node:fs/promises";
|
|
24584
|
+
import { dirname as dirname12, resolve as resolve23 } from "node:path";
|
|
24597
24585
|
var REPORT_INTERVAL_MS = 60 * 60 * 1e3;
|
|
24598
24586
|
var lastLogged = /* @__PURE__ */ new Map();
|
|
24599
24587
|
function updateRollbackAlertPath(runtimeDir) {
|
|
@@ -24602,8 +24590,8 @@ function updateRollbackAlertPath(runtimeDir) {
|
|
|
24602
24590
|
async function writeAlert(runtimeDir, alert) {
|
|
24603
24591
|
const path = updateRollbackAlertPath(runtimeDir);
|
|
24604
24592
|
const staged = `${path}.${process.pid}.tmp`;
|
|
24605
|
-
await
|
|
24606
|
-
await
|
|
24593
|
+
await mkdir16(dirname12(path), { recursive: true });
|
|
24594
|
+
await writeFile12(staged, `${JSON.stringify(alert, null, 2)}
|
|
24607
24595
|
`, { mode: 384 });
|
|
24608
24596
|
await rename4(staged, path);
|
|
24609
24597
|
}
|
|
@@ -24668,11 +24656,11 @@ async function withInstallLock(layout, work, options = {}) {
|
|
|
24668
24656
|
const now2 = options.now ?? Date.now;
|
|
24669
24657
|
const lock = resolve24(layout.releasesRoot, ".state", "install.lock");
|
|
24670
24658
|
const deadline = now2() + (options.waitMs ?? 1e4);
|
|
24671
|
-
await
|
|
24659
|
+
await mkdir17(dirname13(lock), { recursive: true });
|
|
24672
24660
|
for (; ; ) {
|
|
24673
24661
|
try {
|
|
24674
|
-
await
|
|
24675
|
-
await
|
|
24662
|
+
await mkdir17(lock);
|
|
24663
|
+
await writeFile13(resolve24(lock, "owner"), `${process.pid}
|
|
24676
24664
|
${now2()}
|
|
24677
24665
|
`, "utf8");
|
|
24678
24666
|
break;
|
|
@@ -25082,7 +25070,7 @@ async function proveLoadedReleaseReady(layout, runtimeDir, loadedRelease, option
|
|
|
25082
25070
|
});
|
|
25083
25071
|
if (!accepted)
|
|
25084
25072
|
return false;
|
|
25085
|
-
await
|
|
25073
|
+
await writeFile13(resolve24(runtimeDir, "daemon-ready.json"), `${JSON.stringify({
|
|
25086
25074
|
readyAt: (options.now ?? Date.now)(),
|
|
25087
25075
|
loadedRelease,
|
|
25088
25076
|
functionalProof: options.functionalProof
|
|
@@ -25310,8 +25298,8 @@ async function runUpdateCommand(args) {
|
|
|
25310
25298
|
init_self_update();
|
|
25311
25299
|
|
|
25312
25300
|
// apps/body/dist/daemon-failure.js
|
|
25313
|
-
import { mkdir as
|
|
25314
|
-
import { dirname as
|
|
25301
|
+
import { mkdir as mkdir18, readFile as readFile12, rename as rename5, rm as rm7, writeFile as writeFile14 } from "node:fs/promises";
|
|
25302
|
+
import { dirname as dirname14, resolve as resolve25 } from "node:path";
|
|
25315
25303
|
var DAEMON_FAILURE_LIMIT = 3;
|
|
25316
25304
|
var DAEMON_FAILURE_WINDOW_MS = 5 * 6e4;
|
|
25317
25305
|
function daemonFailurePath(runtimeDir) {
|
|
@@ -25331,8 +25319,8 @@ async function readFailureRecord(runtimeDir) {
|
|
|
25331
25319
|
async function writeFailureRecord(runtimeDir, record2) {
|
|
25332
25320
|
const path = daemonFailurePath(runtimeDir);
|
|
25333
25321
|
const staged = `${path}.${process.pid}.tmp`;
|
|
25334
|
-
await
|
|
25335
|
-
await
|
|
25322
|
+
await mkdir18(dirname14(path), { recursive: true, mode: 448 });
|
|
25323
|
+
await writeFile14(staged, `${JSON.stringify(record2, null, 2)}
|
|
25336
25324
|
`, { mode: 384 });
|
|
25337
25325
|
await rename5(staged, path);
|
|
25338
25326
|
}
|
|
@@ -25354,7 +25342,7 @@ async function clearDaemonStartFailures(runtimeDir) {
|
|
|
25354
25342
|
}
|
|
25355
25343
|
|
|
25356
25344
|
// apps/body/dist/update-functional-probe.js
|
|
25357
|
-
import { mkdir as
|
|
25345
|
+
import { mkdir as mkdir19, rm as rm8 } from "node:fs/promises";
|
|
25358
25346
|
import { homedir as homedir10 } from "node:os";
|
|
25359
25347
|
import { resolve as resolve26 } from "node:path";
|
|
25360
25348
|
var UPDATE_PROBE_SESSION_TIMEOUT_MS = 1e4;
|
|
@@ -25400,6 +25388,9 @@ async function probeOutcome(run2) {
|
|
|
25400
25388
|
await run2();
|
|
25401
25389
|
return { kind: "served" };
|
|
25402
25390
|
} catch (error) {
|
|
25391
|
+
if (error instanceof UpdateFunctionalProbeError && error.reason === "sandbox-unavailable") {
|
|
25392
|
+
return { kind: "sandbox-unavailable", reason: error.message };
|
|
25393
|
+
}
|
|
25403
25394
|
if (error instanceof UpdateFunctionalProbeError && error.providerRefusal) {
|
|
25404
25395
|
return { kind: "refused", ...error.providerRefusal };
|
|
25405
25396
|
}
|
|
@@ -25412,6 +25403,8 @@ function describeCurrentReleaseOutcome(outcome) {
|
|
|
25412
25403
|
return "the current release answered";
|
|
25413
25404
|
case "refused":
|
|
25414
25405
|
return `the current release got a different refusal (${outcome.reason})`;
|
|
25406
|
+
case "sandbox-unavailable":
|
|
25407
|
+
return `the current release has the same unavailable sandbox (${outcome.reason})`;
|
|
25415
25408
|
case "unavailable":
|
|
25416
25409
|
return `the current release could not be compared (${outcome.reason})`;
|
|
25417
25410
|
}
|
|
@@ -25423,13 +25416,33 @@ async function runUpdateFunctionalProbe(input) {
|
|
|
25423
25416
|
throw new UpdateFunctionalProbeError("model-unavailable", input.config.modelUnavailable.detail);
|
|
25424
25417
|
}
|
|
25425
25418
|
if (input.sandboxRequired && !input.config.bwrapPath) {
|
|
25426
|
-
|
|
25419
|
+
const detail = input.sandboxUnavailableDetail ?? "the configured bubblewrap boundary did not pass its startup self-test";
|
|
25420
|
+
if (!input.compareWithCurrentRelease) {
|
|
25421
|
+
throw new UpdateFunctionalProbeError("sandbox-unavailable", detail);
|
|
25422
|
+
}
|
|
25423
|
+
const current = await input.compareWithCurrentRelease({
|
|
25424
|
+
kind: "sandbox-unavailable",
|
|
25425
|
+
reason: detail
|
|
25426
|
+
});
|
|
25427
|
+
if (current.kind !== "sandbox-unavailable") {
|
|
25428
|
+
throw new UpdateFunctionalProbeError("sandbox-unavailable", `${detail}; ${describeCurrentReleaseOutcome(current)}`);
|
|
25429
|
+
}
|
|
25430
|
+
console.warn("[body] update probe: the sandbox is unavailable to this release and the current release alike; preserving fail-closed sandboxing and accepting the bundle as inconclusive");
|
|
25431
|
+
return {
|
|
25432
|
+
harness,
|
|
25433
|
+
sandboxed: false,
|
|
25434
|
+
sessionStarted: false,
|
|
25435
|
+
turnCompleted: false,
|
|
25436
|
+
nativeTools: [],
|
|
25437
|
+
modelAnswer: "unavailable",
|
|
25438
|
+
modelAnswerReason: `${detail} (the current release has the same host sandbox failure)`
|
|
25439
|
+
};
|
|
25427
25440
|
}
|
|
25428
25441
|
const root = input.probeRoot ?? resolve26(input.runtimeDir, "update-functional-probe");
|
|
25429
25442
|
const cwd = resolve26(root, "checkout");
|
|
25430
25443
|
const homeRoot = resolve26(root, "agent-home");
|
|
25431
25444
|
await rm8(root, { recursive: true, force: true });
|
|
25432
|
-
await
|
|
25445
|
+
await mkdir19(cwd, { recursive: true, mode: 448 });
|
|
25433
25446
|
let client;
|
|
25434
25447
|
try {
|
|
25435
25448
|
const agentEnv = {
|
|
@@ -25459,7 +25472,7 @@ async function runUpdateFunctionalProbe(input) {
|
|
|
25459
25472
|
const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
|
|
25460
25473
|
const operatorHome = input.config.operatorHome ?? homedir10();
|
|
25461
25474
|
const homeStateDirs = harnessHomeStateDirs(command, agentEnv.HOME ?? operatorHome);
|
|
25462
|
-
await Promise.all(homeStateDirs.map((dir) =>
|
|
25475
|
+
await Promise.all(homeStateDirs.map((dir) => mkdir19(dir, { recursive: true })));
|
|
25463
25476
|
spawnCommand = wrapAgentCommand({
|
|
25464
25477
|
bwrapPath: input.config.bwrapPath,
|
|
25465
25478
|
spec: {
|
|
@@ -25609,7 +25622,7 @@ async function runUpdateFunctionalProbe(input) {
|
|
|
25609
25622
|
|
|
25610
25623
|
// apps/body/dist/current-release-probe.js
|
|
25611
25624
|
import { spawn as spawn7 } from "node:child_process";
|
|
25612
|
-
import { dirname as
|
|
25625
|
+
import { dirname as dirname15, join as join9 } from "node:path";
|
|
25613
25626
|
init_self_update();
|
|
25614
25627
|
var CURRENT_RELEASE_PROBE_TIMEOUT_MS = 12e4;
|
|
25615
25628
|
var UPDATE_PROBE_COMMAND = "update-probe";
|
|
@@ -25631,6 +25644,9 @@ function parseReport(line) {
|
|
|
25631
25644
|
if (report.probe === "failed" && typeof report.reason === "string") {
|
|
25632
25645
|
return { probe: "failed", reason: report.reason };
|
|
25633
25646
|
}
|
|
25647
|
+
if (report.probe === "sandbox-unavailable" && typeof report.reason === "string") {
|
|
25648
|
+
return { probe: "sandbox-unavailable", reason: report.reason };
|
|
25649
|
+
}
|
|
25634
25650
|
return void 0;
|
|
25635
25651
|
}
|
|
25636
25652
|
function outcomeFromReport(report) {
|
|
@@ -25639,12 +25655,17 @@ function outcomeFromReport(report) {
|
|
|
25639
25655
|
return { kind: "served" };
|
|
25640
25656
|
case "refused":
|
|
25641
25657
|
return { kind: "refused", status: report.status, reason: report.reason };
|
|
25658
|
+
case "sandbox-unavailable":
|
|
25659
|
+
return { kind: "sandbox-unavailable", reason: report.reason };
|
|
25642
25660
|
case "failed":
|
|
25661
|
+
if (report.reason.startsWith("functional update probe failed (sandbox-unavailable):")) {
|
|
25662
|
+
return { kind: "sandbox-unavailable", reason: report.reason };
|
|
25663
|
+
}
|
|
25643
25664
|
return { kind: "unavailable", reason: report.reason };
|
|
25644
25665
|
}
|
|
25645
25666
|
}
|
|
25646
25667
|
async function probeReleaseInSubprocess(input) {
|
|
25647
|
-
const bundleDir =
|
|
25668
|
+
const bundleDir = join9(input.layout.releasesRoot, input.releaseId);
|
|
25648
25669
|
const entrypoint = await resolveBundleEntrypoint(bundleDir);
|
|
25649
25670
|
if (!entrypoint) {
|
|
25650
25671
|
return { kind: "unavailable", reason: `release ${input.releaseId} has no runnable CLI entrypoint` };
|
|
@@ -25707,7 +25728,7 @@ async function runUpdateProbeCommand(args, options = {}) {
|
|
|
25707
25728
|
const runtime = await readRuntimeRecord(configPath);
|
|
25708
25729
|
const agent = runtimeAgentCommand(runtime);
|
|
25709
25730
|
const config = loadBodyConfig({
|
|
25710
|
-
workspaceRoot:
|
|
25731
|
+
workspaceRoot: join9(dirname15(configPath), "workspace"),
|
|
25711
25732
|
llmEnvFile: runtime.llmEnvFile,
|
|
25712
25733
|
env: { ...env, BUZZ_AGENT_BIN: agent.command, BUZZ_DEV_MCP_BIN: runtime.mcpBinary },
|
|
25713
25734
|
agent
|
|
@@ -25725,21 +25746,22 @@ async function runUpdateProbeCommand(args, options = {}) {
|
|
|
25725
25746
|
}
|
|
25726
25747
|
const layout = beelineInstallLayout(env);
|
|
25727
25748
|
const releaseId = (layout && await activeReleaseId(layout).catch(() => void 0)) ?? "unknown";
|
|
25728
|
-
const runtimeDir =
|
|
25749
|
+
const runtimeDir = dirname15(configPath);
|
|
25729
25750
|
const outcome = await probeOutcome(() => (options.probe ?? runUpdateFunctionalProbe)({
|
|
25730
25751
|
config,
|
|
25731
25752
|
runtimeDir,
|
|
25732
25753
|
releaseId,
|
|
25733
25754
|
sandboxRequired: runtime.sandbox !== "off",
|
|
25755
|
+
sandboxUnavailableDetail: sandbox.advisory,
|
|
25734
25756
|
// The successor's probe still holds `<runtimeDir>/update-functional-probe`.
|
|
25735
|
-
probeRoot:
|
|
25757
|
+
probeRoot: join9(runtimeDir, "current-release-probe")
|
|
25736
25758
|
}));
|
|
25737
|
-
const report = outcome.kind === "served" ? { probe: "served" } : outcome.kind === "refused" ? { probe: "refused", status: outcome.status, reason: outcome.reason } : { probe: "failed", reason: outcome.reason };
|
|
25759
|
+
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 };
|
|
25738
25760
|
write(JSON.stringify(report));
|
|
25739
25761
|
}
|
|
25740
25762
|
|
|
25741
25763
|
// apps/body/dist/release-status.js
|
|
25742
|
-
import { readFile as readFile13, readdir as readdir5, rename as rename6, writeFile as
|
|
25764
|
+
import { readFile as readFile13, readdir as readdir5, rename as rename6, writeFile as writeFile15 } from "node:fs/promises";
|
|
25743
25765
|
import { resolve as resolve27 } from "node:path";
|
|
25744
25766
|
var DAEMON_RELEASE_STATUS_FILE = "release-status.json";
|
|
25745
25767
|
var RELEASE_VERSION = /^v\d+\.\d+\.\d+$/;
|
|
@@ -25759,7 +25781,7 @@ async function writeDaemonReleaseStatus(runtimeDir, agentPubkey, identity, optio
|
|
|
25759
25781
|
};
|
|
25760
25782
|
const target = resolve27(runtimeDir, DAEMON_RELEASE_STATUS_FILE);
|
|
25761
25783
|
const temporary = `${target}.${status.pid}.tmp`;
|
|
25762
|
-
await
|
|
25784
|
+
await writeFile15(temporary, `${JSON.stringify(status, null, 2)}
|
|
25763
25785
|
`, { mode: 384 });
|
|
25764
25786
|
await rename6(temporary, target);
|
|
25765
25787
|
return status;
|
|
@@ -25893,7 +25915,7 @@ var DaemonExitError = class extends Error {
|
|
|
25893
25915
|
};
|
|
25894
25916
|
async function runStoredDaemon(pathOrPointer) {
|
|
25895
25917
|
const configPath = await resolveRuntimeConfigPath(pathOrPointer);
|
|
25896
|
-
daemonFailureRuntimeDir =
|
|
25918
|
+
daemonFailureRuntimeDir = dirname16(configPath);
|
|
25897
25919
|
const accessMigration = await migrateRuntimeRecordAccessPolicy(configPath);
|
|
25898
25920
|
let runtime = accessMigration.runtime;
|
|
25899
25921
|
if (!runtime.transport) {
|
|
@@ -25905,7 +25927,7 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
25905
25927
|
runtime = activated.runtime;
|
|
25906
25928
|
const daemonApi = activated.client;
|
|
25907
25929
|
const agent = runtimeAgentCommand(runtime);
|
|
25908
|
-
await
|
|
25930
|
+
await writeFile16(resolve29(dirname16(configPath), "daemon.pid"), `${process.pid}
|
|
25909
25931
|
`, { mode: 384 });
|
|
25910
25932
|
const env = {
|
|
25911
25933
|
...process.env,
|
|
@@ -25913,7 +25935,7 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
25913
25935
|
BUZZ_DEV_MCP_BIN: runtime.mcpBinary
|
|
25914
25936
|
};
|
|
25915
25937
|
const config = loadBodyConfig({
|
|
25916
|
-
workspaceRoot: resolve29(
|
|
25938
|
+
workspaceRoot: resolve29(dirname16(configPath), "workspace"),
|
|
25917
25939
|
llmEnvFile: runtime.llmEnvFile,
|
|
25918
25940
|
env,
|
|
25919
25941
|
agent
|
|
@@ -25948,7 +25970,7 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
25948
25970
|
const stop = () => controller.abort();
|
|
25949
25971
|
process.once("SIGINT", stop);
|
|
25950
25972
|
process.once("SIGTERM", stop);
|
|
25951
|
-
const runtimeDir =
|
|
25973
|
+
const runtimeDir = dirname16(configPath);
|
|
25952
25974
|
const layout = beelineInstallLayout(process.env);
|
|
25953
25975
|
const notifier = new SystemdNotifier();
|
|
25954
25976
|
let rollbackAlertDrain;
|
|
@@ -26030,6 +26052,7 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
26030
26052
|
runtimeDir,
|
|
26031
26053
|
releaseId: loadedRelease ?? "unknown",
|
|
26032
26054
|
sandboxRequired: runtime.sandbox !== "off",
|
|
26055
|
+
sandboxUnavailableDetail: sandbox.advisory,
|
|
26033
26056
|
...currentReleaseId ? {
|
|
26034
26057
|
compareWithCurrentRelease: async (appeal) => {
|
|
26035
26058
|
console.warn(`[thin-core] successor probe got no answer from the provider (${appeal.reason}); probing the current release ${currentReleaseId} for the same outcome`);
|
|
@@ -26094,7 +26117,7 @@ async function runStoredDaemon(pathOrPointer) {
|
|
|
26094
26117
|
} finally {
|
|
26095
26118
|
clearInterval(scratchSweepTimer);
|
|
26096
26119
|
await notifier.stopping(stoppingStatus).catch(() => void 0);
|
|
26097
|
-
const pidPath = resolve29(
|
|
26120
|
+
const pidPath = resolve29(dirname16(configPath), "daemon.pid");
|
|
26098
26121
|
const recorded = Number((await readFile14(pidPath, "utf8").catch(() => "")).trim());
|
|
26099
26122
|
if (recorded === process.pid) {
|
|
26100
26123
|
await unlink5(pidPath).catch(() => void 0);
|
|
@@ -26154,10 +26177,7 @@ async function main() {
|
|
|
26154
26177
|
await repairInstallForwarders(startupLayout).catch(() => void 0);
|
|
26155
26178
|
}
|
|
26156
26179
|
const interactiveUi = command !== "daemon" && Boolean(stdin4.isTTY && stdout5.isTTY);
|
|
26157
|
-
const llmEnvFile = process.env.BUZZY_BODY_LLM_FILE;
|
|
26158
|
-
const workspaceRoot = process.env.BUZZY_BODY_WORKSPACE ?? "./body-workspace";
|
|
26159
26180
|
if (command === "--version" || command === "version") {
|
|
26160
|
-
const config = loadBodyConfig({ workspaceRoot, llmEnvFile });
|
|
26161
26181
|
console.log(import_picocolors3.default.bold("beeline 0.0.0"));
|
|
26162
26182
|
const layout = beelineInstallLayout(process.env);
|
|
26163
26183
|
if (layout) {
|
|
@@ -26165,9 +26185,6 @@ async function main() {
|
|
|
26165
26185
|
const active = await activeReleaseId(layout);
|
|
26166
26186
|
console.log(`${import_picocolors3.default.dim("installed bundle:")} ${describeIdentity(identity)}${active ? ` (release ${active})` : ""}`);
|
|
26167
26187
|
}
|
|
26168
|
-
console.log(`${import_picocolors3.default.dim("[body] agent binary:")} ${config.agentCommand ?? config.agentBinary}`);
|
|
26169
|
-
console.log(`${import_picocolors3.default.dim("[body] mcp binary:")} ${config.mcpBinary}`);
|
|
26170
|
-
console.log(`${import_picocolors3.default.dim("[body] read-only mcp:")} ${config.readonlyMcpCommand}`);
|
|
26171
26188
|
return;
|
|
26172
26189
|
}
|
|
26173
26190
|
if (command === "daemon") {
|
|
@@ -26177,7 +26194,7 @@ async function main() {
|
|
|
26177
26194
|
const agentPubkey = agentFlag >= 0 ? args[agentFlag + 1] : void 0;
|
|
26178
26195
|
if (!configPath && agentPubkey) {
|
|
26179
26196
|
const configs = await findAgentRuntimeConfigPaths(process.env, process.cwd());
|
|
26180
|
-
configPath = configs.find((candidate) =>
|
|
26197
|
+
configPath = configs.find((candidate) => dirname16(candidate).endsWith(agentPubkey));
|
|
26181
26198
|
}
|
|
26182
26199
|
if (!configPath && agentPubkey) {
|
|
26183
26200
|
throw new DaemonExitError(`unknown agent ${agentPubkey}: no durable runtime exists; refusing systemd restart loop`, UNKNOWN_AGENT_EXIT_STATUS);
|
|
@@ -26205,7 +26222,7 @@ async function main() {
|
|
|
26205
26222
|
if (!agentPubkey)
|
|
26206
26223
|
throw new Error("stop requires --agent <pubkey>");
|
|
26207
26224
|
const configs = await findAgentRuntimeConfigPaths(process.env, process.cwd());
|
|
26208
|
-
const configPath = configs.find((candidate) =>
|
|
26225
|
+
const configPath = configs.find((candidate) => dirname16(candidate).endsWith(agentPubkey));
|
|
26209
26226
|
if (!configPath)
|
|
26210
26227
|
throw new Error(`no stored runtime found for agent ${agentPubkey}`);
|
|
26211
26228
|
const runtime = await readRuntimeRecord(configPath);
|