usebeeline 0.0.122 → 0.0.123

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 +112 -54
  2. package/package.json +1 -1
@@ -16005,39 +16005,28 @@ function modelSelectionTargets(selection) {
16005
16005
  }
16006
16006
  ];
16007
16007
  }
16008
- function assertModelSelectionAdvertised(advertisedOptions, selection) {
16009
- for (const target of modelSelectionTargets(selection)) {
16010
- if (!target.value)
16011
- continue;
16012
- const axis = advertisedOptions.find((option) => target.categories.includes(option.category));
16013
- if (!axis) {
16014
- continue;
16015
- }
16016
- assertModelConfigAxisAllowed(axis.id, advertisedOptions);
16017
- if (!axis.options.some((choice) => choice.id === target.value)) {
16018
- throw new ModelSelectionUnavailableError({
16019
- label: target.label,
16020
- value: target.value,
16021
- reason: "not-advertised"
16022
- });
16023
- }
16024
- }
16025
- }
16026
16008
  function assertModelConfigAxisAllowed(configId, advertisedOptions) {
16027
16009
  const axis = advertisedOptions.find((option) => option.id === configId);
16028
16010
  if (!axis || !isAllowedAgentModelConfigCategory(axis.category)) {
16029
16011
  throw new DisallowedModelConfigOptionError(configId);
16030
16012
  }
16031
16013
  }
