usebeeline 0.0.54 → 0.0.56

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 +391 -120
  2. package/package.json +1 -1
@@ -956,7 +956,7 @@ var init_self_update = __esm({
956
956
 
957
957
  // apps/body/dist/cli.js
958
958
  import { dirname as dirname15, resolve as resolve29 } from "node:path";
959
- import { readFile as readFile14, unlink as unlink4, writeFile as writeFile15 } from "node:fs/promises";
959
+ import { readFile as readFile14, unlink as unlink5, writeFile as writeFile15 } from "node:fs/promises";
960
960
  import { stdin as stdin4, stdout as stdout5 } from "node:process";
961
961
 
962
962
  // node_modules/@clack/core/dist/index.mjs
@@ -2951,13 +2951,17 @@ function parseSandboxMaskEnv(env) {
2951
2951
  return entries.length ? entries : void 0;
2952
2952
  }
2953
2953
 
2954
- // apps/body/dist/access-policy.js
2954
+ // packages/api-contract/dist/agent-access.js
2955
2955
  var AGENT_ACCESS_POLICIES = ["everyone", "creator", "allowlist"];
2956
- var DEFAULT_ACCESS_POLICY = "creator";
2957
- var LEGACY_ACCESS_POLICY = "everyone";
2956
+ var DEFAULT_AGENT_ACCESS_POLICY = "everyone";
2958
2957
  function isAgentAccessPolicy(value) {
2959
2958
  return AGENT_ACCESS_POLICIES.includes(value);
2960
2959
  }
2960
+ var ACCESS_NOTICE_WINDOW_MS = 10 * 6e4;
2961
+
2962
+ // apps/body/dist/access-policy.js
2963
+ var DEFAULT_ACCESS_POLICY = DEFAULT_AGENT_ACCESS_POLICY;
2964
+ var LEGACY_ACCESS_POLICY = "everyone";
2961
2965
  var ACCESS_REFUSAL_WINDOW_MS = 60 * 60 * 1e3;
2962
2966
  function isSenderPermitted(policy, senderPubkey, ownerPubkey, allowlist = void 0) {
2963
2967
  if (!senderPubkey)
@@ -13280,12 +13284,12 @@ async function activateDaemonTransport(path, fetchImpl = fetch) {
13280
13284
  }
13281
13285
 
13282
13286
  // apps/body/dist/room-runtime.js
13283
- import { execFile as execFile4 } from "node:child_process";
13287
+ import { execFile as execFile5 } from "node:child_process";
13284
13288
  import { createHash as createHash4 } from "node:crypto";
13285
13289
  import { existsSync as existsSync4, mkdirSync } from "node:fs";
13286
13290
  import { mkdir as mkdir11, rm as rm4 } from "node:fs/promises";
13287
13291
  import { dirname as dirname6, resolve as resolve19 } from "node:path";
13288
- import { promisify as promisify3 } from "node:util";
13292
+ import { promisify as promisify4 } from "node:util";
13289
13293
 
13290
13294
  // apps/body/dist/grant-runner.js
13291
13295
  import { execFile as execFile2 } from "node:child_process";
@@ -14125,11 +14129,11 @@ var GrantRunnerServer = class {
14125
14129
  };
14126
14130
 
14127
14131
  // apps/body/dist/monolith-corner-turn.js
14128
- import { execFile as execFile3 } from "node:child_process";
14132
+ import { execFile as execFile4 } from "node:child_process";
14129
14133
  import { mkdir as mkdir9 } from "node:fs/promises";
14130
14134
  import { homedir as homedir6 } from "node:os";
14131
14135
  import { join as join5 } from "node:path";
14132
- import { promisify as promisify2 } from "node:util";
14136
+ import { promisify as promisify3 } from "node:util";
14133
14137
 
14134
14138
  // apps/body/dist/agent-home.js
14135
14139
  import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
@@ -14177,7 +14181,7 @@ function isAgentPairingCode(value) {
14177
14181
  var USING_BEELINE_SKILL_NAME = "using-beeline";
14178
14182
  var BEELINE_ROOM_CAPABILITIES = [
14179
14183
  "The repository filesystem is read-only in this Room session.",
14180
- "You may address any Room member, including another agent, by writing @name in your reply; the server routes that mention to them.",
14184
+ "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.",
14181
14185
  "Tag another agent only when you need something from them: a question, a handoff, a task. Never tag to acknowledge, agree, or say you are ready. If nothing is actionable, do not reply.",
14182
14186
  "Tag the user only when you need a decision or input, or when the task they asked for is finished. Never tag for progress, acknowledgement, or questions the transcript already answers.",
14183
14187
  "Every MCP server mounted into this session is approved tool by tool - use operator and host tools freely; the read-only filesystem sandbox is the boundary, not a tool list. Network web search is enabled.",
@@ -15931,6 +15935,55 @@ export default async function (pi) {
15931
15935
  }
15932
15936
  `;
15933
15937
 
15938
+ // apps/body/dist/corner-branch-sync.js
15939
+ import { execFile as execFile3 } from "node:child_process";
15940
+ import { promisify as promisify2 } from "node:util";
15941
+ var execFileAsync2 = promisify2(execFile3);
15942
+ var CornerBranchDivergedError = class extends Error {
15943
+ name = "CornerBranchDivergedError";
15944
+ };
15945
+ async function syncCornerBranch(input) {
15946
+ const git = input.git ?? (async (args) => (await execFileAsync2("git", ["-C", input.worktreePath, ...args], {
15947
+ ...input.env ? { env: input.env } : {},
15948
+ maxBuffer: 4 * 1024 * 1024
15949
+ })).stdout);
15950
+ const remoteRef = `refs/remotes/origin/${input.featureBranch}`;
15951
+ const fetched = await git([
15952
+ "fetch",
15953
+ "origin",
15954
+ `+refs/heads/${input.featureBranch}:${remoteRef}`
15955
+ ]).then(() => true, () => false);
15956
+ if (!fetched)
15957
+ return "unchanged";
15958
+ const remote = await git(["rev-parse", remoteRef]).then((value) => value.trim(), () => "");
15959
+ if (!remote)
15960
+ return "unchanged";
15961
+ const local = (await git(["rev-parse", "HEAD"])).trim();
15962
+ if (local === remote)
15963
+ return "unchanged";
15964
+ if (await contains(git, local, remote))
15965
+ return "unchanged";
15966
+ const behind = await contains(git, remote, local);
15967
+ try {
15968
+ await git(behind ? ["merge", "--ff-only", remote] : ["rebase", remote]);
15969
+ return behind ? "fast-forwarded" : "rebased";
15970
+ } catch (error) {
15971
+ await git(["rebase", "--abort"]).catch(() => void 0);
15972
+ throw new CornerBranchDivergedError(divergedReason(input.featureBranch, error));
15973
+ }
15974
+ }
15975
+ async function contains(git, head, ancestor) {
15976
+ return git(["merge-base", "--is-ancestor", ancestor, head]).then(() => true, () => false);
15977
+ }
15978
+ function divergedReason(featureBranch, error) {
15979
+ const detail = String(error?.stderr ?? "").split("\n").map((line) => line.trim()).find((line) => /conflict|could not|error:/i.test(line));
15980
+ return [
15981
+ `could not rebase onto origin/${featureBranch}: another agent pushed to this corner's branch`,
15982
+ detail ? ` (${detail})` : "",
15983
+ ". Nothing was pushed; the branch needs a person."
15984
+ ].join("").slice(0, 200);
15985
+ }
15986
+
15934
15987
  // apps/body/dist/room-session.js
15935
15988
  import { resolve as resolve15 } from "node:path";
15936
15989
 
@@ -16740,7 +16793,7 @@ process.exit(result.status ?? 1);
16740
16793
  }
16741
16794
 
16742
16795
  // apps/body/dist/monolith-corner-turn.js
16743
- var execFileAsync2 = promisify2(execFile3);
16796
+ var execFileAsync3 = promisify3(execFile4);
16744
16797
  var TOOL_ARGUMENT_MAX_BYTES = 1200;
16745
16798
  var TOOL_OUTPUT_MAX_BYTES = 3200;
16746
16799
  var TOOL_PATH_LIMIT = 12;
@@ -16844,7 +16897,7 @@ async function cornerToolActivity(call, worktreePath, requestedBy) {
16844
16897
  let title = oneLine(redactToolDetail(call.title ?? "")) || `${operation} tool`;
16845
16898
  if (isSuccessfulCommit(call)) {
16846
16899
  try {
16847
- const shown = await execFileAsync2("git", ["-C", worktreePath, "show", "--format=%s", "--name-only", "--no-renames", "HEAD"], { maxBuffer: 1024 * 1024 });
16900
+ const shown = await execFileAsync3("git", ["-C", worktreePath, "show", "--format=%s", "--name-only", "--no-renames", "HEAD"], { maxBuffer: 1024 * 1024 });
16848
16901
  const lines = shown.stdout.split(/\r?\n/);
16849
16902
  const subject = oneLine(lines.shift() ?? "commit");
16850
16903
  const files = new Set(lines.map(oneLine).filter(Boolean));
@@ -16900,6 +16953,10 @@ var MonolithCornerTurnLoop = class {
16900
16953
  /** Operator-local turn traces; built once when the daemon configured a directory. */
16901
16954
  turnTraceSink;
16902
16955
  memberNames = /* @__PURE__ */ new Map();
16956
+ /** Agent identities in this Workspace, so a mention can be told from a human's. */
16957
+ agentMembers = /* @__PURE__ */ new Set();
16958
+ /** The member agent that answered in this corner last (`carriesCorner`). */
16959
+ carrier;
16903
16960
  /** The last server check state that started a turn; the same state never starts another. */
16904
16961
  lastChecksState;
16905
16962
  constructor(options) {
@@ -16941,6 +16998,7 @@ var MonolithCornerTurnLoop = class {
16941
16998
  workspaceId: this.options.workspaceId
16942
16999
  });
16943
17000
  this.memberNames = new Map(roster.members.map((member) => [member.identityId, member.name]));
17001
+ this.agentMembers = new Set(roster.members.filter((member) => member.kind === "agent").map((member) => member.identityId));
16944
17002
  return roster;
16945
17003
  }
16946
17004
  /**
@@ -17017,8 +17075,8 @@ var MonolithCornerTurnLoop = class {
17017
17075
  GITHUB_TOKEN: this.options.githubToken
17018
17076
  };
17019
17077
  if (this.options.config.runtimeConfigPath && this.options.config.agentHomeRoot) {
17020
- const gitBinary = (await execFileAsync2("which", ["git"])).stdout.trim();
17021
- const ghBinary = await execFileAsync2("which", ["gh"]).then((result) => result.stdout.trim()).catch(() => void 0);
17078
+ const gitBinary = (await execFileAsync3("which", ["git"])).stdout.trim();
17079
+ const ghBinary = await execFileAsync3("which", ["gh"]).then((result) => result.stdout.trim()).catch(() => void 0);
17022
17080
  githubEnv = await installCornerGitHubWrappers({
17023
17081
  root: this.options.config.agentHomeRoot,
17024
17082
  runtimeConfigPath: this.options.config.runtimeConfigPath,
@@ -17123,6 +17181,7 @@ var MonolithCornerTurnLoop = class {
17123
17181
  personaInstructions,
17124
17182
  `You are in an isolated git worktree on ${this.options.featureBranch}, targeting ${this.options.targetBranch}.`,
17125
17183
  "Work normally with the full coding tools. Commit and push only this feature branch. Use gh to open its pull request.",
17184
+ `This corner is shared: any of its member agents may be addressed in it and work on ${this.options.featureBranch}. Run git pull --rebase origin ${this.options.featureBranch} before you push, and never force-push it.`,
17126
17185
  "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.",
17127
17186
  '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.',
17128
17187
  "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.",
@@ -17222,6 +17281,7 @@ var MonolithCornerTurnLoop = class {
17222
17281
  ...this.memberNames.get(requestedById) ? { name: this.memberNames.get(requestedById) } : {}
17223
17282
  } : void 0;
17224
17283
  this.currentTurn = { requestId, ...requester ? { requester } : {} };
17284
+ this.carrier = this.agent.publicKey;
17225
17285
  const trace = this.beginTurnTrace(requestId);
17226
17286
  try {
17227
17287
  await withTurnReceiptHeartbeat(api, {
@@ -17238,6 +17298,7 @@ var MonolithCornerTurnLoop = class {
17238
17298
  if (this.forcedStop)
17239
17299
  throw new Error("corner turn stopped for daemon handoff");
17240
17300
  this.busy = true;
17301
+ await this.syncBranch();
17241
17302
  const [conversation, roster, delivered] = await trace.measure("context-fetch", () => Promise.all([
17242
17303
  api.execute("getRoomConversation", { roomId: cornerId, limit: 200 }),
17243
17304
  this.roster(),
@@ -17361,6 +17422,63 @@ ${trigger}`,
17361
17422
  this.currentTurn = void 0;
17362
17423
  }
17363
17424
  }
17425
+ /** Whether this agent opened the corner. A corner with no recorded opener
17426
+ * behaves exactly as it did before members could carry it. */
17427
+ isOpener() {
17428
+ return !this.options.openedBy || this.options.openedBy === this.agent.publicKey;
17429
+ }
17430
+ /**
17431
+ * Whether this agent is the one carrying the corner right now.
17432
+ *
17433
+ * Every member agent polls the corner, but its lifecycle — a server check
17434
+ * note, a close request answered with work — is ONE fact and must start ONE
17435
+ * turn, not one per member (the "one check turn per changed server state"
17436
+ * rule). The carrier is whoever answered in the corner last, which is the
17437
+ * opener until a human hands the work to someone else.
17438
+ */
17439
+ carriesCorner() {
17440
+ return (this.carrier ?? this.options.openedBy ?? this.agent.publicKey) === this.agent.publicKey;
17441
+ }
17442
+ /** Notes an agent's durable message as the corner changing hands. */
17443
+ noteCarrier(authorId) {
17444
+ if (this.agentMembers.has(authorId))
17445
+ this.carrier = authorId;
17446
+ }
17447
+ /**
17448
+ * Whether a human message in this corner is addressed to THIS agent.
17449
+ *
17450
+ * A corner now runs like a Room — every member agent polls it — so an
17451
+ * unrouted message would start one turn per member on one branch. A mention
17452
+ * routes: the mentioned agent answers and nobody else. A message that names
17453
+ * no agent at all keeps the old behaviour and falls to the opener, which is
17454
+ * every single-agent corner ever opened.
17455
+ */
17456
+ async addressesThisAgent(item) {
17457
+ if (item.mentionIds.includes(this.agent.publicKey))
17458
+ return true;
17459
+ if (!item.mentionIds.length)
17460
+ return this.carriesCorner();
17461
+ await this.roster().catch(() => void 0);
17462
+ return item.mentionIds.some((id) => this.agentMembers.has(id)) ? false : this.carriesCorner();
17463
+ }
17464
+ /**
17465
+ * Bring this worktree onto the corner's branch as GitHub currently has it,
17466
+ * before any work is done on top of it.
17467
+ *
17468
+ * The branch is the shared artifact: another member agent may have pushed to
17469
+ * it since this helper last looked, and a first touch of a corner this
17470
+ * helper did not open starts from whatever `room-runtime.ts` restored. A
17471
+ * divergence this cannot rebase away is raised, not pushed over — the turn
17472
+ * fails with that sentence and the server inscribes it in the corner.
17473
+ */
17474
+ async syncBranch() {
17475
+ const token = await this.options.api.execute("getRoomGitHubToken", { roomId: this.options.parentRoomId }).then((granted) => granted.token).catch(() => this.options.githubToken);
17476
+ await syncCornerBranch({
17477
+ worktreePath: this.options.worktreePath,
17478
+ featureBranch: this.options.featureBranch,
17479
+ env: { ...process.env, GH_TOKEN: token, GITHUB_TOKEN: token, GIT_TERMINAL_PROMPT: "0" }
17480
+ });
17481
+ }
17364
17482
  /** The server's check state for this head, or the notes' own verdict when the server carries none. */
17365
17483
  async checksState(notes) {
17366
17484
  try {
@@ -17379,8 +17497,12 @@ ${trigger}`,
17379
17497
  const { api, cornerId, signal } = this.options;
17380
17498
  let cursor3 = (await api.execute("getRoomInbox", { roomId: cornerId, startAtLatest: true })).cursor;
17381
17499
  const history = await api.execute("getRoomConversation", { roomId: cornerId, limit: 200 });
17500
+ await this.roster().catch(() => void 0);
17501
+ for (const item of history.items)
17502
+ if (item.type === "message")
17503
+ this.noteCarrier(item.authorId);
17382
17504
  const durableAgentReplies = history.items.filter((item) => item.type === "message" && item.authorId === this.agent.publicKey && item.body.trim() !== this.options.objective.trim());
17383
- if (durableAgentReplies.length === 0) {
17505
+ if (durableAgentReplies.length === 0 && this.isOpener()) {
17384
17506
  await this.prompt(history.items.find((item) => item.requestId)?.requestId ?? cornerId.replaceAll("-", ""), this.options.objective);
17385
17507
  }
17386
17508
  try {
@@ -17398,8 +17520,11 @@ ${trigger}`,
17398
17520
  const checkNotes = [];
17399
17521
  for (const item of inbox.items) {
17400
17522
  if (item.type === "message") {
17523
+ this.noteCarrier(item.authorId);
17401
17524
  if (item.authorId === this.agent.publicKey)
17402
17525
  continue;
17526
+ if (!await this.addressesThisAgent(item))
17527
+ continue;
17403
17528
  const authority = await api.execute("getRoomAuthority", {
17404
17529
  roomId: cornerId,
17405
17530
  principalId: item.authorId
@@ -17424,7 +17549,7 @@ This answers your grant request; resume the paused work. If approved and it is a
17424
17549
  if (completedCheckNote(item))
17425
17550
  checkNotes.push(item);
17426
17551
  }
17427
- if (checkNotes.length) {
17552
+ if (checkNotes.length && this.carriesCorner()) {
17428
17553
  const state = await this.checksState(checkNotes);
17429
17554
  if (state && state !== "pending" && state !== this.lastChecksState) {
17430
17555
  this.lastChecksState = state;
@@ -17500,7 +17625,13 @@ function isRoomMcpPermissionRequest(request, mountedServers = ROOM_MOUNTED_MCP_S
17500
17625
  return isMountedMcpToolPermissionRequest(request, mountedServers);
17501
17626
  }
17502
17627
  function roomPrincipalMayAddressAgent(authority, humanPermitted) {
17503
- return authority.member && (authority.principalKind === "agent" || authority.principalKind === "human" && humanPermitted);
17628
+ if (!authority.member)
17629
+ return false;
17630
+ if (authority.principalKind === "agent")
17631
+ return true;
17632
+ if (authority.principalKind !== "human")
17633
+ return false;
17634
+ return authority.mayAddressAgent ?? humanPermitted;
17504
17635
  }
17505
17636
  function isScheduledPrompt(item, agentId) {
17506
17637
  if (item.type !== "system" || !item.mentionIds.includes(agentId))
@@ -17547,6 +17678,27 @@ function pendingGrantToolCall(call) {
17547
17678
  return false;
17548
17679
  return /pending, card posted/i.test(typeof call.content === "string" ? call.content : JSON.stringify(call.content ?? ""));
17549
17680
  }
17681
+ function roomMentionDirectory(roster, selfId) {
17682
+ const rows = [];
17683
+ for (const member of roster.members) {
17684
+ if (member.identityId === selfId)
17685
+ continue;
17686
+ const handle = member.handle?.trim().replace(/^@/, "");
17687
+ const name = member.name?.trim() ?? "";
17688
+ const alias = handle || name;
17689
+ if (!alias)
17690
+ continue;
17691
+ const kind = member.kind === "agent" ? "agent" : "person";
17692
+ rows.push(`- @${alias}${name && name !== alias ? ` \u2014 ${name}` : ""} (${kind})`);
17693
+ }
17694
+ if (!rows.length)
17695
+ return "";
17696
+ return [
17697
+ "Room members, and the exact spelling that tags each one:",
17698
+ ...rows,
17699
+ "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."
17700
+ ].join("\n");
17701
+ }
17550
17702
  function escapeRegExp(value) {
17551
17703
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
17552
17704
  }
@@ -18057,6 +18209,7 @@ var MonolithRoomTurnLoop = class {
18057
18209
  "If it was approved and it is a command grant, run it with run_granted_command and the exact argv.",
18058
18210
  "If it was declined, try another way or say plainly what you cannot do."
18059
18211
  ].join(" ") : "",
18212
+ roomMentionDirectory(roster, this.agent.publicKey),
18060
18213
  [
18061
18214
  "Write only the substantive Room message you want the human to read.",
18062
18215
  "Do not repeat or paraphrase these instructions.",
@@ -18681,8 +18834,77 @@ var ROOM_JOIN_CONCURRENCY = 4;
18681
18834
  var DEFAULT_ROOM_WATCHDOG_STALE_MS = 9e4;
18682
18835
  var DEFAULT_RECONCILE_HEARTBEAT_MS = 6e4;
18683
18836
  var DEFAULT_DRAIN_DEADLINE_MS = 30 * 6e4;
18684
- function shouldPostInitialCornerWorkingState(restore) {
18685
- return !restore.featureBranch && !restore.lifecycle?.branch && !restore.lifecycle?.pr;
18837
+ function shouldPostInitialCornerWorkingState(restore, isOpener = true) {
18838
+ return isOpener && !restore.featureBranch && !restore.lifecycle?.branch && !restore.lifecycle?.pr;
18839
+ }
18840
+ async function materializeCornerWorktree(input) {
18841
+ const remote = roomCheckoutRemote(input.remote);
18842
+ const repositoryHash = createHash4("sha256").update(remote).digest("hex").slice(0, 24);
18843
+ const gitCommonDir = resolve19(input.supervisorRoot, "beeline", "repositories", `${repositoryHash}.git`);
18844
+ const path = resolve19(input.supervisorRoot, "beeline", "corners", input.cornerId);
18845
+ await mkdir11(dirname6(gitCommonDir), { recursive: true, mode: 448 });
18846
+ await mkdir11(dirname6(path), { recursive: true, mode: 448 });
18847
+ const authEnv = githubGitEnv(input.token);
18848
+ if (!existsSync4(resolve19(gitCommonDir, "HEAD"))) {
18849
+ await execFileAsync4("git", ["clone", "--bare", remote, gitCommonDir], {
18850
+ env: authEnv,
18851
+ maxBuffer: 4 * 1024 * 1024
18852
+ });
18853
+ }
18854
+ await execFileAsync4("git", [
18855
+ `--git-dir=${gitCommonDir}`,
18856
+ "fetch",
18857
+ "--prune",
18858
+ "origin",
18859
+ `+refs/heads/${input.targetBranch}:refs/remotes/origin/${input.targetBranch}`
18860
+ ], { env: authEnv, maxBuffer: 4 * 1024 * 1024 });
18861
+ const restored = await execFileAsync4("git", [
18862
+ `--git-dir=${gitCommonDir}`,
18863
+ "fetch",
18864
+ "origin",
18865
+ `+refs/heads/${input.featureBranch}:refs/remotes/origin/${input.featureBranch}`
18866
+ ], { env: authEnv, maxBuffer: 4 * 1024 * 1024 }).then(() => true, () => false);
18867
+ if (!existsSync4(resolve19(path, ".git"))) {
18868
+ await rm4(path, { recursive: true, force: true });
18869
+ await execFileAsync4("git", [
18870
+ `--git-dir=${gitCommonDir}`,
18871
+ "worktree",
18872
+ "add",
18873
+ "-B",
18874
+ input.featureBranch,
18875
+ path,
18876
+ restored ? `refs/remotes/origin/${input.featureBranch}` : `refs/remotes/origin/${input.targetBranch}`
18877
+ ], { env: authEnv, maxBuffer: 4 * 1024 * 1024 });
18878
+ }
18879
+ await execFileAsync4("git", [
18880
+ `--git-dir=${gitCommonDir}`,
18881
+ "config",
18882
+ "extensions.worktreeConfig",
18883
+ "true"
18884
+ ]);
18885
+ await execFileAsync4("git", ["-C", path, "config", "--worktree", "core.bare", "false"]);
18886
+ await execFileAsync4("git", [
18887
+ "-C",
18888
+ path,
18889
+ "config",
18890
+ "--worktree",
18891
+ "credential.https://github.com.helper",
18892
+ "!f() { echo username=x-access-token; echo password=$GH_TOKEN; }; f"
18893
+ ]);
18894
+ await execFileAsync4("git", ["-C", path, "config", "--worktree", "user.name", input.committer.name]);
18895
+ await execFileAsync4("git", [
18896
+ "-C",
18897
+ path,
18898
+ "config",
18899
+ "--worktree",
18900
+ "user.email",
18901
+ `${input.committer.publicKey.slice(0, 16)}@users.noreply.github.com`
18902
+ ]);
18903
+ const top = await execFileAsync4("git", ["-C", path, "rev-parse", "--show-toplevel"]);
18904
+ if (resolve19(top.stdout.trim()) !== resolve19(path)) {
18905
+ throw new Error(`corner worktree escaped its isolated root: ${top.stdout.trim()}`);
18906
+ }
18907
+ return { path, gitCommonDir };
18686
18908
  }
18687
18909
  function reconcileRetryMs(error, pollMs) {
18688
18910
  const match = String(error).match(/retry in\s+(\d+)s/i);
@@ -18697,7 +18919,7 @@ async function mapWithConcurrency(values, limit, visit) {
18697
18919
  }
18698
18920
  }));
18699
18921
  }
18700
- var execFileAsync3 = promisify3(execFile4);
18922
+ var execFileAsync4 = promisify4(execFile5);
18701
18923
  var RoomRuntimeCoordinator = class {
18702
18924
  configPath;
18703
18925
  baseConfig;
@@ -18705,6 +18927,8 @@ var RoomRuntimeCoordinator = class {
18705
18927
  runtime;
18706
18928
  running = /* @__PURE__ */ new Map();
18707
18929
  startingCorners = /* @__PURE__ */ new Set();
18930
+ /** Corners whose start failure has already been said out loud, once each. */
18931
+ reportedCornerStartFailures = /* @__PURE__ */ new Set();
18708
18932
  scheduler;
18709
18933
  agent;
18710
18934
  /** Parent ownership retained so a failed corner listing never authorizes removal. */
@@ -18830,7 +19054,8 @@ var RoomRuntimeCoordinator = class {
18830
19054
  desired.add(corner.cornerId);
18831
19055
  desiredCorners.set(corner.cornerId, {
18832
19056
  cornerId: corner.cornerId,
18833
- parentRoomId: room.roomId
19057
+ parentRoomId: room.roomId,
19058
+ ...corner.createdBy ? { openedBy: corner.createdBy } : {}
18834
19059
  });
18835
19060
  }
18836
19061
  }
@@ -18972,12 +19197,12 @@ var RoomRuntimeCoordinator = class {
18972
19197
  const token = remote.startsWith("https://github.com/") ? await this.options.daemonApi.execute("getRoomGitHubToken", { roomId }) : void 0;
18973
19198
  const env = token ? githubGitEnv(token.token) : process.env;
18974
19199
  if (!existsSync4(resolve19(path, ".git"))) {
18975
- await execFileAsync3("git", ["clone", "--no-checkout", remote, path], {
19200
+ await execFileAsync4("git", ["clone", "--no-checkout", remote, path], {
18976
19201
  env,
18977
19202
  maxBuffer: 4 * 1024 * 1024
18978
19203
  });
18979
19204
  }
18980
- await execFileAsync3("git", [
19205
+ await execFileAsync4("git", [
18981
19206
  "-C",
18982
19207
  path,
18983
19208
  "fetch",
@@ -18985,7 +19210,7 @@ var RoomRuntimeCoordinator = class {
18985
19210
  "origin",
18986
19211
  `+refs/heads/${targetBranch}:refs/remotes/origin/${targetBranch}`
18987
19212
  ], { env, maxBuffer: 4 * 1024 * 1024 });
18988
- await execFileAsync3("git", ["-C", path, "checkout", "--detach", "--force", `origin/${targetBranch}`], {
19213
+ await execFileAsync4("git", ["-C", path, "checkout", "--detach", "--force", `origin/${targetBranch}`], {
18989
19214
  env,
18990
19215
  maxBuffer: 4 * 1024 * 1024
18991
19216
  });
@@ -19028,7 +19253,8 @@ var RoomRuntimeCoordinator = class {
19028
19253
  featureBranch,
19029
19254
  token: granted.token
19030
19255
  });
19031
- if (shouldPostInitialCornerWorkingState(restore)) {
19256
+ const isOpener = !corner.openedBy || corner.openedBy === this.agent.publicKey;
19257
+ if (shouldPostInitialCornerWorkingState(restore, isOpener)) {
19032
19258
  await this.options.daemonApi.execute("postCornerRemoteState", {
19033
19259
  cornerId: corner.cornerId,
19034
19260
  branch: featureBranch,
@@ -19045,6 +19271,7 @@ var RoomRuntimeCoordinator = class {
19045
19271
  ...grantRunnerEndpoint ? { grantRunnerEndpoint } : {},
19046
19272
  parentRoomId: corner.parentRoomId,
19047
19273
  workspaceId: this.runtime.communityId,
19274
+ ...corner.openedBy ? { openedBy: corner.openedBy } : {},
19048
19275
  objective,
19049
19276
  featureBranch,
19050
19277
  targetBranch,
@@ -19086,79 +19313,60 @@ var RoomRuntimeCoordinator = class {
19086
19313
  branch: featureBranch
19087
19314
  }
19088
19315
  });
19316
+ this.reportedCornerStartFailures.delete(corner.cornerId);
19089
19317
  console.log(`[thin-core] serving corner ${corner.cornerId} on ${featureBranch} at ${worktree.path}`);
19090
19318
  } catch (error) {
19091
19319
  console.error(`[thin-core] failed to start corner ${corner.cornerId}:`, error);
19320
+ await this.reportCornerStartFailure(corner.cornerId, error);
19092
19321
  } finally {
19093
19322
  this.startingCorners.delete(corner.cornerId);
19094
19323
  }
19095
19324
  }
19096
- async materializeCornerWorktree(input) {
19097
- const remote = githubHttpsRemote(input.remote);
19098
- const repositoryHash = createHash4("sha256").update(remote).digest("hex").slice(0, 24);
19099
- const gitCommonDir = resolve19(this.runtime.supervisorRoot, "beeline", "repositories", `${repositoryHash}.git`);
19100
- const path = resolve19(this.runtime.supervisorRoot, "beeline", "corners", input.cornerId);
19101
- await mkdir11(dirname6(gitCommonDir), { recursive: true, mode: 448 });
19102
- await mkdir11(dirname6(path), { recursive: true, mode: 448 });
19103
- const authEnv = githubGitEnv(input.token);
19104
- if (!existsSync4(resolve19(gitCommonDir, "HEAD"))) {
19105
- await execFileAsync3("git", ["clone", "--bare", remote, gitCommonDir], {
19106
- env: authEnv,
19107
- maxBuffer: 4 * 1024 * 1024
19325
+ /**
19326
+ * An agent addressed in a corner it then could not restore must not be
19327
+ * silent about it.
19328
+ *
19329
+ * The corner never starts, so no turn ever runs and nothing else in the
19330
+ * daemon has a Room to say it in. This posts a FAILED receipt against the
19331
+ * message that asked, which the server inscribes as `<agent> could not
19332
+ * answer · <reason>` in the corner itself. Once per corner per process: the
19333
+ * reconciliation heartbeat retries the start for as long as it keeps
19334
+ * failing, and the fact is worth saying once, not once a minute.
19335
+ */
19336
+ async reportCornerStartFailure(cornerId, error) {
19337
+ if (this.reportedCornerStartFailures.has(cornerId))
19338
+ return;
19339
+ this.reportedCornerStartFailures.add(cornerId);
19340
+ try {
19341
+ const conversation = await this.options.daemonApi.execute("getRoomConversation", {
19342
+ roomId: cornerId,
19343
+ limit: 50
19108
19344
  });
19345
+ const asked = [...conversation.items].reverse().find((item) => item.type === "message" && item.mentionIds.includes(this.agent.publicKey));
19346
+ if (!asked)
19347
+ return;
19348
+ await this.options.daemonApi.execute("postAgentTurnReceipt", {
19349
+ agentId: this.agent.publicKey,
19350
+ roomId: cornerId,
19351
+ requestId: asked.id,
19352
+ status: "failed",
19353
+ generationId: `${this.agent.publicKey}:${cornerId}`,
19354
+ reason: distillTurnFailureReason(error)
19355
+ });
19356
+ } catch (reportError) {
19357
+ console.error(`[thin-core] corner ${cornerId} start-failure report failed:`, reportError);
19109
19358
  }
19110
- await execFileAsync3("git", [
19111
- `--git-dir=${gitCommonDir}`,
19112
- "fetch",
19113
- "--prune",
19114
- "origin",
19115
- `+refs/heads/${input.targetBranch}:refs/remotes/origin/${input.targetBranch}`
19116
- ], { env: authEnv, maxBuffer: 4 * 1024 * 1024 });
19117
- if (!existsSync4(resolve19(path, ".git"))) {
19118
- await rm4(path, { recursive: true, force: true });
19119
- await execFileAsync3("git", [
19120
- `--git-dir=${gitCommonDir}`,
19121
- "worktree",
19122
- "add",
19123
- "-B",
19124
- input.featureBranch,
19125
- path,
19126
- `refs/remotes/origin/${input.targetBranch}`
19127
- ], { env: authEnv, maxBuffer: 4 * 1024 * 1024 });
19128
- }
19129
- await execFileAsync3("git", [
19130
- `--git-dir=${gitCommonDir}`,
19131
- "config",
19132
- "extensions.worktreeConfig",
19133
- "true"
19134
- ]);
19135
- await execFileAsync3("git", ["-C", path, "config", "--worktree", "core.bare", "false"]);
19136
- await execFileAsync3("git", [
19137
- "-C",
19138
- path,
19139
- "config",
19140
- "--worktree",
19141
- "credential.https://github.com.helper",
19142
- "!f() { echo username=x-access-token; echo password=$GH_TOKEN; }; f"
19143
- ]);
19144
- await execFileAsync3("git", ["-C", path, "config", "--worktree", "user.name", this.agent.name]);
19145
- await execFileAsync3("git", [
19146
- "-C",
19147
- path,
19148
- "config",
19149
- "--worktree",
19150
- "user.email",
19151
- `${this.agent.publicKey.slice(0, 16)}@users.noreply.github.com`
19152
- ]);
19153
- const top = await execFileAsync3("git", ["-C", path, "rev-parse", "--show-toplevel"]);
19154
- if (resolve19(top.stdout.trim()) !== resolve19(path)) {
19155
- throw new Error(`corner worktree escaped its isolated root: ${top.stdout.trim()}`);
19156
- }
19157
- return { path, gitCommonDir };
19359
+ }
19360
+ async materializeCornerWorktree(input) {
19361
+ return materializeCornerWorktree({
19362
+ ...input,
19363
+ supervisorRoot: this.runtime.supervisorRoot,
19364
+ committer: { name: this.agent.name, publicKey: this.agent.publicKey }
19365
+ });
19158
19366
  }
19159
19367
  async reapCornerWorktree(worktree) {
19160
19368
  if (existsSync4(worktree.path)) {
19161
- await execFileAsync3("git", [
19369
+ await execFileAsync4("git", [
19162
19370
  `--git-dir=${worktree.gitCommonDir}`,
19163
19371
  "worktree",
19164
19372
  "remove",
@@ -19328,13 +19536,13 @@ var ThinDaemonCore = class {
19328
19536
  };
19329
19537
 
19330
19538
  // apps/body/dist/systemd.js
19331
- import { execFile as execFile5 } from "node:child_process";
19539
+ import { execFile as execFile6 } from "node:child_process";
19332
19540
  import { mkdir as mkdir12, readFile as readFile8, writeFile as writeFile8 } from "node:fs/promises";
19333
19541
  import { homedir as homedir8 } from "node:os";
19334
19542
  import { dirname as dirname7, resolve as resolve20 } from "node:path";
19335
19543
  import { setTimeout as sleep } from "node:timers/promises";
19336
- import { promisify as promisify4 } from "node:util";
19337
- var execFileAsync4 = promisify4(execFile5);
19544
+ import { promisify as promisify5 } from "node:util";
19545
+ var execFileAsync5 = promisify5(execFile6);
19338
19546
  var DELIBERATE_REMOVAL_EXIT_STATUS = 78;
19339
19547
  var DAEMON_DISTRESS_EXIT_STATUS = 77;
19340
19548
  var UNKNOWN_AGENT_EXIT_STATUS = 79;
@@ -19387,7 +19595,7 @@ function systemdUserUnitPath(env = process.env) {
19387
19595
  return resolve20(configRoot, "systemd", "user", SYSTEMD_UNIT_NAME);
19388
19596
  }
19389
19597
  var runSystemctl = async (args) => {
19390
- const result = await execFileAsync4("systemctl", ["--user", ...args], {
19598
+ const result = await execFileAsync5("systemctl", ["--user", ...args], {
19391
19599
  timeout: SYSTEMD_COMMAND_TIMEOUT_MS,
19392
19600
  encoding: "utf8"
19393
19601
  });
@@ -19453,7 +19661,7 @@ async function disableAgentService(publicKey, options = {}) {
19453
19661
  async function notify(fields) {
19454
19662
  if (process.env.BEELINE_MANAGED_BY_SYSTEMD !== "1")
19455
19663
  return;
19456
- await execFileAsync4("systemd-notify", fields, { timeout: SYSTEMD_COMMAND_TIMEOUT_MS });
19664
+ await execFileAsync5("systemd-notify", fields, { timeout: SYSTEMD_COMMAND_TIMEOUT_MS });
19457
19665
  }
19458
19666
  async function extendSystemdStartTimeout(ms) {
19459
19667
  await notify([`EXTEND_TIMEOUT_USEC=${Math.max(0, Math.round(ms)) * 1e3}`]).catch(() => void 0);
@@ -20414,8 +20622,10 @@ import { mkdir as mkdir16, rm as rm6, stat as stat2, writeFile as writeFile12 }
20414
20622
  import { dirname as dirname12, resolve as resolve24 } from "node:path";
20415
20623
 
20416
20624
  // apps/body/dist/update-rollback-alert.js
20417
- import { mkdir as mkdir15, readFile as readFile11, rename as rename4, writeFile as writeFile11 } from "node:fs/promises";
20625
+ import { mkdir as mkdir15, readFile as readFile11, rename as rename4, unlink as unlink3, writeFile as writeFile11 } from "node:fs/promises";
20418
20626
  import { dirname as dirname11, resolve as resolve23 } from "node:path";
20627
+ var REPORT_INTERVAL_MS = 60 * 60 * 1e3;
20628
+ var lastLogged = /* @__PURE__ */ new Map();
20419
20629
  function updateRollbackAlertPath(runtimeDir) {
20420
20630
  return resolve23(runtimeDir, "update-rollback-alert.json");
20421
20631
  }
@@ -20443,11 +20653,37 @@ async function readUpdateRollbackAlert(runtimeDir) {
20443
20653
  return void 0;
20444
20654
  }
20445
20655
  }
20656
+ async function clearUpdateRollbackAlert(runtimeDir) {
20657
+ const existing = await readUpdateRollbackAlert(runtimeDir);
20658
+ if (!existing)
20659
+ return false;
20660
+ await unlink3(updateRollbackAlertPath(runtimeDir)).catch(() => void 0);
20661
+ lastLogged.delete(runtimeDir);
20662
+ return true;
20663
+ }
20664
+ async function clearUpdateRollbackAlertIfConfirmed(runtimeDir, loadedRelease) {
20665
+ if (!loadedRelease)
20666
+ return false;
20667
+ const existing = await readUpdateRollbackAlert(runtimeDir);
20668
+ if (!existing || existing.releaseId !== loadedRelease)
20669
+ return false;
20670
+ await unlink3(updateRollbackAlertPath(runtimeDir)).catch(() => void 0);
20671
+ lastLogged.delete(runtimeDir);
20672
+ return true;
20673
+ }
20446
20674
  async function reportUpdateRollback(input) {
20447
20675
  const pending = await readUpdateRollbackAlert(input.runtimeDir);
20448
- if (!pending)
20676
+ if (!pending) {
20677
+ lastLogged.delete(input.runtimeDir);
20678
+ return false;
20679
+ }
20680
+ const now2 = input.now ?? Date.now();
20681
+ const last = lastLogged.get(input.runtimeDir);
20682
+ if (last && last.releaseId === pending.releaseId && now2 - last.loggedAt < REPORT_INTERVAL_MS) {
20449
20683
  return false;
20684
+ }
20450
20685
  console.error(`[thin-core] UPDATE ROLLBACK: ${pending.releaseId}; durable operator record: ` + updateRollbackAlertPath(input.runtimeDir));
20686
+ lastLogged.set(input.runtimeDir, { releaseId: pending.releaseId, loggedAt: now2 });
20451
20687
  return true;
20452
20688
  }
20453
20689
 
@@ -21152,6 +21388,7 @@ import { resolve as resolve26 } from "node:path";
21152
21388
  var UPDATE_PROBE_SESSION_TIMEOUT_MS = 1e4;
21153
21389
  var UPDATE_PROBE_SESSION_OPEN_TIMEOUT_MS = 2e4;
21154
21390
  var UPDATE_PROBE_TURN_TIMEOUT_MS = 45e3;
21391
+ var UPDATE_PROBE_RETRY_DELAY_MS = 2e3;
21155
21392
  var ACP_ERROR_CODE = /\bACP error (-\d+):/;
21156
21393
  var ACP_PROMPT_INACTIVITY = /\bACP session\/prompt timed out after \d+ms of inactivity\b/;
21157
21394
  function isServerInternalCode(code) {
@@ -21275,8 +21512,7 @@ async function runUpdateFunctionalProbe(input) {
21275
21512
  });
21276
21513
  const sessionTimeoutMs = input.sessionTimeoutMs ?? UPDATE_PROBE_SESSION_TIMEOUT_MS;
21277
21514
  const sessionOpenTimeoutMs = input.sessionOpenTimeoutMs ?? input.sessionTimeoutMs ?? UPDATE_PROBE_SESSION_OPEN_TIMEOUT_MS;
21278
- try {
21279
- await client.start(sessionTimeoutMs);
21515
+ const openSessionAndPrompt = async () => {
21280
21516
  const opened = await client.sessionNew({
21281
21517
  cwd,
21282
21518
  mcpServers: [],
@@ -21287,15 +21523,22 @@ async function runUpdateFunctionalProbe(input) {
21287
21523
  if (input.config.modelSelection) {
21288
21524
  await applyAgentModelSelection(client, opened.sessionId, parseAdvertisedConfigOptions(opened.raw, input.config.modelSelection.model, isGrokAgentCommand(selectedAgent)), input.config.modelSelection);
21289
21525
  }
21526
+ return {
21527
+ sessionId: opened.sessionId,
21528
+ served: await client.sessionPrompt(opened.sessionId, "Reply READY.", input.turnTimeoutMs ?? UPDATE_PROBE_TURN_TIMEOUT_MS)
21529
+ };
21530
+ };
21531
+ try {
21532
+ await client.start(sessionTimeoutMs);
21290
21533
  try {
21291
- const served = await client.sessionPrompt(opened.sessionId, "Reply READY.", input.turnTimeoutMs ?? UPDATE_PROBE_TURN_TIMEOUT_MS);
21534
+ const { sessionId, served } = await openSessionAndPrompt();
21292
21535
  if (served.agentText.trim()) {
21293
21536
  modelAnswer = { modelAnswer: "served" };
21294
21537
  } else {
21295
21538
  const explained = await explainEmptyAgentTurn({
21296
21539
  agentLabel: command,
21297
21540
  agentEnv,
21298
- sessionId: opened.sessionId,
21541
+ sessionId,
21299
21542
  result: served
21300
21543
  });
21301
21544
  const modelSide = isAccountOrProviderRefusal(explained.record) || explained.record?.kind === "empty";
@@ -21323,29 +21566,55 @@ async function runUpdateFunctionalProbe(input) {
21323
21566
  modelAnswer = { modelAnswer: "unavailable", modelAnswerReason: explained.reason };
21324
21567
  }
21325
21568
  }
21326
- } catch (error) {
21327
- if (error instanceof UpdateFunctionalProbeError)
21328
- throw error;
21329
- const detail = error instanceof Error ? error.message : String(error);
21330
- const failure = classifyAcpTurnFailure(error);
21331
- if (!failure || !input.compareWithCurrentRelease) {
21332
- throw new UpdateFunctionalProbeError("turn-failed", detail, { cause: error });
21569
+ } catch (initialError) {
21570
+ if (initialError instanceof UpdateFunctionalProbeError)
21571
+ throw initialError;
21572
+ let error = initialError;
21573
+ let detail = error instanceof Error ? error.message : String(error);
21574
+ let failure = classifyAcpTurnFailure(error);
21575
+ if (failure) {
21576
+ await new Promise((resolveWait) => setTimeout(resolveWait, input.retryDelayMs ?? UPDATE_PROBE_RETRY_DELAY_MS));
21577
+ try {
21578
+ const retried = await openSessionAndPrompt();
21579
+ if (retried.served.agentText.trim()) {
21580
+ turnCompleted = true;
21581
+ modelAnswer = { modelAnswer: "served" };
21582
+ console.warn(`[body] update probe: the probe turn failed once (${detail}) but a fresh retry answered; treating the first failure as the provider's, not this bundle's`);
21583
+ error = void 0;
21584
+ }
21585
+ } catch (retryError) {
21586
+ const retryDetail = retryError instanceof Error ? retryError.message : String(retryError);
21587
+ const retryFailure = classifyAcpTurnFailure(retryError);
21588
+ if (!retryFailure || !sameAcpTurnFailure(failure, retryFailure)) {
21589
+ throw new UpdateFunctionalProbeError("turn-failed", retryDetail, {
21590
+ cause: retryError
21591
+ });
21592
+ }
21593
+ error = retryError;
21594
+ detail = retryDetail;
21595
+ failure = retryFailure;
21596
+ }
21333
21597
  }
21334
- const current = await input.compareWithCurrentRelease({
21335
- kind: "acp-turn-failure",
21336
- reason: detail,
21337
- failure
21338
- });
21339
- const currentFailure = current.kind === "unavailable" ? classifyAcpTurnFailure(current.reason) : void 0;
21340
- if (!currentFailure || !sameAcpTurnFailure(failure, currentFailure)) {
21341
- throw new UpdateFunctionalProbeError("turn-failed", `${detail}; ${describeCurrentReleaseOutcome(current)}`, { cause: error });
21598
+ if (error === void 0) {
21599
+ } else if (!failure || !input.compareWithCurrentRelease) {
21600
+ throw new UpdateFunctionalProbeError("turn-failed", detail, { cause: error });
21601
+ } else {
21602
+ const current = await input.compareWithCurrentRelease({
21603
+ kind: "acp-turn-failure",
21604
+ reason: detail,
21605
+ failure
21606
+ });
21607
+ const currentFailure = current.kind === "unavailable" ? classifyAcpTurnFailure(current.reason) : void 0;
21608
+ if (!currentFailure || !sameAcpTurnFailure(failure, currentFailure)) {
21609
+ throw new UpdateFunctionalProbeError("turn-failed", `${detail}; ${describeCurrentReleaseOutcome(current)}`, { cause: error });
21610
+ }
21611
+ console.warn(`[body] update probe: the probe turn failed the same way on this release and the current release (${detail}); that failure is not this bundle's doing, so the probe passes as inconclusive`);
21612
+ turnCompleted = false;
21613
+ modelAnswer = {
21614
+ modelAnswer: "unavailable",
21615
+ modelAnswerReason: `${detail} (the current release fails the same way)`
21616
+ };
21342
21617
  }
21343
- console.warn(`[body] update probe: the probe turn failed the same way on this release and the current release (${detail}); that failure is not this bundle's doing, so the probe passes as inconclusive`);
21344
- turnCompleted = false;
21345
- modelAnswer = {
21346
- modelAnswer: "unavailable",
21347
- modelAnswerReason: `${detail} (the current release fails the same way)`
21348
- };
21349
21618
  }
21350
21619
  } catch (error) {
21351
21620
  if (error instanceof UpdateFunctionalProbeError)
@@ -21525,7 +21794,7 @@ async function writeDaemonReleaseStatus(runtimeDir, agentPubkey, identity, optio
21525
21794
  }
21526
21795
 
21527
21796
  // apps/body/dist/scratch-sweep.js
21528
- import { lstat as lstat3, readdir as readdir6, rmdir, unlink as unlink3 } from "node:fs/promises";
21797
+ import { lstat as lstat3, readdir as readdir6, rmdir, unlink as unlink4 } from "node:fs/promises";
21529
21798
  import { resolve as resolve28 } from "node:path";
21530
21799
  var DEFAULT_SCRATCH_TTL_HOURS = 72;
21531
21800
  var NEVER_SWEEP_SUBDIR_NAMES = new Set(HOME_SUBDIRS.filter((name) => name !== "tmp"));
@@ -21580,7 +21849,7 @@ async function removeStaleFiles(dir, cutoffMs, protectNamesHere) {
21580
21849
  }
21581
21850
  if (!stats.isFile() || stats.mtimeMs >= cutoffMs)
21582
21851
  continue;
21583
- await unlink3(path).catch(() => void 0);
21852
+ await unlink4(path).catch(() => void 0);
21584
21853
  removedFiles += 1;
21585
21854
  removedBytes += stats.size;
21586
21855
  }
@@ -21739,6 +22008,7 @@ async function runStoredDaemon(pathOrPointer) {
21739
22008
  pendingSuccessor = true;
21740
22009
  }
21741
22010
  loadedRelease = await activeReleaseId(layout);
22011
+ await clearUpdateRollbackAlertIfConfirmed(runtimeDir, loadedRelease);
21742
22012
  loadedReleaseIdentity = await readInstalledBundleIdentity(layout);
21743
22013
  config.daemonReleaseVersion = loadedReleaseIdentity?.version;
21744
22014
  config.daemonSourceSha = loadedReleaseIdentity?.commit;
@@ -21807,6 +22077,7 @@ async function runStoredDaemon(pathOrPointer) {
21807
22077
  }
21808
22078
  functionalProof = gate.proof;
21809
22079
  pendingSuccessor = false;
22080
+ await clearUpdateRollbackAlert(runtimeDir);
21810
22081
  console.log(`[thin-core] successor functional probe passed on exact release ${loadedRelease}: ${functionalProof?.harness ?? "unknown"} session/new + turn` + (functionalProof?.modelAnswer === "unavailable" ? ` (model answer unavailable: ${functionalProof.modelAnswerReason})` : ""));
21811
22082
  }
21812
22083
  await clearDaemonStartFailures(runtimeDir);
@@ -21854,7 +22125,7 @@ async function runStoredDaemon(pathOrPointer) {
21854
22125
  const pidPath = resolve29(dirname15(configPath), "daemon.pid");
21855
22126
  const recorded = Number((await readFile14(pidPath, "utf8").catch(() => "")).trim());
21856
22127
  if (recorded === process.pid) {
21857
- await unlink4(pidPath).catch(() => void 0);
22128
+ await unlink5(pidPath).catch(() => void 0);
21858
22129
  }
21859
22130
  }
21860
22131
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usebeeline",
3
- "version": "0.0.54",
3
+ "version": "0.0.56",
4
4
  "description": "Connect an AI coding agent to Beeline with one command.",
5
5
  "homepage": "https://usebeeline.app",
6
6
  "bugs": {