usebeeline 0.0.92 → 0.0.94

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.
Files changed (2) hide show
  1. package/dist/usebeeline.mjs +273 -42
  2. package/package.json +1 -1
@@ -2413,7 +2413,7 @@ var require_websocket = __commonJS({
2413
2413
  var EventEmitter2 = __require("events");
2414
2414
  var https = __require("https");
2415
2415
  var http = __require("http");
2416
- var net = __require("net");
2416
+ var net2 = __require("net");
2417
2417
  var tls = __require("tls");
2418
2418
  var { randomBytes: randomBytes7, createHash: createHash9 } = __require("crypto");
2419
2419
  var { Duplex, Readable } = __require("stream");
@@ -3157,12 +3157,12 @@ var require_websocket = __commonJS({
3157
3157
  }
3158
3158
  function netConnect(options) {
3159
3159
  options.path = options.socketPath;
3160
- return net.connect(options);
3160
+ return net2.connect(options);
3161
3161
  }
3162
3162
  function tlsConnect(options) {
3163
3163
  options.path = void 0;
3164
3164
  if (!options.servername && options.servername !== "") {
3165
- options.servername = net.isIP(options.host) ? "" : options.host;
3165
+ options.servername = net2.isIP(options.host) ? "" : options.host;
3166
3166
  }
3167
3167
  return tls.connect(options);
3168
3168
  }
@@ -4654,6 +4654,19 @@ var init_self_update = __esm({
4654
4654
  }
4655
4655
  });
4656
4656
 
4657
+ // apps/body/dist/network-family-bootstrap.js
4658
+ import * as net from "node:net";
4659
+ var NETWORK_FAMILY_ATTEMPT_TIMEOUT_MS = 5e3;
4660
+ function configureNetworkFamilyDefaults(network = net) {
4661
+ if (typeof network.setDefaultAutoSelectFamilyAttemptTimeout === "function") {
4662
+ network.setDefaultAutoSelectFamilyAttemptTimeout(NETWORK_FAMILY_ATTEMPT_TIMEOUT_MS);
4663
+ return "timeout";
4664
+ }
4665
+ network.setDefaultAutoSelectFamily?.(false);
4666
+ return "autoselection-disabled";
4667
+ }
4668
+ configureNetworkFamilyDefaults();
4669
+
4657
4670
  // apps/body/dist/cli.js
4658
4671
  import { dirname as dirname17, resolve as resolve30 } from "node:path";
4659
4672
  import { readFile as readFile15, unlink as unlink5, writeFile as writeFile16 } from "node:fs/promises";
@@ -8010,7 +8023,24 @@ async function withAgentModelCatalog(agent, agentEnv, selection, inspect, limits
8010
8023
  }
8011
8024
  }
8012
8025
  async function fetchAgentModelCatalog(agent, agentEnv, selection, limits = {}) {
8013
- return withAgentModelCatalog(agent, agentEnv, selection, async ({ raw, catalog }) => ({ raw, catalog }), limits);
8026
+ return withAgentModelCatalog(agent, agentEnv, selection, async ({ client, sessionId, raw, catalog }) => ({
8027
+ raw,
8028
+ catalog: await filterModelChoicesByLiveValidation(client, sessionId, catalog)
8029
+ }), limits);
8030
+ }
8031
+ async function filterModelChoicesByLiveValidation(client, sessionId, catalog) {
8032
+ const modelAxis = catalog.find((axis) => axis.category === "model");
8033
+ if (!modelAxis)
8034
+ return catalog;
8035
+ const available = [];
8036
+ for (const choice of modelAxis.options) {
8037
+ try {
8038
+ await applyAgentModelSelection(client, sessionId, catalog, { model: choice.id });
8039
+ available.push(choice);
8040
+ } catch {
8041
+ }
8042
+ }
8043
+ return catalog.map((axis) => axis === modelAxis ? { ...axis, options: available } : axis);
8014
8044
  }
8015
8045
  async function validateAgentModelSelection(agent, agentEnv, selection) {
8016
8046
  return withAgentModelCatalog(agent, agentEnv, selection, async ({ client, sessionId, raw, catalog }) => {
@@ -8066,8 +8096,12 @@ import { readFile, writeFile } from "node:fs/promises";
8066
8096
  import { resolve as resolve5 } from "node:path";
8067
8097
  var MODEL_CATALOG_PROBE_TIMEOUT_MS = 3e4;
8068
8098
  var MODEL_CATALOG_HASH_FILE = "model-catalog.sha256";
8069
- function modelCatalogHash(options, selection) {
8070
- return createHash("sha256").update(JSON.stringify({ options, selection: selection ?? null })).digest("hex");
8099
+ function modelCatalogHash(options, selection, startupUnavailable) {
8100
+ return createHash("sha256").update(JSON.stringify({
8101
+ options,
8102
+ selection: selection ?? null,
8103
+ startupUnavailable: startupUnavailable ?? null
8104
+ })).digest("hex");
8071
8105
  }
8072
8106
  async function withTimeout(work, timeoutMs, what) {
8073
8107
  let timer;
@@ -8093,8 +8127,8 @@ async function syncAgentModelCatalog(input) {
8093
8127
  ...configuration.model ? { model: configuration.model } : {},
8094
8128
  ...configuration.effort ? { effort: configuration.effort } : {}
8095
8129
  } : input.runtimeSelection;
8096
- const { catalog } = await withTimeout(fetchCatalog(input.agent, input.agentEnv, selection), input.timeoutMs ?? MODEL_CATALOG_PROBE_TIMEOUT_MS, "model catalog probe");
8097
- const hash = modelCatalogHash(catalog, selection);
8130
+ const { catalog } = await withTimeout(fetchCatalog(input.agent, input.agentEnv, input.startupUnavailable ? void 0 : selection), input.timeoutMs ?? MODEL_CATALOG_PROBE_TIMEOUT_MS, "model catalog probe");
8131
+ const hash = modelCatalogHash(catalog, selection, input.startupUnavailable);
8098
8132
  const previous = await readFile(hashPath, "utf8").catch(() => "");
8099
8133
  if (previous.trim() === hash)
8100
8134
  return "unchanged";
@@ -8103,7 +8137,8 @@ async function syncAgentModelCatalog(input) {
8103
8137
  workspaceId: input.workspaceId,
8104
8138
  // `fetchAgentModelCatalog` already applied the category allow-list.
8105
8139
  options: catalog,
8106
- ...selection ? { selection } : {}
8140
+ ...selection ? { selection } : {},
8141
+ ...input.startupUnavailable ? { unavailable: input.startupUnavailable } : {}
8107
8142
  });
8108
8143
  await writeFile(hashPath, `${hash}
8109
8144
  `, { mode: 384 });
@@ -18201,6 +18236,7 @@ function isAgentPairingCode(value) {
18201
18236
 
18202
18237
  // apps/body/dist/beeline-skill.js
18203
18238
  var USING_BEELINE_SKILL_NAME = "using-beeline";
18239
+ var BEELINE_REVIEW_SKILL_NAME = "beeline-review";
18204
18240
  var BEELINE_ROOM_CAPABILITIES = [
18205
18241
  "The repository filesystem is read-only in this Room session.",
18206
18242
  "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.",
@@ -18265,6 +18301,86 @@ description: How to answer inside a Beeline Room.
18265
18301
  You are answering inside a Room whose filesystem is read-only. ${BEELINE_ROOM_CAPABILITIES}
18266
18302
  `;
18267
18303
  }
18304
+ function beelineReviewSkillMarkdown(releaseId) {
18305
+ return `---
18306
+ name: beeline-review
18307
+ description: Review a corner pull request against its objective and the Beeline merge gate.
18308
+ ---
18309
+
18310
+ <!-- beeline-release: ${releaseId} -->
18311
+
18312
+ # Beeline pull-request review
18313
+
18314
+ Follow these steps in order. Do not skip or reorder them.
18315
+
18316
+ ## 1. Isolate the revision
18317
+
18318
+ - Run \`gh pr view N --json headRefOid,files\` and record \`headRefOid\`.
18319
+ - Run \`gh pr diff N\`.
18320
+ - Check out that exact head in a new scratch git worktree. Never use the author's worktree.
18321
+ - Review and test only the recorded revision. If the head moves, start over.
18322
+
18323
+ ## 2. P0 - OBJECTIVE FULFILLED, DEMONSTRATED
18324
+
18325
+ - Quote the corner objective verbatim.
18326
+ - Derive its end-user story in one sentence: \`a user who does X sees Y\`.
18327
+ - Make Y happen against the built PR head: run the app or affected service and perform X.
18328
+ - If no interactive surface is reachable, run the narrowest test or script that exercises the exact user path and prints the observable Y.
18329
+ - Record the command and the observed Y.
18330
+ - A unit test of an inner function, a log line, \`the code looks right\`, or any other proxy does not count.
18331
+ - If the user-visible Y cannot be produced, FAIL now. Nothing below can rescue the review.
18332
+ - State whether the diff fulfills that objective and only that objective.
18333
+
18334
+ ## 3. Empirical pass second
18335
+
18336
+ - Run the repository typecheck and tests touched by the diff.
18337
+ - If the objective names a user path, exercise that path.
18338
+ - Record every command and exit code.
18339
+ - A review with no executed command is invalid and must FAIL.
18340
+
18341
+ ## 4. Adversarial pass
18342
+
18343
+ - For every changed function, name one concrete input or sequence that breaks it.
18344
+ - If none is found, write \`none found\` for that function.
18345
+
18346
+ ## 5. Verify before reporting
18347
+
18348
+ - Confirm every finding by reading the exact line or by running a command.
18349
+ - Put unconfirmed concerns under plausible findings. They never block.
18350
+
18351
+ ## 6. Bloat guard
18352
+
18353
+ - Compare net lines with the objective.
18354
+ - FAIL backwards-compatibility shims, dual paths, feature flags, or abstractions with one caller.
18355
+ - FAIL machinery the objective did not ask for.
18356
+
18357
+ ## 7. Security and data
18358
+
18359
+ - Check credentials, authorization boundaries, and destructive migrations.
18360
+
18361
+ ## 8. Gate and verdict
18362
+
18363
+ - The merge gate is open only when \`pr_checks_status\` reports checks=passed, held=false, approvalPending=false.
18364
+ - Green \`gh pr checks\` alone never opens the gate.
18365
+ - Always use this exact verdict shape:
18366
+
18367
+ \`objective quoted:\`
18368
+ \`user story:\`
18369
+ \`how Y was demonstrated (or FAIL):\`
18370
+ \`commands run + results:\`
18371
+ \`critical findings (block):\`
18372
+ \`plausible findings (do not block):\`
18373
+ \`net lines:\`
18374
+ \`decision: PASS|FAIL\`
18375
+
18376
+ Then take exactly one action:
18377
+
18378
+ - FAIL: reply \`@author\` with the confirmed findings; do not merge.
18379
+ - PASS with pending checks: reply \`approved pending checks <reviewed sha>\` and stop.
18380
+ - PASS with the gate open and your yolo on: run \`gh pr merge --squash --match-head-commit <reviewed sha> N\`.
18381
+ - PASS with the gate open and your yolo off: reply \`approved <reviewed sha>\` and stop.
18382
+ `;
18383
+ }
18268
18384
 
18269
18385
  // apps/body/dist/external-mcp-capabilities.js
18270
18386
  var SQUIRE_MCP_VERSION = "1.1.12";
@@ -18862,7 +18978,10 @@ var SHARED_CREDENTIALS = [
18862
18978
  { dir: "pi", source: ".pi/agent/auth.json", target: "auth.json" }
18863
18979
  ];
18864
18980
  var GOOSE_SHARED_CONFIG_FILES = ["config.yaml", "secrets.yaml"];
18865
- var BEELINE_DEFAULT_SKILL_NAMES = [USING_BEELINE_SKILL_NAME];
18981
+ var BEELINE_DEFAULT_SKILL_NAMES = [
18982
+ BEELINE_REVIEW_SKILL_NAME,
18983
+ USING_BEELINE_SKILL_NAME
18984
+ ];
18866
18985
  var OPERATOR_SKILL_SOURCE_DIRS = [
18867
18986
  ".agents/skills",
18868
18987
  ".claude/skills",
@@ -18941,7 +19060,8 @@ async function prepareRoomAgentHome(input) {
18941
19060
  }
18942
19061
  async function provisionAgentSkillsAndMcp(root, operatorHome, skillReleaseId, failClosed, sharedSkills, skillDir, openRouterRouting) {
18943
19062
  const managedSkills = [
18944
- { name: USING_BEELINE_SKILL_NAME, content: usingBeelineSkillMarkdown(skillReleaseId) }
19063
+ { name: USING_BEELINE_SKILL_NAME, content: usingBeelineSkillMarkdown(skillReleaseId) },
19064
+ { name: BEELINE_REVIEW_SKILL_NAME, content: beelineReviewSkillMarkdown(skillReleaseId) }
18945
19065
  ];
18946
19066
  const shared = await resolveSharedSkillSources(operatorHome, sharedSkills);
18947
19067
  await provisionManagedSkillsDir(resolve13(root, skillDir, "skills"), managedSkills, shared, sharedSkills.length === 0);
@@ -19617,6 +19737,12 @@ var AgentTurnStream = class {
19617
19737
  inFlight;
19618
19738
  /** Closed lanes publish nothing more, so the answer never queues behind a draft. */
19619
19739
  closed = false;
19740
+ /**
19741
+ * The retract this lane already sent. A settled turn that throws afterwards
19742
+ * reaches the same retract a second time, and one empty lane is the whole
19743
+ * point: asking twice would only be a second write saying what is already so.
19744
+ */
19745
+ retraction;
19620
19746
  constructor(options) {
19621
19747
  this.options = options;
19622
19748
  }
@@ -19676,14 +19802,45 @@ var AgentTurnStream = class {
19676
19802
  this.closed = true;
19677
19803
  this.pending = void 0;
19678
19804
  }
19805
+ /**
19806
+ * Dissolve the draft, publishing nothing.
19807
+ *
19808
+ * Every ending uses this: the settle below calls it once the durable reply is
19809
+ * on the wire, and a turn that THROWS calls it directly. A throw never
19810
+ * reaches a settle, and `close()` alone only stops future writes — the last
19811
+ * snapshot stays live on the page, so a turn the Room has already reported
19812
+ * stopped or failed keeps a half-written answer visibly in progress under it.
19813
+ */
19814
+ async retract() {
19815
+ this.close();
19816
+ this.retraction ??= (async () => {
19817
+ await this.inFlight;
19818
+ const { api, agentId, roomId, requestId, label } = this.options;
19819
+ await api.execute("retractAgentLiveOutput", {
19820
+ agentId,
19821
+ roomId,
19822
+ turnId: requestId,
19823
+ kind: "draft"
19824
+ }).catch((error) => console.error(`[thin-core] ${label} draft retract failed:`, error));
19825
+ })();
19826
+ await this.retraction;
19827
+ }
19679
19828
  /**
19680
19829
  * Post the durable reply under the turn's request id and dissolve the draft.
19681
19830
  * An empty reply settles through the turn receipt instead, and the lane is
19682
19831
  * retracted either way.
19832
+ *
19833
+ * The durable reply is this turn's answer and its last word. The draft lane
19834
+ * is presentation, so a refused retract is logged like a refused draft and
19835
+ * the turn still settles complete: raising here failed a turn that had
19836
+ * already answered, which posts a `failed` receipt and inscribes "<agent>
19837
+ * could not answer" UNDER the answer the reader is looking at. The reader
19838
+ * loses nothing by it either — the phone ends a retracted draft on the
19839
+ * turn's own complete receipt (`visibleLiveOverlays`).
19683
19840
  */
19684
19841
  async settle(reply, fields = {}, onReplyPosted) {
19685
19842
  this.close();
19686
- const { api, agentId, roomId, requestId } = this.options;
19843
+ const { api, roomId, requestId } = this.options;
19687
19844
  if (reply) {
19688
19845
  const posted = await api.execute("postRoomMessage", {
19689
19846
  roomId,
@@ -19694,13 +19851,7 @@ var AgentTurnStream = class {
19694
19851
  });
19695
19852
  onReplyPosted?.(posted);
19696
19853
  }
19697
- await this.inFlight;
19698
- await api.execute("retractAgentLiveOutput", {
19699
- agentId,
19700
- roomId,
19701
- turnId: requestId,
19702
- kind: "draft"
19703
- });
19854
+ await this.retract();
19704
19855
  }
19705
19856
  };
19706
19857
 
@@ -19710,13 +19861,18 @@ function redactToolDetail(value) {
19710
19861
  return value.replace(/\b(["']?)(api[_-]?key|token|secret|password|passwd|authorization|credential|cookie|private[_-]?key)\1\s*[:=]\s*(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s,}\]]+)/gi, '"$2": "[REDACTED]"').replace(/\b(?:export\s+)?[A-Za-z_][A-Za-z0-9_]*=(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\S+)/g, (assignment) => `${assignment.slice(0, assignment.indexOf("="))}=[REDACTED]`).replace(/\b(?:gh[pousr]_[A-Za-z0-9_]{12,}|github_pat_[A-Za-z0-9_]{12,})\b/gi, "[REDACTED]").replace(/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g, "[REDACTED]").replace(/\b(Bearer\s+)[^\s,]+/gi, "$1[REDACTED]").replace(/\bsk-[A-Za-z0-9_-]{12,}\b/g, "[REDACTED]").replace(/(--?(?:api[_-]?key|token|secret|password|authorization|credential|cookie)\s+)(?:"[^"]*"|'[^']*'|\S+)/gi, "$1[REDACTED]");
19711
19862
  }
19712
19863
  function distillTurnFailureReason(error) {
19864
+ if (error instanceof ModelSelectionUnavailableError) {
19865
+ return { text: "model selection unavailable", kind: "model-selection-unavailable" };
19866
+ }
19713
19867
  const raw = error instanceof Error ? error.message : typeof error === "string" ? error : error && typeof error === "object" && "message" in error ? String(error.message) : error == null ? "" : String(error);
19714
19868
  const firstLine = raw.split(/\r?\n/).map((line) => line.trim()).find((line) => line && !/^at\s/.test(line)) ?? "";
19715
19869
  const stripped = firstLine.replace(/^(?:[A-Za-z]*Error|Error):\s*/, "").replace(/\s+/g, " ");
19716
19870
  const clean4 = redactToolDetail(stripped).trim();
19717
19871
  if (!clean4)
19718
- return "turn failed";
19719
- return clean4.length > TURN_FAILURE_REASON_MAX ? `${clean4.slice(0, TURN_FAILURE_REASON_MAX - 1)}\u2026` : clean4;
19872
+ return { text: "turn failed" };
19873
+ return {
19874
+ text: clean4.length > TURN_FAILURE_REASON_MAX ? `${clean4.slice(0, TURN_FAILURE_REASON_MAX - 1)}\u2026` : clean4
19875
+ };
19720
19876
  }
19721
19877
 
19722
19878
  // apps/body/dist/tool-call-failure.js
@@ -19750,6 +19906,9 @@ function toolCallText(content) {
19750
19906
  function isFailedToolCall(call) {
19751
19907
  return /^(?:failed|error|denied|rejected)$/i.test(call.status ?? "");
19752
19908
  }
19909
+ function isCompletedToolCall(call) {
19910
+ return /^completed$/i.test(call.status ?? "");
19911
+ }
19753
19912
  function toolCallFailureLine(call) {
19754
19913
  if (!isFailedToolCall(call))
19755
19914
  return void 0;
@@ -19760,14 +19919,17 @@ function toolCallFailureLine(call) {
19760
19919
 
19761
19920
  // apps/body/dist/session-config-fingerprint.js
19762
19921
  function sessionConfigFingerprint(input) {
19763
- return JSON.stringify([
19922
+ const fingerprint = [
19764
19923
  input.model ?? "",
19765
19924
  input.effort ?? "",
19766
19925
  input.soul?.name ?? "",
19767
19926
  input.soul?.instructions ?? "",
19768
19927
  input.agentName ?? "",
19769
19928
  input.yoloMode ?? false
19770
- ]);
19929
+ ];
19930
+ if (input.reviewerHandle !== void 0)
19931
+ fingerprint.push(input.reviewerHandle);
19932
+ return JSON.stringify(fingerprint);
19771
19933
  }
19772
19934
 
19773
19935
  // apps/body/dist/pi-mcp-bridge.js
@@ -21558,6 +21720,7 @@ var MonolithRoomTurnLoop = class {
21558
21720
  const api = this.options.api;
21559
21721
  this.busy = true;
21560
21722
  const trace = this.beginTurnTrace(item.id);
21723
+ let liveStream;
21561
21724
  try {
21562
21725
  if (!this.memberNames.has(item.authorId))
21563
21726
  await this.roster().catch(() => void 0);
@@ -21624,6 +21787,7 @@ var MonolithRoomTurnLoop = class {
21624
21787
  requestId: item.id,
21625
21788
  label: `monolith Room ${this.options.roomId}`
21626
21789
  });
21790
+ liveStream = stream;
21627
21791
  const runPrompt = async () => {
21628
21792
  let nextPrompt = promptWithImages(buildPrompt(), attachmentImageBlocks(delivered, this.acceptsImages()));
21629
21793
  let result2;
@@ -21661,7 +21825,9 @@ var MonolithRoomTurnLoop = class {
21661
21825
  };
21662
21826
  let result = await runPrompt();
21663
21827
  trace.promptSettled();
21664
- let explained = await this.explainEmpty(result);
21828
+ let openCornerCall = openCornerToolCall(result.toolCalls);
21829
+ let cornerOpened = openedACorner(openCornerCall);
21830
+ let explained = cornerOpened ? void 0 : await this.explainEmpty(result);
21665
21831
  if (explained && shouldRetryEmptyTurn(explained)) {
21666
21832
  const silent = this.servingProviders();
21667
21833
  const next = await this.repinNextProvider(trace, explained.reason);
@@ -21669,7 +21835,9 @@ var MonolithRoomTurnLoop = class {
21669
21835
  console.warn(`[thin-core] monolith Room ${this.options.roomId} turn ${item.id}: ${turnFailureReasonWithProvider(explained.reason, silent)}; retrying on ${next}`);
21670
21836
  result = await runPrompt();
21671
21837
  trace.promptSettled();
21672
- explained = await this.explainEmpty(result);
21838
+ openCornerCall = openCornerToolCall(result.toolCalls);
21839
+ cornerOpened = openedACorner(openCornerCall);
21840
+ explained = cornerOpened ? void 0 : await this.explainEmpty(result);
21673
21841
  }
21674
21842
  }
21675
21843
  if (active.cancelled) {
@@ -21683,10 +21851,9 @@ var MonolithRoomTurnLoop = class {
21683
21851
  } else if (resumedRequestId) {
21684
21852
  console.log(`[thin-core] monolith Room ${this.options.roomId} turn ${resumedRequestId} resumed by grant decision ${item.id}`);
21685
21853
  }
21686
- const openCornerCall = result.toolCalls.find((call) => /(?:^|[._:/-])open_corner$/i.test(call.title ?? ""));
21687
21854
  if (openCornerCall) {
21688
21855
  console.log(`[thin-core] monolith Room ${this.options.roomId} tool call: ${openCornerCall.title} (${openCornerCall.status ?? "no status"})`);
21689
- if (!isFailedToolCall(openCornerCall))
21856
+ if (cornerOpened)
21690
21857
  this.options.onCornerOpened?.();
21691
21858
  }
21692
21859
  for (const call of result?.toolCalls ?? []) {
@@ -21704,7 +21871,7 @@ var MonolithRoomTurnLoop = class {
21704
21871
  }
21705
21872
  console.warn(`[thin-core] monolith Room ${this.options.roomId} turn ${item.id}: ${explained.reason}`);
21706
21873
  }
21707
- if (openCornerCall && !isFailedToolCall(openCornerCall)) {
21874
+ if (cornerOpened) {
21708
21875
  reply = "";
21709
21876
  }
21710
21877
  await trace.measure("publish", () => stream.settle(reply, reply ? {
@@ -21726,6 +21893,9 @@ var MonolithRoomTurnLoop = class {
21726
21893
  await trace.finish("cancelled");
21727
21894
  return;
21728
21895
  }
21896
+ await liveStream?.retract().catch((retractError) => {
21897
+ console.error(`[thin-core] monolith Room ${this.options.roomId} draft retract failed:`, retractError);
21898
+ });
21729
21899
  const reason = distillTurnFailureReason(error);
21730
21900
  await api.execute("postAgentTurnReceipt", {
21731
21901
  agentId: this.agent.publicKey,
@@ -21733,9 +21903,10 @@ var MonolithRoomTurnLoop = class {
21733
21903
  requestId: item.id,
21734
21904
  status: "failed",
21735
21905
  generationId: this.commandContext.generationId,
21736
- reason
21906
+ reason: reason.text,
21907
+ ...reason.kind ? { reasonKind: reason.kind } : {}
21737
21908
  });
21738
- await trace.finish("failed", reason);
21909
+ await trace.finish("failed", reason.text);
21739
21910
  throw error;
21740
21911
  } finally {
21741
21912
  this.busy = false;
@@ -21778,6 +21949,12 @@ var MonolithRoomTurnLoop = class {
21778
21949
  }
21779
21950
  }
21780
21951
  };
21952
+ function openCornerToolCall(calls) {
21953
+ return calls.find((call) => /(?:^|[._:/-])open_corner$/i.test(call.title ?? ""));
21954
+ }
21955
+ function openedACorner(call) {
21956
+ return !!call && isCompletedToolCall(call);
21957
+ }
21781
21958
  function roomMessagePrompt(author, body, attachments, delivered, harnessAcceptsImages = true) {
21782
21959
  const message = body.trim() || "(shared attachments)";
21783
21960
  const rendered = author ? `${author}: ${message}` : message;
@@ -21789,10 +21966,31 @@ var execFileAsync3 = promisify3(execFile4);
21789
21966
  var TOOL_ARGUMENT_MAX_BYTES = 1200;
21790
21967
  var TOOL_OUTPUT_MAX_BYTES = 3200;
21791
21968
  var TOOL_PATH_LIMIT = 12;
21792
- function cornerMergeInstruction(yoloMode) {
21969
+ function cornerMergeInstruction(yoloMode, reviewerHandle) {
21970
+ if (reviewerHandle)
21971
+ return `Commit, push, open the pull request, reply with the PR URL and then @${reviewerHandle} please review; never merge this PR yourself. If you asked any other agent to review in this corner, do not merge until they answer.`;
21793
21972
  return yoloMode ? "Yolo is on: when the gate passes, merge this pull request with gh." : "Yolo is off: never merge; wait for explicit human approval in the app.";
21794
21973
  }
21795
- var CORNER_DELIVERY_NUDGE = "Before ending this turn, inspect the repository state and finish delivering the work: commit and push the intended changes and open the pull request if one does not exist. Decide yourself whether any remaining dirty work belongs to the objective; do not discard it merely to make the worktree clean.";
21974
+ function cornerReviewerInstruction(input) {
21975
+ if (!input.reviewerHandle || !input.agentHandle || input.openedByAgent || input.agentHandle.replace(/^@/, "") !== input.reviewerHandle.replace(/^@/, ""))
21976
+ return void 0;
21977
+ const author = input.authorHandle?.replace(/^@/, "") || "author";
21978
+ const number = input.pullRequestNumber ?? "N";
21979
+ return `Review PR #${number} with the beeline-review skill. If it fails, reply @${author} with the findings. If it passes and the gate (pr_checks_status: checks=passed, held=false, approvalPending=false) is open and YOUR yolo is on, merge with gh pr merge --squash --match-head-commit <sha you reviewed>; if your yolo is off, reply approved <sha> and stop; if checks are pending, reply approved pending checks <sha> and stop.`;
21980
+ }
21981
+ var CORNER_AUTHOR_CONTRACT = `The objective text is the user's ask. Keep it verbatim in your head and do not reinterpret it.
21982
+ Before any code, write its end-user story in one sentence: "a person who does X sees Y".
21983
+ 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.
21984
+ Do not write a fix before you have seen the defect. Turn the reproduction into the regression test.
21985
+ Before opening the pull request, produce Y against the built change: run the app or affected service from your branch and perform X.
21986
+ If no interactive surface is reachable, run the narrowest test or script that exercises the exact user path and prints the observable Y.
21987
+ A unit test of an inner function, a log line, or reading the code is not a demonstration.
21988
+ The pull request body MUST contain two sections with exactly these headings: ## Reproduced and ## Demonstrated.
21989
+ Under ## Reproduced, give the steps or command and what was observed; write "not a defect report" for feature work.
21990
+ Under ## Demonstrated, give the command or steps that produced Y and what was observed.
21991
+ A pull request without both sections is not deliverable and the Room's reviewer will fail it.
21992
+ Change only what the objective asks. No unrequested features, flags, compatibility shims, or refactors.`;
21993
+ var CORNER_DELIVERY_NUDGE = "Before ending this turn, inspect the repository state and finish delivering the work: commit and push the intended changes and open the pull request if one does not exist. Decide yourself whether any remaining dirty work belongs to the objective; do not discard it merely to make the worktree clean. The pull request body must carry ## Reproduced and ## Demonstrated; if they are missing, add them before ending the turn.";
21796
21994
  var CORNER_YOLO_MERGE_NUDGE = 'Yolo is on. Check the server merge gate with pr_checks_status now and, if checks="passed", held=false, and approvalPending=false, merge this pull request with gh. Otherwise stop without merging.';
21797
21995
  function isCornerChecksTurn(trigger, restates) {
21798
21996
  return Boolean(restates) || /\b(?:passed|failed) a check\b/i.test(trigger);
@@ -21977,6 +22175,10 @@ var MonolithCornerTurnLoop = class {
21977
22175
  pinnedProviderOverride;
21978
22176
  /** The merge authority baked into the current session. */
21979
22177
  yoloMode = false;
22178
+ /** The live parent-Room reviewer baked into the current session. */
22179
+ reviewerHandle;
22180
+ /** The role-specific second-chance instruction for this session. */
22181
+ cornerTurnEndNudge = CORNER_DELIVERY_NUDGE;
21980
22182
  /** Repository state already given a delivery reminder, until that state changes. */
21981
22183
  lastDeliveryNudgeState;
21982
22184
  turnIdentityInstructions = "";
@@ -22094,7 +22296,8 @@ var MonolithCornerTurnLoop = class {
22094
22296
  effort: configuration.effort ?? this.options.config.modelSelection?.effort,
22095
22297
  soul: configuration.soul ?? self?.soul,
22096
22298
  agentName: self?.name ?? this.agent.name,
22097
- yoloMode: configuration.yoloMode
22299
+ yoloMode: configuration.yoloMode,
22300
+ reviewerHandle: configuration.reviewerHandle
22098
22301
  });
22099
22302
  }
22100
22303
  async activate(trace) {
@@ -22114,9 +22317,30 @@ var MonolithCornerTurnLoop = class {
22114
22317
  effort: configuration.effort ?? this.options.config.modelSelection?.effort,
22115
22318
  soul: configuration.soul ?? self?.soul,
22116
22319
  agentName: self?.name ?? this.agent.name,
22117
- yoloMode: configuration.yoloMode
22320
+ yoloMode: configuration.yoloMode,
22321
+ reviewerHandle: configuration.reviewerHandle
22118
22322
  });
22119
22323
  this.yoloMode = configuration.yoloMode;
22324
+ this.reviewerHandle = configuration.reviewerHandle;
22325
+ const opener = this.options.openedBy ? roster.members.find((member) => member.identityId === this.options.openedBy) : void 0;
22326
+ const reviewerInput = {
22327
+ reviewerHandle: configuration.reviewerHandle,
22328
+ agentHandle: self?.handle,
22329
+ authorHandle: opener?.handle,
22330
+ openedByAgent: !this.options.openedBy || this.options.openedBy === this.agent.publicKey,
22331
+ yoloMode: configuration.yoloMode
22332
+ };
22333
+ let reviewerInstruction = cornerReviewerInstruction(reviewerInput);
22334
+ if (reviewerInstruction) {
22335
+ const restore = await this.options.api.execute("getCornerRestoreState", {
22336
+ cornerId: this.options.cornerId
22337
+ });
22338
+ reviewerInstruction = cornerReviewerInstruction({
22339
+ ...reviewerInput,
22340
+ pullRequestNumber: restore.lifecycle?.pr?.number
22341
+ });
22342
+ }
22343
+ this.cornerTurnEndNudge = reviewerInstruction ?? cornerMergeInstruction(configuration.yoloMode, configuration.reviewerHandle);
22120
22344
  await mkdir12(this.options.worktreePath, { recursive: true });
22121
22345
  const selection = configuration.model || configuration.effort ? { model: configuration.model, effort: configuration.effort } : this.options.config.modelSelection;
22122
22346
  const homeOverlay = this.options.config.agentHomeRoot ? await prepareRoomAgentHome({
@@ -22260,8 +22484,11 @@ var MonolithCornerTurnLoop = class {
22260
22484
  ...repository ? [
22261
22485
  `You are in an isolated git worktree on ${repository.featureBranch}, targeting ${repository.targetBranch}.`,
22262
22486
  `Commit and push only ${repository.featureBranch}; never force-push or write to ${repository.targetBranch}. Before pushing, rebase on origin/${repository.featureBranch}; resolve conflicts autonomously, realigning to that remote branch and redoing the objective if needed, then rerun affected tests. Open the pull request with gh.`,
22263
- '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; only a later explicit human resume clears a hold.',
22264
- cornerMergeInstruction(configuration.yoloMode),
22487
+ ...reviewerInstruction ? [reviewerInstruction] : [
22488
+ configuration.reviewerHandle ? `Once the pull request exists, reply with its full URL, then @${configuration.reviewerHandle} please review, and end the turn; do not 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; only a later explicit human resume clears a hold.',
22489
+ CORNER_AUTHOR_CONTRACT,
22490
+ cornerMergeInstruction(configuration.yoloMode, configuration.reviewerHandle)
22491
+ ],
22265
22492
  "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.",
22266
22493
  "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. When approval is pending, wait for the server close request. Never merge another pull request."
22267
22494
  ] : [
@@ -22532,12 +22759,12 @@ ${trigger}`,
22532
22759
  const deliveryState = !checksTurn && this.options.repository ? await cornerUndeliveredRepositoryState(this.options.worktreePath, this.options.repository.featureBranch, this.options.repository.targetBranch) : void 0;
22533
22760
  const needsDeliveryNudge = deliveryState !== void 0 && deliveryState !== this.lastDeliveryNudgeState;
22534
22761
  let replyBeforeNudge = "";
22535
- if (!explained && (needsDeliveryNudge || checksTurn && this.yoloMode)) {
22762
+ if (!explained && (needsDeliveryNudge || checksTurn && (this.yoloMode || Boolean(this.reviewerHandle)))) {
22536
22763
  if (needsDeliveryNudge)
22537
22764
  this.lastDeliveryNudgeState = deliveryState;
22538
22765
  replyBeforeNudge = durableReplyText(result.agentText);
22539
22766
  await flushToolCalls(result.toolCalls, "");
22540
- result = await runPrompt(checksTurn ? CORNER_YOLO_MERGE_NUDGE : CORNER_DELIVERY_NUDGE);
22767
+ result = await runPrompt(this.reviewerHandle ? this.cornerTurnEndNudge : checksTurn ? CORNER_YOLO_MERGE_NUDGE : CORNER_DELIVERY_NUDGE);
22541
22768
  trace.promptSettled();
22542
22769
  explained = await this.explainEmpty(result);
22543
22770
  }
@@ -22589,9 +22816,10 @@ ${trigger}`,
22589
22816
  requestId,
22590
22817
  status: "failed",
22591
22818
  generationId: this.commandContext.generationId,
22592
- reason
22819
+ reason: reason.text,
22820
+ ...reason.kind ? { reasonKind: reason.kind } : {}
22593
22821
  });
22594
- await trace.finish("failed", reason);
22822
+ await trace.finish("failed", reason.text);
22595
22823
  throw error;
22596
22824
  } finally {
22597
22825
  this.busy = false;
@@ -23696,13 +23924,15 @@ var RoomRuntimeCoordinator = class {
23696
23924
  const asked = [...conversation.items].reverse().find((item) => item.type === "message" && item.mentionIds.includes(this.agent.publicKey));
23697
23925
  if (!asked)
23698
23926
  return;
23927
+ const reason = distillTurnFailureReason(error);
23699
23928
  await this.options.daemonApi.execute("postAgentTurnReceipt", {
23700
23929
  agentId: this.agent.publicKey,
23701
23930
  roomId: cornerId,
23702
23931
  requestId: asked.id,
23703
23932
  status: "failed",
23704
23933
  generationId: `${this.agent.publicKey}:${cornerId}`,
23705
- reason: distillTurnFailureReason(error)
23934
+ reason: reason.text,
23935
+ ...reason.kind ? { reasonKind: reason.kind } : {}
23706
23936
  });
23707
23937
  } catch (reportError) {
23708
23938
  console.error(`[thin-core] corner ${cornerId} start-failure report failed:`, reportError);
@@ -26613,7 +26843,8 @@ async function runStoredDaemon(pathOrPointer) {
26613
26843
  agentId: runtime.agent.publicKey,
26614
26844
  workspaceId: runtime.communityId,
26615
26845
  runtimeDir,
26616
- ...runtime.modelSelection ? { runtimeSelection: runtime.modelSelection } : {}
26846
+ ...runtime.modelSelection ? { runtimeSelection: runtime.modelSelection } : {},
26847
+ ...config.modelUnavailable ? { startupUnavailable: config.modelUnavailable.unavailable.label } : {}
26617
26848
  });
26618
26849
  },
26619
26850
  onProgress: async (status) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usebeeline",
3
- "version": "0.0.92",
3
+ "version": "0.0.94",
4
4
  "description": "Connect an AI coding agent to Beeline with one command.",
5
5
  "homepage": "https://usebeeline.app",
6
6
  "bugs": {