usebeeline 0.0.104 → 0.0.106

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 CHANGED
@@ -141,7 +141,7 @@ Two MCP surfaces are mounted into every agent session.
141
141
  | ------------------------------------------------------ | --------------- | ------------------------------------------------------------- |
142
142
  | `open_corner` | Top-level Rooms | Open one write-enabled corner with a ≤24-word objective |
143
143
  | `pr_checks_status` | Corners | Read checks, human hold, and PR/head-bound merge approval |
144
- | `attach_file` | Everywhere | Attach one file from the checkout or scratch dir to the reply |
144
+ | `post_artifact` | Everywhere | Upload one file (path or html/bytes) as an attachment |
145
145
  | `create_schedule`, `list_schedules`, `delete_schedule` | Everywhere | Run a prompt again later — interval minutes or a 5-field cron |
146
146
  | `request_grant` | Everywhere | Ask the owner for reach outside the sandbox |
147
147
  | `run_granted_command` | Everywhere | Run a command an approved grant covers, outside the sandbox |
@@ -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 spawn6 } 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 = spawn6(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 = spawn6("tar", ["-tzf", tempArchive], { stdio: ["ignore", "pipe", "inherit"] });
4214
4214
  let out = "";
4215
4215
  child.stdout?.on("data", (chunk) => {
4216
4216
  out += chunk.toString("utf8");
@@ -6898,14 +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
- const stripped = text2.replace(/\r?\n$/, "");
6905
- return stripped || text2;
6906
- }
6907
- function normalizeStreamDelta(text2, agentLabel) {
6908
- return agentLabel && PI_ACP_HARNESS.test(agentLabel) ? withoutOneTrailingLineEnding(text2) : text2;
6901
+ function normalizeStreamDelta(text2, _agentLabel) {
6902
+ return text2;
6909
6903
  }
6910
6904
  function agentMessageRuns(updates, agentLabel) {
6911
6905
  const runs = [];
@@ -8162,6 +8156,534 @@ async function syncAgentModelCatalog(input) {
8162
8156
  }
8163
8157
  }
8164
8158
 
8159
+ // apps/body/dist/connector-squire.js
8160
+ import { execFile, spawn as spawn2 } from "node:child_process";
8161
+ var REMOTE_LOGIN_BINARIES = ["xvfb-run", "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 CONNECT_TIMEOUT_MS = 3e5;
8174
+ var defaultStreamedRunner = (command, args) => new Promise((resolve31) => {
8175
+ const child = spawn2(command, args, {
8176
+ stdio: ["ignore", "pipe", "pipe"]
8177
+ });
8178
+ let stdout6 = "";
8179
+ let stderr = "";
8180
+ let resolved = false;
8181
+ const safetyTimer = setTimeout(() => {
8182
+ if (!resolved) {
8183
+ resolved = true;
8184
+ resolve31({ stdout: stdout6, stderr, signIn: void 0, abort: () => {
8185
+ } });
8186
+ }
8187
+ child.kill();
8188
+ }, CONNECT_TIMEOUT_MS);
8189
+ const abort = () => {
8190
+ clearTimeout(safetyTimer);
8191
+ child.kill();
8192
+ };
8193
+ const finish = (result) => {
8194
+ if (!resolved) {
8195
+ resolved = true;
8196
+ clearTimeout(safetyTimer);
8197
+ resolve31(result);
8198
+ }
8199
+ };
8200
+ const checkOutput = () => {
8201
+ const combined = `${stdout6}
8202
+ ${stderr}`;
8203
+ const signIn = parseConnectOutput(combined);
8204
+ if (signIn) {
8205
+ finish({ stdout: stdout6, stderr, signIn, abort });
8206
+ }
8207
+ };
8208
+ child.stdout?.on("data", (chunk) => {
8209
+ stdout6 += String(chunk);
8210
+ checkOutput();
8211
+ });
8212
+ child.stderr?.on("data", (chunk) => {
8213
+ stderr += String(chunk);
8214
+ checkOutput();
8215
+ });
8216
+ child.on("close", () => {
8217
+ finish({ stdout: stdout6, stderr, signIn: void 0, abort: () => {
8218
+ } });
8219
+ });
8220
+ child.on("error", () => {
8221
+ finish({ stdout: stdout6, stderr, signIn: void 0, abort: () => {
8222
+ } });
8223
+ });
8224
+ });
8225
+ var step = (label, status, reason) => ({
8226
+ label,
8227
+ status,
8228
+ ...reason ? { reason } : {}
8229
+ });
8230
+ function binaryExists(binary) {
8231
+ return new Promise((resolve31) => {
8232
+ execFile("sh", ["-c", `command -v ${JSON.stringify(binary)}`], (error, stdout6) => {
8233
+ const path = String(stdout6 ?? "").trim();
8234
+ resolve31({ binary, found: !error && path.length > 0, ...path ? { path } : {} });
8235
+ });
8236
+ });
8237
+ }
8238
+ async function checkRemoteLoginPrerequisites(probe = binaryExists) {
8239
+ return Promise.all(REMOTE_LOGIN_BINARIES.map(probe));
8240
+ }
8241
+ function missingPrerequisiteStep(checks) {
8242
+ const missing = checks.filter((check) => !check.found).map((check) => check.binary);
8243
+ return step("remote sign-in prerequisites", "failed", `missing on this helper: ${missing.join(", ")}`);
8244
+ }
8245
+ function parseConnectOutput(output) {
8246
+ const url = output.match(/https:\/\/[^\s"'<>]+/)?.[0];
8247
+ if (!url)
8248
+ return void 0;
8249
+ if (/oauth|authorize/i.test(url) || /oauth/i.test(output)) {
8250
+ return { method: "oauth", url };
8251
+ }
8252
+ if (/novnc|vnc\.html|remote|stream/i.test(url) || /novnc|remote login|vnc/i.test(output)) {
8253
+ return { method: "streamed-page", url };
8254
+ }
8255
+ return { method: "streamed-page", url };
8256
+ }
8257
+ async function installedSquireVersion(run2) {
8258
+ const probe = await run2("npx", ["-y", SQUIRE_CONNECT_PACKAGE, "--version"]);
8259
+ const version = probe.stdout.match(/\d+\.\d+\.\d+[^\s]*/)?.[0];
8260
+ return version;
8261
+ }
8262
+ function parseSignedInAs(output) {
8263
+ return output.match(/signed in as ([^\s,;]+)/i)?.[1];
8264
+ }
8265
+ async function installSquire(options) {
8266
+ const run2 = options.run ?? defaultShellRunner;
8267
+ const streamRun = options.streamRun ?? defaultStreamedRunner;
8268
+ const steps = [step("helper reached", "done")];
8269
+ const emit = () => options.onProgress?.([...steps]);
8270
+ const push = (next) => {
8271
+ steps.push(next);
8272
+ emit();
8273
+ };
8274
+ const fail = (reason) => {
8275
+ steps.push(step("waiting for sign-in", "pending"));
8276
+ emit();
8277
+ return { status: "error", steps, errorMessage: reason };
8278
+ };
8279
+ emit();
8280
+ const checks = await checkRemoteLoginPrerequisites(options.probeBinary);
8281
+ if (checks.some((check) => !check.found)) {
8282
+ push(missingPrerequisiteStep(checks));
8283
+ return fail("this helper cannot host the remote sign-in surface");
8284
+ }
8285
+ push(step("remote sign-in prerequisites", "done"));
8286
+ const install = await streamRun("xvfb-run", [
8287
+ "-a",
8288
+ "npx",
8289
+ "-y",
8290
+ SQUIRE_CONNECT_PACKAGE,
8291
+ "connect",
8292
+ "--force-relogin=google",
8293
+ "--target=codex"
8294
+ ]);
8295
+ if (!install.signIn) {
8296
+ const stderr = install.stderr.trim();
8297
+ push(step("trusty-squire installed", "failed", stderr || "connect printed no sign-in URL"));
8298
+ return fail(stderr || "the trusty-squire connect command printed no sign-in surface");
8299
+ }
8300
+ const version = await installedSquireVersion(run2);
8301
+ push(step(`trusty-squire${version ? ` ${version}` : ""} installed`, "done"));
8302
+ const signIn = install.signIn;
8303
+ const signedInAs = parseSignedInAs(`${install.stdout}
8304
+ ${install.stderr}`);
8305
+ push(step("waiting for sign-in", "done"));
8306
+ const pair = await pairSquire(options.mcp, options.workspaceId);
8307
+ if (!pair.ok) {
8308
+ push(step("paired to workspace", "failed", pair.reason));
8309
+ return {
8310
+ status: "installing",
8311
+ steps,
8312
+ signIn,
8313
+ ...version ? { squireVersion: version } : {},
8314
+ ...signedInAs ? { signedInAs } : {}
8315
+ };
8316
+ }
8317
+ push(step("paired to workspace", "done"));
8318
+ return {
8319
+ status: "connected",
8320
+ steps,
8321
+ signIn,
8322
+ ...version ? { squireVersion: version } : {},
8323
+ ...signedInAs ? { signedInAs } : {}
8324
+ };
8325
+ }
8326
+ async function pairSquire(mcp, _workspaceId) {
8327
+ if (!mcp)
8328
+ return { ok: false, reason: "the Squire MCP surface is not mounted on this helper" };
8329
+ try {
8330
+ await mcp.call("list_credentials", { fields: "summary" });
8331
+ return { ok: true };
8332
+ } catch (error) {
8333
+ return { ok: false, reason: error instanceof Error ? error.message : String(error) };
8334
+ }
8335
+ }
8336
+ function asRecord(value) {
8337
+ return value && typeof value === "object" ? value : {};
8338
+ }
8339
+ function asArray(value) {
8340
+ return Array.isArray(value) ? value : [];
8341
+ }
8342
+ function stringList(value) {
8343
+ return asArray(value).filter((entry) => typeof entry === "string");
8344
+ }
8345
+ function numberOrNull(value) {
8346
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
8347
+ }
8348
+ function vaultConnectionMeta(raw) {
8349
+ const record3 = asRecord(raw);
8350
+ const reference = String(record3.reference ?? record3.id ?? "");
8351
+ return {
8352
+ reference,
8353
+ service: typeof record3.service === "string" ? record3.service : null,
8354
+ label: String(record3.label ?? record3.service ?? reference),
8355
+ fieldNames: stringList(record3.field_names ?? record3.fieldNames),
8356
+ allowedHosts: stringList(record3.allowed_hosts ?? record3.allowedHosts ?? record3.login_hosts),
8357
+ createdAt: numberOrNull(record3.created_at ?? record3.createdAt) ?? 0,
8358
+ stale: record3.stale === true,
8359
+ state: record3.state === "error" ? "error" : "active"
8360
+ };
8361
+ }
8362
+ async function readVault(mcp) {
8363
+ const result = asRecord(await mcp.call("list_credentials"));
8364
+ return asArray(result.credentials ?? result.items ?? result).map(vaultConnectionMeta);
8365
+ }
8366
+ function connectionGrant(raw) {
8367
+ const record3 = asRecord(raw);
8368
+ return {
8369
+ grantId: String(record3.grant_id ?? record3.grantId ?? record3.id ?? ""),
8370
+ credentialRef: String(record3.credential_ref ?? record3.credentialRef ?? record3.reference ?? ""),
8371
+ createdAt: numberOrNull(record3.created_at ?? record3.createdAt) ?? 0,
8372
+ ...numberOrNull(record3.revoked_at ?? record3.revokedAt) !== null ? { revokedAt: numberOrNull(record3.revoked_at ?? record3.revokedAt) } : {},
8373
+ ...numberOrNull(record3.rate_limit_per_hour) !== null ? { rateLimitPerHour: numberOrNull(record3.rate_limit_per_hour) } : {},
8374
+ ...numberOrNull(record3.spend_cap_usd) !== null ? { spendCapUsd: numberOrNull(record3.spend_cap_usd) } : {}
8375
+ };
8376
+ }
8377
+ async function readGrants(mcp, ref) {
8378
+ const result = asRecord(await mcp.call("list_app_access", {}));
8379
+ return asArray(result.grants ?? result.items ?? result).map(connectionGrant).filter((grant) => grant.credentialRef === ref && grant.revokedAt === void 0);
8380
+ }
8381
+ async function revokeGrants(mcp, ref) {
8382
+ const grants = await readGrants(mcp, ref);
8383
+ let revoked = 0;
8384
+ let failed = 0;
8385
+ for (const grant of grants) {
8386
+ try {
8387
+ const result = asRecord(await mcp.call("revoke_app_access", { grant_id: grant.grantId }));
8388
+ if (result.revoked === false)
8389
+ failed += 1;
8390
+ else
8391
+ revoked += 1;
8392
+ } catch {
8393
+ failed += 1;
8394
+ }
8395
+ }
8396
+ return { revoked, failed };
8397
+ }
8398
+
8399
+ // apps/body/dist/squire-mcp-client.js
8400
+ import { spawn as spawn3 } from "node:child_process";
8401
+ var INITIALIZE_TIMEOUT_MS = 3e4;
8402
+ var CALL_TIMEOUT_MS = 12e4;
8403
+ var StdioSquireMcpClient = class {
8404
+ options;
8405
+ child;
8406
+ nextId = 1;
8407
+ pending = /* @__PURE__ */ new Map();
8408
+ buffer = "";
8409
+ initialized;
8410
+ closed = false;
8411
+ log;
8412
+ constructor(options = {}) {
8413
+ this.options = options;
8414
+ this.log = options.log ?? (() => {
8415
+ });
8416
+ }
8417
+ /** One Squire MCP tool call; resolves with the tool's parsed result. */
8418
+ async call(tool, args = {}) {
8419
+ await this.ensureSession();
8420
+ const response = await this.request("tools/call", { name: tool, arguments: args });
8421
+ if (response.isError) {
8422
+ const text3 = response.content?.find((entry) => entry.type === "text")?.text ?? "tool error";
8423
+ throw new Error(`${tool} failed: ${text3}`);
8424
+ }
8425
+ const text2 = response.content?.find((entry) => entry.type === "text")?.text;
8426
+ if (typeof text2 !== "string")
8427
+ return response;
8428
+ try {
8429
+ return JSON.parse(text2);
8430
+ } catch {
8431
+ return response;
8432
+ }
8433
+ }
8434
+ /** Tear the session down; safe to call repeatedly. */
8435
+ close() {
8436
+ this.closed = true;
8437
+ this.initialized = void 0;
8438
+ for (const entry of this.pending.values()) {
8439
+ clearTimeout(entry.timer);
8440
+ entry.reject(new Error("Squire MCP session closed"));
8441
+ }
8442
+ this.pending.clear();
8443
+ this.child?.kill();
8444
+ this.child = void 0;
8445
+ }
8446
+ ensureSession() {
8447
+ if (this.closed)
8448
+ throw new Error("Squire MCP client is closed");
8449
+ this.initialized ??= this.initialize().catch((error) => {
8450
+ this.initialized = void 0;
8451
+ throw error;
8452
+ });
8453
+ return this.initialized;
8454
+ }
8455
+ initialize() {
8456
+ const child = (this.options.spawn ?? spawn3)(this.options.command ?? "npx", [
8457
+ ...this.options.args ?? ["-y", "@trusty-squire/mcp"]
8458
+ ]);
8459
+ this.child = child;
8460
+ this.buffer = "";
8461
+ child.stdout.setEncoding("utf8");
8462
+ child.stderr.setEncoding("utf8");
8463
+ child.stdout.on("data", (chunk) => this.onData(chunk));
8464
+ child.stderr.on("data", (chunk) => this.log(`squire mcp stderr: ${chunk.trim()}`));
8465
+ child.on("exit", (code) => {
8466
+ this.log(`squire mcp exited (${String(code)})`);
8467
+ this.initialized = void 0;
8468
+ this.child = void 0;
8469
+ for (const entry of this.pending.values()) {
8470
+ clearTimeout(entry.timer);
8471
+ entry.reject(new Error("Squire MCP server exited"));
8472
+ }
8473
+ this.pending.clear();
8474
+ });
8475
+ return this.request("initialize", {
8476
+ protocolVersion: "2024-11-05",
8477
+ capabilities: {},
8478
+ clientInfo: { name: "beeline-helper", version: "1.0.0" }
8479
+ }).then(() => {
8480
+ this.notify("notifications/initialized");
8481
+ });
8482
+ }
8483
+ notify(method, params) {
8484
+ this.child?.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, ...params ? { params } : {} })}
8485
+ `);
8486
+ }
8487
+ request(method, params) {
8488
+ const id = this.nextId++;
8489
+ const child = this.child;
8490
+ if (!child)
8491
+ return Promise.reject(new Error("Squire MCP session is not running"));
8492
+ return new Promise((resolve31, reject) => {
8493
+ const timer = setTimeout(() => {
8494
+ this.pending.delete(id);
8495
+ reject(new Error(`${method} timed out`));
8496
+ }, method === "initialize" ? INITIALIZE_TIMEOUT_MS : CALL_TIMEOUT_MS);
8497
+ this.pending.set(id, { resolve: resolve31, reject, timer });
8498
+ child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}
8499
+ `);
8500
+ });
8501
+ }
8502
+ onData(chunk) {
8503
+ this.buffer += chunk;
8504
+ for (; ; ) {
8505
+ const newline = this.buffer.indexOf("\n");
8506
+ if (newline < 0)
8507
+ return;
8508
+ const line = this.buffer.slice(0, newline).trim();
8509
+ this.buffer = this.buffer.slice(newline + 1);
8510
+ if (!line)
8511
+ continue;
8512
+ let message;
8513
+ try {
8514
+ message = JSON.parse(line);
8515
+ } catch {
8516
+ continue;
8517
+ }
8518
+ const id = typeof message.id === "number" ? message.id : void 0;
8519
+ if (id === void 0)
8520
+ continue;
8521
+ const entry = this.pending.get(id);
8522
+ if (!entry)
8523
+ continue;
8524
+ this.pending.delete(id);
8525
+ clearTimeout(entry.timer);
8526
+ if (message.error) {
8527
+ const error = message.error;
8528
+ entry.reject(new Error(error.message ?? "Squire MCP error"));
8529
+ } else {
8530
+ entry.resolve(message.result);
8531
+ }
8532
+ }
8533
+ }
8534
+ };
8535
+ function defaultSquireMcpClient() {
8536
+ return new StdioSquireMcpClient();
8537
+ }
8538
+
8539
+ // apps/body/dist/connector-assignments.js
8540
+ var CONNECTOR_POLL_INTERVAL_MS = 1e4;
8541
+ var ConnectorAssignmentLoop = class {
8542
+ agentId;
8543
+ api;
8544
+ intervalMs;
8545
+ log;
8546
+ install;
8547
+ readVaultFn;
8548
+ revokeGrantsFn;
8549
+ schedule;
8550
+ cancel;
8551
+ timer;
8552
+ started = false;
8553
+ stopped = false;
8554
+ /** One install at a time per connector; other polls skip it. */
8555
+ inFlight = /* @__PURE__ */ new Set();
8556
+ mcp;
8557
+ constructor(options) {
8558
+ this.agentId = options.agentId;
8559
+ this.api = options.api;
8560
+ this.intervalMs = options.intervalMs ?? CONNECTOR_POLL_INTERVAL_MS;
8561
+ this.log = options.log ?? (() => {
8562
+ });
8563
+ this.install = options.install ?? installSquire;
8564
+ this.readVaultFn = options.readVault ?? readVault;
8565
+ this.revokeGrantsFn = options.revokeGrants ?? revokeGrants;
8566
+ this.schedule = options.schedule ?? ((fn, ms) => {
8567
+ const timer = setTimeout(fn, ms);
8568
+ timer.unref?.();
8569
+ return timer;
8570
+ });
8571
+ this.cancel = options.cancel ?? ((handle) => clearTimeout(handle));
8572
+ }
8573
+ start() {
8574
+ if (this.stopped || this.started)
8575
+ return;
8576
+ this.started = true;
8577
+ void this.runOnce();
8578
+ this.timer = this.schedule(() => this.poll(), this.intervalMs);
8579
+ }
8580
+ stop() {
8581
+ this.stopped = true;
8582
+ if (this.timer !== void 0) {
8583
+ this.cancel(this.timer);
8584
+ this.timer = void 0;
8585
+ }
8586
+ }
8587
+ /** One interval tick: poll, then re-arm. */
8588
+ poll() {
8589
+ if (this.stopped)
8590
+ return;
8591
+ void this.runOnce();
8592
+ this.timer = this.schedule(() => this.poll(), this.intervalMs);
8593
+ }
8594
+ /** Drain the queue once; every failure is logged, never raised. */
8595
+ async runOnce() {
8596
+ let assignments;
8597
+ try {
8598
+ const result = await this.api.execute("getConnectorAssignments", { agentId: this.agentId });
8599
+ assignments = result.assignments;
8600
+ } catch (error) {
8601
+ this.log(`connector assignments unavailable: ${describe(error)}`);
8602
+ return;
8603
+ }
8604
+ for (const assignment of assignments) {
8605
+ const key = `${assignment.kind}:${assignment.connectorId}`;
8606
+ if (assignment.kind === "uninstall")
8607
+ continue;
8608
+ if (this.inFlight.has(key))
8609
+ continue;
8610
+ this.inFlight.add(key);
8611
+ void this.handle(assignment).catch((error) => this.log(`connector assignment ${key} failed: ${describe(error)}`)).finally(() => this.inFlight.delete(key));
8612
+ }
8613
+ }
8614
+ squire() {
8615
+ this.mcp ??= defaultSquireMcpClient();
8616
+ return this.mcp;
8617
+ }
8618
+ async handle(assignment) {
8619
+ if (assignment.kind === "install")
8620
+ await this.runInstall(assignment.connectorId);
8621
+ else if (assignment.kind === "sync")
8622
+ await this.runSync();
8623
+ else if (assignment.kind === "revoke-grants")
8624
+ await this.runRevoke(assignment.connectorId, assignment.reference);
8625
+ }
8626
+ /** Install Trusty Squire, reporting every step as it settles. */
8627
+ async runInstall(connectorId) {
8628
+ const report = async (steps) => {
8629
+ try {
8630
+ await this.api.execute("postConnectorStatus", { agentId: this.agentId, connectorId, steps });
8631
+ } catch (error) {
8632
+ this.log(`step report failed: ${describe(error)}`);
8633
+ }
8634
+ };
8635
+ const result = await this.install({
8636
+ workspaceId: this.agentId,
8637
+ mcp: this.squire(),
8638
+ onProgress: report
8639
+ });
8640
+ if (result.status === "error") {
8641
+ await this.api.execute("postConnectorStatus", {
8642
+ agentId: this.agentId,
8643
+ connectorId,
8644
+ steps: result.steps,
8645
+ errorMessage: result.errorMessage
8646
+ });
8647
+ return;
8648
+ }
8649
+ if (result.status === "connected") {
8650
+ await this.api.execute("installConnector", {
8651
+ agentId: this.agentId,
8652
+ connectorId,
8653
+ ...result.squireVersion ? { squireVersion: result.squireVersion } : {},
8654
+ ...result.signedInAs ? { signedInAs: result.signedInAs } : {},
8655
+ ...result.signIn ? { signIn: result.signIn } : {}
8656
+ });
8657
+ await this.reportVault(connectorId);
8658
+ return;
8659
+ }
8660
+ await this.api.execute("postConnectorStatus", {
8661
+ agentId: this.agentId,
8662
+ connectorId,
8663
+ steps: result.steps,
8664
+ ...result.squireVersion ? { squireVersion: result.squireVersion } : {},
8665
+ ...result.signedInAs ? { signedInAs: result.signedInAs } : {},
8666
+ ...result.signIn ? { signIn: result.signIn } : {}
8667
+ });
8668
+ }
8669
+ /** One vault report covers every live trusty-squire connector on this helper. */
8670
+ async runSync() {
8671
+ await this.reportVault();
8672
+ }
8673
+ async runRevoke(connectorId, reference) {
8674
+ const outcome = await this.revokeGrantsFn(this.squire(), reference);
8675
+ this.log(`revoked ${outcome.revoked} grant(s) on ${reference}` + (outcome.failed ? `, ${outcome.failed} failed` : ""));
8676
+ void connectorId;
8677
+ }
8678
+ async reportVault(_connectorId) {
8679
+ const connections = await this.readVaultFn(this.squire());
8680
+ await this.api.execute("postConnectorVault", { agentId: this.agentId, connections });
8681
+ }
8682
+ };
8683
+ function describe(error) {
8684
+ return error instanceof Error ? error.message : String(error);
8685
+ }
8686
+
8165
8687
  // packages/api-contract/dist/daemon-operations.js
