usebeeline 0.0.72 → 0.0.90

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 +184 -60
  2. package/package.json +1 -1
@@ -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";
@@ -21382,8 +21373,6 @@ var MonolithRoomTurnLoop = class {
21382
21373
  const buildPrompt = () => [
21383
21374
  this.turnInstructionPrefix,
21384
21375
  WarmTranscript.render(this.warmTranscript.select(this.sessionId, transcriptRows), "Room conversation so far:", "New in the Room since your last turn (the earlier conversation is already in this session):"),
21385
- `Newest message from ${inboxItemAuthorName(item, this.agent.publicKey, names)}:`,
21386
- roomMessagePrompt("", inboxItemPromptBody(item, this.agent.publicKey), item.attachments, delivered, this.acceptsImages()),
21387
21376
  grantDecision ? [
21388
21377
  "This is the answer to your grant request; your paused work resumes now.",
21389
21378
  "If it was approved and it is a command grant, run it with run_granted_command and the exact argv.",
@@ -21393,9 +21382,11 @@ var MonolithRoomTurnLoop = class {
21393
21382
  [
21394
21383
  "Write only the substantive Room message you want the human to read.",
21395
21384
  "Do not repeat or paraphrase these instructions.",
21396
- "If the newest message is only a nudge to respond, answer the most recent unanswered human message in the conversation instead of echoing the nudge.",
21385
+ "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.",
21397
21386
  MAINTAIN_ASSIGNED_IDENTITY_DIRECTIVE
21398
- ].join(" ")
21387
+ ].join(" "),
21388
+ `Current task selected by the server from ${inboxItemAuthorName(item, this.agent.publicKey, names)}:`,
21389
+ roomMessagePrompt("", inboxItemPromptBody(item, this.agent.publicKey), item.attachments, delivered, this.acceptsImages())
21399
21390
  ].filter(Boolean).join("\n\n");
21400
21391
  const stream = new AgentTurnStream({
21401
21392
  api,
@@ -21571,6 +21562,9 @@ var execFileAsync3 = promisify3(execFile4);
21571
21562
  var TOOL_ARGUMENT_MAX_BYTES = 1200;
21572
21563
  var TOOL_OUTPUT_MAX_BYTES = 3200;
21573
21564
  var TOOL_PATH_LIMIT = 12;
21565
+ function cornerMergeInstruction(yoloMode) {
21566
+ return yoloMode ? "Yolo mode is on: when that merge gate passes, merge this pull request yourself with gh." : "Yolo mode is off: never merge the pull request yourself; leave it for human approval in the app.";
21567
+ }
21574
21568
  function oneLine(value) {
21575
21569
  return value.replace(/\s+/g, " ").trim();
21576
21570
  }
@@ -21831,7 +21825,8 @@ var MonolithCornerTurnLoop = class {
21831
21825
  model: configuration.model ?? this.options.config.modelSelection?.model,
21832
21826
  effort: configuration.effort ?? this.options.config.modelSelection?.effort,
21833
21827
  soul: configuration.soul ?? self?.soul,
21834
- agentName: self?.name ?? this.agent.name
21828
+ agentName: self?.name ?? this.agent.name,
21829
+ yoloMode: configuration.yoloMode
21835
21830
  });
21836
21831
  }
21837
21832
  async activate(trace) {
@@ -21850,7 +21845,8 @@ var MonolithCornerTurnLoop = class {
21850
21845
  model: configuration.model ?? this.options.config.modelSelection?.model,
21851
21846
  effort: configuration.effort ?? this.options.config.modelSelection?.effort,
21852
21847
  soul: configuration.soul ?? self?.soul,
21853
- agentName: self?.name ?? this.agent.name
21848
+ agentName: self?.name ?? this.agent.name,
21849
+ yoloMode: configuration.yoloMode
21854
21850
  });
21855
21851
  await mkdir11(this.options.worktreePath, { recursive: true });
21856
21852
  const selection = configuration.model || configuration.effort ? { model: configuration.model, effort: configuration.effort } : this.options.config.modelSelection;
@@ -21981,16 +21977,13 @@ var MonolithCornerTurnLoop = class {
21981
21977
  personaInstructions,
21982
21978
  ...repository ? [
21983
21979
  `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.`,
21980
+ `Work normally, then commit and push only ${repository.featureBranch}; never force-push or write to ${repository.targetBranch}. Open its pull request with gh. Before each push, rebase on origin/${repository.featureBranch}; resolve conflicts autonomously and rerun affected tests.`,
21986
21981
  "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.",
21982
+ 'On a later server checks turn, call beeline-agent pr_checks_status. Never merge unless it returns checks="passed", held=false, and approvalPending=false; a human hold remains until a later human explicitly resumes.',
21983
+ cornerMergeInstruction(configuration.yoloMode),
21990
21984
  "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
21985
  '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."
21986
+ "When approval is pending, wait for the server close request. Never merge a different pull request."
21994
21987
  ] : [
21995
21988
  "This is a chat-only corner with no repository or GitHub workflow.",
21996
21989
  "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.",
@@ -22320,8 +22313,8 @@ ${trigger}`,
22320
22313
  * The branch is the shared artifact: another member agent may have pushed to
22321
22314
  * it since this helper last looked, and a first touch of a corner this
22322
22315
  * 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.
22316
+ * conflicting local commits are realigned to that remote branch so the
22317
+ * fixed objective can be attempted again without clobbering a sibling push.
22325
22318
  */
22326
22319
  async syncBranch() {
22327
22320
  const repository = this.options.repository;
@@ -22781,6 +22774,7 @@ var ROOM_JOIN_CONCURRENCY = 4;
22781
22774
  var DEFAULT_ROOM_WATCHDOG_STALE_MS = 9e4;
22782
22775
  var DEFAULT_RECONCILE_HEARTBEAT_MS = 6e4;
22783
22776
  var DEFAULT_DRAIN_DEADLINE_MS = 30 * 6e4;
22777
+ var CORNER_BRANCH_DELETE_ATTEMPTS = 3;
22784
22778
  function shouldPostInitialCornerWorkingState(restore, isOpener = true) {
22785
22779
  return isOpener && !restore.featureBranch && !restore.lifecycle?.branch && !restore.lifecycle?.pr;
22786
22780
  }
@@ -22873,6 +22867,52 @@ async function mapWithConcurrency(values, limit, visit) {
22873
22867
  }
22874
22868
  }));
22875
22869
  }
22870
+ async function removeCornerWorktreeAndBranches(worktree) {
22871
+ const localRef = `refs/heads/${worktree.branch}`;
22872
+ const remoteRef = `refs/heads/${worktree.branch}`;
22873
+ const worktreeExists = existsSync4(worktree.path);
22874
+ const localExists = await gitRefExists(worktree.gitCommonDir, localRef);
22875
+ if (worktreeExists) {
22876
+ const checkedOut = await execFileAsync4("git", [
22877
+ "-C",
22878
+ worktree.path,
22879
+ "symbolic-ref",
22880
+ "--quiet",
22881
+ "HEAD"
22882
+ ]);
22883
+ if (checkedOut.stdout.trim() !== localRef) {
22884
+ throw new Error(`corner worktree branch mismatch: expected ${localRef}`);
22885
+ }
22886
+ await execFileAsync4("git", [
22887
+ `--git-dir=${worktree.gitCommonDir}`,
22888
+ "worktree",
22889
+ "remove",
22890
+ "--force",
22891
+ worktree.path
22892
+ ]);
22893
+ } else if (!localExists) {
22894
+ await deleteExactRemoteBranch(worktree.gitCommonDir, remoteRef, worktree.token, {
22895
+ requireAbsent: true
22896
+ });
22897
+ return;
22898
+ } else {
22899
+ const head = await repositoryHeadRef(worktree.gitCommonDir);
22900
+ if (head === localRef) {
22901
+ throw new Error(`refusing to delete repository HEAD ${localRef}`);
22902
+ }
22903
+ }
22904
+ await deleteExactRemoteBranch(worktree.gitCommonDir, remoteRef, worktree.token);
22905
+ if (await gitRefExists(worktree.gitCommonDir, localRef)) {
22906
+ await execFileAsync4("git", [
22907
+ `--git-dir=${worktree.gitCommonDir}`,
22908
+ "branch",
22909
+ "--delete",
22910
+ "--force",
22911
+ "--",
22912
+ worktree.branch
22913
+ ]);
22914
+ }
22915
+ }
22876
22916
  var execFileAsync4 = promisify4(execFile5);
22877
22917
  async function removeCornerScratchWorkspace(input) {
22878
22918
  const expected = resolve19(input.roomRoot, "scratch");
@@ -22887,6 +22927,8 @@ var RoomRuntimeCoordinator = class {
22887
22927
  options;
22888
22928
  runtime;
22889
22929
  running = /* @__PURE__ */ new Map();
22930
+ /** Close/reconcile leftovers retried until local and remote refs are gone. */
22931
+ pendingCornerReaps = /* @__PURE__ */ new Map();
22890
22932
  startingCorners = /* @__PURE__ */ new Set();
22891
22933
  /** Corners whose start failure has already been said out loud, once each. */
22892
22934
  reportedCornerStartFailures = /* @__PURE__ */ new Set();
@@ -23029,6 +23071,7 @@ var RoomRuntimeCoordinator = class {
23029
23071
  });
23030
23072
  for (const channelId of desired)
23031
23073
  this.roomRemovalConfirmations.delete(channelId);
23074
+ await this.retryPendingCornerReaps(desired);
23032
23075
  for (const [channelId, running] of [...this.running]) {
23033
23076
  if (desired.has(channelId))
23034
23077
  continue;
@@ -23040,10 +23083,15 @@ var RoomRuntimeCoordinator = class {
23040
23083
  }
23041
23084
  running.controller.abort();
23042
23085
  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);
23086
+ try {
23087
+ if (running.worktree)
23088
+ await this.reapCornerWorktree(running.worktree);
23089
+ else if (running.scratch)
23090
+ await this.reapCornerScratch(running.scratch);
23091
+ } catch (error) {
23092
+ console.error(`[thin-core] corner ${channelId} cleanup failed; will retry:`, error);
23093
+ this.confirmationPending = true;
23094
+ }
23047
23095
  }
23048
23096
  await mapWithConcurrency(desiredTopRooms, ROOM_JOIN_CONCURRENCY, async (roomId) => {
23049
23097
  if (!this.running.has(roomId))
@@ -23182,18 +23230,10 @@ var RoomRuntimeCoordinator = class {
23182
23230
  return;
23183
23231
  this.startingCorners.add(corner.cornerId);
23184
23232
  try {
23185
- const [restore, repository, conversation] = await Promise.all([
23233
+ const [restore, repository] = await Promise.all([
23186
23234
  this.options.daemonApi.execute("getCornerRestoreState", { cornerId: corner.cornerId }),
23187
23235
  this.options.daemonApi.execute("getRoomRepositoryState", {
23188
23236
  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
23237
  })
23198
23238
  ]);
23199
23239
  if (repository.resolution === "unverified") {
@@ -23202,9 +23242,9 @@ var RoomRuntimeCoordinator = class {
23202
23242
  if (repository.resolution === "repository" && (!repository.remote || !repository.key)) {
23203
23243
  throw new Error("corner parent Room has an incomplete repository binding");
23204
23244
  }
23205
- const objective = conversation.items.find((item) => item.type === "message")?.body.trim();
23245
+ const objective = restore.objective.trim();
23206
23246
  if (!objective)
23207
- throw new Error("corner has no durable objective post");
23247
+ throw new Error("corner has no authoritative objective fact");
23208
23248
  const repositoryBacked = repository.resolution === "repository";
23209
23249
  const targetBranch = repositoryBacked ? repository.targetBranch || "main" : void 0;
23210
23250
  const featureBranch = repositoryBacked ? restore.featureBranch ?? `feature/corner-${corner.cornerId.replaceAll("-", "").slice(0, 12)}` : void 0;
@@ -23260,7 +23300,9 @@ var RoomRuntimeCoordinator = class {
23260
23300
  onCloseRequested: () => worktree ? this.reapCornerWorktree({
23261
23301
  ...worktree,
23262
23302
  cornerId: corner.cornerId,
23263
- branch: featureBranch
23303
+ branch: featureBranch,
23304
+ parentRoomId: corner.parentRoomId,
23305
+ token: ""
23264
23306
  }) : this.reapCornerScratch({ path: workspacePath, cornerId: corner.cornerId })
23265
23307
  });
23266
23308
  const promise = loop.run().catch((error) => {
@@ -23283,7 +23325,9 @@ var RoomRuntimeCoordinator = class {
23283
23325
  worktree: {
23284
23326
  ...worktree,
23285
23327
  cornerId: corner.cornerId,
23286
- branch: featureBranch
23328
+ branch: featureBranch,
23329
+ parentRoomId: corner.parentRoomId,
23330
+ token: ""
23287
23331
  }
23288
23332
  } : {},
23289
23333
  ...!worktree ? { scratch: { path: workspacePath, cornerId: corner.cornerId } } : {}
@@ -23339,22 +23383,40 @@ var RoomRuntimeCoordinator = class {
23339
23383
  committer: { name: this.agent.name, publicKey: this.agent.publicKey }
23340
23384
  });
23341
23385
  }
23386
+ async retryPendingCornerReaps(desired) {
23387
+ for (const [cornerId, worktree] of [...this.pendingCornerReaps]) {
23388
+ if (desired.has(cornerId)) {
23389
+ this.pendingCornerReaps.delete(cornerId);
23390
+ continue;
23391
+ }
23392
+ try {
23393
+ await this.reapCornerWorktree(worktree);
23394
+ } catch (error) {
23395
+ console.error(`[thin-core] corner ${cornerId} branch cleanup retry failed:`, error);
23396
+ this.confirmationPending = true;
23397
+ }
23398
+ }
23399
+ }
23342
23400
  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
- ]);
23401
+ try {
23402
+ const parentRoomId = worktree.parentRoomId;
23403
+ if (!parentRoomId) {
23404
+ throw new Error(`corner ${worktree.cornerId} has no parent Room for GitHub token`);
23405
+ }
23406
+ const token = (await this.options.daemonApi.execute("getRoomGitHubToken", { roomId: parentRoomId })).token;
23407
+ await removeCornerWorktreeAndBranches({ ...worktree, token });
23408
+ await this.options.daemonApi.execute("postCornerRemoteState", {
23409
+ cornerId: worktree.cornerId,
23410
+ branch: worktree.branch,
23411
+ state: "gone",
23412
+ checks: "unknown"
23413
+ });
23414
+ this.pendingCornerReaps.delete(worktree.cornerId);
23415
+ } catch (error) {
23416
+ this.pendingCornerReaps.set(worktree.cornerId, { ...worktree, token: "" });
23417
+ this.confirmationPending = true;
23418
+ throw error;
23351
23419
  }
23352
- await this.options.daemonApi.execute("postCornerRemoteState", {
23353
- cornerId: worktree.cornerId,
23354
- branch: worktree.branch,
23355
- state: "gone",
23356
- checks: "unknown"
23357
- });
23358
23420
  }
23359
23421
  async reapCornerScratch(scratch) {
23360
23422
  await removeCornerScratchWorkspace({
@@ -23437,6 +23499,68 @@ function githubGitEnv(token) {
23437
23499
  GIT_TERMINAL_PROMPT: "0"
23438
23500
  };
23439
23501
  }
23502
+ async function gitRefExists(gitCommonDir, ref) {
23503
+ try {
23504
+ await execFileAsync4("git", [`--git-dir=${gitCommonDir}`, "show-ref", "--verify", "--quiet", ref]);
23505
+ return true;
23506
+ } catch (error) {
23507
+ if (error.code === 1)
23508
+ return false;
23509
+ throw error;
23510
+ }
23511
+ }
23512
+ async function repositoryHeadRef(gitCommonDir) {
23513
+ try {
23514
+ const head = await execFileAsync4("git", [
23515
+ `--git-dir=${gitCommonDir}`,
23516
+ "symbolic-ref",
23517
+ "--quiet",
23518
+ "HEAD"
23519
+ ]);
23520
+ return head.stdout.trim() || void 0;
23521
+ } catch {
23522
+ return void 0;
23523
+ }
23524
+ }
23525
+ function gitStderr(error) {
23526
+ return typeof error === "object" && error && "stderr" in error ? String(error.stderr ?? "") : "";
23527
+ }
23528
+ function isRemoteRefMissing(error) {
23529
+ return /remote ref does not exist|remote reference does not exist/i.test(`${error instanceof Error ? error.message : String(error)}
23530
+ ${gitStderr(error)}`);
23531
+ }
23532
+ async function deleteExactRemoteBranch(gitCommonDir, remoteRef, token, options = {}) {
23533
+ const authEnv = githubGitEnv(token);
23534
+ const listRemote = () => execFileAsync4("git", [`--git-dir=${gitCommonDir}`, "ls-remote", "--heads", "origin", remoteRef], {
23535
+ env: authEnv,
23536
+ maxBuffer: 4 * 1024 * 1024
23537
+ });
23538
+ if (options.requireAbsent) {
23539
+ const remote = await listRemote();
23540
+ if (remote.stdout.trim()) {
23541
+ throw new Error(`cannot delete ${remoteRef} without the local ref that proves this corner owns it`);
23542
+ }
23543
+ return;
23544
+ }
23545
+ let remoteError;
23546
+ for (let attempt = 1; attempt <= CORNER_BRANCH_DELETE_ATTEMPTS; attempt += 1) {
23547
+ try {
23548
+ const remote = await listRemote();
23549
+ if (remote.stdout.trim()) {
23550
+ await execFileAsync4("git", [`--git-dir=${gitCommonDir}`, "push", "origin", `:${remoteRef}`], {
23551
+ env: authEnv,
23552
+ maxBuffer: 4 * 1024 * 1024
23553
+ });
23554
+ }
23555
+ return;
23556
+ } catch (error) {
23557
+ if (isRemoteRefMissing(error))
23558
+ return;
23559
+ remoteError = error;
23560
+ }
23561
+ }
23562
+ throw remoteError;
23563
+ }
23440
23564
 
23441
23565
  // apps/body/dist/thin-core.js
23442
23566
  async function waitForNextTick(ms, signal) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usebeeline",
3
- "version": "0.0.72",
3
+ "version": "0.0.90",
4
4
  "description": "Connect an AI coding agent to Beeline with one command.",
5
5
  "homepage": "https://usebeeline.app",
6
6
  "bugs": {