usebeeline 0.0.92 → 0.0.95

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 +299 -55
  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";
@@ -7893,6 +7906,16 @@ var ModelSelectionUnavailableError = class extends Error {
7893
7906
  this.guidance = input.guidance;
7894
7907
  }
7895
7908
  };
7909
+ function withEffectiveCurrentValues(options, selection) {
7910
+ if (!selection || !selection.model && !selection.effort)
7911
+ return options;
7912
+ return options.map((option) => {
7913
+ const target = modelSelectionTargets(selection).find((entry) => entry.categories.includes(option.category));
7914
+ if (!target?.value || option.currentValue === target.value)
7915
+ return option;
7916
+ return { ...option, currentValue: target.value };
7917
+ });
7918
+ }
7896
7919
  function modelSelectionTargets(selection) {
7897
7920
  return [
7898
7921
  { categories: ["model"], label: "model", value: selection.model },
@@ -8010,7 +8033,24 @@ async function withAgentModelCatalog(agent, agentEnv, selection, inspect, limits
8010
8033
  }
8011
8034
  }
8012
8035
  async function fetchAgentModelCatalog(agent, agentEnv, selection, limits = {}) {
8013
- return withAgentModelCatalog(agent, agentEnv, selection, async ({ raw, catalog }) => ({ raw, catalog }), limits);
8036
+ return withAgentModelCatalog(agent, agentEnv, selection, async ({ client, sessionId, raw, catalog }) => ({
8037
+ raw,
8038
+ catalog: await filterModelChoicesByLiveValidation(client, sessionId, catalog)
8039
+ }), limits);
8040
+ }
8041
+ async function filterModelChoicesByLiveValidation(client, sessionId, catalog) {
8042
+ const modelAxis = catalog.find((axis) => axis.category === "model");
8043
+ if (!modelAxis)
8044
+ return catalog;
8045
+ const available = [];
8046
+ for (const choice of modelAxis.options) {
8047
+ try {
8048
+ await applyAgentModelSelection(client, sessionId, catalog, { model: choice.id });
8049
+ available.push(choice);
8050
+ } catch {
8051
+ }
8052
+ }
8053
+ return catalog.map((axis) => axis === modelAxis ? { ...axis, options: available } : axis);
8014
8054
  }
8015
8055
  async function validateAgentModelSelection(agent, agentEnv, selection) {
8016
8056
  return withAgentModelCatalog(agent, agentEnv, selection, async ({ client, sessionId, raw, catalog }) => {
@@ -8066,8 +8106,12 @@ import { readFile, writeFile } from "node:fs/promises";
8066
8106
  import { resolve as resolve5 } from "node:path";
8067
8107
  var MODEL_CATALOG_PROBE_TIMEOUT_MS = 3e4;
8068
8108
  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");
8109
+ function modelCatalogHash(options, selection, startupUnavailable) {
8110
+ return createHash("sha256").update(JSON.stringify({
8111
+ options,
8112
+ selection: selection ?? null,
8113
+ startupUnavailable: startupUnavailable ?? null
8114
+ })).digest("hex");
8071
8115
  }
8072
8116
  async function withTimeout(work, timeoutMs, what) {
8073
8117
  let timer;
@@ -8093,8 +8137,9 @@ async function syncAgentModelCatalog(input) {
8093
8137
  ...configuration.model ? { model: configuration.model } : {},
8094
8138
  ...configuration.effort ? { effort: configuration.effort } : {}
8095
8139
  } : 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);
8140
+ const { catalog } = await withTimeout(fetchCatalog(input.agent, input.agentEnv, input.startupUnavailable ? void 0 : selection), input.timeoutMs ?? MODEL_CATALOG_PROBE_TIMEOUT_MS, "model catalog probe");
8141
+ const effectiveCatalog = input.startupUnavailable ? catalog : withEffectiveCurrentValues(catalog, selection);
8142
+ const hash = modelCatalogHash(effectiveCatalog, selection, input.startupUnavailable);
8098
8143
  const previous = await readFile(hashPath, "utf8").catch(() => "");
8099
8144
  if (previous.trim() === hash)
8100
8145
  return "unchanged";
@@ -8102,8 +8147,9 @@ async function syncAgentModelCatalog(input) {
8102
8147
  agentId: input.agentId,
8103
8148
  workspaceId: input.workspaceId,
8104
8149
  // `fetchAgentModelCatalog` already applied the category allow-list.
8105
- options: catalog,
8106
- ...selection ? { selection } : {}
8150
+ options: effectiveCatalog,
8151
+ ...selection ? { selection } : {},
8152
+ ...input.startupUnavailable ? { unavailable: input.startupUnavailable } : {}
8107
8153
  });
8108
8154
  await writeFile(hashPath, `${hash}
8109
8155
  `, { mode: 384 });
@@ -8134,7 +8180,7 @@ function isAgentCommand(value) {
8134
8180
  if (!["input", "resume", "stop"].includes(String(c2.action)) || !Number.isInteger(c2.agentDepth) || Number(c2.agentDepth) < 0 || Number(c2.agentDepth) > 3)
8135
8181
  return false;
8136
8182
  const source = c2.source;
8137
- return Boolean(source && typeof source.id === "string" && typeof source.authorId === "string" && typeof source.body === "string" && typeof source.createdAt === "number" && Array.isArray(source.attachments) && Array.isArray(source.mentionIds));
8183
+ return Boolean(source && typeof source.id === "string" && typeof source.authorId === "string" && typeof source.body === "string" && typeof source.createdAt === "number" && Array.isArray(source.attachments));
8138
8184
  }
8139
8185
 
8140
8186
  // packages/api-contract/dist/system-events.js
@@ -18201,6 +18247,7 @@ function isAgentPairingCode(value) {
18201
18247
 
18202
18248
  // apps/body/dist/beeline-skill.js
18203
18249
  var USING_BEELINE_SKILL_NAME = "using-beeline";
18250
+ var BEELINE_REVIEW_SKILL_NAME = "beeline-review";
18204
18251
  var BEELINE_ROOM_CAPABILITIES = [
18205
18252
  "The repository filesystem is read-only in this Room session.",
18206
18253
  "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.",
@@ -18213,6 +18260,8 @@ var BEELINE_ROOM_CAPABILITIES = [
18213
18260
  `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.`,
18214
18261
  "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.",
18215
18262
  "When repository work is needed, you MUST call beeline-agent open_corner with a name of at most three words - it titles the corner everywhere - and a complete objective of no more than 24 words. The host-governed call is the only way to start write work.",
18263
+ "Never open a corner from your own reading of a person's ask. For every ask, first reply on one line `Proposed corner: <name> \u2014 <objective>`, using the exact title and objective you would pass to open_corner, then stop and wait. If one message contains several asks, list one numbered `Proposed corner:` line per ask; `go on 1 and 3` opens exactly those objectives and leaves the others proposed. A person's `go`, `yes`, or equivalent opens exactly the proposed corner; if they edit it, their edited text is the objective. Skip this ceremony only when the message itself explicitly commands a corner and states its scope, such as `open a corner and do X` or `go build X in a corner`; open that scope immediately.",
18264
+ "Agreement is not action: never merely acknowledge an ask. Reply with a proposed corner, a question, or a line beginning `parked:` with the reason.",
18216
18265
  "When open_corner succeeds, the server posts the corner card: do not announce or restate the opening. End the turn with nothing more unless the person asked something else.",
18217
18266
  "Never claim an action or reply happened unless the prompt or a tool result proves it."
18218
18267
  ].join(" ");
@@ -18265,6 +18314,86 @@ description: How to answer inside a Beeline Room.
18265
18314
  You are answering inside a Room whose filesystem is read-only. ${BEELINE_ROOM_CAPABILITIES}
18266
18315
  `;
18267
18316
  }
18317
+ function beelineReviewSkillMarkdown(releaseId) {
18318
+ return `---
18319
+ name: beeline-review
18320
+ description: Review a corner pull request against its objective and the Beeline merge gate.
18321
+ ---
18322
+
18323
+ <!-- beeline-release: ${releaseId} -->
18324
+
18325
+ # Beeline pull-request review
18326
+
18327
+ Follow these steps in order. Do not skip or reorder them.
18328
+
18329
+ ## 1. Isolate the revision
18330
+
18331
+ - Run \`gh pr view N --json headRefOid,files\` and record \`headRefOid\`.
18332
+ - Run \`gh pr diff N\`.
18333
+ - Check out that exact head in a new scratch git worktree. Never use the author's worktree.
18334
+ - Review and test only the recorded revision. If the head moves, start over.
18335
+
18336
+ ## 2. P0 - OBJECTIVE FULFILLED, DEMONSTRATED
18337
+
18338
+ - Quote the corner objective verbatim.
18339
+ - Derive its end-user story in one sentence: \`a user who does X sees Y\`.
18340
+ - Make Y happen against the built PR head: run the app or affected service and perform X.
18341
+ - If no interactive surface is reachable, run the narrowest test or script that exercises the exact user path and prints the observable Y.
18342
+ - Record the command and the observed Y.
18343
+ - A unit test of an inner function, a log line, \`the code looks right\`, or any other proxy does not count.
18344
+ - If the user-visible Y cannot be produced, FAIL now. Nothing below can rescue the review.
18345
+ - State whether the diff fulfills that objective and only that objective.
18346
+
18347
+ ## 3. Empirical pass second
18348
+
18349
+ - Run the repository typecheck and tests touched by the diff.
18350
+ - If the objective names a user path, exercise that path.
18351
+ - Record every command and exit code.
18352
+ - A review with no executed command is invalid and must FAIL.
18353
+
18354
+ ## 4. Adversarial pass
18355
+
18356
+ - For every changed function, name one concrete input or sequence that breaks it.
18357
+ - If none is found, write \`none found\` for that function.
18358
+
18359
+ ## 5. Verify before reporting
18360
+
18361
+ - Confirm every finding by reading the exact line or by running a command.
18362
+ - Put unconfirmed concerns under plausible findings. They never block.
18363
+
18364
+ ## 6. Bloat guard
18365
+
18366
+ - Compare net lines with the objective.
18367
+ - FAIL backwards-compatibility shims, dual paths, feature flags, or abstractions with one caller.
18368
+ - FAIL machinery the objective did not ask for.
18369
+
18370
+ ## 7. Security and data
18371
+
18372
+ - Check credentials, authorization boundaries, and destructive migrations.
18373
+
18374
+ ## 8. Gate and verdict
18375
+
18376
+ - The merge gate is open only when \`pr_checks_status\` reports checks=passed, held=false, approvalPending=false.
18377
+ - Green \`gh pr checks\` alone never opens the gate.
18378
+ - Always use this exact verdict shape:
18379
+
18380
+ \`objective quoted:\`
18381
+ \`user story:\`
18382
+ \`how Y was demonstrated (or FAIL):\`
18383
+ \`commands run + results:\`
18384
+ \`critical findings (block):\`
18385
+ \`plausible findings (do not block):\`
18386
+ \`net lines:\`
18387
+ \`decision: PASS|FAIL\`
18388
+
18389
+ Then take exactly one action:
18390
+
18391
+ - FAIL: reply \`@author\` with the confirmed findings; do not merge.
18392
+ - PASS with pending checks: reply \`approved pending checks <reviewed sha>\` and stop.
18393
+ - PASS with the gate open and your yolo on: run \`gh pr merge --squash --match-head-commit <reviewed sha> N\`.
18394
+ - PASS with the gate open and your yolo off: reply \`approved <reviewed sha>\` and stop.
18395
+ `;
18396
+ }
18268
18397
 
18269
18398
  // apps/body/dist/external-mcp-capabilities.js
18270
18399
  var SQUIRE_MCP_VERSION = "1.1.12";
@@ -18862,7 +18991,10 @@ var SHARED_CREDENTIALS = [
18862
18991
  { dir: "pi", source: ".pi/agent/auth.json", target: "auth.json" }
18863
18992
  ];
18864
18993
  var GOOSE_SHARED_CONFIG_FILES = ["config.yaml", "secrets.yaml"];
18865
- var BEELINE_DEFAULT_SKILL_NAMES = [USING_BEELINE_SKILL_NAME];
18994
+ var BEELINE_DEFAULT_SKILL_NAMES = [
18995
+ BEELINE_REVIEW_SKILL_NAME,
18996
+ USING_BEELINE_SKILL_NAME
18997
+ ];
18866
18998
  var OPERATOR_SKILL_SOURCE_DIRS = [
18867
18999
  ".agents/skills",
18868
19000
  ".claude/skills",
@@ -18941,7 +19073,8 @@ async function prepareRoomAgentHome(input) {
18941
19073
  }
18942
19074
  async function provisionAgentSkillsAndMcp(root, operatorHome, skillReleaseId, failClosed, sharedSkills, skillDir, openRouterRouting) {
18943
19075
  const managedSkills = [
18944
- { name: USING_BEELINE_SKILL_NAME, content: usingBeelineSkillMarkdown(skillReleaseId) }
19076
+ { name: USING_BEELINE_SKILL_NAME, content: usingBeelineSkillMarkdown(skillReleaseId) },
19077
+ { name: BEELINE_REVIEW_SKILL_NAME, content: beelineReviewSkillMarkdown(skillReleaseId) }
18945
19078
  ];
18946
19079
  const shared = await resolveSharedSkillSources(operatorHome, sharedSkills);
18947
19080
  await provisionManagedSkillsDir(resolve13(root, skillDir, "skills"), managedSkills, shared, sharedSkills.length === 0);
@@ -19617,6 +19750,12 @@ var AgentTurnStream = class {
19617
19750
  inFlight;
19618
19751
  /** Closed lanes publish nothing more, so the answer never queues behind a draft. */
19619
19752
  closed = false;
19753
+ /**
19754
+ * The retract this lane already sent. A settled turn that throws afterwards
19755
+ * reaches the same retract a second time, and one empty lane is the whole
19756
+ * point: asking twice would only be a second write saying what is already so.
19757
+ */
19758
+ retraction;
19620
19759
  constructor(options) {
19621
19760
  this.options = options;
19622
19761
  }
@@ -19676,31 +19815,56 @@ var AgentTurnStream = class {
19676
19815
  this.closed = true;
19677
19816
  this.pending = void 0;
19678
19817
  }
19818
+ /**
19819
+ * Dissolve the draft, publishing nothing.
19820
+ *
19821
+ * Every ending uses this: the settle below calls it once the durable reply is
19822
+ * on the wire, and a turn that THROWS calls it directly. A throw never
19823
+ * reaches a settle, and `close()` alone only stops future writes — the last
19824
+ * snapshot stays live on the page, so a turn the Room has already reported
19825
+ * stopped or failed keeps a half-written answer visibly in progress under it.
19826
+ */
19827
+ async retract() {
19828
+ this.close();
19829
+ this.retraction ??= (async () => {
19830
+ await this.inFlight;
19831
+ const { api, agentId, roomId, requestId, label } = this.options;
19832
+ await api.execute("retractAgentLiveOutput", {
19833
+ agentId,
19834
+ roomId,
19835
+ turnId: requestId,
19836
+ kind: "draft"
19837
+ }).catch((error) => console.error(`[thin-core] ${label} draft retract failed:`, error));
19838
+ })();
19839
+ await this.retraction;
19840
+ }
19679
19841
  /**
19680
19842
  * Post the durable reply under the turn's request id and dissolve the draft.
19681
19843
  * An empty reply settles through the turn receipt instead, and the lane is
19682
19844
  * retracted either way.
19845
+ *
19846
+ * The durable reply is this turn's answer and its last word. The draft lane
19847
+ * is presentation, so a refused retract is logged like a refused draft and
19848
+ * the turn still settles complete: raising here failed a turn that had
19849
+ * already answered, which posts a `failed` receipt and inscribes "<agent>
19850
+ * could not answer" UNDER the answer the reader is looking at. The reader
19851
+ * loses nothing by it either — the phone ends a retracted draft on the
19852
+ * turn's own complete receipt (`visibleLiveOverlays`).
19683
19853
  */
19684
19854
  async settle(reply, fields = {}, onReplyPosted) {
19685
19855
  this.close();
19686
- const { api, agentId, roomId, requestId } = this.options;
19856
+ const { api, roomId, requestId } = this.options;
19687
19857
  if (reply) {
19688
- const posted = await api.execute("postRoomMessage", {
19858
+ await api.execute("postRoomMessage", {
19689
19859
  roomId,
19690
19860
  requestId,
19691
19861
  text: reply,
19692
19862
  presentation: "message",
19693
19863
  ...fields
19694
19864
  });
19695
- onReplyPosted?.(posted);
19865
+ onReplyPosted?.();
19696
19866
  }
19697
- await this.inFlight;
19698
- await api.execute("retractAgentLiveOutput", {
19699
- agentId,
19700
- roomId,
19701
- turnId: requestId,
19702
- kind: "draft"
19703
- });
19867
+ await this.retract();
19704
19868
  }
19705
19869
  };
19706
19870
 
@@ -19710,13 +19874,18 @@ function redactToolDetail(value) {
19710
19874
  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
19875
  }
19712
19876
  function distillTurnFailureReason(error) {
19877
+ if (error instanceof ModelSelectionUnavailableError) {
19878
+ return { text: "model selection unavailable", kind: "model-selection-unavailable" };
19879
+ }
19713
19880
  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
19881
  const firstLine = raw.split(/\r?\n/).map((line) => line.trim()).find((line) => line && !/^at\s/.test(line)) ?? "";
19715
19882
  const stripped = firstLine.replace(/^(?:[A-Za-z]*Error|Error):\s*/, "").replace(/\s+/g, " ");
19716
19883
  const clean4 = redactToolDetail(stripped).trim();
19717
19884
  if (!clean4)
19718
- return "turn failed";
19719
- return clean4.length > TURN_FAILURE_REASON_MAX ? `${clean4.slice(0, TURN_FAILURE_REASON_MAX - 1)}\u2026` : clean4;
19885
+ return { text: "turn failed" };
19886
+ return {
19887
+ text: clean4.length > TURN_FAILURE_REASON_MAX ? `${clean4.slice(0, TURN_FAILURE_REASON_MAX - 1)}\u2026` : clean4
19888
+ };
19720
19889
  }
19721
19890
 
19722
19891
  // apps/body/dist/tool-call-failure.js
@@ -19750,6 +19919,9 @@ function toolCallText(content) {
19750
19919
  function isFailedToolCall(call) {
19751
19920
  return /^(?:failed|error|denied|rejected)$/i.test(call.status ?? "");
19752
19921
  }
19922
+ function isCompletedToolCall(call) {
19923
+ return /^completed$/i.test(call.status ?? "");
19924
+ }
19753
19925
  function toolCallFailureLine(call) {
19754
19926
  if (!isFailedToolCall(call))
19755
19927
  return void 0;
@@ -19760,14 +19932,17 @@ function toolCallFailureLine(call) {
19760
19932
 
19761
19933
  // apps/body/dist/session-config-fingerprint.js
19762
19934
  function sessionConfigFingerprint(input) {
19763
- return JSON.stringify([
19935
+ const fingerprint = [
19764
19936
  input.model ?? "",
19765
19937
  input.effort ?? "",
19766
19938
  input.soul?.name ?? "",
19767
19939
  input.soul?.instructions ?? "",
19768
19940
  input.agentName ?? "",
19769
19941
  input.yoloMode ?? false
19770
- ]);
19942
+ ];
19943
+ if (input.reviewerHandle !== void 0)
19944
+ fingerprint.push(input.reviewerHandle);
19945
+ return JSON.stringify(fingerprint);
19771
19946
  }
19772
19947
 
19773
19948
  // apps/body/dist/pi-mcp-bridge.js
@@ -21078,23 +21253,23 @@ function isRoomMcpPermissionRequest(request, mountedServers = ROOM_MOUNTED_MCP_S
21078
21253
  return false;
21079
21254
  return isMountedMcpToolPermissionRequest(request, mountedServers);
21080
21255
  }
21081
- function isScheduledPrompt(item, agentId) {
21082
- if (item.type !== "system" || !item.mentionIds.includes(agentId))
21256
+ function isScheduledPrompt(item) {
21257
+ if (item.type !== "system")
21083
21258
  return false;
21084
21259
  if (item.systemEvent?.kind)
21085
21260
  return item.systemEvent.kind === "schedule-ran";
21086
21261
  return item.systemEvent?.verb === SCHEDULE_RAN_VERB;
21087
21262
  }
21088
- function inboxItemAuthorName(item, agentId, names) {
21089
- if (isScheduledPrompt(item, agentId))
21263
+ function inboxItemAuthorName(item, names) {
21264
+ if (isScheduledPrompt(item))
21090
21265
  return SCHEDULE_SCHEDULER_NAME;
21091
21266
  const subject = item.systemEvent?.subject;
21092
21267
  if (item.type === "system" && subject?.name)
21093
21268
  return subject.name;
21094
21269
  return names.get(item.authorId) ?? item.authorId.slice(0, 12);
21095
21270
  }
21096
- function inboxItemPromptBody(item, agentId) {
21097
- return isScheduledPrompt(item, agentId) ? item.systemEvent?.consequence ?? item.body : item.body;
21271
+ function inboxItemPromptBody(item) {
21272
+ return isScheduledPrompt(item) ? item.systemEvent?.consequence ?? item.body : item.body;
21098
21273
  }
21099
21274
  function pendingGrantToolCall(call) {
21100
21275
  if (!/(?:^|[._:/-])request_grant$/i.test(call.title ?? ""))
@@ -21558,6 +21733,7 @@ var MonolithRoomTurnLoop = class {
21558
21733
  const api = this.options.api;
21559
21734
  this.busy = true;
21560
21735
  const trace = this.beginTurnTrace(item.id);
21736
+ let liveStream;
21561
21737
  try {
21562
21738
  if (!this.memberNames.has(item.authorId))
21563
21739
  await this.roster().catch(() => void 0);
@@ -21614,8 +21790,8 @@ var MonolithRoomTurnLoop = class {
21614
21790
  "If the current task is only a nudge to respond, answer the most recent unanswered human message in the conversation instead of echoing the nudge.",
21615
21791
  MAINTAIN_ASSIGNED_IDENTITY_DIRECTIVE
21616
21792
  ].join(" "),
21617
- `Current task selected by the server from ${inboxItemAuthorName(item, this.agent.publicKey, names)}:`,
21618
- roomMessagePrompt("", inboxItemPromptBody(item, this.agent.publicKey), item.attachments, delivered, this.acceptsImages())
21793
+ `Current task selected by the server from ${inboxItemAuthorName(item, names)}:`,
21794
+ roomMessagePrompt("", inboxItemPromptBody(item), item.attachments, delivered, this.acceptsImages())
21619
21795
  ].filter(Boolean).join("\n\n");
21620
21796
  const stream = new AgentTurnStream({
21621
21797
  api,
@@ -21624,6 +21800,7 @@ var MonolithRoomTurnLoop = class {
21624
21800
  requestId: item.id,
21625
21801
  label: `monolith Room ${this.options.roomId}`
21626
21802
  });
21803
+ liveStream = stream;
21627
21804
  const runPrompt = async () => {
21628
21805
  let nextPrompt = promptWithImages(buildPrompt(), attachmentImageBlocks(delivered, this.acceptsImages()));
21629
21806
  let result2;
@@ -21661,7 +21838,9 @@ var MonolithRoomTurnLoop = class {
21661
21838
  };
21662
21839
  let result = await runPrompt();
21663
21840
  trace.promptSettled();
21664
- let explained = await this.explainEmpty(result);
21841
+ let openCornerCall = openCornerToolCall(result.toolCalls);
21842
+ let cornerOpened = openedACorner(openCornerCall);
21843
+ let explained = cornerOpened ? void 0 : await this.explainEmpty(result);
21665
21844
  if (explained && shouldRetryEmptyTurn(explained)) {
21666
21845
  const silent = this.servingProviders();
21667
21846
  const next = await this.repinNextProvider(trace, explained.reason);
@@ -21669,7 +21848,9 @@ var MonolithRoomTurnLoop = class {
21669
21848
  console.warn(`[thin-core] monolith Room ${this.options.roomId} turn ${item.id}: ${turnFailureReasonWithProvider(explained.reason, silent)}; retrying on ${next}`);
21670
21849
  result = await runPrompt();
21671
21850
  trace.promptSettled();
21672
- explained = await this.explainEmpty(result);
21851
+ openCornerCall = openCornerToolCall(result.toolCalls);
21852
+ cornerOpened = openedACorner(openCornerCall);
21853
+ explained = cornerOpened ? void 0 : await this.explainEmpty(result);
21673
21854
  }
21674
21855
  }
21675
21856
  if (active.cancelled) {
@@ -21683,10 +21864,9 @@ var MonolithRoomTurnLoop = class {
21683
21864
  } else if (resumedRequestId) {
21684
21865
  console.log(`[thin-core] monolith Room ${this.options.roomId} turn ${resumedRequestId} resumed by grant decision ${item.id}`);
21685
21866
  }
21686
- const openCornerCall = result.toolCalls.find((call) => /(?:^|[._:/-])open_corner$/i.test(call.title ?? ""));
21687
21867
  if (openCornerCall) {
21688
21868
  console.log(`[thin-core] monolith Room ${this.options.roomId} tool call: ${openCornerCall.title} (${openCornerCall.status ?? "no status"})`);
21689
- if (!isFailedToolCall(openCornerCall))
21869
+ if (cornerOpened)
21690
21870
  this.options.onCornerOpened?.();
21691
21871
  }
21692
21872
  for (const call of result?.toolCalls ?? []) {
@@ -21704,7 +21884,7 @@ var MonolithRoomTurnLoop = class {
21704
21884
  }
21705
21885
  console.warn(`[thin-core] monolith Room ${this.options.roomId} turn ${item.id}: ${explained.reason}`);
21706
21886
  }
21707
- if (openCornerCall && !isFailedToolCall(openCornerCall)) {
21887
+ if (cornerOpened) {
21708
21888
  reply = "";
21709
21889
  }
21710
21890
  await trace.measure("publish", () => stream.settle(reply, reply ? {
@@ -21726,6 +21906,9 @@ var MonolithRoomTurnLoop = class {
21726
21906
  await trace.finish("cancelled");
21727
21907
  return;
21728
21908
  }
21909
+ await liveStream?.retract().catch((retractError) => {
21910
+ console.error(`[thin-core] monolith Room ${this.options.roomId} draft retract failed:`, retractError);
21911
+ });
21729
21912
  const reason = distillTurnFailureReason(error);
21730
21913
  await api.execute("postAgentTurnReceipt", {
21731
21914
  agentId: this.agent.publicKey,
@@ -21733,9 +21916,10 @@ var MonolithRoomTurnLoop = class {
21733
21916
  requestId: item.id,
21734
21917
  status: "failed",
21735
21918
  generationId: this.commandContext.generationId,
21736
- reason
21919
+ reason: reason.text,
21920
+ ...reason.kind ? { reasonKind: reason.kind } : {}
21737
21921
  });
21738
- await trace.finish("failed", reason);
21922
+ await trace.finish("failed", reason.text);
21739
21923
  throw error;
21740
21924
  } finally {
21741
21925
  this.busy = false;
@@ -21778,6 +21962,12 @@ var MonolithRoomTurnLoop = class {
21778
21962
  }
21779
21963
  }
21780
21964
  };
21965
+ function openCornerToolCall(calls) {
21966
+ return calls.find((call) => /(?:^|[._:/-])open_corner$/i.test(call.title ?? ""));
21967
+ }
21968
+ function openedACorner(call) {
21969
+ return !!call && isCompletedToolCall(call);
21970
+ }
21781
21971
  function roomMessagePrompt(author, body, attachments, delivered, harnessAcceptsImages = true) {
21782
21972
  const message = body.trim() || "(shared attachments)";
21783
21973
  const rendered = author ? `${author}: ${message}` : message;
@@ -21789,10 +21979,31 @@ var execFileAsync3 = promisify3(execFile4);
21789
21979
  var TOOL_ARGUMENT_MAX_BYTES = 1200;
21790
21980
  var TOOL_OUTPUT_MAX_BYTES = 3200;
21791
21981
  var TOOL_PATH_LIMIT = 12;
21792
- function cornerMergeInstruction(yoloMode) {
21982
+ function cornerMergeInstruction(yoloMode, reviewerHandle) {
21983
+ if (reviewerHandle)
21984
+ 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
21985
  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
21986
  }
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.";
21987
+ function cornerReviewerInstruction(input) {
21988
+ if (!input.reviewerHandle || !input.agentHandle || input.openedByAgent || input.agentHandle.replace(/^@/, "") !== input.reviewerHandle.replace(/^@/, ""))
21989
+ return void 0;
21990
+ const author = input.authorHandle?.replace(/^@/, "") || "author";
21991
+ const number = input.pullRequestNumber ?? "N";
21992
+ 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.`;
21993
+ }
21994
+ var CORNER_AUTHOR_CONTRACT = `The objective text is the user's ask. Keep it verbatim in your head and do not reinterpret it.
21995
+ Before any code, write its end-user story in one sentence: "a person who does X sees Y".
21996
+ 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.
21997
+ Do not write a fix before you have seen the defect. Turn the reproduction into the regression test.
21998
+ Before opening the pull request, produce Y against the built change: run the app or affected service from your branch and perform X.
21999
+ If no interactive surface is reachable, run the narrowest test or script that exercises the exact user path and prints the observable Y.
22000
+ A unit test of an inner function, a log line, or reading the code is not a demonstration.
22001
+ The pull request body MUST contain two sections with exactly these headings: ## Reproduced and ## Demonstrated.
22002
+ Under ## Reproduced, give the steps or command and what was observed; write "not a defect report" for feature work.
22003
+ Under ## Demonstrated, give the command or steps that produced Y and what was observed.
22004
+ A pull request without both sections is not deliverable and the Room's reviewer will fail it.
22005
+ Change only what the objective asks. No unrequested features, flags, compatibility shims, or refactors.`;
22006
+ 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
22007
  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
22008
  function isCornerChecksTurn(trigger, restates) {
21798
22009
  return Boolean(restates) || /\b(?:passed|failed) a check\b/i.test(trigger);
@@ -21977,6 +22188,10 @@ var MonolithCornerTurnLoop = class {
21977
22188
  pinnedProviderOverride;
21978
22189
  /** The merge authority baked into the current session. */
21979
22190
  yoloMode = false;
22191
+ /** The live parent-Room reviewer baked into the current session. */
22192
+ reviewerHandle;
22193
+ /** The role-specific second-chance instruction for this session. */
22194
+ cornerTurnEndNudge = CORNER_DELIVERY_NUDGE;
21980
22195
  /** Repository state already given a delivery reminder, until that state changes. */
21981
22196
  lastDeliveryNudgeState;
21982
22197
  turnIdentityInstructions = "";
@@ -22094,7 +22309,8 @@ var MonolithCornerTurnLoop = class {
22094
22309
  effort: configuration.effort ?? this.options.config.modelSelection?.effort,
22095
22310
  soul: configuration.soul ?? self?.soul,
22096
22311
  agentName: self?.name ?? this.agent.name,
22097
- yoloMode: configuration.yoloMode
22312
+ yoloMode: configuration.yoloMode,
22313
+ reviewerHandle: configuration.reviewerHandle
22098
22314
  });
22099
22315
  }
22100
22316
  async activate(trace) {
@@ -22114,9 +22330,30 @@ var MonolithCornerTurnLoop = class {
22114
22330
  effort: configuration.effort ?? this.options.config.modelSelection?.effort,
22115
22331
  soul: configuration.soul ?? self?.soul,
22116
22332
  agentName: self?.name ?? this.agent.name,
22117
- yoloMode: configuration.yoloMode
22333
+ yoloMode: configuration.yoloMode,
22334
+ reviewerHandle: configuration.reviewerHandle
22118
22335
  });
22119
22336
  this.yoloMode = configuration.yoloMode;
22337
+ this.reviewerHandle = configuration.reviewerHandle;
22338
+ const opener = this.options.openedBy ? roster.members.find((member) => member.identityId === this.options.openedBy) : void 0;
22339
+ const reviewerInput = {
22340
+ reviewerHandle: configuration.reviewerHandle,
22341
+ agentHandle: self?.handle,
22342
+ authorHandle: opener?.handle,
22343
+ openedByAgent: !this.options.openedBy || this.options.openedBy === this.agent.publicKey,
22344
+ yoloMode: configuration.yoloMode
22345
+ };
22346
+ let reviewerInstruction = cornerReviewerInstruction(reviewerInput);
22347
+ if (reviewerInstruction) {
22348
+ const restore = await this.options.api.execute("getCornerRestoreState", {
22349
+ cornerId: this.options.cornerId
22350
+ });
22351
+ reviewerInstruction = cornerReviewerInstruction({
22352
+ ...reviewerInput,
22353
+ pullRequestNumber: restore.lifecycle?.pr?.number
22354
+ });
22355
+ }
22356
+ this.cornerTurnEndNudge = reviewerInstruction ?? cornerMergeInstruction(configuration.yoloMode, configuration.reviewerHandle);
22120
22357
  await mkdir12(this.options.worktreePath, { recursive: true });
22121
22358
  const selection = configuration.model || configuration.effort ? { model: configuration.model, effort: configuration.effort } : this.options.config.modelSelection;
22122
22359
  const homeOverlay = this.options.config.agentHomeRoot ? await prepareRoomAgentHome({
@@ -22260,8 +22497,11 @@ var MonolithCornerTurnLoop = class {
22260
22497
  ...repository ? [
22261
22498
  `You are in an isolated git worktree on ${repository.featureBranch}, targeting ${repository.targetBranch}.`,
22262
22499
  `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),
22500
+ ...reviewerInstruction ? [reviewerInstruction] : [
22501
+ 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.',
22502
+ CORNER_AUTHOR_CONTRACT,
22503
+ cornerMergeInstruction(configuration.yoloMode, configuration.reviewerHandle)
22504
+ ],
22265
22505
  "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
22506
  "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
22507
  ] : [
@@ -22532,12 +22772,12 @@ ${trigger}`,
22532
22772
  const deliveryState = !checksTurn && this.options.repository ? await cornerUndeliveredRepositoryState(this.options.worktreePath, this.options.repository.featureBranch, this.options.repository.targetBranch) : void 0;
22533
22773
  const needsDeliveryNudge = deliveryState !== void 0 && deliveryState !== this.lastDeliveryNudgeState;
22534
22774
  let replyBeforeNudge = "";
22535
- if (!explained && (needsDeliveryNudge || checksTurn && this.yoloMode)) {
22775
+ if (!explained && (needsDeliveryNudge || checksTurn && (this.yoloMode || Boolean(this.reviewerHandle)))) {
22536
22776
  if (needsDeliveryNudge)
22537
22777
  this.lastDeliveryNudgeState = deliveryState;
22538
22778
  replyBeforeNudge = durableReplyText(result.agentText);
22539
22779
  await flushToolCalls(result.toolCalls, "");
22540
- result = await runPrompt(checksTurn ? CORNER_YOLO_MERGE_NUDGE : CORNER_DELIVERY_NUDGE);
22780
+ result = await runPrompt(this.reviewerHandle ? this.cornerTurnEndNudge : checksTurn ? CORNER_YOLO_MERGE_NUDGE : CORNER_DELIVERY_NUDGE);
22541
22781
  trace.promptSettled();
22542
22782
  explained = await this.explainEmpty(result);
22543
22783
  }
@@ -22589,9 +22829,10 @@ ${trigger}`,
22589
22829
  requestId,
22590
22830
  status: "failed",
22591
22831
  generationId: this.commandContext.generationId,
22592
- reason
22832
+ reason: reason.text,
22833
+ ...reason.kind ? { reasonKind: reason.kind } : {}
22593
22834
  });
22594
- await trace.finish("failed", reason);
22835
+ await trace.finish("failed", reason.text);
22595
22836
  throw error;
22596
22837
  } finally {
22597
22838
  this.busy = false;
@@ -23693,16 +23934,18 @@ var RoomRuntimeCoordinator = class {
23693
23934
  roomId: cornerId,
23694
23935
  limit: 50
23695
23936
  });
23696
- const asked = [...conversation.items].reverse().find((item) => item.type === "message" && item.mentionIds.includes(this.agent.publicKey));
23937
+ const asked = [...conversation.items].reverse().find((item) => item.type === "message" && item.authorId !== this.agent.publicKey);
23697
23938
  if (!asked)
23698
23939
  return;
23940
+ const reason = distillTurnFailureReason(error);
23699
23941
  await this.options.daemonApi.execute("postAgentTurnReceipt", {
23700
23942
  agentId: this.agent.publicKey,
23701
23943
  roomId: cornerId,
23702
23944
  requestId: asked.id,
23703
23945
  status: "failed",
23704
23946
  generationId: `${this.agent.publicKey}:${cornerId}`,
23705
- reason: distillTurnFailureReason(error)
23947
+ reason: reason.text,
23948
+ ...reason.kind ? { reasonKind: reason.kind } : {}
23706
23949
  });
23707
23950
  } catch (reportError) {
23708
23951
  console.error(`[thin-core] corner ${cornerId} start-failure report failed:`, reportError);
@@ -26613,7 +26856,8 @@ async function runStoredDaemon(pathOrPointer) {
26613
26856
  agentId: runtime.agent.publicKey,
26614
26857
  workspaceId: runtime.communityId,
26615
26858
  runtimeDir,
26616
- ...runtime.modelSelection ? { runtimeSelection: runtime.modelSelection } : {}
26859
+ ...runtime.modelSelection ? { runtimeSelection: runtime.modelSelection } : {},
26860
+ ...config.modelUnavailable ? { startupUnavailable: config.modelUnavailable.unavailable.label } : {}
26617
26861
  });
26618
26862
  },
26619
26863
  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.95",
4
4
  "description": "Connect an AI coding agent to Beeline with one command.",
5
5
  "homepage": "https://usebeeline.app",
6
6
  "bugs": {