usebeeline 0.0.84 → 0.0.91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -137,7 +137,7 @@ Two MCP surfaces are mounted into every agent session.
137
137
  | Tool | Where | What it does |
138
138
  | ------------------------------------------------------ | --------------- | ------------------------------------------------------------- |
139
139
  | `open_corner` | Top-level Rooms | Open one write-enabled corner with a ≤24-word objective |
140
- | `pr_checks_status` | Corners | Read the server-posted checks verdict and human hold state |
140
+ | `pr_checks_status` | Corners | Read checks, human hold, and PR/head-bound merge approval |
141
141
  | `attach_file` | Everywhere | Attach one file from the checkout or scratch dir to the reply |
142
142
  | `create_schedule`, `list_schedules`, `delete_schedule` | Everywhere | Run a prompt again later — interval minutes or a 5-field cron |
143
143
  | `request_grant` | Everywhere | Ask the owner for reach outside the sandbox |
@@ -19777,7 +19777,8 @@ function sessionConfigFingerprint(input) {
19777
19777
  input.effort ?? "",
19778
19778
  input.soul?.name ?? "",
19779
19779
  input.soul?.instructions ?? "",
19780
- input.agentName ?? ""
19780
+ input.agentName ?? "",
19781
+ input.yoloMode ?? false
19781
19782
  ]);
19782
19783
  }
19783
19784
 