16032
- async function applyAgentModelSelection(client, sessionId, advertisedOptions, selection) {
16033
- assertModelSelectionAdvertised(advertisedOptions, selection);
16014
+ async function applyAgentModelSelectionWithUpdatedCatalog(client, sessionId, advertisedOptions, selection) {
16015
+ let currentOptions = advertisedOptions;
16034
16016
  for (const target of modelSelectionTargets(selection)) {
16035
16017
  if (!target.value)
16036
16018
  continue;
16037
- const axis = advertisedOptions.find((option) => target.categories.includes(option.category));
16019
+ const axis = currentOptions.find((option) => target.categories.includes(option.category));
16038
16020
  if (!axis)
16039
16021
  continue;
16040
- assertModelConfigAxisAllowed(axis.id, advertisedOptions);
16022
+ assertModelConfigAxisAllowed(axis.id, currentOptions);
16023
+ if (!axis.options.some((choice) => choice.id === target.value)) {
16024
+ throw new ModelSelectionUnavailableError({
16025
+ label: target.label,
16026
+ value: target.value,
16027
+ reason: "not-advertised"
16028
+ });
16029
+ }
16041
16030
  try {
16042
16031
  if (axis.id === GROK_SESSION_MODEL_AXIS_ID) {
16043
16032
  if (!client.setModel)
@@ -16048,7 +16037,10 @@ async function applyAgentModelSelection(client, sessionId, advertisedOptions, se
16048
16037
  throw new Error(`Grok started with reasoning effort "${axis.currentValue ?? "unknown"}", not "${target.value}"`);
16049
16038
  }
16050
16039
  } else {
16051
- await client.setConfigOption(sessionId, axis.id, target.value);
16040
+ const updated = await client.setConfigOption(sessionId, axis.id, target.value);
16041
+ const refreshed = filterAllowedModelConfigOptions(parseAdvertisedConfigOptions(updated, selection.model));
16042
+ if (refreshed.length > 0)
16043
+ currentOptions = refreshed;
16052
16044
  }
16053
16045
  } catch (error) {
16054
16046
  throw new ModelSelectionUnavailableError({
@@ -16059,6 +16051,10 @@ async function applyAgentModelSelection(client, sessionId, advertisedOptions, se
16059
16051
  });
16060
16052
  }
16061
16053
  }
16054
+ return currentOptions;
16055
+ }
16056
+ async function applyAgentModelSelection(client, sessionId, advertisedOptions, selection) {
16057
+ await applyAgentModelSelectionWithUpdatedCatalog(client, sessionId, advertisedOptions, selection);
16062
16058
  }
16063
16059
 
16064
16060
  // apps/body/dist/model-catalog.js
@@ -16109,27 +16105,34 @@ async function withAgentModelCatalog(agent, agentEnv, selection, inspect, limits
16109
16105
  async function fetchAgentModelCatalog(agent, agentEnv, selection, limits = {}) {
16110
16106
  return withAgentModelCatalog(agent, agentEnv, selection, async ({ client, sessionId, raw, catalog }) => ({
16111
16107
  raw,
16112
- catalog: await filterModelChoicesByLiveValidation(client, sessionId, catalog)
16108
+ catalog: await filterModelChoicesByLiveValidation(client, sessionId, catalog, selection?.model)
16113
16109
  }), limits);
16114
16110
  }
16115
- async function filterModelChoicesByLiveValidation(client, sessionId, catalog) {
16111
+ async function filterModelChoicesByLiveValidation(client, sessionId, catalog, preferredModelId) {
16116
16112
  const modelAxis = catalog.find((axis) => axis.category === "model");
16117
16113
  if (!modelAxis)
16118
16114
  return catalog;
16119
16115
  const available = [];
16116
+ const preferred = preferredModelId ?? modelAxis.currentValue;
16117
+ let preferredCatalog;
16120
16118
  for (const choice of modelAxis.options) {
16121
16119
  try {
16122
- await applyAgentModelSelection(client, sessionId, catalog, { model: choice.id });
16120
+ const updated = await applyAgentModelSelectionWithUpdatedCatalog(client, sessionId, catalog, {
16121
+ model: choice.id
16122
+ });
16123
16123
  available.push(choice);
16124
+ if (choice.id === preferred)
16125
+ preferredCatalog = updated;
16124
16126
  } catch {
16125
16127
  }
16126
16128
  }
16127
- return catalog.map((axis) => axis === modelAxis ? { ...axis, options: available } : axis);
16129
+ const effective = preferredCatalog ?? catalog;
16130
+ return effective.map((axis) => axis.category === "model" ? { ...axis, options: available } : axis);
16128
16131
  }
16129
16132
  async function validateAgentModelSelection(agent, agentEnv, selection) {
16130
16133
  return withAgentModelCatalog(agent, agentEnv, selection, async ({ client, sessionId, raw, catalog }) => {
16131
- await applyAgentModelSelection(client, sessionId, catalog, selection);
16132
- return { raw, catalog };
16134
+ const applied = await applyAgentModelSelectionWithUpdatedCatalog(client, sessionId, catalog, selection);
16135
+ return { raw, catalog: applied };
16133
16136
  });
16134
16137
  }
16135
16138
 
@@ -16215,7 +16218,7 @@ async function syncAgentModelCatalog(input) {
16215
16218
  const effectiveCatalog = input.startupUnavailable ? catalog : withEffectiveCurrentValues(catalog, selection);
16216
16219
  const hash = modelCatalogHash(effectiveCatalog, selection, input.startupUnavailable);
16217
16220
  const previous = await readFile(hashPath, "utf8").catch(() => "");
16218
- if (previous.trim() === hash)
16221
+ if (!input.force && previous.trim() === hash)
16219
16222
  return "unchanged";
16220
16223
  await input.api.execute("postAgentModelCatalog", {
16221
16224
  agentId: input.agentId,
@@ -31580,14 +31583,18 @@ export default async function (pi) {
31580
31583
  label: labelOf(tool.name),
31581
31584
  description: typeof tool.description === 'string' ? tool.description : tool.name,
31582
31585
  parameters: schema,
31583
- async execute(_toolCallId, params, signal) {
31586
+ async execute(toolCallId, params, signal) {
31584
31587
  const result = await callServer(
31585
31588
  server,
31586
31589
  {
31587
31590
  jsonrpc: '2.0',
31588
31591
  id: 1,
31589
31592
  method: 'tools/call',
31590
- params: { name: tool.name, arguments: params ?? {} },
31593
+ params: {
31594
+ name: tool.name,
31595
+ arguments: params ?? {},
31596
+ _meta: { beelineToolCallId: toolCallId },
31597
+ },
31591
31598
  },
31592
31599
  signal,
31593
31600
  );
@@ -32082,6 +32089,7 @@ function beelineAgentMcpServer(config, api, context) {
32082
32089
  { name: "BEELINE_DAEMON_ROOM_ID", value: context.roomId },
32083
32090
  { name: "BEELINE_DAEMON_WORKSPACE_ID", value: context.workspaceId },
32084
32091
  ...context.cornerId ? [{ name: "BEELINE_DAEMON_CORNER_ID", value: context.cornerId }] : [],
32092
+ ...context.agentMayCloseCorner ? [{ name: "BEELINE_CORNER_AGENT_CLOSE", value: "1" }] : [],
32085
32093
  ...context.reviewer ? [{ name: "BEELINE_CORNER_REVIEWER", value: "1" }] : [],
32086
32094
  ...context.attachRoot ? [{ name: "BEELINE_ATTACH_ROOT", value: context.attachRoot }] : [],
32087
32095
  ...context.attachScratchRoot ? [{ name: "BEELINE_ATTACH_SCRATCH_ROOT", value: context.attachScratchRoot }] : [],
@@ -34026,6 +34034,8 @@ function cornerReviewerInstruction(input) {
34026
34034
  const headSha = input.headSha ?? "<head sha>";
34027
34035
  return `Checks are green on PR #${number} at ${headSha}. Review it now with the beeline-review skill against that exact head. FAIL: reply \`@${author}\` with the confirmed findings to fix. PASS: call the approve_merge tool for ${headSha}, then reply \`@${author} approved ${headSha}, merge\`. Never merge yourself. Never say you are holding or waiting for checks.`;
34028
34036
  }
34037
+ var CORNER_REVIEWER_SESSION_INSTRUCTION = "You are this Room's configured reviewer. The active turn prompt names the latest stable green PR head. Review and approve only that exact head; if no stable green head is named, end the turn without a verdict. Never merge yourself.";
34038
+ var CORNER_REVIEWER_UNSTABLE_HEAD_INSTRUCTION = "There is no stable green PR head for this active reviewer turn. Do not review or call approve_merge. End this turn without a verdict; the next green transition will wake you.";
34029
34039
  function cornerSelfReviewerInstruction(input) {
34030
34040
  if (!input.reviewerHandle || !input.agentHandle || !input.openedByAgent || input.agentHandle.replace(/^@/, "") !== input.reviewerHandle.replace(/^@/, ""))
34031
34041
  return void 0;
@@ -34247,6 +34257,8 @@ var MonolithCornerTurnLoop = class {
34247
34257
  yoloMode = false;
34248
34258
  /** The live parent-Room reviewer baked into the current session. */
34249
34259
  reviewerHandle;
34260
+ /** Identity-only reviewer context; the exact PR head is refreshed inside each active turn. */
34261
+ reviewerInstructionInput;
34250
34262
  /** The role-specific second-chance instruction for this session. */
34251
34263
  cornerTurnEndNudge = CORNER_DELIVERY_NUDGE;
34252
34264
  /** Repository state already given a delivery reminder, until that state changes. */
@@ -34421,17 +34433,8 @@ var MonolithCornerTurnLoop = class {
34421
34433
  authorHandle: opener?.handle,
34422
34434
  openedByAgent: !this.options.openedBy || this.options.openedBy === this.agent.publicKey
34423
34435
  };
34424
- let reviewerInstruction = cornerReviewerInstruction(reviewerInput);
34425
- if (reviewerInstruction) {
34426
- const restore = await this.options.api.execute("getCornerRestoreState", {
34427
- cornerId: this.options.cornerId
34428
- });
34429
- reviewerInstruction = cornerReviewerInstruction({
34430
- ...reviewerInput,
34431
- pullRequestNumber: restore.lifecycle?.pr?.number,
34432
- headSha: restore.lifecycle?.pr?.headSha
34433
- });
34434
- }
34436
+ const reviewerInstruction = cornerReviewerInstruction(reviewerInput) ? CORNER_REVIEWER_SESSION_INSTRUCTION : void 0;
34437
+ this.reviewerInstructionInput = reviewerInstruction ? reviewerInput : void 0;
34435
34438
  const selfReviewerInstruction = cornerSelfReviewerInstruction(reviewerInput);
34436
34439
  this.cornerTurnEndNudge = reviewerInstruction ?? selfReviewerInstruction ?? cornerMergeInstruction(configuration.yoloMode, configuration.reviewerHandle);
34437
34440
  await mkdir14(this.options.worktreePath, { recursive: true });
@@ -34579,6 +34582,7 @@ var MonolithCornerTurnLoop = class {
34579
34582
  roomId: this.options.parentRoomId,
34580
34583
  workspaceId: this.options.workspaceId,
34581
34584
  cornerId: this.options.cornerId,
34585
+ agentMayCloseCorner: Boolean(repository),
34582
34586
  reviewer: Boolean(reviewerInstruction),
34583
34587
  attachRoot: this.options.worktreePath,
34584
34588
  // The whole per-session overlay, not an enumerated subset: see
@@ -34636,10 +34640,9 @@ var MonolithCornerTurnLoop = class {
34636
34640
  "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.",
34637
34641
  "Do not initialize a repository, create a branch, commit, push, open a pull request, or wait for GitHub checks.",
34638
34642
  // This lane has no pull request URL and no merge card, so its
34639
- // attached final reply is the requester-facing completion
34640
- // signal. close_corner is still the lifecycle completion: it
34641
- // archives the corner and reaps this scratch workspace.
34642
- this.options.requesterHandle ? `Deliver the result as artifacts: post_artifact everything the objective asked for, then call close_corner. Finish the turn by replying with @${this.options.requesterHandle} and one line on what you posted; that attached reply is the requester-facing completion signal.` : `Deliver the result as artifacts: post_artifact everything the objective asked for, then call close_corner. Finish the turn by replying with one line on what you posted; that attached reply is the requester-facing completion signal.`
34643
+ // attached final reply reports delivery to the requester. The
34644
+ // corner remains open until a human explicitly closes it.
34645
+ this.options.requesterHandle ? `Deliver the result as artifacts: post_artifact everything the objective asked for. Finish the turn by replying with @${this.options.requesterHandle} and one line on what you posted. The corner stays open until a human explicitly closes it.` : `Deliver the result as artifacts: post_artifact everything the objective asked for. Finish the turn by replying with one line on what you posted. The corner stays open until a human explicitly closes it.`
34643
34646
  ]
34644
34647
  ].filter(Boolean).join("\n\n")
34645
34648
  });
@@ -34652,6 +34655,41 @@ var MonolithCornerTurnLoop = class {
34652
34655
  }
34653
34656
  return opened.sessionId;
34654
34657
  }
34658
+ /**
34659
+ * Resolve the review target at prompt time, not session activation time.
34660
+ *
34661
+ * A push can land after a reviewer session starts but before its first
34662
+ * prompt, or while that prompt is running. The active prompt and its bounded
34663
+ * second pass therefore each read the current green lifecycle head. The
34664
+ * server still compares approve_merge's SHA with the current head, so a push
34665
+ * after this read fails closed too.
34666
+ */
34667
+ async activeReviewerInstruction() {
34668
+ const input = this.reviewerInstructionInput;
34669
+ if (!input)
34670
+ return void 0;
34671
+ try {
34672
+ const [restore, localHead] = await Promise.all([
34673
+ this.options.api.execute("getCornerRestoreState", {
34674
+ cornerId: this.options.cornerId
34675
+ }),
34676
+ execFileAsync4("git", ["-C", this.options.worktreePath, "rev-parse", "HEAD"]).then(({ stdout: stdout6 }) => stdout6.trim())
34677
+ ]);
34678
+ const pr = restore.lifecycle?.pr;
34679
+ if (restore.lifecycle?.checks !== "passing" || !pr?.number || !pr.headSha || pr.headSha !== localHead)
34680
+ return CORNER_REVIEWER_UNSTABLE_HEAD_INSTRUCTION;
34681
+ return [
34682
+ "Current stable reviewer target for this active turn; it supersedes any older head in the trigger or transcript:",
34683
+ cornerReviewerInstruction({
34684
+ ...input,
34685
+ pullRequestNumber: pr.number,
34686
+ headSha: pr.headSha
34687
+ })
34688
+ ].join("\n");
34689
+ } catch {
34690
+ return CORNER_REVIEWER_UNSTABLE_HEAD_INSTRUCTION;
34691
+ }
34692
+ }
34655
34693
  /** The scheduler seam: `queue-wait` closes when a slot buys a session. */
34656
34694
  lifecycle(trace) {
34657
34695
  return {
@@ -34757,10 +34795,11 @@ var MonolithCornerTurnLoop = class {
34757
34795
  throw new Error("corner turn stopped for daemon handoff");
34758
34796
  this.busy = true;
34759
34797
  await this.syncBranch();
34760
- const [conversation, roster, delivered] = await trace.measure("context-fetch", () => Promise.all([
34798
+ const [conversation, roster, delivered, activeReviewerInstruction] = await trace.measure("context-fetch", () => Promise.all([
34761
34799
  api.execute("getRoomConversation", { roomId: cornerId, limit: 200 }),
34762
34800
  this.roster(),
34763
- this.attachmentDir && attachments.length ? deliverAttachments(attachments, join12(this.attachmentDir, requestId.replace(/[^\w-]/g, "_")), this.options.fetchImpl) : Promise.resolve([])
34801
+ this.attachmentDir && attachments.length ? deliverAttachments(attachments, join12(this.attachmentDir, requestId.replace(/[^\w-]/g, "_")), this.options.fetchImpl) : Promise.resolve([]),
34802
+ this.activeReviewerInstruction()
34764
34803
  ]));
34765
34804
  const names = new Map(roster.members.map((member) => [member.identityId, member.name]));
34766
34805
  const requestedBy = requester && !requester.name && names.get(requester.pubkey) ? { ...requester, name: names.get(requester.pubkey) } : requester;
@@ -34779,13 +34818,14 @@ var MonolithCornerTurnLoop = class {
34779
34818
  ${this.options.objective}`,
34780
34819
  WarmTranscript.render(this.warmTranscript.select(this.sessionId, transcriptRows), "Corner transcript:", "New in the corner since your last turn (the earlier transcript is already in this session):"),
34781
34820
  roomMentionDirectory(roster, this.agent.publicKey),
34821
+ activeReviewerInstruction,
34782
34822
  [
34783
34823
  ...sourceMessageId ? [`Reaction target message id: ${sourceMessageId}`] : [],
34784
34824
  `Newest trigger:
34785
34825
  ${trigger}`,
34786
34826
  ...attachmentPromptLines(attachments, delivered, this.acceptsImages())
34787
34827
  ].join("\n"),
34788
- this.options.repository ? "Continue the objective. Obey the PR checks and human hold rules in your session instructions." : "Continue the objective. Attach completed files before calling close_corner.",
34828
+ this.options.repository ? "Continue the objective. Obey the PR checks and human hold rules in your session instructions." : "Continue the objective. Attach completed files; only a human can close this corner.",
34789
34829
  MAINTAIN_ASSIGNED_IDENTITY_DIRECTIVE
34790
34830
  ].filter(Boolean).join("\n\n");
34791
34831
  const stream = new AgentTurnStream({
@@ -34926,7 +34966,9 @@ ${trigger}`,
34926
34966
  this.lastDeliveryNudgeState = deliveryState;
34927
34967
  replyBeforeNudge = durableReplyText(result.agentText);
34928
34968
  await flushToolCalls(result.toolCalls, "");
34929
- result = await runPrompt(this.reviewerHandle ? this.cornerTurnEndNudge : checksTurn ? CORNER_YOLO_MERGE_NUDGE : CORNER_DELIVERY_NUDGE);
34969
+ if (this.reviewerInstructionInput)
34970
+ await this.syncBranch();
34971
+ result = await runPrompt(this.reviewerHandle ? await this.activeReviewerInstruction() ?? this.cornerTurnEndNudge : checksTurn ? CORNER_YOLO_MERGE_NUDGE : CORNER_DELIVERY_NUDGE);
34930
34972
  trace.promptSettled();
34931
34973
  explained = await this.explainEmpty(result);
34932
34974
  }
@@ -35784,6 +35826,7 @@ var RoomRuntimeCoordinator = class {
35784
35826
  });
35785
35827
  this.options.daemonApi.setConfigChangedListener?.(() => {
35786
35828
  void this.scheduler.suspendIdle().catch((error) => console.error("[body] config-change session restart failed", error));
35829
+ void Promise.resolve(this.options.onConfigChanged?.()).catch((error) => console.error("[body] config-change catalog refresh failed", error));
35787
35830
  });
35788
35831
  this.options.daemonApi.setHiccupRestartListener?.((attempt) => {
35789
35832
  this.options.onHiccupRestart?.(attempt);
@@ -38211,8 +38254,6 @@ async function collectConnectWizard(prompts = clackPrompts, loadModels = loadCon
38211
38254
  return (picked ?? "").trim();
38212
38255
  };
38213
38256
  const askEffort = async (catalog2, forHarness, model2, forProvider, forKey) => {
38214
- if (!catalog2.effort)
38215
- return void 0;
38216
38257
  let axis = catalog2.effort;
38217
38258
  if (model2 !== catalog2.currentValue) {
38218
38259
  const reread = await loadModels({
@@ -38224,7 +38265,7 @@ async function collectConnectWizard(prompts = clackPrompts, loadModels = loadCon
38224
38265
  }).catch(() => void 0);
38225
38266
  axis = reread?.effort ?? axis;
38226
38267
  }
38227
- if (!axis.options.length)
38268
+ if (!axis?.options.length)
38228
38269
  return void 0;
38229
38270
  const picked = await prompts.select({
38230
38271
  message: brass("Choose reasoning effort"),
@@ -39394,10 +39435,27 @@ async function runStoredDaemon(pathOrPointer) {
39394
39435
  scratchSweepTimer.unref();
39395
39436
  let ready = false;
39396
39437
  let connectorLoop;
39438
+ let catalogRefresh;
39439
+ const refreshCatalog = () => {
39440
+ catalogRefresh ??= syncAgentModelCatalog({
39441
+ api: daemonApi,
39442
+ agent,
39443
+ agentEnv: config.agentEnv,
39444
+ agentId: runtime.agent.publicKey,
39445
+ workspaceId: runtime.communityId,
39446
+ runtimeDir,
39447
+ ...runtime.modelSelection ? { runtimeSelection: runtime.modelSelection } : {},
39448
+ force: true
39449
+ }).then(() => void 0).finally(() => {
39450
+ catalogRefresh = void 0;
39451
+ });
39452
+ return catalogRefresh;
39453
+ };
39397
39454
  let stoppingStatus = "daemon stopped";
39398
39455
  try {
39399
39456
  const core = new ThinDaemonCore(runtime, configPath, config, {
39400
39457
  daemonApi,
39458
+ onConfigChanged: refreshCatalog,
39401
39459
  onHiccupRestart: (attempt) => {
39402
39460
  const delay = hiccupBackoffMs(attempt);
39403
39461
  console.warn(`[thin-core] hiccup restart attempt ${attempt}; exiting so systemd can start a fresh helper`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usebeeline",
3
- "version": "0.0.122",
3
+ "version": "0.0.123",
4
4
  "description": "Connect an AI coding agent to Beeline with one command.",
5
5
  "homepage": "https://usebeeline.app",
6
6
  "bugs": {