usebeeline 0.0.100 → 0.0.105

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.
@@ -3987,7 +3987,7 @@ __export(self_update_exports, {
3987
3987
  import { createHash as createHash7 } from "node:crypto";
3988
3988
  import { constants as fsConstants2 } from "node:fs";
3989
3989
  import { access, chmod as chmod5, lstat as lstat3, mkdir as mkdir15, open, readFile as readFile10, rename as rename4, rm as rm6, symlink as symlink3, writeFile as writeFile10 } from "node:fs/promises";
3990
- import { spawn as spawn4 } from "node:child_process";
3990
+ import { spawn as spawn5 } from "node:child_process";
3991
3991
  import { homedir as homedir9 } from "node:os";
3992
3992
  import { dirname as dirname11, join as join9, resolve as resolve22 } from "node:path";
3993
3993
  function anchorLayout(rawLibDir) {
@@ -4137,7 +4137,7 @@ async function fetchText(url, fetchImpl) {
4137
4137
  }
4138
4138
  function run(command, args, timeoutMs) {
4139
4139
  return new Promise((resolveRun) => {
4140
- const child = spawn4(command, args, { stdio: ["ignore", "ignore", "pipe"] });
4140
+ const child = spawn5(command, args, { stdio: ["ignore", "ignore", "pipe"] });
4141
4141
  let stderr = "";
4142
4142
  child.stderr?.setEncoding("utf8");
4143
4143
  child.stderr?.on("data", (chunk) => {
@@ -4210,7 +4210,7 @@ async function stageRelease(layout, manifestUrl, published, opts = {}) {
4210
4210
  }
4211
4211
  await writeFile10(tempArchive, Buffer.concat(chunks), { mode: 384 });
4212
4212
  const entries = (await new Promise((resolveList, rejectList) => {
4213
- const child = spawn4("tar", ["-tzf", tempArchive], { stdio: ["ignore", "pipe", "inherit"] });
4213
+ const child = spawn5("tar", ["-tzf", tempArchive], { stdio: ["ignore", "pipe", "inherit"] });
4214
4214
  let out = "";
4215
4215
  child.stdout?.on("data", (chunk) => {
4216
4216
  out += chunk.toString("utf8");
@@ -4389,44 +4389,44 @@ async function readUpdateAttempt(layout) {
4389
4389
  return void 0;
4390
4390
  }
4391
4391
  }
4392
- async function writeUpdateAttempt(layout, record2) {
4392
+ async function writeUpdateAttempt(layout, record3) {
4393
4393
  await mkdir15(join9(layout.releasesRoot, ".state"), { recursive: true });
4394
4394
  const path = updateAttemptPath(layout);
4395
4395
  const staged = `${path}.${process.pid}.tmp`;
4396
- await writeFile10(staged, `${JSON.stringify(record2, null, 2)}
4396
+ await writeFile10(staged, `${JSON.stringify(record3, null, 2)}
4397
4397
  `, { mode: 384 });
4398
4398
  await rename4(staged, path);
4399
4399
  }
4400
- async function replaceUpdateAttempt(layout, record2) {
4401
- await writeUpdateAttempt(layout, record2);
4400
+ async function replaceUpdateAttempt(layout, record3) {
4401
+ await writeUpdateAttempt(layout, record3);
4402
4402
  }
4403
- async function writeUpdateAttemptFixture(layout, record2) {
4403
+ async function writeUpdateAttemptFixture(layout, record3) {
4404
4404
  await writeUpdateAttempt(layout, {
4405
- ...record2,
4406
- version: record2.version ?? 1,
4407
- confirmBy: record2.confirmBy ?? record2.appliedAt + DEFAULT_UPDATE_CONFIRM_WINDOW_MS,
4408
- status: record2.status ?? "pending"
4405
+ ...record3,
4406
+ version: record3.version ?? 1,
4407
+ confirmBy: record3.confirmBy ?? record3.appliedAt + DEFAULT_UPDATE_CONFIRM_WINDOW_MS,
4408
+ status: record3.status ?? "pending"
4409
4409
  });
4410
4410
  }
4411
4411
  async function settleUpdateAttemptOnStart(layout, opts = {}) {
4412
- const record2 = await readUpdateAttempt(layout);
4413
- if (!record2 || record2.status !== "pending")
4412
+ const record3 = await readUpdateAttempt(layout);
4413
+ if (!record3 || record3.status !== "pending")
4414
4414
  return { kind: "none" };
4415
4415
  const now2 = opts.now ?? Date.now;
4416
- const confirmAt = opts.confirmWindowMs ? record2.appliedAt + opts.confirmWindowMs : record2.confirmBy;
4416
+ const confirmAt = opts.confirmWindowMs ? record3.appliedAt + opts.confirmWindowMs : record3.confirmBy;
4417
4417
  if (now2() <= confirmAt) {
4418
- return { kind: "pending", confirmAt, record: record2 };
4418
+ return { kind: "pending", confirmAt, record: record3 };
4419
4419
  }
4420
- if (record2.previousReleaseId) {
4421
- await rollbackToPreviousRelease(layout, record2.previousReleaseId);
4420
+ if (record3.previousReleaseId) {
4421
+ await rollbackToPreviousRelease(layout, record3.previousReleaseId);
4422
4422
  }
4423
4423
  await writeUpdateAttempt(layout, {
4424
- ...record2,
4424
+ ...record3,
4425
4425
  confirmBy: confirmAt,
4426
4426
  status: "reverted",
4427
4427
  failure: "update did not serve a verified turn before its confirmation deadline"
4428
4428
  });
4429
- return { kind: "rolled-back", record: record2 };
4429
+ return { kind: "rolled-back", record: record3 };
4430
4430
  }
4431
4431
  function describeIdentity(identity) {
4432
4432
  if (!identity)
@@ -6864,13 +6864,13 @@ function parseAvailableCommands(value) {
6864
6864
  for (const entry of value) {
6865
6865
  if (!entry || typeof entry !== "object")
6866
6866
  continue;
6867
- const record2 = entry;
6868
- const rawName = typeof record2.name === "string" ? record2.name.trim().replace(/^\/+/, "") : "";
6867
+ const record3 = entry;
6868
+ const rawName = typeof record3.name === "string" ? record3.name.trim().replace(/^\/+/, "") : "";
6869
6869
  if (!rawName || rawName.length > 80 || seen.has(rawName))
6870
6870
  continue;
6871
6871
  seen.add(rawName);
6872
- const description = typeof record2.description === "string" && record2.description.trim() ? record2.description.trim().slice(0, 300) : void 0;
6873
- const input = record2.input;
6872
+ const description = typeof record3.description === "string" && record3.description.trim() ? record3.description.trim().slice(0, 300) : void 0;
6873
+ const input = record3.input;
6874
6874
  const hintRaw = input && typeof input.hint === "string" ? input.hint : void 0;
6875
6875
  const inputHint = hintRaw && hintRaw.trim() ? hintRaw.trim().slice(0, 120) : void 0;
6876
6876
  result.push({
@@ -6898,13 +6898,8 @@ var PI_ACP_HARNESS = /(^|[/\\])pi-acp(?:\.[a-z]+)?$/i;
6898
6898
  function isPiAcpHarness(agentLabel) {
6899
6899
  return Boolean(agentLabel && PI_ACP_HARNESS.test(agentLabel));
6900
6900
  }
6901
- function withoutOneTrailingLineEnding(text2) {
6902
- if (/\r?\n\r?\n$/.test(text2))
6903
- return text2;
6904
- return text2.replace(/\r?\n$/, "");
6905
- }
6906
- function normalizeStreamDelta(text2, agentLabel) {
6907
- return agentLabel && PI_ACP_HARNESS.test(agentLabel) ? withoutOneTrailingLineEnding(text2) : text2;
6901
+ function normalizeStreamDelta(text2, _agentLabel) {
6902
+ return text2;
6908
6903
  }
6909
6904
  function agentMessageRuns(updates, agentLabel) {
6910
6905
  const runs = [];
@@ -7727,12 +7722,12 @@ function parseAdvertisedConfigOptions(raw, preferredModelId, includeLaunchEffort
7727
7722
  for (const entry of Array.isArray(configOptions) ? configOptions : []) {
7728
7723
  if (!entry || typeof entry !== "object")
7729
7724
  continue;
7730
- const record2 = entry;
7731
- const id = record2.id;
7732
- const category = record2.category;
7725
+ const record3 = entry;
7726
+ const id = record3.id;
7727
+ const category = record3.category;
7733
7728
  if (typeof id !== "string" || typeof category !== "string")
7734
7729
  continue;
7735
- const rawChoices = Array.isArray(record2.options) ? record2.options : [];
7730
+ const rawChoices = Array.isArray(record3.options) ? record3.options : [];
7736
7731
  const choices = [];
7737
7732
  for (const rawChoice of rawChoices) {
7738
7733
  if (!rawChoice || typeof rawChoice !== "object")
@@ -7749,7 +7744,7 @@ function parseAdvertisedConfigOptions(raw, preferredModelId, includeLaunchEffort
7749
7744
  result.push({
7750
7745
  id,
7751
7746
  category,
7752
- ...typeof record2.currentValue === "string" ? { currentValue: record2.currentValue } : {},
7747
+ ...typeof record3.currentValue === "string" ? { currentValue: record3.currentValue } : {},
7753
7748
  options: category === "model" ? sortModelChoicesNewestFirst(choices) : choices
7754
7749
  });
7755
7750
  }
@@ -7857,8 +7852,8 @@ function credentialedPiCustomProviders(env) {
7857
7852
  };
7858
7853
  if (Array.isArray(providers)) {
7859
7854
  for (const provider of providers) {
7860
- const record2 = provider;
7861
- remember(record2?.name ?? record2?.id, record2);
7855
+ const record3 = provider;
7856
+ remember(record3?.name ?? record3?.id, record3);
7862
7857
  }
7863
7858
  } else if (providers && typeof providers === "object") {
7864
7859
  for (const [name, config] of Object.entries(providers)) {
@@ -8161,6 +8156,483 @@ async function syncAgentModelCatalog(input) {
8161
8156
  }
8162
8157
  }
8163
8158
 
8159
+ // apps/body/dist/connector-squire.js
8160
+ import { execFile } from "node:child_process";
8161
+ var REMOTE_LOGIN_BINARIES = ["Xvfb", "x11vnc", "websockify", "cloudflared"];
8162
+ var SQUIRE_CONNECT_PACKAGE = "@trusty-squire/mcp";
8163
+ var defaultShellRunner = (command, args) => new Promise((resolve31) => {
8164
+ execFile(command, [...args], { timeout: 12e4, maxBuffer: 4 * 1024 * 1024, encoding: "utf8" }, (error, stdout6, stderr) => {
8165
+ const code = error?.code;
8166
+ resolve31({
8167
+ code: typeof code === "number" ? code : error ? 1 : 0,
8168
+ stdout: String(stdout6 ?? ""),
8169
+ stderr: String(stderr ?? "")
8170
+ });
8171
+ });
8172
+ });
8173
+ var step = (label, status, reason) => ({
8174
+ label,
8175
+ status,
8176
+ ...reason ? { reason } : {}
8177
+ });
8178
+ function binaryExists(binary) {
8179
+ return new Promise((resolve31) => {
8180
+ execFile("sh", ["-c", `command -v ${JSON.stringify(binary)}`], (error, stdout6) => {
8181
+ const path = String(stdout6 ?? "").trim();
8182
+ resolve31({ binary, found: !error && path.length > 0, ...path ? { path } : {} });
8183
+ });
8184
+ });
8185
+ }
8186
+ async function checkRemoteLoginPrerequisites(probe = binaryExists) {
8187
+ return Promise.all(REMOTE_LOGIN_BINARIES.map(probe));
8188
+ }
8189
+ function missingPrerequisiteStep(checks) {
8190
+ const missing = checks.filter((check) => !check.found).map((check) => check.binary);
8191
+ return step("remote sign-in prerequisites", "failed", `missing on this helper: ${missing.join(", ")}`);
8192
+ }
8193
+ function parseConnectOutput(output) {
8194
+ const url = output.match(/https:\/\/[^\s"'<>]+/)?.[0];
8195
+ if (!url)
8196
+ return void 0;
8197
+ if (/oauth|authorize/i.test(url) || /oauth/i.test(output)) {
8198
+ return { method: "oauth", url };
8199
+ }
8200
+ if (/novnc|vnc\.html|remote|stream/i.test(url) || /novnc|remote login|vnc/i.test(output)) {
8201
+ return { method: "streamed-page", url };
8202
+ }
8203
+ return { method: "streamed-page", url };
8204
+ }
8205
+ async function installedSquireVersion(run2) {
8206
+ const probe = await run2("npx", ["-y", SQUIRE_CONNECT_PACKAGE, "--version"]);
8207
+ const version = probe.stdout.match(/\d+\.\d+\.\d+[^\s]*/)?.[0];
8208
+ return version;
8209
+ }
8210
+ function parseSignedInAs(output) {
8211
+ return output.match(/signed in as ([^\s,;]+)/i)?.[1];
8212
+ }
8213
+ async function installSquire(options) {
8214
+ const run2 = options.run ?? defaultShellRunner;
8215
+ const steps = [step("helper reached", "done")];
8216
+ const emit = () => options.onProgress?.([...steps]);
8217
+ const push = (next) => {
8218
+ steps.push(next);
8219
+ emit();
8220
+ };
8221
+ const fail = (reason) => {
8222
+ steps.push(step("waiting for sign-in", "pending"));
8223
+ emit();
8224
+ return { status: "error", steps, errorMessage: reason };
8225
+ };
8226
+ emit();
8227
+ const checks = await checkRemoteLoginPrerequisites(options.probeBinary);
8228
+ if (checks.some((check) => !check.found)) {
8229
+ push(missingPrerequisiteStep(checks));
8230
+ return fail("this helper cannot host the remote sign-in surface");
8231
+ }
8232
+ push(step("remote sign-in prerequisites", "done"));
8233
+ const install = await run2("npx", [
8234
+ "-y",
8235
+ SQUIRE_CONNECT_PACKAGE,
8236
+ "connect",
8237
+ "--target=pi",
8238
+ "--skip-browser"
8239
+ ]);
8240
+ if (install.code !== 0) {
8241
+ push(step("trusty-squire installed", "failed", install.stderr.trim() || "connect failed"));
8242
+ return fail("the trusty-squire install command failed");
8243
+ }
8244
+ const version = await installedSquireVersion(run2);
8245
+ push(step(`trusty-squire${version ? ` ${version}` : ""} installed`, "done"));
8246
+ const output = `${install.stdout}
8247
+ ${install.stderr}`;
8248
+ const signIn = parseConnectOutput(output);
8249
+ if (!signIn) {
8250
+ push(step("waiting for sign-in", "failed", "connect printed no sign-in URL"));
8251
+ return fail("the trusty-squire connect command printed no sign-in surface");
8252
+ }
8253
+ push(step("waiting for sign-in", "done"));
8254
+ const signedInAs = parseSignedInAs(output);
8255
+ const pair = await pairSquire(options.mcp, options.workspaceId);
8256
+ if (!pair.ok) {
8257
+ push(step("paired to workspace", "failed", pair.reason));
8258
+ return {
8259
+ status: "installing",
8260
+ steps,
8261
+ signIn,
8262
+ ...version ? { squireVersion: version } : {},
8263
+ ...signedInAs ? { signedInAs } : {}
8264
+ };
8265
+ }
8266
+ push(step("paired to workspace", "done"));
8267
+ return {
8268
+ status: "connected",
8269
+ steps,
8270
+ signIn,
8271
+ ...version ? { squireVersion: version } : {},
8272
+ ...signedInAs ? { signedInAs } : {}
8273
+ };
8274
+ }
8275
+ async function pairSquire(mcp, _workspaceId) {
8276
+ if (!mcp)
8277
+ return { ok: false, reason: "the Squire MCP surface is not mounted on this helper" };
8278
+ try {
8279
+ await mcp.call("list_credentials", { fields: "summary" });
8280
+ return { ok: true };
8281
+ } catch (error) {
8282
+ return { ok: false, reason: error instanceof Error ? error.message : String(error) };
8283
+ }
8284
+ }
8285
+ function asRecord(value) {
8286
+ return value && typeof value === "object" ? value : {};
8287
+ }
8288
+ function asArray(value) {
8289
+ return Array.isArray(value) ? value : [];
8290
+ }
8291
+ function stringList(value) {
8292
+ return asArray(value).filter((entry) => typeof entry === "string");
8293
+ }
8294
+ function numberOrNull(value) {
8295
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
8296
+ }
8297
+ function vaultConnectionMeta(raw) {
8298
+ const record3 = asRecord(raw);
8299
+ const reference = String(record3.reference ?? record3.id ?? "");
8300
+ return {
8301
+ reference,
8302
+ service: typeof record3.service === "string" ? record3.service : null,
8303
+ label: String(record3.label ?? record3.service ?? reference),
8304
+ fieldNames: stringList(record3.field_names ?? record3.fieldNames),
8305
+ allowedHosts: stringList(record3.allowed_hosts ?? record3.allowedHosts ?? record3.login_hosts),
8306
+ createdAt: numberOrNull(record3.created_at ?? record3.createdAt) ?? 0,
8307
+ stale: record3.stale === true,
8308
+ state: record3.state === "error" ? "error" : "active"
8309
+ };
8310
+ }
8311
+ async function readVault(mcp) {
8312
+ const result = asRecord(await mcp.call("list_credentials"));
8313
+ return asArray(result.credentials ?? result.items ?? result).map(vaultConnectionMeta);
8314
+ }
8315
+ function connectionGrant(raw) {
8316
+ const record3 = asRecord(raw);
8317
+ return {
8318
+ grantId: String(record3.grant_id ?? record3.grantId ?? record3.id ?? ""),
8319
+ credentialRef: String(record3.credential_ref ?? record3.credentialRef ?? record3.reference ?? ""),
8320
+ createdAt: numberOrNull(record3.created_at ?? record3.createdAt) ?? 0,
8321
+ ...numberOrNull(record3.revoked_at ?? record3.revokedAt) !== null ? { revokedAt: numberOrNull(record3.revoked_at ?? record3.revokedAt) } : {},
8322
+ ...numberOrNull(record3.rate_limit_per_hour) !== null ? { rateLimitPerHour: numberOrNull(record3.rate_limit_per_hour) } : {},
8323
+ ...numberOrNull(record3.spend_cap_usd) !== null ? { spendCapUsd: numberOrNull(record3.spend_cap_usd) } : {}
8324
+ };
8325
+ }
8326
+ async function readGrants(mcp, ref) {
8327
+ const result = asRecord(await mcp.call("list_app_access", {}));
8328
+ return asArray(result.grants ?? result.items ?? result).map(connectionGrant).filter((grant) => grant.credentialRef === ref && grant.revokedAt === void 0);
8329
+ }
8330
+ async function revokeGrants(mcp, ref) {
8331
+ const grants = await readGrants(mcp, ref);
8332
+ let revoked = 0;
8333
+ let failed = 0;
8334
+ for (const grant of grants) {
8335
+ try {
8336
+ const result = asRecord(await mcp.call("revoke_app_access", { grant_id: grant.grantId }));
8337
+ if (result.revoked === false)
8338
+ failed += 1;
8339
+ else
8340
+ revoked += 1;
8341
+ } catch {
8342
+ failed += 1;
8343
+ }
8344
+ }
8345
+ return { revoked, failed };
8346
+ }
8347
+
8348
+ // apps/body/dist/squire-mcp-client.js
8349
+ import { spawn as spawn2 } from "node:child_process";
8350
+ var INITIALIZE_TIMEOUT_MS = 3e4;
8351
+ var CALL_TIMEOUT_MS = 12e4;
8352
+ var StdioSquireMcpClient = class {
8353
+ options;
8354
+ child;
8355
+ nextId = 1;
8356
+ pending = /* @__PURE__ */ new Map();
8357
+ buffer = "";
8358
+ initialized;
8359
+ closed = false;
8360
+ log;
8361
+ constructor(options = {}) {
8362
+ this.options = options;
8363
+ this.log = options.log ?? (() => {
8364
+ });
8365
+ }
8366
+ /** One Squire MCP tool call; resolves with the tool's parsed result. */
8367
+ async call(tool, args = {}) {
8368
+ await this.ensureSession();
8369
+ const response = await this.request("tools/call", { name: tool, arguments: args });
8370
+ if (response.isError) {
8371
+ const text3 = response.content?.find((entry) => entry.type === "text")?.text ?? "tool error";
8372
+ throw new Error(`${tool} failed: ${text3}`);
8373
+ }
8374
+ const text2 = response.content?.find((entry) => entry.type === "text")?.text;
8375
+ if (typeof text2 !== "string")
8376
+ return response;
8377
+ try {
8378
+ return JSON.parse(text2);
8379
+ } catch {
8380
+ return response;
8381
+ }
8382
+ }
8383
+ /** Tear the session down; safe to call repeatedly. */
8384
+ close() {
8385
+ this.closed = true;
8386
+ this.initialized = void 0;
8387
+ for (const entry of this.pending.values()) {
8388
+ clearTimeout(entry.timer);
8389
+ entry.reject(new Error("Squire MCP session closed"));
8390
+ }
8391
+ this.pending.clear();
8392
+ this.child?.kill();
8393
+ this.child = void 0;
8394
+ }
8395
+ ensureSession() {
8396
+ if (this.closed)
8397
+ throw new Error("Squire MCP client is closed");
8398
+ this.initialized ??= this.initialize().catch((error) => {
8399
+ this.initialized = void 0;
8400
+ throw error;
8401
+ });
8402
+ return this.initialized;
8403
+ }
8404
+ initialize() {
8405
+ const child = (this.options.spawn ?? spawn2)(this.options.command ?? "npx", [
8406
+ ...this.options.args ?? ["-y", "@trusty-squire/mcp"]
8407
+ ]);
8408
+ this.child = child;
8409
+ this.buffer = "";
8410
+ child.stdout.setEncoding("utf8");
8411
+ child.stderr.setEncoding("utf8");
8412
+ child.stdout.on("data", (chunk) => this.onData(chunk));
8413
+ child.stderr.on("data", (chunk) => this.log(`squire mcp stderr: ${chunk.trim()}`));
8414
+ child.on("exit", (code) => {
8415
+ this.log(`squire mcp exited (${String(code)})`);
8416
+ this.initialized = void 0;
8417
+ this.child = void 0;
8418
+ for (const entry of this.pending.values()) {
8419
+ clearTimeout(entry.timer);
8420
+ entry.reject(new Error("Squire MCP server exited"));
8421
+ }
8422
+ this.pending.clear();
8423
+ });
8424
+ return this.request("initialize", {
8425
+ protocolVersion: "2024-11-05",
8426
+ capabilities: {},
8427
+ clientInfo: { name: "beeline-helper", version: "1.0.0" }
8428
+ }).then(() => {
8429
+ this.notify("notifications/initialized");
8430
+ });
8431
+ }
8432
+ notify(method, params) {
8433
+ this.child?.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, ...params ? { params } : {} })}
8434
+ `);
8435
+ }
8436
+ request(method, params) {
8437
+ const id = this.nextId++;
8438
+ const child = this.child;
8439
+ if (!child)
8440
+ return Promise.reject(new Error("Squire MCP session is not running"));
8441
+ return new Promise((resolve31, reject) => {
8442
+ const timer = setTimeout(() => {
8443
+ this.pending.delete(id);
8444
+ reject(new Error(`${method} timed out`));
8445
+ }, method === "initialize" ? INITIALIZE_TIMEOUT_MS : CALL_TIMEOUT_MS);
8446
+ this.pending.set(id, { resolve: resolve31, reject, timer });
8447
+ child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}
8448
+ `);
8449
+ });
8450
+ }
8451
+ onData(chunk) {
8452
+ this.buffer += chunk;
8453
+ for (; ; ) {
8454
+ const newline = this.buffer.indexOf("\n");
8455
+ if (newline < 0)
8456
+ return;
8457
+ const line = this.buffer.slice(0, newline).trim();
8458
+ this.buffer = this.buffer.slice(newline + 1);
8459
+ if (!line)
8460
+ continue;
8461
+ let message;
8462
+ try {
8463
+ message = JSON.parse(line);
8464
+ } catch {
8465
+ continue;
8466
+ }
8467
+ const id = typeof message.id === "number" ? message.id : void 0;
8468
+ if (id === void 0)
8469
+ continue;
8470
+ const entry = this.pending.get(id);
8471
+ if (!entry)
8472
+ continue;
8473
+ this.pending.delete(id);
8474
+ clearTimeout(entry.timer);
8475
+ if (message.error) {
8476
+ const error = message.error;
8477
+ entry.reject(new Error(error.message ?? "Squire MCP error"));
8478
+ } else {
8479
+ entry.resolve(message.result);
8480
+ }
8481
+ }
8482
+ }
8483
+ };
8484
+ function defaultSquireMcpClient() {
8485
+ return new StdioSquireMcpClient();
8486
+ }
8487
+
8488
+ // apps/body/dist/connector-assignments.js
8489
+ var CONNECTOR_POLL_INTERVAL_MS = 1e4;
8490
+ var ConnectorAssignmentLoop = class {
8491
+ agentId;
8492
+ api;
8493
+ intervalMs;
8494
+ log;
8495
+ install;
8496
+ readVaultFn;
8497
+ revokeGrantsFn;
8498
+ schedule;
8499
+ cancel;
8500
+ timer;
8501
+ started = false;
8502
+ stopped = false;
8503
+ /** One install at a time per connector; other polls skip it. */
8504
+ inFlight = /* @__PURE__ */ new Set();
8505
+ mcp;
8506
+ constructor(options) {
8507
+ this.agentId = options.agentId;
8508
+ this.api = options.api;
8509
+ this.intervalMs = options.intervalMs ?? CONNECTOR_POLL_INTERVAL_MS;
8510
+ this.log = options.log ?? (() => {
8511
+ });
8512
+ this.install = options.install ?? installSquire;
8513
+ this.readVaultFn = options.readVault ?? readVault;
8514
+ this.revokeGrantsFn = options.revokeGrants ?? revokeGrants;
8515
+ this.schedule = options.schedule ?? ((fn, ms) => {
8516
+ const timer = setTimeout(fn, ms);
8517
+ timer.unref?.();
8518
+ return timer;
8519
+ });
8520
+ this.cancel = options.cancel ?? ((handle) => clearTimeout(handle));
8521
+ }
8522
+ start() {
8523
+ if (this.stopped || this.started)
8524
+ return;
8525
+ this.started = true;
8526
+ void this.runOnce();
8527
+ this.timer = this.schedule(() => this.poll(), this.intervalMs);
8528
+ }
8529
+ stop() {
8530
+ this.stopped = true;
8531
+ if (this.timer !== void 0) {
8532
+ this.cancel(this.timer);
8533
+ this.timer = void 0;
8534
+ }
8535
+ }
8536
+ /** One interval tick: poll, then re-arm. */
8537
+ poll() {
8538
+ if (this.stopped)
8539
+ return;
8540
+ void this.runOnce();
8541
+ this.timer = this.schedule(() => this.poll(), this.intervalMs);
8542
+ }
8543
+ /** Drain the queue once; every failure is logged, never raised. */
8544
+ async runOnce() {
8545
+ let assignments;
8546
+ try {
8547
+ const result = await this.api.execute("getConnectorAssignments", { agentId: this.agentId });
8548
+ assignments = result.assignments;
8549
+ } catch (error) {
8550
+ this.log(`connector assignments unavailable: ${describe(error)}`);
8551
+ return;
8552
+ }
8553
+ for (const assignment of assignments) {
8554
+ const key = `${assignment.kind}:${assignment.connectorId}`;
8555
+ if (assignment.kind === "uninstall")
8556
+ continue;
8557
+ if (this.inFlight.has(key))
8558
+ continue;
8559
+ this.inFlight.add(key);
8560
+ void this.handle(assignment).catch((error) => this.log(`connector assignment ${key} failed: ${describe(error)}`)).finally(() => this.inFlight.delete(key));
8561
+ }
8562
+ }
8563
+ squire() {
8564
+ this.mcp ??= defaultSquireMcpClient();
8565
+ return this.mcp;
8566
+ }
8567
+ async handle(assignment) {
8568
+ if (assignment.kind === "install")
8569
+ await this.runInstall(assignment.connectorId);
8570
+ else if (assignment.kind === "sync")
8571
+ await this.runSync();
8572
+ else if (assignment.kind === "revoke-grants")
8573
+ await this.runRevoke(assignment.connectorId, assignment.reference);
8574
+ }
8575
+ /** Install Trusty Squire, reporting every step as it settles. */
8576
+ async runInstall(connectorId) {
8577
+ const report = async (steps) => {
8578
+ try {
8579
+ await this.api.execute("postConnectorStatus", { agentId: this.agentId, connectorId, steps });
8580
+ } catch (error) {
8581
+ this.log(`step report failed: ${describe(error)}`);
8582
+ }
8583
+ };
8584
+ const result = await this.install({
8585
+ workspaceId: this.agentId,
8586
+ mcp: this.squire(),
8587
+ onProgress: report
8588
+ });
8589
+ if (result.status === "error") {
8590
+ await this.api.execute("postConnectorStatus", {
8591
+ agentId: this.agentId,
8592
+ connectorId,
8593
+ steps: result.steps,
8594
+ errorMessage: result.errorMessage
8595
+ });
8596
+ return;
8597
+ }
8598
+ if (result.status === "connected") {
8599
+ await this.api.execute("installConnector", {
8600
+ agentId: this.agentId,
8601
+ connectorId,
8602
+ ...result.squireVersion ? { squireVersion: result.squireVersion } : {},
8603
+ ...result.signedInAs ? { signedInAs: result.signedInAs } : {},
8604
+ ...result.signIn ? { signIn: result.signIn } : {}
8605
+ });
8606
+ await this.reportVault(connectorId);
8607
+ return;
8608
+ }
8609
+ await this.api.execute("postConnectorStatus", {
8610
+ agentId: this.agentId,
8611
+ connectorId,
8612
+ steps: result.steps,
8613
+ ...result.squireVersion ? { squireVersion: result.squireVersion } : {},
8614
+ ...result.signedInAs ? { signedInAs: result.signedInAs } : {},
8615
+ ...result.signIn ? { signIn: result.signIn } : {}
8616
+ });
8617
+ }
8618
+ /** One vault report covers every live trusty-squire connector on this helper. */
8619
+ async runSync() {
8620
+ await this.reportVault();
8621
+ }
8622
+ async runRevoke(connectorId, reference) {
8623
+ const outcome = await this.revokeGrantsFn(this.squire(), reference);
8624
+ this.log(`revoked ${outcome.revoked} grant(s) on ${reference}` + (outcome.failed ? `, ${outcome.failed} failed` : ""));
8625
+ void connectorId;
8626
+ }
8627
+ async reportVault(_connectorId) {
8628
+ const connections = await this.readVaultFn(this.squire());
8629
+ await this.api.execute("postConnectorVault", { agentId: this.agentId, connections });
8630
+ }
8631
+ };
8632
+ function describe(error) {
8633
+ return error instanceof Error ? error.message : String(error);
8634
+ }
8635
+
8164
8636
  // packages/api-contract/dist/daemon-operations.js
8165
8637
  function isAgentCommand(value) {
8166
8638
  if (!value || typeof value !== "object")
@@ -8183,6 +8655,9 @@ function isAgentCommand(value) {
8183
8655
  return Boolean(source && typeof source.id === "string" && typeof source.authorId === "string" && typeof source.body === "string" && typeof source.createdAt === "number" && Array.isArray(source.attachments));
8184
8656
  }
8185
8657
 
8658
+ // packages/api-contract/dist/artifacts.js
8659
+ var ARTIFACT_MAXIMUM_BYTES = 25 * 1024 * 1024;
8660
+
8186
8661
  // packages/api-contract/dist/system-events.js
8187
8662
  var SERVER_EVENT_KINDS = [
8188
8663
  "joined",
@@ -8212,12 +8687,12 @@ var wrapper_default = import_websocket.default;
8212
8687
 
8213
8688
  // apps/body/dist/runtime.js
8214
8689
  import { randomBytes as randomBytes5 } from "node:crypto";
8215
- import { execFile } from "node:child_process";
8690
+ import { execFile as execFile2 } from "node:child_process";
8216
8691
  import { closeSync, openSync } from "node:fs";
8217
8692
  import { mkdir, readFile as readFile2, readdir, rename, stat, writeFile as writeFile2 } from "node:fs/promises";
8218
8693
  import { homedir as homedir2 } from "node:os";
8219
8694
  import { dirname as dirname2, resolve as resolve6 } from "node:path";
8220
- import { spawn as spawn2 } from "node:child_process";
8695
+ import { spawn as spawn3 } from "node:child_process";
8221
8696
  import { promisify } from "node:util";
8222
8697
 
8223
8698
  // node_modules/@noble/hashes/_u64.js
@@ -16340,13 +16815,13 @@ var NegentropyStorageVector = class {
16340
16815
  let count = last - first;
16341
16816
  while (count > 0) {
16342
16817
  let it = first;
16343
- let step = Math.floor(count / 2);
16344
- it += step;
16818
+ let step2 = Math.floor(count / 2);
16819
+ it += step2;
16345
16820
  if (cmp(arr[it])) {
16346
16821
  first = ++it;
16347
- count -= step + 1;
16822
+ count -= step2 + 1;
16348
16823
  } else {
16349
- count = step;
16824
+ count = step2;
16350
16825
  }
16351
16826
  }
16352
16827
  return first;
@@ -16748,7 +17223,7 @@ function decodeNsec(nsec) {
16748
17223
  }
16749
17224
 
16750
17225
  // apps/body/dist/runtime.js
16751
- var execFileAsync = promisify(execFile);
17226
+ var execFileAsync = promisify(execFile2);
16752
17227
  var DEFAULT_AGENT_IDENTITY_NAME = "beeline-agent";
16753
17228
  var DEFAULT_BODY_IDENTITY_NAME = "beeline-body";
16754
17229
  var DEFAULT_DAEMON_MONOLITH_BASE_URL = "https://server.usebeeline.app";
@@ -16938,7 +17413,7 @@ async function launchRuntimeDaemon(configPath, opts = {}) {
16938
17413
  const entrypoint = opts.entrypoint ?? process.argv[1];
16939
17414
  if (!entrypoint)
16940
17415
  throw new Error("cannot resolve daemon CLI entrypoint");
16941
- const child = spawn2(process.execPath, [...opts.execArgv ?? [], entrypoint, "daemon", "--config", resolve6(configPath)], {
17416
+ const child = spawn3(process.execPath, [...opts.execArgv ?? [], entrypoint, "daemon", "--config", resolve6(configPath)], {
16942
17417
  cwd: directory,
16943
17418
  env: opts.env ?? process.env,
16944
17419
  detached: !foreground,
@@ -17209,7 +17684,7 @@ async function activateDaemonTransport(path, fetchImpl = fetch) {
17209
17684
  }
17210
17685
 
17211
17686
  // apps/body/dist/room-runtime.js
17212
- import { execFile as execFile5 } from "node:child_process";
17687
+ import { execFile as execFile6 } from "node:child_process";
17213
17688
  import { createHash as createHash6 } from "node:crypto";
17214
17689
  import { existsSync as existsSync4, mkdirSync } from "node:fs";
17215
17690
  import { mkdir as mkdir13, rm as rm5 } from "node:fs/promises";
@@ -17217,7 +17692,7 @@ import { dirname as dirname8, resolve as resolve20 } from "node:path";
17217
17692
  import { promisify as promisify4 } from "node:util";
17218
17693
 
17219
17694
  // apps/body/dist/grant-runner.js
17220
- import { execFile as execFile2 } from "node:child_process";
17695
+ import { execFile as execFile3 } from "node:child_process";
17221
17696
  import { createHash as createHash2, randomBytes as randomBytes6 } from "node:crypto";
17222
17697
  import { readFile as readFile4 } from "node:fs/promises";
17223
17698
  import { createServer } from "node:http";
@@ -17876,9 +18351,9 @@ var GrantCommandRunner = class {
17876
18351
  ...Object.fromEntries(secrets)
17877
18352
  };
17878
18353
  const cap = this.options.outputCapBytes ?? GRANT_COMMAND_OUTPUT_CAP_BYTES;
17879
- const spawn8 = surfaceAllows(policy.surface, "run-host-command") ? { command: argv[0], args: argv.slice(1) } : roomSandboxCommand(policy, room.cwd, argv);
18354
+ const spawn9 = surfaceAllows(policy.surface, "run-host-command") ? { command: argv[0], args: argv.slice(1) } : roomSandboxCommand(policy, room.cwd, argv);
17880
18355
  const outcome = await new Promise((resolveRun) => {
17881
- const child = execFile2(spawn8.command, spawn8.args, {
18356
+ const child = execFile3(spawn9.command, spawn9.args, {
17882
18357
  cwd: room.cwd,
17883
18358
  env,
17884
18359
  timeout: this.options.timeoutMs ?? GRANT_COMMAND_TIMEOUT_MS,
@@ -18047,6 +18522,106 @@ var GrantRunnerServer = class {
18047
18522
  }
18048
18523
  };
18049
18524
 
18525
+ // apps/body/dist/connector-runner.js
18526
+ function toolName(call) {
18527
+ const raw = `${call.title ?? ""} ${call.kind ?? ""}`;
18528
+ const match = raw.match(/(?:^|__|\b)(use_credential|fetch_credential|grant_app_access|revoke_app_access)\b/);
18529
+ return match?.[1] ?? "";
18530
+ }
18531
+ var SQUIRE_EVENT_CLASS = {
18532
+ fetch_credential: "approval",
18533
+ // the credential was read in clear
18534
+ grant_app_access: "approval",
18535
+ // a host was added to a key
18536
+ revoke_app_access: "vault-change"
18537
+ // access removed on the owner's behalf
18538
+ };
18539
+ function record(call) {
18540
+ return call && typeof call === "object" ? call : {};
18541
+ }
18542
+ function serializedSize(value) {
18543
+ if (value == null)
18544
+ return 0;
18545
+ if (typeof value === "string")
18546
+ return value.length;
18547
+ try {
18548
+ return JSON.stringify(value).length;
18549
+ } catch {
18550
+ return 0;
18551
+ }
18552
+ }
18553
+ function squireUsageFromToolCall(call) {
18554
+ const tool = toolName(call);
18555
+ if (!tool)
18556
+ return void 0;
18557
+ const args = record(call.rawInput);
18558
+ const result = record(call.content ?? call.rawOutput);
18559
+ const resultPayload = record(result.content ?? result);
18560
+ const url = typeof args.url === "string" ? args.url : "";
18561
+ const method = typeof args.method === "string" ? args.method.toUpperCase() : tool;
18562
+ const operation = url ? `${method} ${url}` : `${method} ${String(args.reference ?? "")}`.trim();
18563
+ const statusCode = typeof resultPayload.status === "number" ? resultPayload.status : /failed|error|denied/i.test(call.status ?? "") ? 0 : 200;
18564
+ return {
18565
+ ref: String(args.reference ?? args.service ?? ""),
18566
+ service: typeof args.service === "string" ? args.service : null,
18567
+ operation: operation.slice(0, 300),
18568
+ statusCode,
18569
+ bytes: serializedSize(resultPayload.body ?? resultPayload),
18570
+ ...typeof args.grant_id === "string" ? { grantId: args.grant_id } : {},
18571
+ ...SQUIRE_EVENT_CLASS[tool] ? { eventClass: SQUIRE_EVENT_CLASS[tool] } : {}
18572
+ };
18573
+ }
18574
+ var ConnectorUsageRecorder = class {
18575
+ batches = /* @__PURE__ */ new Map();
18576
+ turns = /* @__PURE__ */ new Map();
18577
+ record(turn, usage2) {
18578
+ if (!usage2.ref)
18579
+ return;
18580
+ const existing = this.batches.get(turn.requestId) ?? [];
18581
+ existing.push(usage2);
18582
+ this.batches.set(turn.requestId, existing);
18583
+ if (!this.turns.has(turn.requestId))
18584
+ this.turns.set(turn.requestId, turn);
18585
+ }
18586
+ /** Everything recorded for this turn so far; used by tests and diagnostics. */
18587
+ pending(requestId) {
18588
+ return this.batches.get(requestId) ?? [];
18589
+ }
18590
+ /**
18591
+ * Publish the turn's whole batch as ONE operation call and clear it.
18592
+ * Returns the published record count (0 when nothing was recorded or the
18593
+ * publish failed — a failed usage report never fails the agent turn).
18594
+ */
18595
+ async flush(api, requestId) {
18596
+ const usage2 = this.batches.get(requestId);
18597
+ const turn = this.turns.get(requestId);
18598
+ if (!usage2?.length || !turn)
18599
+ return 0;
18600
+ this.batches.delete(requestId);
18601
+ this.turns.delete(requestId);
18602
+ try {
18603
+ await api.execute("postConnectionUsage", {
18604
+ requestId: turn.requestId,
18605
+ agentId: turn.agentId,
18606
+ ...turn.roomId ? { roomId: turn.roomId } : {},
18607
+ ...turn.cornerId ? { cornerId: turn.cornerId } : {},
18608
+ usage: usage2
18609
+ });
18610
+ return usage2.length;
18611
+ } catch (error) {
18612
+ console.error("[connector] connection usage report failed:", error);
18613
+ return 0;
18614
+ }
18615
+ }
18616
+ };
18617
+ function captureConnectionUsage(recorder, turn, calls) {
18618
+ for (const call of calls) {
18619
+ const usage2 = squireUsageFromToolCall(call);
18620
+ if (usage2)
18621
+ recorder.record(turn, usage2);
18622
+ }
18623
+ }
18624
+
18050
18625
  // apps/body/dist/server-command-intake.js
18051
18626
  import { randomUUID as randomUUID2 } from "node:crypto";
18052
18627
  import { mkdir as mkdir3, writeFile as writeFile4 } from "node:fs/promises";
@@ -18214,7 +18789,7 @@ async function runServerCommandIntake(options) {
18214
18789
  }
18215
18790
 
18216
18791
  // apps/body/dist/monolith-corner-turn.js
18217
- import { execFile as execFile4 } from "node:child_process";
18792
+ import { execFile as execFile5 } from "node:child_process";
18218
18793
  import { createHash as createHash5 } from "node:crypto";
18219
18794
  import { mkdir as mkdir12 } from "node:fs/promises";
18220
18795
  import { homedir as homedir7 } from "node:os";
@@ -18248,6 +18823,9 @@ function isAgentPairingCode(value) {
18248
18823
  // apps/body/dist/beeline-skill.js
18249
18824
  var USING_BEELINE_SKILL_NAME = "using-beeline";
18250
18825
  var BEELINE_REVIEW_SKILL_NAME = "beeline-review";
18826
+ function isConfiguredReviewer(agentHandle, reviewerHandle) {
18827
+ return Boolean(agentHandle && reviewerHandle && agentHandle.replace(/^@/, "") === reviewerHandle.replace(/^@/, ""));
18828
+ }
18251
18829
  var BEELINE_ROOM_CAPABILITIES = [
18252
18830
  "The repository filesystem is read-only in this Room session.",
18253
18831
  "You may address any Room member, including another agent, by writing @name in your reply; the server routes that mention to them. Each turn prompt lists the Room members and the exact spelling that tags each one - use those spellings, and never guess or reuse one from an older message.",
@@ -18255,7 +18833,7 @@ var BEELINE_ROOM_CAPABILITIES = [
18255
18833
  "Tag the user only when you need a decision or input, or when the task they asked for is finished. Never tag for progress, acknowledgement, or questions the transcript already answers.",
18256
18834
  "Every MCP server mounted into this session is approved tool by tool - use operator and host tools freely; the read-only filesystem sandbox is the boundary, not a tool list. Network web search is enabled.",
18257
18835
  "Files and photos people share are downloaded for you: read them at the local path named in the prompt (photos may also arrive inline); never fetch the reference URL.",
18258
- "To create a file (this Room has no other way to write one), call beeline-agent write_scratch_file with a relative path and content - text by default, or base64 for bytes you computed; it returns a path in your writable session home. To send a file, call beeline-agent attach_file with a path inside your checkout or anywhere in your writable session home (wherever a file you or your harness generated actually landed, including one you just wrote); it is attached to your reply. write_scratch_file produces the file, not a picture - turning it into a raster image needs a converter, which needs shell, which this Room does not have.",
18836
+ "To create a file (this Room has no other way to write one), call beeline-agent write_scratch_file with a relative path and content - text by default, or base64 for bytes you computed; it returns a path in your writable session home. To send a file, call beeline-agent post_artifact with a path inside your checkout or anywhere in your writable session home (wherever a file you or your harness generated actually landed, including one you just wrote), or with html/bytes content directly; it is uploaded and attached to your reply, and title and mime default from the file when you post by path. write_scratch_file produces the file, not a picture - turning it into a raster image needs a converter, which needs shell, which this Room does not have.",
18259
18837
  "To run something later or repeatedly, call beeline-agent create_schedule (interval in minutes or a 5-field cron, optional maxRuns); list_schedules / delete_schedule manage them.",
18260
18838
  `To react to things that HAPPEN in this Room rather than only to what is said to you, call beeline-agent subscribe_events with the kinds you want (${SERVER_EVENT_KINDS.join(", ")}); each one then wakes you for a turn. It replaces your list, so send every kind you want - list_event_subscriptions shows the current one. You do this yourself: nobody has to configure it for you. grant-decided carries the grant id and status and resumes the turn that asked for the grant.`,
18261
18839
  "To state something that happened so the Room and other agents can act on it, call beeline-agent emit_event with your own agent:<slug> kind, one sentence, and optionally the agent members to wake. Chains of events are bounded and a refused emit posts nothing.",
@@ -18271,7 +18849,7 @@ var BEELINE_DM_CAPABILITIES = [
18271
18849
  "The repository filesystem is read-only in this session.",
18272
18850
  "Every MCP server mounted into this session is approved tool by tool - use operator and host tools freely; the read-only filesystem sandbox is the boundary, not a tool list. Network web search is enabled.",
18273
18851
  "Files and photos people share are downloaded for you: read them at the local path named in the prompt (photos may also arrive inline); never fetch the reference URL.",
18274
- "To create a file (this Room has no other way to write one), call beeline-agent write_scratch_file with a relative path and content - text by default, or base64 for bytes you computed; it returns a path in your writable session home. To send a file, call beeline-agent attach_file with a path inside your checkout or anywhere in your writable session home (wherever a file you or your harness generated actually landed, including one you just wrote); it is attached to your reply. write_scratch_file produces the file, not a picture - turning it into a raster image needs a converter, which needs shell, which this Room does not have.",
18852
+ "To create a file (this Room has no other way to write one), call beeline-agent write_scratch_file with a relative path and content - text by default, or base64 for bytes you computed; it returns a path in your writable session home. To send a file, call beeline-agent post_artifact with a path inside your checkout or anywhere in your writable session home (wherever a file you or your harness generated actually landed, including one you just wrote), or with html/bytes content directly; it is uploaded and attached to your reply, and title and mime default from the file when you post by path. write_scratch_file produces the file, not a picture - turning it into a raster image needs a converter, which needs shell, which this Room does not have.",
18275
18853
  "Tag the person only when you need a decision or input, or when the task they asked for is finished.",
18276
18854
  "Never claim an action or reply happened unless the prompt or a tool result proves it."
18277
18855
  ].join(" ");
@@ -18312,6 +18890,22 @@ description: How to answer inside a Beeline Room.
18312
18890
  # Using Beeline
18313
18891
 
18314
18892
  You are answering inside a Room whose filesystem is read-only. ${BEELINE_ROOM_CAPABILITIES}
18893
+
18894
+ When your corner's pull request is ready, merging is your step: once the configured reviewer approves and tags you, you run \`gh pr merge\` yourself - nothing merges it for you.
18895
+
18896
+ ## Tools and the Workbench
18897
+
18898
+ A **tool** is something you can use once a human pairs it; a **key** is the credential that tool holds for that human. You spend a key through the mounted connector and never see the credential itself.
18899
+
18900
+ This build knows one tool: Trusty Squire - vaulted credentials and a browser that signs in for you. Wallet and Tailscale are named in the Workbench but not yet available.
18901
+
18902
+ You NEVER pair a tool and never ask for a raw credential in chat. When a task needs one you do not have, say so plainly, name the tool that would solve it, and tell the person exactly where to go: Settings \u2192 Workbench \u2192 Tools.
18903
+
18904
+ Keys are sovereign: they belong to the human who provisioned them. You cannot use another member's key and must not ask a member to share one.
18905
+
18906
+ ## Showing a mock
18907
+
18908
+ When a design decision needs eyes, show it instead of describing it. Build ONE self-contained HTML page and post it with beeline-agent post_artifact (mime "text/html", pass the document as html). Everything is inline: a single <style> element for all CSS and data: URLs for any image - no script, no external dependencies, no network references. The validator refuses every <script>, <link>, <iframe>, <object>, <embed>, <form>, inline event handler, and http(s) URL, so a page that reaches for the network never posts. Use the Obsidian Refined tokens: grayscale surfaces, one brass accent #d7af5f, 3px radii, IBM Plex Sans/Mono type. Lay the user stories out as frames - one bordered, labelled block per story, so each story can be judged on its own. After posting, ask for feedback here in the corner: the artifact is the thing people react to, not your prose.
18315
18909
  `;
18316
18910
  }
18317
18911
  function beelineReviewSkillMarkdown(releaseId) {
@@ -18389,7 +18983,7 @@ Then take exactly one action:
18389
18983
 
18390
18984
  - FAIL: reply \`@author\` with the confirmed findings to fix.
18391
18985
  - PASS: call \`approve_merge\` with the reviewed head SHA, then reply \`@author approved <reviewed sha>, merge\`.
18392
- - Never merge the pull request yourself.
18986
+ - Approving is your last step as reviewer. The author merges it; you never do, and nothing merges it automatically.
18393
18987
  `;
18394
18988
  }
18395
18989
 
@@ -18437,8 +19031,8 @@ function openRouterModelId(model, env = {}) {
18437
19031
  }
18438
19032
  function parseOpenRouterEndpoints(payload) {
18439
19033
  const data = payload?.data;
18440
- const record2 = data && typeof data === "object" ? data : {};
18441
- const raw = Array.isArray(record2.endpoints) ? record2.endpoints : [];
19034
+ const record3 = data && typeof data === "object" ? data : {};
19035
+ const raw = Array.isArray(record3.endpoints) ? record3.endpoints : [];
18442
19036
  const endpoints = [];
18443
19037
  for (const entry of raw) {
18444
19038
  if (!entry || typeof entry !== "object")
@@ -18453,11 +19047,11 @@ function parseOpenRouterEndpoints(payload) {
18453
19047
  const supported = Array.isArray(endpoint2.supported_parameters) ? endpoint2.supported_parameters : [];
18454
19048
  endpoints.push({ provider, uptime, contextLength, tools: supported.includes("tools") });
18455
19049
  }
18456
- const architecture = record2.architecture && typeof record2.architecture === "object" ? record2.architecture : {};
19050
+ const architecture = record3.architecture && typeof record3.architecture === "object" ? record3.architecture : {};
18457
19051
  const modalities = Array.isArray(architecture.input_modalities) ? architecture.input_modalities.filter((value) => typeof value === "string" && value.length > 0) : void 0;
18458
19052
  return {
18459
19053
  endpoints,
18460
- contextLength: typeof record2.context_length === "number" ? record2.context_length : advertisedContextLength(endpoints),
19054
+ contextLength: typeof record3.context_length === "number" ? record3.context_length : advertisedContextLength(endpoints),
18461
19055
  inputModalities: modalities?.length ? modalities : void 0
18462
19056
  };
18463
19057
  }
@@ -19059,7 +19653,7 @@ async function prepareRoomAgentHome(input) {
19059
19653
  await symlink(source, target).catch(() => void 0);
19060
19654
  }
19061
19655
  const prior = agentHomeProvisionQueues.get(root) ?? Promise.resolve();
19062
- const provision = prior.catch(() => void 0).then(() => provisionAgentSkillsAndMcp(root, operatorHome, input.skillReleaseId ?? runningBeelineReleaseId(), input.failClosed ?? false, input.sharedSkills ?? [], agentSkillDir(input.agentKind), input.openRouterRouting));
19656
+ const provision = prior.catch(() => void 0).then(() => provisionAgentSkillsAndMcp(root, operatorHome, input.skillReleaseId ?? runningBeelineReleaseId(), input.failClosed ?? false, input.sharedSkills ?? [], agentSkillDir(input.agentKind), input.openRouterRouting, input.isReviewer ?? false));
19063
19657
  agentHomeProvisionQueues.set(root, provision);
19064
19658
  try {
19065
19659
  await provision;
@@ -19069,10 +19663,10 @@ async function prepareRoomAgentHome(input) {
19069
19663
  }
19070
19664
  return roomAgentHomeEnv(root);
19071
19665
  }
19072
- async function provisionAgentSkillsAndMcp(root, operatorHome, skillReleaseId, failClosed, sharedSkills, skillDir, openRouterRouting) {
19666
+ async function provisionAgentSkillsAndMcp(root, operatorHome, skillReleaseId, failClosed, sharedSkills, skillDir, openRouterRouting, isReviewer) {
19073
19667
  const managedSkills = [
19074
19668
  { name: USING_BEELINE_SKILL_NAME, content: usingBeelineSkillMarkdown(skillReleaseId) },
19075
- { name: BEELINE_REVIEW_SKILL_NAME, content: beelineReviewSkillMarkdown(skillReleaseId) }
19669
+ ...isReviewer ? [{ name: BEELINE_REVIEW_SKILL_NAME, content: beelineReviewSkillMarkdown(skillReleaseId) }] : []
19076
19670
  ];
19077
19671
  const shared = await resolveSharedSkillSources(operatorHome, sharedSkills);
19078
19672
  await provisionManagedSkillsDir(resolve13(root, skillDir, "skills"), managedSkills, shared, sharedSkills.length === 0);
@@ -19904,8 +20498,8 @@ function toolCallText(content) {
19904
20498
  }
19905
20499
  if (!value || typeof value !== "object")
19906
20500
  return;
19907
- const record2 = value;
19908
- for (const [key, nested] of Object.entries(record2)) {
20501
+ const record3 = value;
20502
+ for (const [key, nested] of Object.entries(record3)) {
19909
20503
  if (["text", "content", "message", "error", "output"].includes(key.toLowerCase())) {
19910
20504
  walk(nested, depth + 1);
19911
20505
  }
@@ -20127,9 +20721,9 @@ export default async function (pi) {
20127
20721
  `;
20128
20722
 
20129
20723
  // apps/body/dist/corner-branch-sync.js
20130
- import { execFile as execFile3 } from "node:child_process";
20724
+ import { execFile as execFile4 } from "node:child_process";
20131
20725
  import { promisify as promisify2 } from "node:util";
20132
- var execFileAsync2 = promisify2(execFile3);
20726
+ var execFileAsync2 = promisify2(execFile4);
20133
20727
  async function syncCornerBranch(input) {
20134
20728
  const git = input.git ?? (async (args) => (await execFileAsync2("git", ["-C", input.worktreePath, ...args], {
20135
20729
  ...input.env ? { env: input.env } : {},
@@ -20204,8 +20798,8 @@ function shellPayload(toolCall) {
20204
20798
  return true;
20205
20799
  if (!rawInput || typeof rawInput !== "object" || Array.isArray(rawInput))
20206
20800
  return false;
20207
- const record2 = rawInput;
20208
- return typeof record2.command === "string" || typeof record2.cmd === "string";
20801
+ const record3 = rawInput;
20802
+ return typeof record3.command === "string" || typeof record3.cmd === "string";
20209
20803
  }
20210
20804
  var ROOM_MOUNTED_MCP_SERVERS = [
20211
20805
  READ_ONLY_MCP_SERVER_NAME,
@@ -20217,10 +20811,10 @@ var TOOL_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
20217
20811
  function permissionRecord(rawInput) {
20218
20812
  return rawInput && typeof rawInput === "object" && !Array.isArray(rawInput) ? rawInput : void 0;
20219
20813
  }
20220
- function dispatchedPayloadIsShell(record2) {
20221
- if (!record2)
20814
+ function dispatchedPayloadIsShell(record3) {
20815
+ if (!record3)
20222
20816
  return false;
20223
- return DISPATCHER_TOOL_INPUT_KEYS.some((key) => key in record2 && shellPayload({ rawInput: record2[key] }));
20817
+ return DISPATCHER_TOOL_INPUT_KEYS.some((key) => key in record3 && shellPayload({ rawInput: record3[key] }));
20224
20818
  }
20225
20819
  function toolIdentityCandidates(toolCall) {
20226
20820
  const candidates = [];
@@ -20229,9 +20823,9 @@ function toolIdentityCandidates(toolCall) {
20229
20823
  candidates.push(value.trim());
20230
20824
  };
20231
20825
  add(toolCall?.title);
20232
- const record2 = permissionRecord(toolCall?.rawInput);
20826
+ const record3 = permissionRecord(toolCall?.rawInput);
20233
20827
  for (const key of DISPATCHER_TOOL_NAME_KEYS)
20234
- add(record2?.[key]);
20828
+ add(record3?.[key]);
20235
20829
  return candidates;
20236
20830
  }
20237
20831
  function normalizedServerName(name) {
@@ -20241,8 +20835,8 @@ function resolveMountedMcpToolCall(request, mountedServers) {
20241
20835
  const toolCall = request.toolCall;
20242
20836
  if (shellPayload(toolCall))
20243
20837
  return void 0;
20244
- const record2 = permissionRecord(toolCall?.rawInput);
20245
- if (dispatchedPayloadIsShell(record2))
20838
+ const record3 = permissionRecord(toolCall?.rawInput);
20839
+ if (dispatchedPayloadIsShell(record3))
20246
20840
  return void 0;
20247
20841
  const candidates = toolIdentityCandidates(toolCall);
20248
20842
  for (const server of mountedServers) {
@@ -20308,7 +20902,7 @@ function isMountedMcpToolPermissionRequest(request, mountedServers = ROOM_MOUNTE
20308
20902
  var AGENT_SURFACE_TOOL_NAMES = [
20309
20903
  "open_corner",
20310
20904
  "pr_checks_status",
20311
- "attach_file",
20905
+ "post_artifact",
20312
20906
  "write_scratch_file"
20313
20907
  ];
20314
20908
  var SQUIRE_TITLE_PREFIXES = [
@@ -20519,25 +21113,25 @@ async function explainEmptyAgentTurn(input) {
20519
21113
  const streamFact = describeEmptyTurn(input.result, input.agentLabel);
20520
21114
  if (!isPiAcpHarness(input.agentLabel))
20521
21115
  return { reason: streamFact };
20522
- const record2 = await readPiTurnRecord({ agentEnv: input.agentEnv, sessionId: input.sessionId });
20523
- if (!record2)
21116
+ const record3 = await readPiTurnRecord({ agentEnv: input.agentEnv, sessionId: input.sessionId });
21117
+ if (!record3)
20524
21118
  return { reason: `pi left no readable turn record; ${streamFact}` };
20525
- switch (record2.kind) {
21119
+ switch (record3.kind) {
20526
21120
  case "error":
20527
- return { reason: record2.reason, record: record2 };
21121
+ return { reason: record3.reason, record: record3 };
20528
21122
  case "empty":
20529
21123
  return {
20530
- reason: `the model ended its turn with no text (stop reason ${record2.stopReason})`,
20531
- record: record2
21124
+ reason: `the model ended its turn with no text (stop reason ${record3.stopReason})`,
21125
+ record: record3
20532
21126
  };
20533
21127
  case "answer":
20534
21128
  return {
20535
- recoveredText: record2.text,
21129
+ recoveredText: record3.text,
20536
21130
  reason: "pi recorded the answer but the ACP stream delivered no text",
20537
- record: record2
21131
+ record: record3
20538
21132
  };
20539
21133
  case "missing":
20540
- return { reason: `pi recorded no assistant message for this turn; ${streamFact}`, record: record2 };
21134
+ return { reason: `pi recorded no assistant message for this turn; ${streamFact}`, record: record3 };
20541
21135
  }
20542
21136
  }
20543
21137
  function shouldRetryEmptyTurn(explanation) {
@@ -20564,10 +21158,10 @@ function turnFailureReasonWithProvider(reason, providers) {
20564
21158
  const served = describeTurnProviders(providers);
20565
21159
  return served ? `${reason} \xB7 ${served}` : reason;
20566
21160
  }
20567
- function isAccountOrProviderRefusal(record2) {
20568
- if (!record2 || record2.kind !== "error" || record2.status === void 0)
21161
+ function isAccountOrProviderRefusal(record3) {
21162
+ if (!record3 || record3.kind !== "error" || record3.status === void 0)
20569
21163
  return false;
20570
- return [401, 402, 403, 407, 408, 429].includes(record2.status) || record2.status >= 500;
21164
+ return [401, 402, 403, 407, 408, 429].includes(record3.status) || record3.status >= 500;
20571
21165
  }
20572
21166
 
20573
21167
  // apps/body/dist/response-directives.js
@@ -20824,11 +21418,11 @@ var TurnTrace = class {
20824
21418
  this.end(phase);
20825
21419
  this.promptSettled();
20826
21420
  }
20827
- const record2 = this.snapshot(outcome, reason);
20828
- await this.options.sink?.write(record2).catch((error) => {
21421
+ const record3 = this.snapshot(outcome, reason);
21422
+ await this.options.sink?.write(record3).catch((error) => {
20829
21423
  console.error("[thin-core] turn trace write failed:", error);
20830
21424
  });
20831
- return record2;
21425
+ return record3;
20832
21426
  }
20833
21427
  };
20834
21428
  function round(ms) {
@@ -20853,9 +21447,9 @@ function formatTurnAttempt(attempt) {
20853
21447
  parts.push(`${attempt.toolCalls} tool call${attempt.toolCalls === 1 ? "" : "s"}`);
20854
21448
  return parts.join(" \xB7 ");
20855
21449
  }
20856
- function formatTurnTraceLine(record2) {
20857
- const head = `${record2.surface} ${record2.roomId} turn ${record2.requestId} ${record2.outcome} ${formatDuration(record2.totalMs)}`;
20858
- return [head, ...record2.attempts.map((attempt) => formatTurnAttempt(attempt))].join("\n ");
21450
+ function formatTurnTraceLine(record3) {
21451
+ const head = `${record3.surface} ${record3.roomId} turn ${record3.requestId} ${record3.outcome} ${formatDuration(record3.totalMs)}`;
21452
+ return [head, ...record3.attempts.map((attempt) => formatTurnAttempt(attempt))].join("\n ");
20859
21453
  }
20860
21454
  function turnTraceDirectory(runtimeDir) {
20861
21455
  return resolve17(runtimeDir, "turn-traces");
@@ -20877,16 +21471,16 @@ var TurnTraceFile = class {
20877
21471
  path(now2 = (this.options.clock ?? (() => /* @__PURE__ */ new Date()))()) {
20878
21472
  return resolve17(this.directory, traceFileName(now2));
20879
21473
  }
20880
- write(record2) {
21474
+ write(record3) {
20881
21475
  this.tail = this.tail.catch(() => void 0).then(async () => {
20882
21476
  const now2 = (this.options.clock ?? (() => /* @__PURE__ */ new Date()))();
20883
21477
  const path = this.path(now2);
20884
21478
  await mkdir8(this.directory, { recursive: true, mode: 448 });
20885
- await appendFile(path, `${JSON.stringify(record2)}
21479
+ await appendFile(path, `${JSON.stringify(record3)}
20886
21480
  `, { mode: 384 });
20887
21481
  await this.prune(now2);
20888
21482
  const log2 = this.options.log ?? ((line) => console.log(line));
20889
- log2(`[thin-core] turn trace ${formatTurnTraceLine(record2)}${this.announced ? "" : `
21483
+ log2(`[thin-core] turn trace ${formatTurnTraceLine(record3)}${this.announced ? "" : `
20890
21484
  recorded in ${path}`}`);
20891
21485
  this.announced = true;
20892
21486
  });
@@ -21149,7 +21743,7 @@ async function seedWarmNodeModules(input) {
21149
21743
  for (const target of placed) {
21150
21744
  await rm4(target, { recursive: true, force: true }).catch(() => void 0);
21151
21745
  }
21152
- return { reason: "failed", key: plan.key, detail: describe(error) };
21746
+ return { reason: "failed", key: plan.key, detail: describe2(error) };
21153
21747
  } finally {
21154
21748
  await rm4(staging, { recursive: true, force: true }).catch(() => void 0);
21155
21749
  }
@@ -21192,7 +21786,7 @@ async function harvestWarmNodeModules(input) {
21192
21786
  } catch (error) {
21193
21787
  if (await pathExists(entry))
21194
21788
  return { reason: "already-warm", key: plan.key };
21195
- return { reason: "failed", key: plan.key, detail: describe(error) };
21789
+ return { reason: "failed", key: plan.key, detail: describe2(error) };
21196
21790
  } finally {
21197
21791
  await rm4(staging, { recursive: true, force: true }).catch(() => void 0);
21198
21792
  }
@@ -21335,7 +21929,7 @@ async function deviceOf(path) {
21335
21929
  async function isDirectory(path) {
21336
21930
  return stat2(path).then((info) => info.isDirectory(), () => false);
21337
21931
  }
21338
- function describe(error) {
21932
+ function describe2(error) {
21339
21933
  return error instanceof Error ? error.message : String(error);
21340
21934
  }
21341
21935
 
@@ -21613,10 +22207,13 @@ var MonolithRoomTurnLoop = class {
21613
22207
  });
21614
22208
  const directMessage = Array.isArray(repositoryState.directParticipants) && repositoryState.directParticipants.length === 2;
21615
22209
  await mkdir11(this.options.cwd, { recursive: true });
21616
- const selection = configuration.model || configuration.effort ? { model: configuration.model, effort: configuration.effort } : this.options.config.modelSelection;
22210
+ const selectionModel = configuration.model ?? this.options.config.modelSelection?.model;
22211
+ const selectionEffort = configuration.effort ?? this.options.config.modelSelection?.effort;
22212
+ const selection = selectionModel || selectionEffort ? { model: selectionModel, effort: selectionEffort } : void 0;
21617
22213
  const homeOverlay = this.options.config.agentHomeRoot ? await prepareRoomAgentHome({
21618
22214
  root: this.options.config.agentHomeRoot,
21619
22215
  sharedSkills: this.options.config.sharedSkills ?? [],
22216
+ isReviewer: isConfiguredReviewer(self?.handle, configuration.reviewerHandle),
21620
22217
  ...this.options.config.agentKind ? { agentKind: this.options.config.agentKind } : {},
21621
22218
  ...this.options.config.operatorHome ? { operatorHome: this.options.config.operatorHome } : {},
21622
22219
  ...openRouterRoutingInput(this.options.config, selection, this.options.fetchImpl, {
@@ -21839,6 +22436,7 @@ var MonolithRoomTurnLoop = class {
21839
22436
  this.busy = true;
21840
22437
  const trace = this.beginTurnTrace(item.id);
21841
22438
  let liveStream;
22439
+ let liveCornerOpened = false;
21842
22440
  try {
21843
22441
  if (!this.memberNames.has(item.authorId))
21844
22442
  await this.roster().catch(() => void 0);
@@ -21920,7 +22518,11 @@ ${JSON.stringify((corners.corners ?? []).filter((corner) => !corner.archived))}`
21920
22518
  result2 = await this.client.sessionPrompt(this.sessionId, nextPrompt, ROOM_PROMPT_INACTIVITY_TIMEOUT_MS, (delta, full) => {
21921
22519
  trace.firstModelOutput();
21922
22520
  stream.onChunk(delta, full);
21923
- }, void 0, (calls) => trace.toolCalls(calls));
22521
+ }, void 0, (calls) => {
22522
+ trace.toolCalls(calls);
22523
+ if (openedACorner(openCornerToolCall(calls)))
22524
+ liveCornerOpened = true;
22525
+ });
21924
22526
  } catch (error) {
21925
22527
  promptError = error;
21926
22528
  }
@@ -22014,6 +22616,22 @@ ${JSON.stringify((corners.corners ?? []).filter((corner) => !corner.archived))}`
22014
22616
  await trace.finish("cancelled");
22015
22617
  return;
22016
22618
  }
22619
+ if (liveCornerOpened && error instanceof AcpRequestTimeoutError && error.inactivity && error.method === "session/prompt") {
22620
+ console.log(`[thin-core] monolith Room ${this.options.roomId} turn ${item.id}: inactivity timeout after opening a corner; the work continues in the corner`);
22621
+ this.options.onCornerOpened?.();
22622
+ await liveStream?.retract().catch((retractError) => {
22623
+ console.error(`[thin-core] monolith Room ${this.options.roomId} draft retract failed:`, retractError);
22624
+ });
22625
+ await api.execute("postAgentTurnReceipt", {
22626
+ agentId: this.agent.publicKey,
22627
+ roomId: this.options.roomId,
22628
+ requestId: item.id,
22629
+ status: "complete",
22630
+ generationId: this.commandContext.generationId
22631
+ });
22632
+ await trace.finish("complete");
22633
+ return;
22634
+ }
22017
22635
  await liveStream?.retract().catch((retractError) => {
22018
22636
  console.error(`[thin-core] monolith Room ${this.options.roomId} draft retract failed:`, retractError);
22019
22637
  });
@@ -22083,7 +22701,7 @@ function roomMessagePrompt(author, body, attachments, delivered, harnessAcceptsI
22083
22701
  }
22084
22702
 
22085
22703
  // apps/body/dist/monolith-corner-turn.js
22086
- var execFileAsync3 = promisify3(execFile4);
22704
+ var execFileAsync3 = promisify3(execFile5);
22087
22705
  var TOOL_ARGUMENT_MAX_BYTES = 1200;
22088
22706
  var TOOL_OUTPUT_MAX_BYTES = 3200;
22089
22707
  var TOOL_PATH_LIMIT = 12;
@@ -22100,6 +22718,11 @@ function cornerReviewerInstruction(input) {
22100
22718
  const headSha = input.headSha ?? "<head sha>";
22101
22719
  return `Checks are green on PR #${number} at ${headSha}. Review it now with the beeline-review skill against that exact head. FAIL: reply \`@${author}\` with the confirmed findings to fix. PASS: call the approve_merge tool for ${headSha}, then reply \`@${author} approved ${headSha}, merge\`. Never merge yourself. Never say you are holding or waiting for checks.`;
22102
22720
  }
22721
+ function cornerSelfReviewerInstruction(input) {
22722
+ if (!input.reviewerHandle || !input.agentHandle || !input.openedByAgent || input.agentHandle.replace(/^@/, "") !== input.reviewerHandle.replace(/^@/, ""))
22723
+ return void 0;
22724
+ return "You are this Room's reviewer, so your own pull request needs no review: do not request one, do not tag any agent for review, and merge yourself with gh once checks pass and no hold exists.";
22725
+ }
22103
22726
  var CORNER_AUTHOR_CONTRACT = `The objective text is the user's ask. Keep it verbatim in your head and do not reinterpret it.
22104
22727
  Before any code, write its end-user story in one sentence: "a person who does X sees Y".
22105
22728
  If the objective reports a defect, reproduce it first at the layer where it lives: the command, request, or tap sequence, and what was observed.
@@ -22159,7 +22782,7 @@ function serialized(value) {
22159
22782
  return "";
22160
22783
  }
22161
22784
  }
22162
- function record(value) {
22785
+ function record2(value) {
22163
22786
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
22164
22787
  }
22165
22788
  function clampBytes(value, maxBytes) {
@@ -22199,7 +22822,7 @@ function filePaths(value, worktreePath) {
22199
22822
  candidate.forEach((entry) => visit(entry));
22200
22823
  return;
22201
22824
  }
22202
- const object = record(candidate);
22825
+ const object = record2(candidate);
22203
22826
  if (object)
22204
22827
  Object.entries(object).forEach(([entryKey, entry]) => visit(entry, entryKey));
22205
22828
  };
@@ -22207,9 +22830,9 @@ function filePaths(value, worktreePath) {
22207
22830
  return [...paths];
22208
22831
  }
22209
22832
  function resultStatus(call) {
22210
- const content = record(call.content) ?? (typeof call.content === "string" ? (() => {
22833
+ const content = record2(call.content) ?? (typeof call.content === "string" ? (() => {
22211
22834
  try {
22212
- return record(JSON.parse(call.content));
22835
+ return record2(JSON.parse(call.content));
22213
22836
  } catch {
22214
22837
  return void 0;
22215
22838
  }
@@ -22224,7 +22847,7 @@ function resultStatus(call) {
22224
22847
  return /(?:failed|error|denied)/i.test(call.status ?? "") ? "error" : "ok";
22225
22848
  }
22226
22849
  function toolArguments(call) {
22227
- const raw = record(call.rawInput);
22850
+ const raw = record2(call.rawInput);
22228
22851
  const command = typeof call.rawInput === "string" ? call.rawInput : typeof raw?.command === "string" ? raw.command : typeof raw?.cmd === "string" ? raw.cmd : void 0;
22229
22852
  if (command)
22230
22853
  return { command: clampBytes(redactToolDetail(command), TOOL_ARGUMENT_MAX_BYTES) };
@@ -22462,12 +23085,14 @@ var MonolithCornerTurnLoop = class {
22462
23085
  headSha: restore.lifecycle?.pr?.headSha
22463
23086
  });
22464
23087
  }
22465
- this.cornerTurnEndNudge = reviewerInstruction ?? cornerMergeInstruction(configuration.yoloMode, configuration.reviewerHandle);
23088
+ const selfReviewerInstruction = cornerSelfReviewerInstruction(reviewerInput);
23089
+ this.cornerTurnEndNudge = reviewerInstruction ?? selfReviewerInstruction ?? cornerMergeInstruction(configuration.yoloMode, configuration.reviewerHandle);
22466
23090
  await mkdir12(this.options.worktreePath, { recursive: true });
22467
23091
  const selection = configuration.model || configuration.effort ? { model: configuration.model, effort: configuration.effort } : this.options.config.modelSelection;
22468
23092
  const homeOverlay = this.options.config.agentHomeRoot ? await prepareRoomAgentHome({
22469
23093
  root: this.options.config.agentHomeRoot,
22470
23094
  sharedSkills: this.options.config.sharedSkills ?? [],
23095
+ isReviewer: isConfiguredReviewer(self?.handle, configuration.reviewerHandle),
22471
23096
  ...this.options.config.agentKind ? { agentKind: this.options.config.agentKind } : {},
22472
23097
  ...this.options.config.operatorHome ? { operatorHome: this.options.config.operatorHome } : {},
22473
23098
  ...openRouterRoutingInput(this.options.config, selection, this.options.fetchImpl, {
@@ -22612,13 +23237,13 @@ var MonolithCornerTurnLoop = class {
22612
23237
  ...reviewerInstruction ? [reviewerInstruction] : [
22613
23238
  configuration.reviewerHandle ? `Once the pull request exists, reply with its full URL and end the turn; do not tag the reviewer, check, or wait for CI.` : 'Once the pull request exists, reply only with its full URL and end the turn; do not check or wait for CI. On a later checks turn, call pr_checks_status. Merge only when checks="passed", held=false, and approvalPending=false; if checks="unknown", reply in this corner with the tool reason and stop instead of retrying. Only a later explicit human resume clears a hold.',
22614
23239
  CORNER_AUTHOR_CONTRACT,
22615
- cornerMergeInstruction(configuration.yoloMode, configuration.reviewerHandle)
23240
+ selfReviewerInstruction ?? cornerMergeInstruction(configuration.yoloMode, configuration.reviewerHandle)
22616
23241
  ],
22617
23242
  "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.",
22618
- "Never restate server check or merge notes. On a checks turn, say nothing unless you merge or push a fix, then use one short line. Never merge while approvalPending is true. When approval is pending, wait for the reviewer to tag you. Never merge another pull request."
23243
+ "Never restate server check or merge notes. On a checks turn, say nothing unless you merge or push a fix, then use one short line. Never merge while approvalPending is true. When approval is pending, wait for the reviewer to tag you. Never merge another pull request. Never create a schedule to poll pr_checks_status or the merge gate: the green transition wakes the reviewer and the reviewer's approval tag wakes you, and tagging any agent other than the configured reviewer cannot clear the gate. If a schedule wakes you in this corner anyway, follow the same rule as a checks turn: say nothing unless you merge, push a fix, or report a genuinely new blocker."
22619
23244
  ] : [
22620
23245
  "This is a chat-only corner with no repository or GitHub workflow.",
22621
- "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.",
23246
+ "Work in this corner's writable workspace. Use write_scratch_file or ordinary tools to create files, then post_artifact with the path to send them back to the corner.",
22622
23247
  "Do not initialize a repository, create a branch, push, open a pull request, or wait for GitHub checks."
22623
23248
  ]
22624
23249
  ].filter(Boolean).join("\n\n")
@@ -22781,7 +23406,7 @@ ${trigger}`,
22781
23406
  const pendingToolActivities = /* @__PURE__ */ new Map();
22782
23407
  let lastNarratedToolCall;
22783
23408
  let activityAttempt = 0;
22784
- const publishToolCalls = (calls, settledOnly) => {
23409
+ const publishToolCalls = (calls, settledOnly, exceptKey) => {
22785
23410
  calls.forEach((call, index) => {
22786
23411
  const key = `${activityAttempt}:${toolCallKey(call, index)}`;
22787
23412
  if (settledOnly && !observedToolCalls.has(key)) {
@@ -22791,7 +23416,7 @@ ${trigger}`,
22791
23416
  if (narration2)
22792
23417
  lastNarratedToolCall = key;
22793
23418
  }
22794
- if (settledOnly || publishedToolCalls.has(key) || !toolCallSettled(call))
23419
+ if (settledOnly || publishedToolCalls.has(key) || !toolCallSettled(call) || key === exceptKey)
22795
23420
  return;
22796
23421
  publishedToolCalls.add(key);
22797
23422
  const narration = pendingToolNarrations.get(key) ?? "";
@@ -22827,12 +23452,18 @@ ${trigger}`,
22827
23452
  });
22828
23453
  });
22829
23454
  };
22830
- const flushToolCalls = async (calls, finalReply) => {
23455
+ const flushToolCalls = async (calls, finalReply, final = false) => {
22831
23456
  await this.activityTail;
22832
23457
  if (lastNarratedToolCall && pendingToolNarrations.get(lastNarratedToolCall) === finalReply)
22833
23458
  pendingToolNarrations.delete(lastNarratedToolCall);
22834
23459
  publishToolCalls(calls, false);
22835
23460
  await this.activityTail;
23461
+ if (this.options.connectorUsage) {
23462
+ captureConnectionUsage(this.options.connectorUsage, { requestId, agentId: this.agent.publicKey, cornerId }, calls);
23463
+ if (final)
23464
+ await this.options.connectorUsage.flush(api, requestId);
23465
+ }
23466
+ await this.activityTail;
22836
23467
  publishToolCalls(calls, false);
22837
23468
  await this.activityTail;
22838
23469
  };
@@ -22864,6 +23495,7 @@ ${trigger}`,
22864
23495
  }, void 0, (calls) => {
22865
23496
  trace.toolCalls(calls);
22866
23497
  publishToolCalls(calls, true);
23498
+ publishToolCalls(calls, false, lastNarratedToolCall);
22867
23499
  });
22868
23500
  };
22869
23501
  let result = await runPrompt();
@@ -22901,7 +23533,7 @@ ${trigger}`,
22901
23533
  if (!reply && explained?.recoveredText)
22902
23534
  reply = durableReplyText(explained.recoveredText);
22903
23535
  reply = [replyBeforeNudge, reply].filter(Boolean).join("\n\n");
22904
- await flushToolCalls(result.toolCalls, reply);
23536
+ await flushToolCalls(result.toolCalls, reply, true);
22905
23537
  for (const call of result.toolCalls) {
22906
23538
  const failure = toolCallFailureLine(call);
22907
23539
  if (failure)
@@ -23598,7 +24230,7 @@ async function removeCornerWorktreeAndBranches(worktree) {
23598
24230
  ]);
23599
24231
  }
23600
24232
  }
23601
- var execFileAsync4 = promisify4(execFile5);
24233
+ var execFileAsync4 = promisify4(execFile6);
23602
24234
  async function removeCornerScratchWorkspace(input) {
23603
24235
  const expected = resolve20(input.roomRoot, "scratch");
23604
24236
  if (resolve20(input.scratchPath) !== expected) {
@@ -23632,6 +24264,8 @@ var RoomRuntimeCoordinator = class {
23632
24264
  /** One command-grant runner per daemon; Rooms and corners register their checkouts on it. */
23633
24265
  grantRunner;
23634
24266
  grantRunnerServer;
24267
+ /** Connection usage capture, batched per agent turn. */
24268
+ connectorUsage;
23635
24269
  constructor(runtime, configPath, baseConfig, options) {
23636
24270
  this.configPath = configPath;
23637
24271
  this.baseConfig = baseConfig;
@@ -23646,6 +24280,7 @@ var RoomRuntimeCoordinator = class {
23646
24280
  agentId: this.agent.publicKey
23647
24281
  });
23648
24282
  this.grantRunnerServer = new GrantRunnerServer(this.grantRunner);
24283
+ this.connectorUsage = new ConnectorUsageRecorder();
23649
24284
  this.watchdogStaleMs = options.watchdogStaleMs ?? DEFAULT_ROOM_WATCHDOG_STALE_MS;
23650
24285
  this.reconcileHeartbeatMs = options.reconcileHeartbeatMs ?? DEFAULT_RECONCILE_HEARTBEAT_MS;
23651
24286
  this.drainDeadlineMs = options.drainDeadlineMs ?? DEFAULT_DRAIN_DEADLINE_MS;
@@ -23962,6 +24597,7 @@ var RoomRuntimeCoordinator = class {
23962
24597
  cornerId: corner.cornerId,
23963
24598
  grantRunner: this.grantRunner,
23964
24599
  ...grantRunnerEndpoint ? { grantRunnerEndpoint } : {},
24600
+ connectorUsage: this.connectorUsage,
23965
24601
  parentRoomId: corner.parentRoomId,
23966
24602
  workspaceId: this.runtime.communityId,
23967
24603
  ...corner.openedBy ? { openedBy: corner.openedBy } : {},
@@ -24329,13 +24965,13 @@ var ThinDaemonCore = class {
24329
24965
  };
24330
24966
 
24331
24967
  // apps/body/dist/systemd.js
24332
- import { execFile as execFile6 } from "node:child_process";
24968
+ import { execFile as execFile7 } from "node:child_process";
24333
24969
  import { mkdir as mkdir14, readFile as readFile9, writeFile as writeFile9 } from "node:fs/promises";
24334
24970
  import { homedir as homedir8 } from "node:os";
24335
24971
  import { dirname as dirname9, resolve as resolve21 } from "node:path";
24336
24972
  import { setTimeout as sleep } from "node:timers/promises";
24337
24973
  import { promisify as promisify5 } from "node:util";
24338
- var execFileAsync5 = promisify5(execFile6);
24974
+ var execFileAsync5 = promisify5(execFile7);
24339
24975
  var DELIBERATE_REMOVAL_EXIT_STATUS = 78;
24340
24976
  var DAEMON_DISTRESS_EXIT_STATUS = 77;
24341
24977
  var UNKNOWN_AGENT_EXIT_STATUS = 79;
@@ -24574,7 +25210,7 @@ async function runStartCommand(args, interactiveUi) {
24574
25210
  }
24575
25211
 
24576
25212
  // apps/body/dist/connect-command.js
24577
- import { spawn as spawn5 } from "node:child_process";
25213
+ import { spawn as spawn6 } from "node:child_process";
24578
25214
  import { createHash as createHash8 } from "node:crypto";
24579
25215
  import { chmod as chmod6, mkdir as mkdir16, readFile as readFile11, unlink as unlink2, writeFile as writeFile11 } from "node:fs/promises";
24580
25216
  import { dirname as dirname12, resolve as resolve23 } from "node:path";
@@ -24661,7 +25297,7 @@ async function verifyProviderKey(input) {
24661
25297
  }
24662
25298
 
24663
25299
  // apps/body/dist/pair-agent-selection.js
24664
- import { spawn as spawn3 } from "node:child_process";
25300
+ import { spawn as spawn4 } from "node:child_process";
24665
25301
  import { stdin as stdin2, stdout as stdout3 } from "node:process";
24666
25302
  var NO_AGENT_MESSAGE = `No supported ACP-capable coding agent was detected.
24667
25303
  Install one of these supported agents:
@@ -24689,7 +25325,7 @@ async function clackSelectAgent(candidates) {
24689
25325
  }
24690
25326
  async function installAdapter(install, opts) {
24691
25327
  await new Promise((resolveInstall, rejectInstall) => {
24692
- const child = spawn3(install.command, install.args, {
25328
+ const child = spawn4(install.command, install.args, {
24693
25329
  cwd: opts.cwd,
24694
25330
  env: opts.env ?? process.env,
24695
25331
  stdio: "inherit"
@@ -25284,7 +25920,7 @@ async function writeProviderEnv(selection, agentPubkey) {
25284
25920
  }
25285
25921
  async function runInstalledFinish(binary, grantPath) {
25286
25922
  await new Promise((resolveRun, rejectRun) => {
25287
- const child = spawn5(binary, ["connect-finish", grantPath], {
25923
+ const child = spawn6(binary, ["connect-finish", grantPath], {
25288
25924
  stdio: ["ignore", "pipe", "pipe"]
25289
25925
  });
25290
25926
  let diagnostic = "";
@@ -25457,7 +26093,7 @@ init_self_update_manifest();
25457
26093
 
25458
26094
  // apps/body/dist/managed-update.js
25459
26095
  init_self_update();
25460
- import { spawn as spawn6 } from "node:child_process";
26096
+ import { spawn as spawn7 } from "node:child_process";
25461
26097
  import { mkdir as mkdir18, rm as rm7, stat as stat3, writeFile as writeFile13 } from "node:fs/promises";
25462
26098
  import { dirname as dirname14, resolve as resolve25 } from "node:path";
25463
26099
 
@@ -25668,7 +26304,7 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
25668
26304
  libDir: resolve25(this.#layout.releasesRoot, this.#loadedRelease)
25669
26305
  }).catch(() => void 0) ?? {};
25670
26306
  const to = await readInstalledBundleIdentity(this.#layout).catch(() => void 0) ?? {};
25671
- const record2 = {
26307
+ const record3 = {
25672
26308
  version: 1,
25673
26309
  from,
25674
26310
  to,
@@ -25679,7 +26315,7 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
25679
26315
  status: "pending",
25680
26316
  ...this.#requiredProbeIds.length > 0 ? { requiredProbeIds: this.#requiredProbeIds, confirmedProbeIds: [] } : {}
25681
26317
  };
25682
- await writeUpdateAttempt(this.#layout, record2);
26318
+ await writeUpdateAttempt(this.#layout, record3);
25683
26319
  } else if (this.#requiredProbeIds.length > 0) {
25684
26320
  const requiredProbeIds = [
25685
26321
  .../* @__PURE__ */ new Set([...attempt.requiredProbeIds ?? [], ...this.#requiredProbeIds])
@@ -25871,7 +26507,7 @@ async function runManagedUpdateWorkerProcess() {
25871
26507
  if (!entrypoint)
25872
26508
  throw new Error("cannot resolve the current Beeline entrypoint");
25873
26509
  await new Promise((resolveWorker, rejectWorker) => {
25874
- const child = spawn6(process.execPath, [entrypoint, "managed-update-worker"], {
26510
+ const child = spawn7(process.execPath, [entrypoint, "managed-update-worker"], {
25875
26511
  detached: true,
25876
26512
  env: { ...process.env, BEELINE_INTERNAL_UPDATE_WORKER: "1" },
25877
26513
  stdio: ["ignore", "pipe", "pipe"]
@@ -26198,11 +26834,11 @@ async function readFailureRecord(runtimeDir) {
26198
26834
  return void 0;
26199
26835
  }
26200
26836
  }
26201
- async function writeFailureRecord(runtimeDir, record2) {
26837
+ async function writeFailureRecord(runtimeDir, record3) {
26202
26838
  const path = daemonFailurePath(runtimeDir);
26203
26839
  const staged = `${path}.${process.pid}.tmp`;
26204
26840
  await mkdir19(dirname15(path), { recursive: true, mode: 448 });
26205
- await writeFile14(staged, `${JSON.stringify(record2, null, 2)}
26841
+ await writeFile14(staged, `${JSON.stringify(record3, null, 2)}
26206
26842
  `, { mode: 384 });
26207
26843
  await rename6(staged, path);
26208
26844
  }
@@ -26210,13 +26846,13 @@ async function recordDaemonStartFailure(runtimeDir, error, now2 = Date.now()) {
26210
26846
  const prior = await readFailureRecord(runtimeDir);
26211
26847
  const failures = [...prior?.failures ?? [], now2].filter((failure) => now2 - failure <= DAEMON_FAILURE_WINDOW_MS);
26212
26848
  const distressed = failures.length >= DAEMON_FAILURE_LIMIT;
26213
- const record2 = {
26849
+ const record3 = {
26214
26850
  version: 1,
26215
26851
  failures,
26216
26852
  lastError: (error instanceof Error ? error.message : String(error)).slice(0, 1e3),
26217
26853
  ...distressed ? { distressedAt: now2 } : {}
26218
26854
  };
26219
- await writeFailureRecord(runtimeDir, record2);
26855
+ await writeFailureRecord(runtimeDir, record3);
26220
26856
  return { distressed, count: failures.length, path: daemonFailurePath(runtimeDir) };
26221
26857
  }
26222
26858
  async function clearDaemonStartFailures(runtimeDir) {
@@ -26503,7 +27139,7 @@ async function runUpdateFunctionalProbe(input) {
26503
27139
  }
26504
27140
 
26505
27141
  // apps/body/dist/current-release-probe.js
26506
- import { spawn as spawn7 } from "node:child_process";
27142
+ import { spawn as spawn8 } from "node:child_process";
26507
27143
  import { dirname as dirname16, join as join10 } from "node:path";
26508
27144
  init_self_update();
26509
27145
  var CURRENT_RELEASE_PROBE_TIMEOUT_MS = 12e4;
@@ -26554,7 +27190,7 @@ async function probeReleaseInSubprocess(input) {
26554
27190
  }
26555
27191
  const timeoutMs = input.timeoutMs ?? CURRENT_RELEASE_PROBE_TIMEOUT_MS;
26556
27192
  return new Promise((resolve31) => {
26557
- const child = spawn7(input.execPath ?? process.execPath, [entrypoint, UPDATE_PROBE_COMMAND, "--config", input.runtimeConfigPath], { env: input.env ?? process.env, stdio: ["ignore", "pipe", "pipe"] });
27193
+ const child = spawn8(input.execPath ?? process.execPath, [entrypoint, UPDATE_PROBE_COMMAND, "--config", input.runtimeConfigPath], { env: input.env ?? process.env, stdio: ["ignore", "pipe", "pipe"] });
26558
27194
  let stdout6 = "";
26559
27195
  let stderr = "";
26560
27196
  let settled = false;
@@ -26899,6 +27535,7 @@ async function runStoredDaemon(pathOrPointer) {
26899
27535
  const scratchSweepTimer = setInterval(() => runScratchSweepLogged(runtimeDir), SCRATCH_SWEEP_INTERVAL_MS);
26900
27536
  scratchSweepTimer.unref();
26901
27537
  let ready = false;
27538
+ let connectorLoop;
26902
27539
  let stoppingStatus = "daemon stopped";
26903
27540
  try {
26904
27541
  const core = new ThinDaemonCore(runtime, configPath, config, { daemonApi });
@@ -26971,6 +27608,12 @@ async function runStoredDaemon(pathOrPointer) {
26971
27608
  ...runtime.modelSelection ? { runtimeSelection: runtime.modelSelection } : {},
26972
27609
  ...config.modelUnavailable ? { startupUnavailable: config.modelUnavailable.unavailable.label } : {}
26973
27610
  });
27611
+ connectorLoop ??= new ConnectorAssignmentLoop({
27612
+ api: daemonApi,
27613
+ agentId: runtime.agent.publicKey,
27614
+ log: (message) => console.log(`[body] connector: ${message}`)
27615
+ });
27616
+ connectorLoop.start();
26974
27617
  },
26975
27618
  onProgress: async (status) => {
26976
27619
  void drainRollbackAlert(core.activeRoomIds()[0] ?? runtime.rooms[0]?.channelId);