@@ -19968,9 +19969,6 @@ export default async function (pi) {
19968
19969
  import { execFile as execFile3 } from "node:child_process";
19969
19970
  import { promisify as promisify2 } from "node:util";
19970
19971
  var execFileAsync2 = promisify2(execFile3);
19971
- var CornerBranchDivergedError = class extends Error {
19972
- name = "CornerBranchDivergedError";
19973
- };
19974
19972
  async function syncCornerBranch(input) {
19975
19973
  const git = input.git ?? (async (args) => (await execFileAsync2("git", ["-C", input.worktreePath, ...args], {
19976
19974
  ...input.env ? { env: input.env } : {},
@@ -19996,22 +19994,15 @@ async function syncCornerBranch(input) {
19996
19994
  try {
19997
19995
  await git(behind ? ["merge", "--ff-only", remote] : ["rebase", remote]);
19998
19996
  return behind ? "fast-forwarded" : "rebased";
19999
- } catch (error) {
19997
+ } catch {
20000
19998
  await git(["rebase", "--abort"]).catch(() => void 0);
20001
- throw new CornerBranchDivergedError(divergedReason(input.featureBranch, error));
19999
+ await git(["reset", "--hard", remote]);
20000
+ return "realigned";
20002
20001
  }
20003
20002
  }
20004
20003
  async function contains(git, head, ancestor) {
20005
20004
  return git(["merge-base", "--is-ancestor", ancestor, head]).then(() => true, () => false);
20006
20005
  }
20007
- function divergedReason(featureBranch, error) {
20008
- const detail = String(error?.stderr ?? "").split("\n").map((line) => line.trim()).find((line) => /conflict|could not|error:/i.test(line));
20009
- return [
20010
- `could not rebase onto origin/${featureBranch}: another agent pushed to this corner's branch`,
20011
- detail ? ` (${detail})` : "",
20012
- ". Nothing was pushed; the branch needs a person."
20013
- ].join("").slice(0, 200);
20014
- }
20015
20006
 
20016
20007
  // apps/body/dist/room-session.js
20017
20008
  import { resolve as resolve15 } from "node:path";
@@ -20845,12 +20836,11 @@ function roomMentionDirectory(roster, selfId) {
20845
20836
  if (member.identityId === selfId)
20846
20837
  continue;
20847
20838
  const handle = member.handle?.trim().replace(/^@/, "");
20848
- const name = member.name?.trim() ?? "";
20849
- const alias = handle || name;
20850
- if (!alias)
20839
+ if (!handle)
20851
20840
  continue;
20841
+ const name = member.name?.trim() ?? "";
20852
20842
  const kind = member.kind === "agent" ? "agent" : "person";
20853
- rows.push(`- @${alias}${name && name !== alias ? ` \u2014 ${name}` : ""} (${kind})`);
20843
+ rows.push(`- @${handle}${name && name !== handle ? ` \u2014 ${name}` : ""} (${kind})`);
20854
20844
  }
20855
20845
  if (!rows.length)
20856
20846
  return "";
@@ -20861,47 +20851,6 @@ function roomMentionDirectory(roster, selfId) {
20861
20851
  "Write a tag exactly as spelled here. An @name spelled any other way is plain text: it reaches nobody, and nobody is told it was meant for them. Never invent a handle, shorten one, or copy an @name out of the conversation \u2014 old messages carry spellings that no longer exist."
20862
20852
  ].join("\n");
20863
20853
  }
20864
- function escapeRegExp(value) {
20865
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
20866
- }
20867
- function agentReplyMentionIds(text2, roster, authorId) {
20868
- const aliases = /* @__PURE__ */ new Map();
20869
- for (const member of roster.members) {
20870
- if (member.identityId === authorId)
20871
- continue;
20872
- const rawAliases = member.kind === "agent" ? [member.handle] : [member.name, member.handle, member.soul?.name];
20873
- for (const raw of rawAliases) {
20874
- const display = raw?.trim().replace(/^@/, "");
20875
- if (!display)
20876
- continue;
20877
- const key = display.toLocaleLowerCase();
20878
- const entry = aliases.get(key) ?? { display, ids: /* @__PURE__ */ new Set() };
20879
- entry.ids.add(member.identityId);
20880
- aliases.set(key, entry);
20881
- }
20882
- }
20883
- for (const member of roster.members) {
20884
- if (member.identityId === authorId || member.kind === "agent" || !member.handle)
20885
- continue;
20886
- const handle = member.handle.trim().replace(/^@/, "").toLocaleLowerCase();
20887
- const canonical = aliases.get(handle);
20888
- const legacy = `a_${handle}`;
20889
- if (canonical && !aliases.has(legacy))
20890
- aliases.set(legacy, { ...canonical, display: legacy });
20891
- }
20892
- const mentioned = [];
20893
- for (const { display, ids } of [...aliases.values()].sort((left, right) => right.display.length - left.display.length)) {
20894
- if (ids.size !== 1)
20895
- continue;
20896
- const pattern = new RegExp(`(^|[\\s([{])@${escapeRegExp(display)}(?=$|[\\s.,!?;:)\\]}])`, "iu");
20897
- if (!pattern.test(text2))
20898
- continue;
20899
- const identityId = [...ids][0];
20900
- if (!mentioned.includes(identityId))
20901
- mentioned.push(identityId);
20902
- }
20903
- return mentioned;
20904
- }
20905
20854
  var MonolithRoomTurnLoop = class {
20906
20855
  options;
20907
20856
  commandContext;
@@ -21487,10 +21436,8 @@ var MonolithRoomTurnLoop = class {
21487
21436
  if (openCornerCall && !isFailedToolCall(openCornerCall)) {
21488
21437
  reply = stripCornerOpenEcho(reply);
21489
21438
  }
21490
- const mentionIds = reply ? agentReplyMentionIds(reply, roster, this.agent.publicKey) : [];
21491
21439
  await trace.measure("publish", () => stream.settle(reply, reply ? {
21492
- triggerMessageId: item.id,
21493
- mentionIds
21440
+ triggerMessageId: item.id
21494
21441
  } : {}));
21495
21442
  }, { priority: "interactive", roomKey: this.options.roomId });
21496
21443
  }, (error) => console.error(`[thin-core] monolith Room ${this.options.roomId} receipt heartbeat failed:`, error));
@@ -21571,6 +21518,9 @@ var execFileAsync3 = promisify3(execFile4);
21571
21518
  var TOOL_ARGUMENT_MAX_BYTES = 1200;
21572
21519
  var TOOL_OUTPUT_MAX_BYTES = 3200;
21573
21520
  var TOOL_PATH_LIMIT = 12;
21521
+ function cornerMergeInstruction(yoloMode) {
21522
+ 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.";
21523
+ }
21574
21524
  function oneLine(value) {
21575
21525
  return value.replace(/\s+/g, " ").trim();
21576
21526
  }
@@ -21831,7 +21781,8 @@ var MonolithCornerTurnLoop = class {
21831
21781
  model: configuration.model ?? this.options.config.modelSelection?.model,
21832
21782
  effort: configuration.effort ?? this.options.config.modelSelection?.effort,
21833
21783
  soul: configuration.soul ?? self?.soul,
21834
- agentName: self?.name ?? this.agent.name
21784
+ agentName: self?.name ?? this.agent.name,
21785
+ yoloMode: configuration.yoloMode
21835
21786
  });
21836
21787
  }
21837
21788
  async activate(trace) {
@@ -21850,7 +21801,8 @@ var MonolithCornerTurnLoop = class {
21850
21801
  model: configuration.model ?? this.options.config.modelSelection?.model,
21851
21802
  effort: configuration.effort ?? this.options.config.modelSelection?.effort,
21852
21803
  soul: configuration.soul ?? self?.soul,
21853
- agentName: self?.name ?? this.agent.name
21804
+ agentName: self?.name ?? this.agent.name,
21805
+ yoloMode: configuration.yoloMode
21854
21806
  });
21855
21807
  await mkdir11(this.options.worktreePath, { recursive: true });
21856
21808
  const selection = configuration.model || configuration.effort ? { model: configuration.model, effort: configuration.effort } : this.options.config.modelSelection;
@@ -21981,16 +21933,11 @@ var MonolithCornerTurnLoop = class {
21981
21933
  personaInstructions,
21982
21934
  ...repository ? [
21983
21935
  `You are in an isolated git worktree on ${repository.featureBranch}, targeting ${repository.targetBranch}.`,
21984
- "Work normally with the full coding tools. Commit and push only this feature branch. Use gh to open its pull request.",
21985
- `This corner is shared: any of its member agents may be addressed in it and work on ${repository.featureBranch}. Run git pull --rebase origin ${repository.featureBranch} before you push, and never force-push it.`,
21986
- "PR-opening turn rule: as soon as a pull request exists, print its full GitHub URL as your final response and end the turn immediately. Do not call pr_checks_status in that same turn and do not wait for checks inside it. Then stay idle until a later corner fact or human message starts another turn.",
21987
- 'Never merge because local tests pass or because gh reports passing checks. On a later turn triggered by a server-posted checks-passed note, call beeline-agent pr_checks_status. Merge only when it returns checks="passed", held=false, and approvalPending=false.',
21988
- "Merge the PR yourself only after the checks-passed event shows every check green; if any check failed or is still running, say exactly which and stop - never merge red.",
21989
- "If any human in this corner says hold or do not merge, do not merge until a later human explicitly resumes it.",
21936
+ `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.`,
21937
+ '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.',
21938
+ cornerMergeInstruction(configuration.yoloMode),
21990
21939
  "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.",
21991
- 'GitHub check and merge notes are server lines already in the corner: never restate them (no "checks passed", "CI is green", "PR ready for review"). On a checks turn, say nothing unless you act - a merge or a pushed fix - and then one short line about that.',
21992
- "A human approval in the app asks the server to merge. When approval is pending, wait for the server close request instead of racing it with gh. If checks passed, no hold exists, and no approval is pending, merge the pull request yourself with gh.",
21993
- "Never push directly to the target branch. Never merge a different pull request."
21940
+ "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."
21994
21941
  ] : [
21995
21942
  "This is a chat-only corner with no repository or GitHub workflow.",
21996
21943
  "Work in this corner's writable workspace. Use write_scratch_file or ordinary tools to create files, then attach_file to send them back to the corner.",
@@ -22276,10 +22223,8 @@ ${trigger}`,
22276
22223
  console.warn(`[thin-core] corner ${cornerId} turn ${requestId}: ${explained.reason}`);
22277
22224
  }
22278
22225
  const durableReply = spoken(reply);
22279
- const mentionIds = durableReply ? agentReplyMentionIds(durableReply, roster, this.agent.publicKey) : [];
22280
22226
  await trace.measure("publish", () => stream.settle(durableReply, durableReply ? {
22281
- ...requestedById ? { triggerMessageId: requestId } : {},
22282
- mentionIds
22227
+ ...requestedById ? { triggerMessageId: requestId } : {}
22283
22228
  } : {}));
22284
22229
  }, { priority: "interactive", roomKey: cornerId });
22285
22230
  }, (error) => console.error(`[thin-core] corner ${cornerId} receipt heartbeat failed:`, error));
@@ -22320,8 +22265,8 @@ ${trigger}`,
22320
22265
  * The branch is the shared artifact: another member agent may have pushed to
22321
22266
  * it since this helper last looked, and a first touch of a corner this
22322
22267
  * helper did not open starts from whatever `room-runtime.ts` restored. A
22323
- * divergence this cannot rebase away is raised, not pushed over — the turn
22324
- * fails with that sentence and the server inscribes it in the corner.
22268
+ * conflicting local commits are realigned to that remote branch so the
22269
+ * fixed objective can be attempted again without clobbering a sibling push.
22325
22270
  */
22326
22271
  async syncBranch() {
22327
22272
  const repository = this.options.repository;
@@ -22781,6 +22726,7 @@ var ROOM_JOIN_CONCURRENCY = 4;
22781
22726
  var DEFAULT_ROOM_WATCHDOG_STALE_MS = 9e4;
22782
22727
  var DEFAULT_RECONCILE_HEARTBEAT_MS = 6e4;
22783
22728
  var DEFAULT_DRAIN_DEADLINE_MS = 30 * 6e4;
22729
+ var CORNER_BRANCH_DELETE_ATTEMPTS = 3;
22784
22730
  function shouldPostInitialCornerWorkingState(restore, isOpener = true) {
22785
22731
  return isOpener && !restore.featureBranch && !restore.lifecycle?.branch && !restore.lifecycle?.pr;
22786
22732
  }
@@ -22873,6 +22819,52 @@ async function mapWithConcurrency(values, limit, visit) {
22873
22819
  }
22874
22820
  }));
22875
22821
  }