8166
8688
  function isAgentCommand(value) {
8167
8689
  if (!value || typeof value !== "object")
@@ -8185,7 +8707,7 @@ function isAgentCommand(value) {
8185
8707
  }
8186
8708
 
8187
8709
  // packages/api-contract/dist/artifacts.js
8188
- var ARTIFACT_MAXIMUM_BYTES = 2 * 1024 * 1024;
8710
+ var ARTIFACT_MAXIMUM_BYTES = 25 * 1024 * 1024;
8189
8711
 
8190
8712
  // packages/api-contract/dist/system-events.js
8191
8713
  var SERVER_EVENT_KINDS = [
@@ -8216,12 +8738,12 @@ var wrapper_default = import_websocket.default;
8216
8738
 
8217
8739
  // apps/body/dist/runtime.js
8218
8740
  import { randomBytes as randomBytes5 } from "node:crypto";
8219
- import { execFile } from "node:child_process";
8741
+ import { execFile as execFile2 } from "node:child_process";
8220
8742
  import { closeSync, openSync } from "node:fs";
8221
8743
  import { mkdir, readFile as readFile2, readdir, rename, stat, writeFile as writeFile2 } from "node:fs/promises";
8222
8744
  import { homedir as homedir2 } from "node:os";
8223
8745
  import { dirname as dirname2, resolve as resolve6 } from "node:path";
8224
- import { spawn as spawn2 } from "node:child_process";
8746
+ import { spawn as spawn4 } from "node:child_process";
8225
8747
  import { promisify } from "node:util";
8226
8748
 
8227
8749
  // node_modules/@noble/hashes/_u64.js
@@ -16344,13 +16866,13 @@ var NegentropyStorageVector = class {
16344
16866
  let count = last - first;
16345
16867
  while (count > 0) {
16346
16868
  let it = first;
16347
- let step = Math.floor(count / 2);
16348
- it += step;
16869
+ let step2 = Math.floor(count / 2);
16870
+ it += step2;
16349
16871
  if (cmp(arr[it])) {
16350
16872
  first = ++it;
16351
- count -= step + 1;
16873
+ count -= step2 + 1;
16352
16874
  } else {
16353
- count = step;
16875
+ count = step2;
16354
16876
  }
16355
16877
  }
16356
16878
  return first;
@@ -16752,7 +17274,7 @@ function decodeNsec(nsec) {
16752
17274
  }
16753
17275
 
16754
17276
  // apps/body/dist/runtime.js
16755
- var execFileAsync = promisify(execFile);
17277
+ var execFileAsync = promisify(execFile2);
16756
17278
  var DEFAULT_AGENT_IDENTITY_NAME = "beeline-agent";
16757
17279
  var DEFAULT_BODY_IDENTITY_NAME = "beeline-body";
16758
17280
  var DEFAULT_DAEMON_MONOLITH_BASE_URL = "https://server.usebeeline.app";
@@ -16942,7 +17464,7 @@ async function launchRuntimeDaemon(configPath, opts = {}) {
16942
17464
  const entrypoint = opts.entrypoint ?? process.argv[1];
16943
17465
  if (!entrypoint)
16944
17466
  throw new Error("cannot resolve daemon CLI entrypoint");
16945
- const child = spawn2(process.execPath, [...opts.execArgv ?? [], entrypoint, "daemon", "--config", resolve6(configPath)], {
17467
+ const child = spawn4(process.execPath, [...opts.execArgv ?? [], entrypoint, "daemon", "--config", resolve6(configPath)], {
16946
17468
  cwd: directory,
16947
17469
  env: opts.env ?? process.env,
16948
17470
  detached: !foreground,
@@ -17213,7 +17735,7 @@ async function activateDaemonTransport(path, fetchImpl = fetch) {
17213
17735
  }
17214
17736
 
17215
17737
  // apps/body/dist/room-runtime.js
17216
- import { execFile as execFile5 } from "node:child_process";
17738
+ import { execFile as execFile6 } from "node:child_process";
17217
17739
  import { createHash as createHash6 } from "node:crypto";
17218
17740
  import { existsSync as existsSync4, mkdirSync } from "node:fs";
17219
17741
  import { mkdir as mkdir13, rm as rm5 } from "node:fs/promises";
@@ -17221,7 +17743,7 @@ import { dirname as dirname8, resolve as resolve20 } from "node:path";
17221
17743
  import { promisify as promisify4 } from "node:util";
17222
17744
 
17223
17745
  // apps/body/dist/grant-runner.js
17224
- import { execFile as execFile2 } from "node:child_process";
17746
+ import { execFile as execFile3 } from "node:child_process";
17225
17747
  import { createHash as createHash2, randomBytes as randomBytes6 } from "node:crypto";
17226
17748
  import { readFile as readFile4 } from "node:fs/promises";
17227
17749
  import { createServer } from "node:http";
@@ -17880,9 +18402,9 @@ var GrantCommandRunner = class {
17880
18402
  ...Object.fromEntries(secrets)
17881
18403
  };
17882
18404
  const cap = this.options.outputCapBytes ?? GRANT_COMMAND_OUTPUT_CAP_BYTES;
17883
- const spawn8 = surfaceAllows(policy.surface, "run-host-command") ? { command: argv[0], args: argv.slice(1) } : roomSandboxCommand(policy, room.cwd, argv);
18405
+ const spawn10 = surfaceAllows(policy.surface, "run-host-command") ? { command: argv[0], args: argv.slice(1) } : roomSandboxCommand(policy, room.cwd, argv);
17884
18406
  const outcome = await new Promise((resolveRun) => {
17885
- const child = execFile2(spawn8.command, spawn8.args, {
18407
+ const child = execFile3(spawn10.command, spawn10.args, {
17886
18408
  cwd: room.cwd,
17887
18409
  env,
17888
18410
  timeout: this.options.timeoutMs ?? GRANT_COMMAND_TIMEOUT_MS,
@@ -18318,7 +18840,7 @@ async function runServerCommandIntake(options) {
18318
18840
  }
18319
18841
 
18320
18842
  // apps/body/dist/monolith-corner-turn.js
18321
- import { execFile as execFile4 } from "node:child_process";
18843
+ import { execFile as execFile5 } from "node:child_process";
18322
18844
  import { createHash as createHash5 } from "node:crypto";
18323
18845
  import { mkdir as mkdir12 } from "node:fs/promises";
18324
18846
  import { homedir as homedir7 } from "node:os";
@@ -18352,6 +18874,9 @@ function isAgentPairingCode(value) {
18352
18874
  // apps/body/dist/beeline-skill.js
18353
18875
  var USING_BEELINE_SKILL_NAME = "using-beeline";
18354
18876
  var BEELINE_REVIEW_SKILL_NAME = "beeline-review";
18877
+ function isConfiguredReviewer(agentHandle, reviewerHandle) {
18878
+ return Boolean(agentHandle && reviewerHandle && agentHandle.replace(/^@/, "") === reviewerHandle.replace(/^@/, ""));
18879
+ }
18355
18880
  var BEELINE_ROOM_CAPABILITIES = [
18356
18881
  "The repository filesystem is read-only in this Room session.",
18357
18882
  "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.",
@@ -18359,7 +18884,7 @@ var BEELINE_ROOM_CAPABILITIES = [
18359
18884
  "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.",
18360
18885
  "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.",
18361
18886
  "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.",
18362
- "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.",
18887
+ "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.",
18363
18888
  "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.",
18364
18889
  `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.`,
18365
18890
  "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.",
@@ -18375,7 +18900,7 @@ var BEELINE_DM_CAPABILITIES = [
18375
18900
  "The repository filesystem is read-only in this session.",
18376
18901
  "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.",
18377
18902
  "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.",
18378
- "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.",
18903
+ "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.",
18379
18904
  "Tag the person only when you need a decision or input, or when the task they asked for is finished.",
18380
18905
  "Never claim an action or reply happened unless the prompt or a tool result proves it."
18381
18906
  ].join(" ");
@@ -18417,6 +18942,8 @@ description: How to answer inside a Beeline Room.
18417
18942
 
18418
18943
  You are answering inside a Room whose filesystem is read-only. ${BEELINE_ROOM_CAPABILITIES}
18419
18944
 
18945
+ 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.
18946
+
18420
18947
  ## Tools and the Workbench
18421
18948
 
18422
18949
  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.
@@ -18507,7 +19034,7 @@ Then take exactly one action:
18507
19034
 
18508
19035
  - FAIL: reply \`@author\` with the confirmed findings to fix.
18509
19036
  - PASS: call \`approve_merge\` with the reviewed head SHA, then reply \`@author approved <reviewed sha>, merge\`.
18510
- - Never merge the pull request yourself.
19037
+ - Approving is your last step as reviewer. The author merges it; you never do, and nothing merges it automatically.
18511
19038
  `;
18512
19039
  }
18513
19040
 
@@ -19177,7 +19704,7 @@ async function prepareRoomAgentHome(input) {
19177
19704
  await symlink(source, target).catch(() => void 0);
19178
19705
  }
19179
19706
  const prior = agentHomeProvisionQueues.get(root) ?? Promise.resolve();
19180
- const provision = prior.catch(() => void 0).then(() => provisionAgentSkillsAndMcp(root, operatorHome, input.skillReleaseId ?? runningBeelineReleaseId(), input.failClosed ?? false, input.sharedSkills ?? [], agentSkillDir(input.agentKind), input.openRouterRouting));
19707
+ 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));
19181
19708
  agentHomeProvisionQueues.set(root, provision);
19182
19709
  try {
19183
19710
  await provision;
@@ -19187,10 +19714,10 @@ async function prepareRoomAgentHome(input) {
19187
19714
  }
19188
19715
  return roomAgentHomeEnv(root);
19189
19716
  }
19190
- async function provisionAgentSkillsAndMcp(root, operatorHome, skillReleaseId, failClosed, sharedSkills, skillDir, openRouterRouting) {
19717
+ async function provisionAgentSkillsAndMcp(root, operatorHome, skillReleaseId, failClosed, sharedSkills, skillDir, openRouterRouting, isReviewer) {
19191
19718
  const managedSkills = [
19192
19719
  { name: USING_BEELINE_SKILL_NAME, content: usingBeelineSkillMarkdown(skillReleaseId) },
19193
- { name: BEELINE_REVIEW_SKILL_NAME, content: beelineReviewSkillMarkdown(skillReleaseId) }
19720
+ ...isReviewer ? [{ name: BEELINE_REVIEW_SKILL_NAME, content: beelineReviewSkillMarkdown(skillReleaseId) }] : []
19194
19721
  ];
19195
19722
  const shared = await resolveSharedSkillSources(operatorHome, sharedSkills);
19196
19723
  await provisionManagedSkillsDir(resolve13(root, skillDir, "skills"), managedSkills, shared, sharedSkills.length === 0);
@@ -20245,9 +20772,9 @@ export default async function (pi) {
20245
20772
  `;
20246
20773
 
20247
20774
  // apps/body/dist/corner-branch-sync.js
20248
- import { execFile as execFile3 } from "node:child_process";
20775
+ import { execFile as execFile4 } from "node:child_process";
20249
20776
  import { promisify as promisify2 } from "node:util";
20250
- var execFileAsync2 = promisify2(execFile3);
20777
+ var execFileAsync2 = promisify2(execFile4);
20251
20778
  async function syncCornerBranch(input) {
20252
20779
  const git = input.git ?? (async (args) => (await execFileAsync2("git", ["-C", input.worktreePath, ...args], {
20253
20780
  ...input.env ? { env: input.env } : {},
@@ -20426,7 +20953,7 @@ function isMountedMcpToolPermissionRequest(request, mountedServers = ROOM_MOUNTE
20426
20953
  var AGENT_SURFACE_TOOL_NAMES = [
20427
20954
  "open_corner",
20428
20955
  "pr_checks_status",
20429
- "attach_file",
20956
+ "post_artifact",
20430
20957
  "write_scratch_file"
20431
20958
  ];
20432
20959
  var SQUIRE_TITLE_PREFIXES = [
@@ -21267,7 +21794,7 @@ async function seedWarmNodeModules(input) {
21267
21794
  for (const target of placed) {
21268
21795
  await rm4(target, { recursive: true, force: true }).catch(() => void 0);
21269
21796
  }
21270
- return { reason: "failed", key: plan.key, detail: describe(error) };
21797
+ return { reason: "failed", key: plan.key, detail: describe2(error) };
21271
21798
  } finally {
21272
21799
  await rm4(staging, { recursive: true, force: true }).catch(() => void 0);
21273
21800
  }
@@ -21310,7 +21837,7 @@ async function harvestWarmNodeModules(input) {
21310
21837
  } catch (error) {
21311
21838
  if (await pathExists(entry))
21312
21839
  return { reason: "already-warm", key: plan.key };
21313
- return { reason: "failed", key: plan.key, detail: describe(error) };
21840
+ return { reason: "failed", key: plan.key, detail: describe2(error) };
21314
21841
  } finally {
21315
21842
  await rm4(staging, { recursive: true, force: true }).catch(() => void 0);
21316
21843
  }
@@ -21453,7 +21980,7 @@ async function deviceOf(path) {
21453
21980
  async function isDirectory(path) {
21454
21981
  return stat2(path).then((info) => info.isDirectory(), () => false);
21455
21982
  }
21456
- function describe(error) {
21983
+ function describe2(error) {
21457
21984
  return error instanceof Error ? error.message : String(error);
21458
21985
  }
21459
21986
 
@@ -21731,10 +22258,13 @@ var MonolithRoomTurnLoop = class {
21731
22258
  });
21732
22259
  const directMessage = Array.isArray(repositoryState.directParticipants) && repositoryState.directParticipants.length === 2;
21733
22260
  await mkdir11(this.options.cwd, { recursive: true });
21734
- const selection = configuration.model || configuration.effort ? { model: configuration.model, effort: configuration.effort } : this.options.config.modelSelection;
22261
+ const selectionModel = configuration.model ?? this.options.config.modelSelection?.model;
22262
+ const selectionEffort = configuration.effort ?? this.options.config.modelSelection?.effort;
22263
+ const selection = selectionModel || selectionEffort ? { model: selectionModel, effort: selectionEffort } : void 0;
21735
22264
  const homeOverlay = this.options.config.agentHomeRoot ? await prepareRoomAgentHome({
21736
22265
  root: this.options.config.agentHomeRoot,
21737
22266
  sharedSkills: this.options.config.sharedSkills ?? [],
22267
+ isReviewer: isConfiguredReviewer(self?.handle, configuration.reviewerHandle),
21738
22268
  ...this.options.config.agentKind ? { agentKind: this.options.config.agentKind } : {},
21739
22269
  ...this.options.config.operatorHome ? { operatorHome: this.options.config.operatorHome } : {},
21740
22270
  ...openRouterRoutingInput(this.options.config, selection, this.options.fetchImpl, {
@@ -21957,6 +22487,7 @@ var MonolithRoomTurnLoop = class {
21957
22487
  this.busy = true;
21958
22488
  const trace = this.beginTurnTrace(item.id);
21959
22489
  let liveStream;
22490
+ let liveCornerOpened = false;
21960
22491
  try {
21961
22492
  if (!this.memberNames.has(item.authorId))
21962
22493
  await this.roster().catch(() => void 0);
@@ -22038,7 +22569,11 @@ ${JSON.stringify((corners.corners ?? []).filter((corner) => !corner.archived))}`
22038
22569
  result2 = await this.client.sessionPrompt(this.sessionId, nextPrompt, ROOM_PROMPT_INACTIVITY_TIMEOUT_MS, (delta, full) => {
22039
22570
  trace.firstModelOutput();
22040
22571
  stream.onChunk(delta, full);
22041
- }, void 0, (calls) => trace.toolCalls(calls));
22572
+ }, void 0, (calls) => {
22573
+ trace.toolCalls(calls);
22574
+ if (openedACorner(openCornerToolCall(calls)))
22575
+ liveCornerOpened = true;
22576
+ });
22042
22577
  } catch (error) {
22043
22578
  promptError = error;
22044
22579
  }
@@ -22132,6 +22667,22 @@ ${JSON.stringify((corners.corners ?? []).filter((corner) => !corner.archived))}`
22132
22667
  await trace.finish("cancelled");
22133
22668
  return;
22134
22669
  }
22670
+ if (liveCornerOpened && error instanceof AcpRequestTimeoutError && error.inactivity && error.method === "session/prompt") {
22671
+ console.log(`[thin-core] monolith Room ${this.options.roomId} turn ${item.id}: inactivity timeout after opening a corner; the work continues in the corner`);
22672
+ this.options.onCornerOpened?.();
22673
+ await liveStream?.retract().catch((retractError) => {
22674
+ console.error(`[thin-core] monolith Room ${this.options.roomId} draft retract failed:`, retractError);
22675
+ });
22676
+ await api.execute("postAgentTurnReceipt", {
22677
+ agentId: this.agent.publicKey,
22678
+ roomId: this.options.roomId,
22679
+ requestId: item.id,
22680
+ status: "complete",
22681
+ generationId: this.commandContext.generationId
22682
+ });
22683
+ await trace.finish("complete");
22684
+ return;
22685
+ }
22135
22686
  await liveStream?.retract().catch((retractError) => {
22136
22687
  console.error(`[thin-core] monolith Room ${this.options.roomId} draft retract failed:`, retractError);
22137
22688
  });
@@ -22201,7 +22752,7 @@ function roomMessagePrompt(author, body, attachments, delivered, harnessAcceptsI
22201
22752
  }
22202
22753
 
22203
22754
  // apps/body/dist/monolith-corner-turn.js
22204
- var execFileAsync3 = promisify3(execFile4);
22755
+ var execFileAsync3 = promisify3(execFile5);
22205
22756
  var TOOL_ARGUMENT_MAX_BYTES = 1200;
22206
22757
  var TOOL_OUTPUT_MAX_BYTES = 3200;
22207
22758
  var TOOL_PATH_LIMIT = 12;
@@ -22592,6 +23143,7 @@ var MonolithCornerTurnLoop = class {
22592
23143
  const homeOverlay = this.options.config.agentHomeRoot ? await prepareRoomAgentHome({
22593
23144
  root: this.options.config.agentHomeRoot,
22594
23145
  sharedSkills: this.options.config.sharedSkills ?? [],
23146
+ isReviewer: isConfiguredReviewer(self?.handle, configuration.reviewerHandle),
22595
23147
  ...this.options.config.agentKind ? { agentKind: this.options.config.agentKind } : {},
22596
23148
  ...this.options.config.operatorHome ? { operatorHome: this.options.config.operatorHome } : {},
22597
23149
  ...openRouterRoutingInput(this.options.config, selection, this.options.fetchImpl, {
@@ -22742,7 +23294,7 @@ var MonolithCornerTurnLoop = class {
22742
23294
  "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."
22743
23295
  ] : [
22744
23296
  "This is a chat-only corner with no repository or GitHub workflow.",
22745
- "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.",
23297
+ "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.",
22746
23298
  "Do not initialize a repository, create a branch, push, open a pull request, or wait for GitHub checks."
22747
23299
  ]
22748
23300
  ].filter(Boolean).join("\n\n")
@@ -23729,7 +24281,7 @@ async function removeCornerWorktreeAndBranches(worktree) {
23729
24281
  ]);
23730
24282
  }
23731
24283
  }
23732
- var execFileAsync4 = promisify4(execFile5);
24284
+ var execFileAsync4 = promisify4(execFile6);
23733
24285
  async function removeCornerScratchWorkspace(input) {
23734
24286
  const expected = resolve20(input.roomRoot, "scratch");
23735
24287
  if (resolve20(input.scratchPath) !== expected) {
@@ -24464,13 +25016,13 @@ var ThinDaemonCore = class {
24464
25016
  };
24465
25017
 
24466
25018
  // apps/body/dist/systemd.js
24467
- import { execFile as execFile6 } from "node:child_process";
25019
+ import { execFile as execFile7 } from "node:child_process";
24468
25020
  import { mkdir as mkdir14, readFile as readFile9, writeFile as writeFile9 } from "node:fs/promises";
24469
25021
  import { homedir as homedir8 } from "node:os";
24470
25022
  import { dirname as dirname9, resolve as resolve21 } from "node:path";
24471
25023
  import { setTimeout as sleep } from "node:timers/promises";
24472
25024
  import { promisify as promisify5 } from "node:util";
24473
- var execFileAsync5 = promisify5(execFile6);
25025
+ var execFileAsync5 = promisify5(execFile7);
24474
25026
  var DELIBERATE_REMOVAL_EXIT_STATUS = 78;
24475
25027
  var DAEMON_DISTRESS_EXIT_STATUS = 77;
24476
25028
  var UNKNOWN_AGENT_EXIT_STATUS = 79;
@@ -24709,9 +25261,10 @@ async function runStartCommand(args, interactiveUi) {
24709
25261
  }
24710
25262
 
24711
25263
  // apps/body/dist/connect-command.js
24712
- import { spawn as spawn5 } from "node:child_process";
24713
- import { createHash as createHash8 } from "node:crypto";
25264
+ import { spawn as spawn7 } from "node:child_process";
25265
+ import { createHash as createHash8, randomUUID as randomUUID5 } from "node:crypto";
24714
25266
  import { chmod as chmod6, mkdir as mkdir16, readFile as readFile11, unlink as unlink2, writeFile as writeFile11 } from "node:fs/promises";
25267
+ import { homedir as homedir10, hostname } from "node:os";
24715
25268
  import { dirname as dirname12, resolve as resolve23 } from "node:path";
24716
25269
  import { stdin as stdin3, stdout as stdout4 } from "node:process";
24717
25270
 
@@ -24796,7 +25349,7 @@ async function verifyProviderKey(input) {
24796
25349
  }
24797
25350
 
24798
25351
  // apps/body/dist/pair-agent-selection.js
24799
- import { spawn as spawn3 } from "node:child_process";
25352
+ import { spawn as spawn5 } from "node:child_process";
24800
25353
  import { stdin as stdin2, stdout as stdout3 } from "node:process";
24801
25354
  var NO_AGENT_MESSAGE = `No supported ACP-capable coding agent was detected.
24802
25355
  Install one of these supported agents:
@@ -24824,7 +25377,7 @@ async function clackSelectAgent(candidates) {
24824
25377
  }
24825
25378
  async function installAdapter(install, opts) {
24826
25379
  await new Promise((resolveInstall, rejectInstall) => {
24827
- const child = spawn3(install.command, install.args, {
25380
+ const child = spawn5(install.command, install.args, {
24828
25381
  cwd: opts.cwd,
24829
25382
  env: opts.env ?? process.env,
24830
25383
  stdio: "inherit"
@@ -25325,7 +25878,29 @@ function parseConnectSubscriptions(value) {
25325
25878
  ...new Set((value ?? "").split(",").map((entry) => entry.trim().toLowerCase()).filter(Boolean))
25326
25879
  ];
25327
25880
  }
25328
- function requestConnectGrant(baseUrl, pairingCode, selection, fetchImpl) {
25881
+ async function readMachineId(env = process.env) {
25882
+ const configDir = resolve23(env.XDG_CONFIG_HOME ?? resolve23(homedir10(), ".config"), "beeline");
25883
+ const machineIdPath = resolve23(configDir, "machine-id");
25884
+ let machineId;
25885
+ let machineName = hostname();
25886
+ try {
25887
+ const existing = await readFile11(machineIdPath, "utf8");
25888
+ machineId = existing.trim();
25889
+ if (!/^[0-9a-f-]{32,}$/.test(machineId))
25890
+ throw new Error("invalid persisted machine id");
25891
+ } catch {
25892
+ machineId = randomUUID5();
25893
+ try {
25894
+ await mkdir16(configDir, { recursive: true, mode: 448 });
25895
+ await writeFile11(machineIdPath, `${machineId}
25896
+ `, { mode: 384 });
25897
+ } catch {
25898
+ machineId = createHash8("sha256").update(machineName).digest("hex").slice(0, 36);
25899
+ }
25900
+ }
25901
+ return { machineId, machineName };
25902
+ }
25903
+ function requestConnectGrant(baseUrl, pairingCode, selection, fetchImpl, machineInfo) {
25329
25904
  const normalizedPairingCode = normalizeAgentPairingCode(pairingCode);
25330
25905
  if (!normalizedPairingCode)
25331
25906
  throw new Error("invalid pairing code");
@@ -25340,7 +25915,8 @@ function requestConnectGrant(baseUrl, pairingCode, selection, fetchImpl) {
25340
25915
  // This wizard always finishes the join itself (`finishConnectedAgentPairing`,
25341
25916
  // called once the rename prompt below settles), so the claim must not
25342
25917
  // join Rooms or announce yet.
25343
- defer_join: true
25918
+ defer_join: true,
25919
+ ...machineInfo ? { machine_id: machineInfo.machineId, machine_name: machineInfo.machineName } : {}
25344
25920
  }, fetchImpl);
25345
25921
  }
25346
25922
  async function renameConnectedAgent(baseUrl, pairingCode, name, fetchImpl) {
@@ -25419,7 +25995,7 @@ async function writeProviderEnv(selection, agentPubkey) {
25419
25995
  }
25420
25996
  async function runInstalledFinish(binary, grantPath) {
25421
25997
  await new Promise((resolveRun, rejectRun) => {
25422
- const child = spawn5(binary, ["connect-finish", grantPath], {
25998
+ const child = spawn7(binary, ["connect-finish", grantPath], {
25423
25999
  stdio: ["ignore", "pipe", "pipe"]
25424
26000
  });
25425
26001
  let diagnostic = "";
@@ -25470,7 +26046,8 @@ async function runConnectWizard(code, fetchImpl, eventSubscriptions, accessPolic
25470
26046
  });
25471
26047
  const selection = await collectConnectWizard(clackPrompts, loadConnectModelCatalog, fileConnectKeyStore, process.env, (input) => verifyProviderKey({ ...input, fetchImpl }));
25472
26048
  const baseUrl = (process.env.BEELINE_AUTH_URL ?? "https://server.usebeeline.app").replace(/\/$/, "");
25473
- const claimed = await brassSpinner("Connecting to your Beeline Workspace\u2026", () => requestConnectGrant(baseUrl, pairingCode, selection, fetchImpl), (connectedGrant) => `Connected to ${connectedGrant.workspace_name}`);
26049
+ const machineInfo = await readMachineId(process.env);
26050
+ const claimed = await brassSpinner("Connecting to your Beeline Workspace\u2026", () => requestConnectGrant(baseUrl, pairingCode, selection, fetchImpl, machineInfo), (connectedGrant) => `Connected to ${connectedGrant.workspace_name}`);
25474
26051
  const grant = { ...claimed, agent_name: await confirmSeededName(baseUrl, pairingCode, claimed, fetchImpl) };
25475
26052
  await finishConnectedAgentPairing(baseUrl, pairingCode, grant.workspace_joined, eventSubscriptions, fetchImpl);
25476
26053
  const installedRelease = await brassSpinner("Installing the Beeline daemon\u2026", () => installCurrentRelease(fetchImpl), (release) => `Installed Beeline helper ${release.version}`);
@@ -25592,7 +26169,7 @@ init_self_update_manifest();
25592
26169
 
25593
26170
  // apps/body/dist/managed-update.js
25594
26171
  init_self_update();
25595
- import { spawn as spawn6 } from "node:child_process";
26172
+ import { spawn as spawn8 } from "node:child_process";
25596
26173
  import { mkdir as mkdir18, rm as rm7, stat as stat3, writeFile as writeFile13 } from "node:fs/promises";
25597
26174
  import { dirname as dirname14, resolve as resolve25 } from "node:path";
25598
26175
 
@@ -26006,7 +26583,7 @@ async function runManagedUpdateWorkerProcess() {
26006
26583
  if (!entrypoint)
26007
26584
  throw new Error("cannot resolve the current Beeline entrypoint");
26008
26585
  await new Promise((resolveWorker, rejectWorker) => {
26009
- const child = spawn6(process.execPath, [entrypoint, "managed-update-worker"], {
26586
+ const child = spawn8(process.execPath, [entrypoint, "managed-update-worker"], {
26010
26587
  detached: true,
26011
26588
  env: { ...process.env, BEELINE_INTERNAL_UPDATE_WORKER: "1" },
26012
26589
  stdio: ["ignore", "pipe", "pipe"]
@@ -26360,7 +26937,7 @@ async function clearDaemonStartFailures(runtimeDir) {
26360
26937
 
26361
26938
  // apps/body/dist/update-functional-probe.js
26362
26939
  import { mkdir as mkdir20, rm as rm9 } from "node:fs/promises";
26363
- import { homedir as homedir10 } from "node:os";
26940
+ import { homedir as homedir11 } from "node:os";
26364
26941
  import { resolve as resolve27 } from "node:path";
26365
26942
  var UPDATE_PROBE_SESSION_TIMEOUT_MS = 1e4;
26366
26943
  var UPDATE_PROBE_SESSION_OPEN_TIMEOUT_MS = 2e4;
@@ -26466,7 +27043,7 @@ async function runUpdateFunctionalProbe(input) {
26466
27043
  ...input.config.agentEnv,
26467
27044
  ...await prepareRoomAgentHome({
26468
27045
  root: homeRoot,
26469
- operatorHome: input.config.operatorHome ?? homedir10(),
27046
+ operatorHome: input.config.operatorHome ?? homedir11(),
26470
27047
  sharedSkills: input.config.sharedSkills ?? [],
26471
27048
  ...input.config.agentKind ? { agentKind: input.config.agentKind } : {},
26472
27049
  skillReleaseId: input.releaseId,
@@ -26487,7 +27064,7 @@ async function runUpdateFunctionalProbe(input) {
26487
27064
  let turnCompleted = true;
26488
27065
  if (input.config.bwrapPath) {
26489
27066
  const { stateDirs, tmpDir } = harnessStateDirsFromEnv(agentEnv);
26490
- const operatorHome = input.config.operatorHome ?? homedir10();
27067
+ const operatorHome = input.config.operatorHome ?? homedir11();
26491
27068
  const homeStateDirs = harnessHomeStateDirs(command, agentEnv.HOME ?? operatorHome);
26492
27069
  await Promise.all(homeStateDirs.map((dir) => mkdir20(dir, { recursive: true })));
26493
27070
  spawnCommand = wrapAgentCommand({
@@ -26638,7 +27215,7 @@ async function runUpdateFunctionalProbe(input) {
26638
27215
  }
26639
27216
 
26640
27217
  // apps/body/dist/current-release-probe.js
26641
- import { spawn as spawn7 } from "node:child_process";
27218
+ import { spawn as spawn9 } from "node:child_process";
26642
27219
  import { dirname as dirname16, join as join10 } from "node:path";
26643
27220
  init_self_update();
26644
27221
  var CURRENT_RELEASE_PROBE_TIMEOUT_MS = 12e4;
@@ -26689,7 +27266,7 @@ async function probeReleaseInSubprocess(input) {
26689
27266
  }
26690
27267
  const timeoutMs = input.timeoutMs ?? CURRENT_RELEASE_PROBE_TIMEOUT_MS;
26691
27268
  return new Promise((resolve31) => {
26692
- const child = spawn7(input.execPath ?? process.execPath, [entrypoint, UPDATE_PROBE_COMMAND, "--config", input.runtimeConfigPath], { env: input.env ?? process.env, stdio: ["ignore", "pipe", "pipe"] });
27269
+ const child = spawn9(input.execPath ?? process.execPath, [entrypoint, UPDATE_PROBE_COMMAND, "--config", input.runtimeConfigPath], { env: input.env ?? process.env, stdio: ["ignore", "pipe", "pipe"] });
26693
27270
  let stdout6 = "";
26694
27271
  let stderr = "";
26695
27272
  let settled = false;
@@ -27034,6 +27611,7 @@ async function runStoredDaemon(pathOrPointer) {
27034
27611
  const scratchSweepTimer = setInterval(() => runScratchSweepLogged(runtimeDir), SCRATCH_SWEEP_INTERVAL_MS);
27035
27612
  scratchSweepTimer.unref();
27036
27613
  let ready = false;
27614
+ let connectorLoop;
27037
27615
  let stoppingStatus = "daemon stopped";
27038
27616
  try {
27039
27617
  const core = new ThinDaemonCore(runtime, configPath, config, { daemonApi });
@@ -27106,6 +27684,13 @@ async function runStoredDaemon(pathOrPointer) {
27106
27684
  ...runtime.modelSelection ? { runtimeSelection: runtime.modelSelection } : {},
27107
27685
  ...config.modelUnavailable ? { startupUnavailable: config.modelUnavailable.unavailable.label } : {}
27108
27686
  });
27687
+ void readMachineId(process.env).then(({ machineId, machineName }) => daemonApi.execute("postAgentMachineReport", { machineId, machineName }));
27688
+ connectorLoop ??= new ConnectorAssignmentLoop({
27689
+ api: daemonApi,
27690
+ agentId: runtime.agent.publicKey,
27691
+ log: (message) => console.log(`[body] connector: ${message}`)
27692
+ });
27693
+ connectorLoop.start();
27109
27694
  },
27110
27695
  onProgress: async (status) => {
27111
27696
  void drainRollbackAlert(core.activeRoomIds()[0] ?? runtime.rooms[0]?.channelId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usebeeline",
3
- "version": "0.0.104",
3
+ "version": "0.0.106",
4
4
  "description": "Connect an AI coding agent to Beeline with one command.",
5
5
  "homepage": "https://usebeeline.app",
6
6
  "bugs": {