22822
+ async function removeCornerWorktreeAndBranches(worktree) {
22823
+ const localRef = `refs/heads/${worktree.branch}`;
22824
+ const remoteRef = `refs/heads/${worktree.branch}`;
22825
+ const worktreeExists = existsSync4(worktree.path);
22826
+ const localExists = await gitRefExists(worktree.gitCommonDir, localRef);
22827
+ if (worktreeExists) {
22828
+ const checkedOut = await execFileAsync4("git", [
22829
+ "-C",
22830
+ worktree.path,
22831
+ "symbolic-ref",
22832
+ "--quiet",
22833
+ "HEAD"
22834
+ ]);
22835
+ if (checkedOut.stdout.trim() !== localRef) {
22836
+ throw new Error(`corner worktree branch mismatch: expected ${localRef}`);
22837
+ }
22838
+ await execFileAsync4("git", [
22839
+ `--git-dir=${worktree.gitCommonDir}`,
22840
+ "worktree",
22841
+ "remove",
22842
+ "--force",
22843
+ worktree.path
22844
+ ]);
22845
+ } else if (!localExists) {
22846
+ await deleteExactRemoteBranch(worktree.gitCommonDir, remoteRef, worktree.token, {
22847
+ requireAbsent: true
22848
+ });
22849
+ return;
22850
+ } else {
22851
+ const head = await repositoryHeadRef(worktree.gitCommonDir);
22852
+ if (head === localRef) {
22853
+ throw new Error(`refusing to delete repository HEAD ${localRef}`);
22854
+ }
22855
+ }
22856
+ await deleteExactRemoteBranch(worktree.gitCommonDir, remoteRef, worktree.token);
22857
+ if (await gitRefExists(worktree.gitCommonDir, localRef)) {
22858
+ await execFileAsync4("git", [
22859
+ `--git-dir=${worktree.gitCommonDir}`,
22860
+ "branch",
22861
+ "--delete",
22862
+ "--force",
22863
+ "--",
22864
+ worktree.branch
22865
+ ]);
22866
+ }
22867
+ }
22876
22868
  var execFileAsync4 = promisify4(execFile5);
22877
22869
  async function removeCornerScratchWorkspace(input) {
22878
22870
  const expected = resolve19(input.roomRoot, "scratch");
@@ -22887,6 +22879,8 @@ var RoomRuntimeCoordinator = class {
22887
22879
  options;
22888
22880
  runtime;
22889
22881
  running = /* @__PURE__ */ new Map();
22882
+ /** Close/reconcile leftovers retried until local and remote refs are gone. */
22883
+ pendingCornerReaps = /* @__PURE__ */ new Map();
22890
22884
  startingCorners = /* @__PURE__ */ new Set();
22891
22885
  /** Corners whose start failure has already been said out loud, once each. */
22892
22886
  reportedCornerStartFailures = /* @__PURE__ */ new Set();
@@ -23029,6 +23023,7 @@ var RoomRuntimeCoordinator = class {
23029
23023
  });
23030
23024
  for (const channelId of desired)
23031
23025
  this.roomRemovalConfirmations.delete(channelId);
23026
+ await this.retryPendingCornerReaps(desired);
23032
23027
  for (const [channelId, running] of [...this.running]) {
23033
23028
  if (desired.has(channelId))
23034
23029
  continue;
@@ -23040,10 +23035,15 @@ var RoomRuntimeCoordinator = class {
23040
23035
  }
23041
23036
  running.controller.abort();
23042
23037
  await running.promise.catch(() => void 0);
23043
- if (running.worktree)
23044
- await this.reapCornerWorktree(running.worktree);
23045
- else if (running.scratch)
23046
- await this.reapCornerScratch(running.scratch);
23038
+ try {
23039
+ if (running.worktree)
23040
+ await this.reapCornerWorktree(running.worktree);
23041
+ else if (running.scratch)
23042
+ await this.reapCornerScratch(running.scratch);
23043
+ } catch (error) {
23044
+ console.error(`[thin-core] corner ${channelId} cleanup failed; will retry:`, error);
23045
+ this.confirmationPending = true;
23046
+ }
23047
23047
  }
23048
23048
  await mapWithConcurrency(desiredTopRooms, ROOM_JOIN_CONCURRENCY, async (roomId) => {
23049
23049
  if (!this.running.has(roomId))
@@ -23182,18 +23182,10 @@ var RoomRuntimeCoordinator = class {
23182
23182
  return;
23183
23183
  this.startingCorners.add(corner.cornerId);
23184
23184
  try {
23185
- const [restore, repository, conversation] = await Promise.all([
23185
+ const [restore, repository] = await Promise.all([
23186
23186
  this.options.daemonApi.execute("getCornerRestoreState", { cornerId: corner.cornerId }),
23187
23187
  this.options.daemonApi.execute("getRoomRepositoryState", {
23188
23188
  roomId: corner.parentRoomId
23189
- }),
23190
- // Startup recovers the objective from the corner's FIRST durable
23191
- // message, so this is the one conversation read that wants the oldest
23192
- // end of the Room. Every other read defaults to the newest page.
23193
- this.options.daemonApi.execute("getRoomConversation", {
23194
- roomId: corner.cornerId,
23195
- limit: 200,
23196
- window: "earliest"
23197
23189
  })
23198
23190
  ]);
23199
23191
  if (repository.resolution === "unverified") {
@@ -23202,9 +23194,9 @@ var RoomRuntimeCoordinator = class {
23202
23194
  if (repository.resolution === "repository" && (!repository.remote || !repository.key)) {
23203
23195
  throw new Error("corner parent Room has an incomplete repository binding");
23204
23196
  }
23205
- const objective = conversation.items.find((item) => item.type === "message")?.body.trim();
23197
+ const objective = restore.objective.trim();
23206
23198
  if (!objective)
23207
- throw new Error("corner has no durable objective post");
23199
+ throw new Error("corner has no authoritative objective fact");
23208
23200
  const repositoryBacked = repository.resolution === "repository";
23209
23201
  const targetBranch = repositoryBacked ? repository.targetBranch || "main" : void 0;
23210
23202
  const featureBranch = repositoryBacked ? restore.featureBranch ?? `feature/corner-${corner.cornerId.replaceAll("-", "").slice(0, 12)}` : void 0;
@@ -23260,7 +23252,9 @@ var RoomRuntimeCoordinator = class {
23260
23252
  onCloseRequested: () => worktree ? this.reapCornerWorktree({
23261
23253
  ...worktree,
23262
23254
  cornerId: corner.cornerId,
23263
- branch: featureBranch
23255
+ branch: featureBranch,
23256
+ parentRoomId: corner.parentRoomId,
23257
+ token: ""
23264
23258
  }) : this.reapCornerScratch({ path: workspacePath, cornerId: corner.cornerId })
23265
23259
  });
23266
23260
  const promise = loop.run().catch((error) => {
@@ -23283,7 +23277,9 @@ var RoomRuntimeCoordinator = class {
23283
23277
  worktree: {
23284
23278
  ...worktree,
23285
23279
  cornerId: corner.cornerId,
23286
- branch: featureBranch
23280
+ branch: featureBranch,
23281
+ parentRoomId: corner.parentRoomId,
23282
+ token: ""
23287
23283
  }
23288
23284
  } : {},
23289
23285
  ...!worktree ? { scratch: { path: workspacePath, cornerId: corner.cornerId } } : {}
@@ -23339,22 +23335,40 @@ var RoomRuntimeCoordinator = class {
23339
23335
  committer: { name: this.agent.name, publicKey: this.agent.publicKey }
23340
23336
  });
23341
23337
  }
23338
+ async retryPendingCornerReaps(desired) {
23339
+ for (const [cornerId, worktree] of [...this.pendingCornerReaps]) {
23340
+ if (desired.has(cornerId)) {
23341
+ this.pendingCornerReaps.delete(cornerId);
23342
+ continue;
23343
+ }
23344
+ try {
23345
+ await this.reapCornerWorktree(worktree);
23346
+ } catch (error) {
23347
+ console.error(`[thin-core] corner ${cornerId} branch cleanup retry failed:`, error);
23348
+ this.confirmationPending = true;
23349
+ }
23350
+ }
23351
+ }
23342
23352
  async reapCornerWorktree(worktree) {
23343
- if (existsSync4(worktree.path)) {
23344
- await execFileAsync4("git", [
23345
- `--git-dir=${worktree.gitCommonDir}`,
23346
- "worktree",
23347
- "remove",
23348
- "--force",
23349
- worktree.path
23350
- ]);
23353
+ try {
23354
+ const parentRoomId = worktree.parentRoomId;
23355
+ if (!parentRoomId) {
23356
+ throw new Error(`corner ${worktree.cornerId} has no parent Room for GitHub token`);
23357
+ }
23358
+ const token = (await this.options.daemonApi.execute("getRoomGitHubToken", { roomId: parentRoomId })).token;
23359
+ await removeCornerWorktreeAndBranches({ ...worktree, token });
23360
+ await this.options.daemonApi.execute("postCornerRemoteState", {
23361
+ cornerId: worktree.cornerId,
23362
+ branch: worktree.branch,
23363
+ state: "gone",
23364
+ checks: "unknown"
23365
+ });
23366
+ this.pendingCornerReaps.delete(worktree.cornerId);
23367
+ } catch (error) {
23368
+ this.pendingCornerReaps.set(worktree.cornerId, { ...worktree, token: "" });
23369
+ this.confirmationPending = true;
23370
+ throw error;
23351
23371
  }
23352
- await this.options.daemonApi.execute("postCornerRemoteState", {
23353
- cornerId: worktree.cornerId,
23354
- branch: worktree.branch,
23355
- state: "gone",
23356
- checks: "unknown"
23357
- });
23358
23372
  }
23359
23373
  async reapCornerScratch(scratch) {
23360
23374
  await removeCornerScratchWorkspace({
@@ -23437,6 +23451,68 @@ function githubGitEnv(token) {
23437
23451
  GIT_TERMINAL_PROMPT: "0"
23438
23452
  };
23439
23453
  }
23454
+ async function gitRefExists(gitCommonDir, ref) {
23455
+ try {
23456
+ await execFileAsync4("git", [`--git-dir=${gitCommonDir}`, "show-ref", "--verify", "--quiet", ref]);
23457
+ return true;
23458
+ } catch (error) {
23459
+ if (error.code === 1)
23460
+ return false;
23461
+ throw error;
23462
+ }
23463
+ }
23464
+ async function repositoryHeadRef(gitCommonDir) {
23465
+ try {
23466
+ const head = await execFileAsync4("git", [
23467
+ `--git-dir=${gitCommonDir}`,
23468
+ "symbolic-ref",
23469
+ "--quiet",
23470
+ "HEAD"
23471
+ ]);
23472
+ return head.stdout.trim() || void 0;
23473
+ } catch {
23474
+ return void 0;
23475
+ }
23476
+ }
23477
+ function gitStderr(error) {
23478
+ return typeof error === "object" && error && "stderr" in error ? String(error.stderr ?? "") : "";
23479
+ }
23480
+ function isRemoteRefMissing(error) {
23481
+ return /remote ref does not exist|remote reference does not exist/i.test(`${error instanceof Error ? error.message : String(error)}
23482
+ ${gitStderr(error)}`);
23483
+ }
23484
+ async function deleteExactRemoteBranch(gitCommonDir, remoteRef, token, options = {}) {
23485
+ const authEnv = githubGitEnv(token);
23486
+ const listRemote = () => execFileAsync4("git", [`--git-dir=${gitCommonDir}`, "ls-remote", "--heads", "origin", remoteRef], {
23487
+ env: authEnv,
23488
+ maxBuffer: 4 * 1024 * 1024
23489
+ });
23490
+ if (options.requireAbsent) {
23491
+ const remote = await listRemote();
23492
+ if (remote.stdout.trim()) {
23493
+ throw new Error(`cannot delete ${remoteRef} without the local ref that proves this corner owns it`);
23494
+ }
23495
+ return;
23496
+ }
23497
+ let remoteError;
23498
+ for (let attempt = 1; attempt <= CORNER_BRANCH_DELETE_ATTEMPTS; attempt += 1) {
23499
+ try {
23500
+ const remote = await listRemote();
23501
+ if (remote.stdout.trim()) {
23502
+ await execFileAsync4("git", [`--git-dir=${gitCommonDir}`, "push", "origin", `:${remoteRef}`], {
23503
+ env: authEnv,
23504
+ maxBuffer: 4 * 1024 * 1024
23505
+ });
23506
+ }
23507
+ return;
23508
+ } catch (error) {
23509
+ if (isRemoteRefMissing(error))
23510
+ return;
23511
+ remoteError = error;
23512
+ }
23513
+ }
23514
+ throw remoteError;
23515
+ }
23440
23516
 
23441
23517
  // apps/body/dist/thin-core.js
23442
23518
  async function waitForNextTick(ms, signal) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usebeeline",
3
- "version": "0.0.84",
3
+ "version": "0.0.91",
4
4
  "description": "Connect an AI coding agent to Beeline with one command.",
5
5
  "homepage": "https://usebeeline.app",
6
6
  "bugs": {