usebeeline 0.1.13 → 0.1.16

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 +266 -55
  2. package/package.json +1 -1
@@ -18020,6 +18020,15 @@ function isAgentCommand(value) {
18020
18020
  return Boolean(source && typeof source.id === "string" && typeof source.authorId === "string" && typeof source.body === "string" && typeof source.createdAt === "number" && Array.isArray(source.attachments));
18021
18021
  }
18022
18022
 
18023
+ // packages/api-contract/dist/reply-context.js
18024
+ var REPLY_EXCERPT_MAX_LENGTH = 300;
18025
+ function boundReplyExcerpt(text2) {
18026
+ if (text2.length <= REPLY_EXCERPT_MAX_LENGTH)
18027
+ return text2.trim();
18028
+ const prefix = text2.slice(0, REPLY_EXCERPT_MAX_LENGTH - 1);
18029
+ return `${prefix.replace(/\s+\S*$/, "").trim()}\u2026`;
18030
+ }
18031
+
18023
18032
  // packages/api-contract/dist/artifacts.js
18024
18033
  var ARTIFACT_EXTENSIONS_BY_MIME = {
18025
18034
  "text/html": [".html", ".htm"],
@@ -18100,6 +18109,8 @@ function eventKindCatalogLines(kinds = SUBSCRIBABLE_EVENT_KINDS) {
18100
18109
  function isWebhookKind(value) {
18101
18110
  return typeof value === "string" && /^webhook:[a-z0-9-]{1,40}$/.test(value);
18102
18111
  }
18112
+ var AGENT_EVENT_SLUG_MAX_LENGTH = 40;
18113
+ var AGENT_KIND = new RegExp(`^agent:[a-z0-9-]{1,${AGENT_EVENT_SLUG_MAX_LENGTH}}$`);
18103
18114
 
18104
18115
  // packages/api-contract/dist/outside-data.js
18105
18116
  function quoteOutsideData(origin, data) {
@@ -30896,7 +30907,7 @@ var AcpClient = class extends EventEmitter {
30896
30907
 
30897
30908
  // apps/body/dist/agent-home.js
30898
30909
  var import_yaml2 = __toESM(require_dist(), 1);
30899
- import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
30910
+ import { existsSync as existsSync7, readFileSync as readFileSync5, watch } from "node:fs";
30900
30911
 
30901
30912
  // node_modules/smol-toml/dist/date.js
30902
30913
  var DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[T ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|[-+]\d{2}:\d{2})?$/i;
@@ -32349,6 +32360,15 @@ function fetchPointer(row, reason) {
32349
32360
  }
32350
32361
 
32351
32362
  // apps/body/dist/prompt-assembly.js
32363
+ function renderReplyContext(message) {
32364
+ if (!message.replyToMessageId)
32365
+ return "";
32366
+ const author = message.replyToAuthorName ?? message.replyToAuthorId ?? "unknown author";
32367
+ return [
32368
+ `Reply to message ${message.replyToMessageId} by ${JSON.stringify(author)}:`,
32369
+ ...message.replyToExcerpt !== void 0 ? [`> ${JSON.stringify(boundReplyExcerpt(message.replyToExcerpt))}`] : []
32370
+ ].join("\n");
32371
+ }
32352
32372
  var PROMPT_SURFACES = [
32353
32373
  "room",
32354
32374
  "dm",
@@ -32369,7 +32389,37 @@ intent, base, tests, docs, lint_types, publication, final_authorization | implem
32369
32389
  review | configured reviewer records the review stage | current revision/head and reviewer identity
32370
32390
  ci | implementer | current revision/head; passed requires passing checks
32371
32391
  The server merge gate is the authority: pr_checks_status reports mergeAllowed true for the current head; the server then merges that head. final_authorization grants nothing; do not wait for mergeAllowed.`;
32372
- var CORNER_AUTHOR_CONTRACT = `The current brief's spec \u2014 stories, non-goals, risks \u2014 defines scope and done. The human approval quote wins any conflict. The short objective is navigation-only, never scope.
32392
+ function cornerHasRepositoryWork(brief) {
32393
+ if (!brief)
32394
+ return true;
32395
+ if (brief.repositoryWork !== void 0)
32396
+ return brief.repositoryWork;
32397
+ const nonGoals = brief.spec.match(/^## Non-goals\s*\n([\s\S]*?)(?=^## |$(?![\s\S]))/mi)?.[1] ?? "";
32398
+ const prohibition = /^(?:repository edits?|pull requests?|(?:edit|modify|change) (?:the )?repository)[.!;]*$/i;
32399
+ for (const line of nonGoals.split("\n")) {
32400
+ const bullet = /^\s*[-*]\s+/.test(line);
32401
+ const clause = line.trim().replace(/^[-*]\s+/, "");
32402
+ const negative = /^(?:no|never|do not|forbid(?:s|den)?)\s+/i;
32403
+ if (!bullet && !negative.test(clause))
32404
+ continue;
32405
+ const items = clause.replace(negative, "").split(/,\s*|\s+(?:and|or)\s+/i);
32406
+ if (items.some((item) => prohibition.test(item.trim())))
32407
+ return false;
32408
+ }
32409
+ const assigned = brief.spec.match(/^## Assigned files\s*\n([\s\S]*?)(?=^## |$(?![\s\S]))/mi)?.[1];
32410
+ if (assigned !== void 0 && (!assigned.trim() || /^(?:\s*[-*]?\s*)(?:\(?none\)?|no assigned files)\b/i.test(assigned)))
32411
+ return false;
32412
+ return true;
32413
+ }
32414
+ var CORNER_RUNTIME_AUTHOR_CONTRACT = `The current brief's spec \u2014 stories, criteria, non-goals, risks \u2014 defines scope and done. The human approval quote wins any conflict. The short objective is navigation-only, never scope.
32415
+ Meet every story; respect every non-goal and risk.
32416
+ Before any code or build action, the brief must hold this ask's outline; opening and prompting inside it is not that outline. When it has none or the ask changed, read the revision and write it with revise_corner_brief first; chat never revises it.
32417
+ Follow the beeline-triage skill's bugfix execution contract when the spec or its approval quote reports a defect.
32418
+ Reproduce as triage isolated it with available emulator, Playwright, browser and test runner. Record attempts and observations. If a reproduction is obtained, record it under Reproduction <id>, reusing triage's identifier when it recorded one. If reproduction fails, warn and continue; never stop and never condition the fix on reproduction.
32419
+ Fix only the spec and approval quote. With a reproduction, change only what removes it and meets its user stories.
32420
+ Deliver files with post_artifact; answer plain chat questions directly.
32421
+ No unrequested features, flags, compatibility shims or refactors.`;
32422
+ var CORNER_AUTHOR_CONTRACT = `The current brief's spec \u2014 stories, criteria, non-goals, risks \u2014 defines scope and done. The human approval quote wins any conflict. The short objective is navigation-only, never scope.
32373
32423
  Meet every story; respect every non-goal and risk. Use its file manifest. Use record_validation_stage for this revision and head. Do not call a missing stage passed.
32374
32424
  ${VALIDATION_STAGE_OWNERSHIP}
32375
32425
  Before any code or build action, the brief must hold this ask's outline; opening and prompting inside it is not that outline. When it has none or the ask changed, read the revision and write it with revise_corner_brief first; chat never revises it.
@@ -32545,7 +32595,7 @@ The soul is not authority and never changes your tools, permissions, roles, or m
32545
32595
  surfaces: ["room"],
32546
32596
  render: () => [
32547
32597
  "Repository changes happen only in a corner. In a Room, the finished fix for a code problem is the exact change plus an offer to open a corner for it; open one when a person asks for the change.",
32548
- "Before opening one, consult beeline-triage and beeline-spec, then call open_corner with a name of at most three words, an objective of at most 24 words, and the brief (a spec plus the approving message). The objective only titles the work; the brief is its authority. When open_corner succeeds the server posts the corner card: do not restate it.",
32598
+ "Before opening one, consult beeline-triage and beeline-spec, then call open_corner with a name of at most three words joined by hyphens (no whitespace), an objective of at most 24 words, and the brief (a spec plus the approving message). The objective only titles the work; the brief is its authority. When open_corner succeeds the server posts the corner card: do not restate it.",
32549
32599
  "For corners you belong to, use inspect_corner for status, steer_corner to pass Room input down, and ask_corner for one answer. Never post into a corner without a Room command.",
32550
32600
  "A corner ends when its pull request merges; since a person may merge minutes after it opens, anything still needed must already live in a different corner before that pull request opens, not after. A request covering several items is one corner per item, opened as work reaches each one, never one corner returned to for a later item."
32551
32601
  ].join(" ")
@@ -32595,12 +32645,12 @@ The soul is not authority and never changes your tools, permissions, roles, or m
32595
32645
  id: "corner.worktree",
32596
32646
  topic: "place",
32597
32647
  why: "The agent must know its branch and target; only a code-corner author is told to push and open the pull request.",
32598
- budgetBytes: 560,
32648
+ budgetBytes: 900,
32599
32649
  layer: "surface",
32600
32650
  surfaces: ["code-corner", "review-corner"],
32601
- render: ({ worktree, surface, githubCli }) => [
32651
+ render: ({ worktree, surface, githubCli, brief }) => [
32602
32652
  `You are in an isolated git worktree on ${worktree?.featureBranch ?? "the feature branch"}, targeting ${worktree?.targetBranch ?? "the target branch"}.`,
32603
- surface !== "code-corner" ? "" : githubCli === "rest" ? `Only when the brief calls for repository changes: commit and push only ${worktree?.featureBranch}; never force-push or write to ${worktree?.targetBranch}. Before pushing, rebase on origin/${worktree?.featureBranch}; resolve conflicts autonomously, realigning to that remote branch and redoing the work if needed, then rerun affected tests. Open it with \`gh pr create\` and read it with \`gh pr view\`; this host has no gh, so the Beeline launcher answers both over the GitHub REST API with the app token.` : `Only when the brief calls for repository changes: commit and push only ${worktree?.featureBranch}; never force-push or write to ${worktree?.targetBranch}. Before pushing, rebase on origin/${worktree?.featureBranch}; resolve conflicts autonomously, realigning to that remote branch and redoing the work if needed, then rerun affected tests. Open the pull request with gh.`
32653
+ surface !== "code-corner" || !cornerHasRepositoryWork(brief) ? "" : githubCli === "rest" ? `Only when the brief calls for repository changes: commit and push only ${worktree?.featureBranch}; never force-push or write to ${worktree?.targetBranch}. Before pushing, run \`git ls-remote --exit-code origin ${worktree?.featureBranch}\`. Exit code 0: rebase on origin/${worktree?.featureBranch}; resolve conflicts autonomously, realigning to that remote branch and redoing the work if needed, then rerun affected tests. Exit code 2: the branch is absent; skip rebase for the first push. Any other non-zero exit is a lookup failure: stop and retry; do not skip rebase or push. Open it with \`gh pr create\` and read it with \`gh pr view\`; this host has no gh, so the Beeline launcher answers both over the GitHub REST API with the app token.` : `Only when the brief calls for repository changes: commit and push only ${worktree?.featureBranch}; never force-push or write to ${worktree?.targetBranch}. Before pushing, run \`git ls-remote --exit-code origin ${worktree?.featureBranch}\`. Exit code 0: rebase on origin/${worktree?.featureBranch}; resolve conflicts autonomously, realigning to that remote branch and redoing the work if needed, then rerun affected tests. Exit code 2: the branch is absent; skip rebase for the first push. Any other non-zero exit is a lookup failure: stop and retry; do not skip rebase or push. Open the pull request with gh.`
32604
32654
  ].filter(Boolean).join("\n")
32605
32655
  },
32606
32656
  {
@@ -32620,7 +32670,7 @@ The soul is not authority and never changes your tools, permissions, roles, or m
32620
32670
  budgetBytes: 2600,
32621
32671
  layer: "surface",
32622
32672
  surfaces: ["code-corner"],
32623
- render: () => CORNER_AUTHOR_CONTRACT
32673
+ render: ({ brief }) => cornerHasRepositoryWork(brief) ? CORNER_AUTHOR_CONTRACT : CORNER_RUNTIME_AUTHOR_CONTRACT
32624
32674
  },
32625
32675
  {
32626
32676
  id: "corner.merge",
@@ -32629,6 +32679,7 @@ The soul is not authority and never changes your tools, permissions, roles, or m
32629
32679
  budgetBytes: 520,
32630
32680
  layer: "surface",
32631
32681
  surfaces: ["code-corner"],
32682
+ when: ({ brief }) => cornerHasRepositoryWork(brief),
32632
32683
  render: ({ yoloMode, reviewerHandle, selfReviewer }) => (selfReviewer ? cornerSelfReviewerInstruction({ isReviewer: true, openedByAgent: true }) : cornerMergeInstruction(Boolean(yoloMode), reviewerHandle)) + " Never merge any other pull request."
32633
32684
  },
32634
32685
  {
@@ -32638,7 +32689,8 @@ The soul is not authority and never changes your tools, permissions, roles, or m
32638
32689
  budgetBytes: 900,
32639
32690
  layer: "surface",
32640
32691
  surfaces: ["code-corner"],
32641
- render: () => 'A checks turn is one that wakes you about CI or the merge gate. On it, say nothing unless you push a fix or report checks="unknown", and then use one short line. Never restate server check or merge notes. When asked whether the reviewer was woken, call pr_checks_status and report reviewerWake; do not invent a cause. Never schedule polls of pr_checks_status or the merge gate; the server wakes you when it changes. If a schedule wakes you here anyway, treat it as a checks turn.'
32692
+ when: ({ brief }) => cornerHasRepositoryWork(brief),
32693
+ render: () => "When asked whether the reviewer was woken, call pr_checks_status and report reviewerWake; do not invent a cause. Never schedule polls of pr_checks_status or the merge gate; the server wakes you when it changes."
32642
32694
  },
32643
32695
  {
32644
32696
  id: "corner.review",
@@ -32728,6 +32780,16 @@ var TURN_SECTIONS = [
32728
32780
  surfaces: EVERYWHERE,
32729
32781
  render: ({ sessionPrefix }) => sessionPrefix ?? ""
32730
32782
  },
32783
+ {
32784
+ id: "turn.checks",
32785
+ topic: "checks-turns",
32786
+ why: "Only CI or merge-gate wakes suppress ordinary reporting; user schedules must deliver their task results.",
32787
+ budgetBytes: 350,
32788
+ layer: "turn",
32789
+ surfaces: ["code-corner"],
32790
+ when: ({ task }) => task.commandReason !== "schedule" && (["corner_check", "corner_merge_refused", "corner_merge_conflict"].includes(task.commandReason ?? "") || ["check-passed", "check-failed"].includes(task.outsideEvent?.kind ?? "")),
32791
+ render: () => 'This is a checks turn about CI or the merge gate. On it, say nothing unless you push a fix or report checks="unknown", and then use one short line. Never restate server check or merge notes.'
32792
+ },
32731
32793
  {
32732
32794
  id: "turn.objective",
32733
32795
  topic: "objective",
@@ -32744,7 +32806,7 @@ var TURN_SECTIONS = [
32744
32806
  budgetBytes: 600,
32745
32807
  layer: "turn",
32746
32808
  surfaces: CORNERS,
32747
- render: ({ generatedTitle, brief }) => generatedTitle ? `This human-opened corner still has its generated name, "${generatedTitle}". If the newest human message states the work, call rename_corner once before you reply with a name of at most three words and a short objective${brief ? "" : ", plus a brief grounded in that message with brief.approval.sourceMessageId set to the human message id"}. A bare mention does not state the work; leave the name until a message does.` : ""
32809
+ render: ({ generatedTitle, brief }) => generatedTitle ? `This human-opened corner still has its generated name, "${generatedTitle}". If the newest human message states the work, call rename_corner once before you reply with a name of at most three words joined by hyphens (no whitespace) and a short objective${brief ? "" : ", plus a brief grounded in that message with brief.approval.sourceMessageId set to the human message id"}. A bare mention does not state the work; leave the name until a message does.` : ""
32748
32810
  },
32749
32811
  {
32750
32812
  id: "turn.brief",
@@ -32753,10 +32815,13 @@ var TURN_SECTIONS = [
32753
32815
  budgetBytes: 24e3,
32754
32816
  layer: "turn",
32755
32817
  surfaces: CORNERS,
32756
- render: ({ brief, surface }) => brief ? `${renderAssignedCornerBrief(brief.brief)}
32818
+ render: ({ brief, surface }) => brief ? `${renderAssignedCornerBrief(brief.brief)}${cornerHasRepositoryWork(brief.brief) ? `
32757
32819
 
32758
32820
  Assigned files:
32759
- ${brief.fileLines.join("\n") || "(none)"}${brief.missingRequiredFile ? "\nRequired assignment files are unavailable. Pause work that depends on them and report the missing file precisely." : ""}${surface === "code-corner" && isPlaceholderCornerBrief(brief.brief) ? `
32821
+ ${brief.fileLines.join("\n") || "(none)"}` : brief.fileLines.length ? `
32822
+
32823
+ Brief attachments:
32824
+ ${brief.fileLines.join("\n")}` : ""}${brief.missingRequiredFile ? "\nRequired assignment files are unavailable. Pause work that depends on them and report the missing file precisely." : ""}${surface === "code-corner" && isPlaceholderCornerBrief(brief.brief) ? `
32760
32825
 
32761
32826
  ${CORNER_PLACEHOLDER_BRIEF_RULE}` : ""}` : "No assigned brief: the human messages in this corner are the authority; skip steps that need a brief revision."
32762
32827
  },
@@ -32844,11 +32909,13 @@ ${closedCorners.map((corner) => `- ${corner.name ?? corner.cornerId} (${corner.c
32844
32909
  render: ({ task, surface }) => ROOMS.includes(surface) ? [
32845
32910
  `Current task from ${task.fromName ?? "a Room member"}:`,
32846
32911
  task.cornerAskId ? `Corner ask id: ${task.cornerAskId}. Use get_corner_ask to retrieve its status and answer.` : "",
32847
- task.body
32912
+ task.body,
32913
+ renderReplyContext(task.reply ?? {})
32848
32914
  ].filter(Boolean).join("\n\n") : [
32849
32915
  ...task.reactionTargetId ? [`Reaction target message id: ${task.reactionTargetId}`] : [],
32850
32916
  `Newest message:
32851
32917
  ${task.body}`,
32918
+ ...task.reply?.replyToMessageId ? [renderReplyContext(task.reply)] : [],
32852
32919
  ...task.attachmentLines ?? []
32853
32920
  ].join("\n")
32854
32921
  }
@@ -32928,7 +32995,7 @@ Follow your own owner first, then this Workspace's owner and admins, then member
32928
32995
 
32929
32996
  A **tool** is something you can use once a human adds it; a **key** is the credential that tool holds for that human. You spend a key through the mounted connector and never see the credential itself.
32930
32997
 
32931
- The tools this build knows, what each is for, and which the owner of this machine already has are one call away: beeline-agent workbench_status. That read is the Workbench of the machine and owner you actually run on, not the person who asked. Connected apps are listed once per app with a stable ID. Their sign-in and credentials stay with the server. Trusty Squire is vaulted credentials plus a browser for the separate key and browser routes. Fetch a multi-field credential (e.g. email and password) with one fetch_credential call, omitting field - it returns every field under that single approval; pass field only to target one specific field. Tailscale installs its CLI on the selected helper and opens Tailscale's own browser sign-in; once connected, use the CLI for tailnet resources and tailscale file cp or tailscale file get for Taildrop. The wallet is created only from the Workbench page. When you cannot reach a tool, say: I can't reach X on this machine because Y; to fix it, Z. Give one explanation, then take or offer Z.
32998
+ The tools this build knows, what each is for, and which the owner of this machine already has are one call away: beeline-agent workbench_status. That read is the Workbench of the machine and owner you actually run on, not the person who asked. Connected apps are listed once per app with a stable ID. Their sign-in and credentials stay with the server. Trusty Squire is vaulted credentials plus a browser for the separate key and browser routes. Drive a multi-step browser flow such as checkout or signup with operate_drive rather than one operate_act call per step. Tailscale installs its CLI on the selected helper and opens Tailscale's own browser sign-in; once connected, use the CLI for tailnet resources and tailscale file cp or tailscale file get for Taildrop. The wallet is created only from the Workbench page. When you cannot reach a tool, say: I can't reach X on this machine because Y; to fix it, Z. Give one explanation, then take or offer Z.
32932
32999
 
32933
33000
  **Inspect your own host before answering.** When a question is about a system on the machine you run on, inspect its live state with the tools you have: processes, logs, local endpoints or service status. Say what you checked. Memory and Room history are not evidence of current state.
32934
33001
 
@@ -32981,9 +33048,9 @@ Follow these steps in order. Do not skip or reorder them.
32981
33048
 
32982
33049
  ## 2. P0 - OUTLINE FULFILLED, DEMONSTRATED
32983
33050
 
32984
- Read the server-assigned brief and its current revision, including every required file. Quote the approval with its message ID and approver before considering the short objective. The spec's outline (user stories, non-goals, risks) is the scope, and the approval quote is the human's own words: it wins any conflict with the spec. The short objective is navigation-only text and the PR description cannot add, remove, or narrow scope. A revision with no recorded approval is reviewed against its spec alone. If a required file is unavailable, or the revision changes during review, refuse approval and describe what is missing. A requirement may become out of scope only through a new human-authorized brief revision; reviewer discretion, implementation difficulty, and silence never remove it. A passing narrow test does not prove a broader requirement. Review a deliberate human choice as written, even if it is unusual.
33051
+ Read the server-assigned brief and its current revision, including every required file. Quote the approval with its message ID and approver before considering the short objective. The spec's outline (user stories, acceptance criteria, non-goals, risks) is the scope, and the approval quote is the human's own words: it wins any conflict with the spec. The short objective is navigation-only text and the PR description cannot add, remove, or narrow scope. A revision with no recorded approval is reviewed against its spec alone. If a required file is unavailable, or the revision changes during review, refuse approval and describe what is missing. A requirement may become out of scope only through a new human-authorized brief revision; reviewer discretion, implementation difficulty, and silence never remove it. A passing narrow test does not prove a broader requirement. Review a deliberate human choice as written, even if it is unusual.
32985
33052
 
32986
- Before general correctness, produce an outline ledger in spec order: list every \`## User stories\` line exactly once as met, unmet, unverified, or out of scope, followed by concrete evidence - no stories to list is a FAIL, sent back to the author to write the outline; confirm nothing under \`## Non-goals\` was done; confirm every \`## Risks\` line was addressed or explicitly accepted with a stated reason. For a frontend change, open the rendered screens and compare them against the attached mock, noting every visible deviation. Any missing, unmet, unverified story, done non-goal, unaddressed risk, or unreviewed mock mismatch blocks PASS. Out of scope is valid only when the current revision itself records the human-authorized removal.
33053
+ Before general correctness, produce an outline ledger in spec order: list every \`## User stories\` and \`## Acceptance criteria\` line exactly once as met, unmet, unverified, or out of scope, followed by concrete evidence (for a criterion, the named test run against the PR head) - no stories to list is a FAIL, sent back to the author to write the outline; confirm nothing under \`## Non-goals\` was done; confirm every \`## Risks\` line was addressed or explicitly accepted with a stated reason. For a frontend change, open the rendered screens and compare them against the attached mock, noting every visible deviation. Any missing, unmet, unverified story or acceptance criterion, done non-goal, unaddressed risk, or unreviewed mock mismatch blocks PASS. Out of scope is valid only when the current revision itself records the human-authorized removal.
32987
33054
 
32988
33055
  Before judging the implementation, independently repeat the two judgment legs from request triage:
32989
33056
 
@@ -33056,7 +33123,7 @@ When the diff touches UI and the repository has a DESIGN.md, check every changed
33056
33123
  \`how Y was demonstrated (or FAIL):\`
33057
33124
  \`commands run + results:\`
33058
33125
  \`brief revision:\`
33059
- \`outline ledger (every user story, non-goal and risk + status + evidence; mock-vs-screen notes for frontend work):\`
33126
+ \`outline ledger (every user story, acceptance criterion, non-goal and risk + status + evidence; mock-vs-screen notes for frontend work):\`
33060
33127
  \`validation stages and evidence:\`
33061
33128
  \`product-completeness findings (block):\`
33062
33129
  \`engineering findings (block):\`
@@ -33086,6 +33153,7 @@ Create an assignment a fresh session can execute without the parent transcript.
33086
33153
  - \`spec\`: one Markdown doc you write, with these headings:
33087
33154
  - \`## Intent\`: the human's own words, quoted exactly, each with its Room message ID. Never substitute a summary.
33088
33155
  - \`## User stories\`: what this enables, one per line as "a person who does X sees Y". Only stories traceable to the quoted Intent; never import a plan, a teammate's suggestion, or your own idea as a story. Keep unaffected lines as they are across revisions.
33156
+ - \`## Acceptance criteria\`: the requester's own rules from \`## Intent\`, quoted verbatim and numbered, each naming the test (existing or to be written) that fails if the rule is broken anywhere. Criteria are the invariant; stories are examples of it. Write "None beyond the stories." when the request states no rule.
33089
33157
  - \`## Non-goals\`: explicit exclusions.
33090
33158
  - \`## Risks\`: what could go wrong, and for whom.
33091
33159
  - \`## References\`: each reference or mock with what it is and whose call it is; the mock posted here (DESIGN.md, else existing app styles) is the reviewer's acceptance reference, not any other optional or agent-generated mock.
@@ -34241,6 +34309,32 @@ async function writeBackRoomClaudeLogin(input) {
34241
34309
  return;
34242
34310
  await writeBackDetachedClaudeLogin(target, source);
34243
34311
  }
34312
+ function watchRoomClaudeLogin(input) {
34313
+ const claude = SHARED_CREDENTIALS.find((credential) => credential.dir === "claude");
34314
+ if (!existsSync7(resolve16(input.operatorHome ?? homedir11(), claude.source)))
34315
+ return void 0;
34316
+ const dir = resolve16(input.root, claude.dir);
34317
+ let pass = Promise.resolve();
34318
+ let watcher;
34319
+ try {
34320
+ watcher = watch(dir, (_event, filename) => {
34321
+ if (filename !== claude.target)
34322
+ return;
34323
+ pass = pass.then(() => writeBackRoomClaudeLogin(input)).catch((error) => {
34324
+ console.error(`[body] could not write back a refreshed Claude login under ${dir}:`, error);
34325
+ });
34326
+ });
34327
+ } catch (error) {
34328
+ console.error(`[body] could not watch the Claude login under ${dir}:`, error);
34329
+ return void 0;
34330
+ }
34331
+ watcher.on("error", (error) => {
34332
+ console.error(`[body] stopped watching the Claude login under ${dir}:`, error);
34333
+ watcher.close();
34334
+ });
34335
+ watcher.unref();
34336
+ return watcher;
34337
+ }
34244
34338
  async function prepareRoomAgentHome(input) {
34245
34339
  const root = resolve16(input.root);
34246
34340
  const operatorHome = input.operatorHome ?? homedir11();
@@ -41041,6 +41135,8 @@ function sessionConfigFingerprint(input) {
41041
41135
  input.yoloMode ?? false,
41042
41136
  mountedMcpSet(input.mcpServers)
41043
41137
  ];
41138
+ if (input.repositoryWork !== void 0)
41139
+ fingerprint.push({ repositoryWork: input.repositoryWork });
41044
41140
  if (input.reviewerHandle !== void 0)
41045
41141
  fingerprint.push(input.reviewerHandle);
41046
41142
  if (input.devices?.length)
@@ -42367,6 +42463,7 @@ var MonolithRoomTurnLoop = class {
42367
42463
  this.wakeIntake = void 0;
42368
42464
  }
42369
42465
  client;
42466
+ claudeLoginWatch;
42370
42467
  sessionId;
42371
42468
  sessionCwd;
42372
42469
  sessionCommit;
@@ -42588,8 +42685,26 @@ var MonolithRoomTurnLoop = class {
42588
42685
  ...promptBytes ? { promptBytes } : {}
42589
42686
  };
42590
42687
  }
42688
+ /**
42689
+ * Watch this client's Claude login so a refresh made while the
42690
+ * Room is idle reaches other Rooms at once (see `watchRoomClaudeLogin`).
42691
+ */
42692
+ watchClaudeLogin() {
42693
+ this.stopClaudeLoginWatch();
42694
+ if (this.options.config.agentKind !== "claude" || !this.options.config.agentHomeRoot)
42695
+ return;
42696
+ this.claudeLoginWatch = watchRoomClaudeLogin({
42697
+ root: this.options.config.agentHomeRoot,
42698
+ operatorHome: this.options.config.operatorHome
42699
+ });
42700
+ }
42701
+ stopClaudeLoginWatch() {
42702
+ this.claudeLoginWatch?.close();
42703
+ this.claudeLoginWatch = void 0;
42704
+ }
42591
42705
  async discardSession() {
42592
42706
  const client = this.client;
42707
+ this.stopClaudeLoginWatch();
42593
42708
  this.client = void 0;
42594
42709
  this.sessionId = void 0;
42595
42710
  this.sessionCwd = void 0;
@@ -42897,6 +43012,7 @@ var MonolithRoomTurnLoop = class {
42897
43012
  };
42898
43013
  this.client = (this.options.createAcpClient ?? ((value) => new AcpClient(value)))(clientOptions);
42899
43014
  await this.client.start();
43015
+ this.watchClaudeLogin();
42900
43016
  const persona = configuration.soul ?? self?.soul;
42901
43017
  const repositoryInfo = repositoryState.resolution === "repository" && repositoryState.key ? {
42902
43018
  name: repositoryState.key,
@@ -43012,6 +43128,7 @@ var MonolithRoomTurnLoop = class {
43012
43128
  return void 0;
43013
43129
  trace?.retry({ provider: next, ...reason ? { reason } : {} });
43014
43130
  const client = this.client;
43131
+ this.stopClaudeLoginWatch();
43015
43132
  this.client = void 0;
43016
43133
  this.sessionId = void 0;
43017
43134
  this.sessionFingerprint = void 0;
@@ -43157,7 +43274,7 @@ var MonolithRoomTurnLoop = class {
43157
43274
  const transcriptRows = conversation.items.filter((message) => message.type === "message" && message.id !== item.id && !active.steers.some((steerItem) => steerItem.id === message.id)).slice(-80).map((message) => ({
43158
43275
  id: message.id,
43159
43276
  authorId: message.authorId,
43160
- line: transcriptMessagePrompt(names.get(message.authorId) ?? message.authorId.slice(0, 12), message.body, message.attachments, message.id)
43277
+ line: transcriptMessagePrompt(names.get(message.authorId) ?? message.authorId.slice(0, 12), message.body, message.attachments, message.id, renderReplyContext(message))
43161
43278
  }));
43162
43279
  const command = this.commandContext.current;
43163
43280
  const grantDecision = command?.action === "resume";
@@ -43190,6 +43307,7 @@ var MonolithRoomTurnLoop = class {
43190
43307
  closedCorners,
43191
43308
  task: {
43192
43309
  fromName: inboxItemAuthorName(item, names),
43310
+ reply: item,
43193
43311
  outsideEvent: this.commandContext.current?.source.systemEvent,
43194
43312
  ...item.cornerAskId ? { cornerAskId: item.cornerAskId } : {},
43195
43313
  body: roomMessagePrompt("", inboxItemPromptBody(item), item.attachments, delivered, this.acceptsImages(), item.type === "message" ? item.id : void 0)
@@ -43269,7 +43387,7 @@ var MonolithRoomTurnLoop = class {
43269
43387
  "The previous run was cancelled because its harness could not accept every live steer.",
43270
43388
  "Resume the same turn. Keep the original request and everything that happened before it was cancelled.",
43271
43389
  "Human messages that arrived after the original request, in transcript order:",
43272
- ...active.steers.map((steerItem) => roomMessagePrompt(steerItem.authorId.slice(0, 12), steerItem.body, steerItem.attachments, this.deliveredAttachments.get(steerItem.id), this.acceptsImages(), steerItem.type === "message" ? steerItem.id : void 0)),
43390
+ ...active.steers.map((steerItem) => roomMessagePrompt(steerItem.authorId.slice(0, 12), steerItem.body, steerItem.attachments, this.deliveredAttachments.get(steerItem.id), this.acceptsImages(), steerItem.type === "message" ? steerItem.id : void 0, renderReplyContext(steerItem))),
43273
43391
  "Continue now and answer the updated request without erasing the earlier context."
43274
43392
  ].join("\n\n");
43275
43393
  }
@@ -43488,19 +43606,21 @@ function openCornerToolCall(calls) {
43488
43606
  function openedACorner(call) {
43489
43607
  return !!call && isCompletedToolCall(call);
43490
43608
  }
43491
- function transcriptMessagePrompt(author, body, attachments, messageId) {
43609
+ function transcriptMessagePrompt(author, body, attachments, messageId, replyContext = "") {
43492
43610
  return [
43493
43611
  `[message id: ${messageId}]`,
43494
43612
  `${author}: ${body.trim() || "(shared attachments)"}`,
43613
+ ...replyContext ? [replyContext] : [],
43495
43614
  ...attachmentMarkerLines(attachments)
43496
43615
  ].join("\n");
43497
43616
  }
43498
- function roomMessagePrompt(author, body, attachments, delivered, harnessAcceptsImages = true, messageId) {
43617
+ function roomMessagePrompt(author, body, attachments, delivered, harnessAcceptsImages = true, messageId, replyContext = "") {
43499
43618
  const message = body.trim() || "(shared attachments)";
43500
43619
  const rendered = author ? `${author}: ${message}` : message;
43501
43620
  return [
43502
43621
  ...messageId ? [`[message id: ${messageId}]`] : [],
43503
43622
  rendered,
43623
+ ...replyContext ? [replyContext] : [],
43504
43624
  ...attachmentPromptLines(attachments, delivered, harnessAcceptsImages)
43505
43625
  ].join("\n");
43506
43626
  }
@@ -44474,6 +44594,7 @@ var MonolithCornerTurnLoop = class {
44474
44594
  this.wakeIntake?.();
44475
44595
  }
44476
44596
  client;
44597
+ claudeLoginWatch;
44477
44598
  sessionId;
44478
44599
  /** The configuration the live session baked in; a change invalidates it. */
44479
44600
  sessionFingerprint;
@@ -44509,6 +44630,7 @@ var MonolithCornerTurnLoop = class {
44509
44630
  sessionPromptSectionIds = [];
44510
44631
  sessionSurface = "code-corner";
44511
44632
  sessionPromptContext;
44633
+ repositoryWork = true;
44512
44634
  /** Whether this corner's gh launcher uses a host binary or the REST fallback. */
44513
44635
  cornerGitHubCli = "host";
44514
44636
  busy = false;
@@ -44665,8 +44787,26 @@ var MonolithCornerTurnLoop = class {
44665
44787
  ...promptBytes ? { promptBytes } : {}
44666
44788
  };
44667
44789
  }
44790
+ /**
44791
+ * Watch this client's Claude login so a refresh made while the
44792
+ * corner is idle reaches other Rooms at once (see `watchRoomClaudeLogin`).
44793
+ */
44794
+ watchClaudeLogin() {
44795
+ this.stopClaudeLoginWatch();
44796
+ if (this.options.config.agentKind !== "claude" || !this.options.config.agentHomeRoot)
44797
+ return;
44798
+ this.claudeLoginWatch = watchRoomClaudeLogin({
44799
+ root: this.options.config.agentHomeRoot,
44800
+ operatorHome: this.options.config.operatorHome
44801
+ });
44802
+ }
44803
+ stopClaudeLoginWatch() {
44804
+ this.claudeLoginWatch?.close();
44805
+ this.claudeLoginWatch = void 0;
44806
+ }
44668
44807
  async discardSession() {
44669
44808
  const client = this.client;
44809
+ this.stopClaudeLoginWatch();
44670
44810
  this.client = void 0;
44671
44811
  this.sessionId = void 0;
44672
44812
  this.sessionFingerprint = void 0;
@@ -44683,13 +44823,14 @@ var MonolithCornerTurnLoop = class {
44683
44823
  return await this.currentSessionFingerprint() === this.sessionFingerprint;
44684
44824
  }
44685
44825
  async currentSessionFingerprint() {
44686
- const [configuration, roster, grants] = await Promise.all([
44826
+ const [configuration, roster, grants, restored] = await Promise.all([
44687
44827
  this.options.api.execute("getAgentConfiguration", {
44688
44828
  agentId: this.agent.publicKey,
44689
44829
  roomId: this.options.cornerId
44690
44830
  }),
44691
44831
  this.roster(),
44692
- this.grantedResources()
44832
+ this.grantedResources(),
44833
+ this.options.api.execute("getCornerRestoreState", { cornerId: this.options.cornerId })
44693
44834
  ]);
44694
44835
  const { hostRoutes: grantedHostRoutes, devices: grantedDevices } = grants;
44695
44836
  const self = roster.members.find((member) => member.identityId === this.agent.publicKey);
@@ -44702,6 +44843,7 @@ var MonolithCornerTurnLoop = class {
44702
44843
  soul: configuration.soul ?? self?.soul,
44703
44844
  agentName: self?.name ?? this.agent.name,
44704
44845
  yoloMode: configuration.yoloMode,
44846
+ repositoryWork: cornerHasRepositoryWork(restored.brief),
44705
44847
  mcpServers: codegraphFingerprintServers(this.options.config, [
44706
44848
  ...expectedMountedImportedMcpServerNames({
44707
44849
  operatorHome: this.options.config.operatorHome,
@@ -44751,13 +44893,14 @@ var MonolithCornerTurnLoop = class {
44751
44893
  return this.sessionId;
44752
44894
  }
44753
44895
  trace?.noteActivation("cold");
44754
- const [configuration, roster, grants] = await Promise.all([
44896
+ const [configuration, roster, grants, restored] = await Promise.all([
44755
44897
  this.options.api.execute("getAgentConfiguration", {
44756
44898
  agentId: this.agent.publicKey,
44757
44899
  roomId: this.options.cornerId
44758
44900
  }),
44759
44901
  this.roster(),
44760
- this.grantedResources()
44902
+ this.grantedResources(),
44903
+ this.options.api.execute("getCornerRestoreState", { cornerId: this.options.cornerId })
44761
44904
  ]);
44762
44905
  const { hostRoutes: grantedHostRoutes, devices: grantedDevices } = grants;
44763
44906
  const self = roster.members.find((member) => member.identityId === this.agent.publicKey);
@@ -44772,7 +44915,9 @@ var MonolithCornerTurnLoop = class {
44772
44915
  this.reviewerInstructionInput = reviewerInstruction ? reviewerInput : void 0;
44773
44916
  const selfReviewerInstruction = cornerSelfReviewerInstruction(reviewerInput);
44774
44917
  this.sessionSurface = !this.options.repository ? "no-code-corner" : reviewerInstruction ? "review-corner" : "code-corner";
44918
+ this.repositoryWork = cornerHasRepositoryWork(restored.brief);
44775
44919
  this.sessionPromptContext = {
44920
+ brief: restored.brief,
44776
44921
  surface: this.sessionSurface,
44777
44922
  agentName: self?.name ?? this.agent.name,
44778
44923
  ...configuration.reviewerHandle ? { reviewerHandle: configuration.reviewerHandle } : {},
@@ -44943,6 +45088,7 @@ var MonolithCornerTurnLoop = class {
44943
45088
  };
44944
45089
  this.client = (this.options.createAcpClient ?? ((value) => new AcpClient(value)))(clientOptions);
44945
45090
  await this.client.start();
45091
+ this.watchClaudeLogin();
44946
45092
  const codegraphReady = await prepareCodegraphIndex(this.options.config, this.options.worktreePath);
44947
45093
  const fingerprint = sessionConfigFingerprint({
44948
45094
  devices: grantedDevices,
@@ -44952,6 +45098,7 @@ var MonolithCornerTurnLoop = class {
44952
45098
  soul: configuration.soul ?? self?.soul,
44953
45099
  agentName: self?.name ?? this.agent.name,
44954
45100
  yoloMode: configuration.yoloMode,
45101
+ repositoryWork: cornerHasRepositoryWork(restored.brief),
44955
45102
  mcpServers: codegraphFingerprintServers(this.options.config, [
44956
45103
  ...expectedMountedImportedMcpServerNames({
44957
45104
  operatorHome: this.options.config.operatorHome,
@@ -45183,6 +45330,7 @@ var MonolithCornerTurnLoop = class {
45183
45330
  return void 0;
45184
45331
  trace?.retry({ provider: next, ...reason ? { reason } : {} });
45185
45332
  const client = this.client;
45333
+ this.stopClaudeLoginWatch();
45186
45334
  this.client = void 0;
45187
45335
  this.sessionId = void 0;
45188
45336
  this.sessionFingerprint = void 0;
@@ -45285,20 +45433,29 @@ var MonolithCornerTurnLoop = class {
45285
45433
  if (institutionalContext.embeddingOutcome !== void 0) {
45286
45434
  trace.noteInstitutionalMemoryEmbedding(institutionalContext.embeddingOutcome, institutionalContext.embeddingMs ?? 0);
45287
45435
  }
45288
- const briefAttachments = (restored.brief?.attachments ?? []).map((file) => ({
45289
- url: new URL(`/v1/media/${file.objectId}`, api.baseUrl).toString(),
45290
- name: file.title,
45291
- mimeType: file.mime,
45292
- size: file.size
45293
- }));
45294
- const allAttachments = [...attachments, ...briefAttachments];
45295
- const delivered = this.attachmentDir && allAttachments.length ? await deliverAttachments(allAttachments, join20(this.attachmentDir, requestId.replace(/[^\w-]/g, "_")), this.options.fetchImpl) : [];
45296
- const briefFileLines = await Promise.all((restored.brief?.attachments ?? []).map(async (file, index) => {
45297
- const entry = delivered.find((item) => item.attachment === briefAttachments[index]);
45298
- const verified = entry?.path && createHash14("sha256").update(await readFile29(entry.path)).digest("hex") === file.sha256;
45299
- return `- ${file.title} (${file.purpose}; ${file.required ? "required" : "optional"}; sha256 ${file.sha256}): ${verified ? entry.path : "UNAVAILABLE OR CONTENT MISMATCH"}`;
45300
- }));
45301
- const missingRequiredBriefFile = (restored.brief?.attachments ?? []).some((file, index) => file.required && briefFileLines[index]?.includes("UNAVAILABLE OR CONTENT MISMATCH"));
45436
+ let repositoryWork = cornerHasRepositoryWork(restored.brief);
45437
+ let assignedBrief = restored.brief;
45438
+ const taskDelivered = this.attachmentDir && attachments.length ? await deliverAttachments(attachments, join20(this.attachmentDir, requestId.replace(/[^\w-]/g, "_")), this.options.fetchImpl) : [];
45439
+ let delivered = taskDelivered;
45440
+ let briefFileLines = [];
45441
+ let missingRequiredBriefFile = false;
45442
+ const deliverBrief = async () => {
45443
+ const briefAttachments = (assignedBrief?.attachments ?? []).map((file) => ({
45444
+ url: new URL(`/v1/media/${file.objectId}`, api.baseUrl).toString(),
45445
+ name: file.title,
45446
+ mimeType: file.mime,
45447
+ size: file.size
45448
+ }));
45449
+ const briefDelivered = this.attachmentDir && briefAttachments.length ? await deliverAttachments(briefAttachments, join20(this.attachmentDir, requestId.replace(/[^\w-]/g, "_"), "brief", String(assignedBrief.revision)), this.options.fetchImpl) : [];
45450
+ briefFileLines = await Promise.all((assignedBrief?.attachments ?? []).map(async (file, index) => {
45451
+ const entry = briefDelivered.find((item) => item.attachment === briefAttachments[index]);
45452
+ const verified = entry?.path && createHash14("sha256").update(await readFile29(entry.path)).digest("hex") === file.sha256;
45453
+ return `- ${file.title} (${file.purpose}; ${file.required ? "required" : "optional"}; sha256 ${file.sha256}): ${verified ? entry.path : "UNAVAILABLE OR CONTENT MISMATCH"}`;
45454
+ }));
45455
+ missingRequiredBriefFile = (assignedBrief?.attachments ?? []).some((file, index) => file.required && briefFileLines[index]?.includes("UNAVAILABLE OR CONTENT MISMATCH"));
45456
+ delivered = [...taskDelivered, ...briefDelivered];
45457
+ };
45458
+ await deliverBrief();
45302
45459
  const names = new Map(roster.members.map((member) => [member.identityId, member.name]));
45303
45460
  const requestedBy = requester && !requester.name && names.get(requester.pubkey) ? { ...requester, name: names.get(requester.pubkey) } : requester;
45304
45461
  if (requestedBy)
@@ -45309,24 +45466,54 @@ var MonolithCornerTurnLoop = class {
45309
45466
  line: [
45310
45467
  ...message.type === "message" ? [`[message id: ${message.id}]`] : [],
45311
45468
  `${names.get(message.authorId) ?? "Beeline"} [${message.type}]: ${message.body.trim() ? message.body : "(shared attachments)"}`,
45469
+ ...message.replyToMessageId ? [renderReplyContext(message)] : [],
45312
45470
  ...attachmentMarkerLines(message.attachments ?? [])
45313
45471
  ].join("\n")
45314
45472
  }));
45315
- const buildPrompt = () => {
45473
+ const buildPrompt = async (followup) => {
45474
+ for (; ; ) {
45475
+ const { revisions } = await api.execute("listCornerBriefRevisions", {
45476
+ cornerId,
45477
+ afterRevision: assignedBrief?.revision ?? 0,
45478
+ limit: 1
45479
+ });
45480
+ const latestBrief = revisions[0];
45481
+ if (!latestBrief || latestBrief.revision <= (assignedBrief?.revision ?? 0))
45482
+ break;
45483
+ assignedBrief = latestBrief;
45484
+ await deliverBrief();
45485
+ }
45486
+ repositoryWork = cornerHasRepositoryWork(assignedBrief);
45487
+ if (this.repositoryWork !== repositoryWork) {
45488
+ await this.discardSession();
45489
+ await trace.measure("activation", () => this.activate(trace));
45490
+ }
45491
+ if (followup === CORNER_DELIVERY_NUDGE && !repositoryWork)
45492
+ followup = "Deliver files with post_artifact; answer plain chat questions directly.";
45493
+ if (followup && !assignedBrief)
45494
+ return followup;
45495
+ const briefContext = {
45496
+ ...assignedBrief ? {
45497
+ brief: {
45498
+ brief: assignedBrief,
45499
+ fileLines: briefFileLines,
45500
+ missingRequiredFile: missingRequiredBriefFile
45501
+ }
45502
+ } : {}
45503
+ };
45316
45504
  const transcript = this.warmTranscript.select(this.sessionId, transcriptRows, this.agent.publicKey);
45317
- const assembled = assembleTurnPrompt({
45505
+ const assembled = assembleTurnPrompt(followup ? {
45506
+ surface: this.sessionSurface,
45507
+ modelContextTokens: this.modelContextTokens,
45508
+ ...briefContext,
45509
+ task: { body: followup }
45510
+ } : {
45318
45511
  surface: this.sessionSurface,
45319
45512
  modelContextTokens: this.modelContextTokens,
45320
45513
  sessionPrefix: this.turnSessionPrefix,
45321
45514
  objective: this.options.objective,
45322
45515
  ...restored.titleGenerated && restored.title ? { generatedTitle: restored.title } : {},
45323
- ...restored.brief ? {
45324
- brief: {
45325
- brief: restored.brief,
45326
- fileLines: briefFileLines,
45327
- missingRequiredFile: missingRequiredBriefFile
45328
- }
45329
- } : {},
45516
+ ...briefContext,
45330
45517
  transcript: {
45331
45518
  lines: transcript.rows.map((row) => row.line),
45332
45519
  sinceLastTurn: transcript.warm
@@ -45338,6 +45525,8 @@ var MonolithCornerTurnLoop = class {
45338
45525
  task: {
45339
45526
  ...sourceMessageId ? { reactionTargetId: sourceMessageId } : {},
45340
45527
  body: trigger,
45528
+ reply: this.commandContext.current?.source,
45529
+ commandReason: this.commandContext.current?.reason,
45341
45530
  outsideEvent: this.commandContext.current?.source.systemEvent,
45342
45531
  attachmentLines: attachmentPromptLines(attachments, delivered, this.acceptsImages())
45343
45532
  }
@@ -45478,9 +45667,15 @@ var MonolithCornerTurnLoop = class {
45478
45667
  }));
45479
45668
  };
45480
45669
  let loginRetryUsed = false;
45481
- const runPrompt = async (prompt = buildPrompt()) => {
45670
+ const runPrompt = async (prompt, continuation) => {
45671
+ const activePrompt = async () => {
45672
+ const assembled = await buildPrompt(prompt);
45673
+ return continuation ? `${assembled}
45674
+
45675
+ ${continuation}` : assembled;
45676
+ };
45482
45677
  try {
45483
- return await runPromptAttempt(prompt);
45678
+ return await runPromptAttempt(await activePrompt());
45484
45679
  } catch (error) {
45485
45680
  if (!isExpiredHarnessLoginError(error))
45486
45681
  throw error;
@@ -45504,7 +45699,7 @@ var MonolithCornerTurnLoop = class {
45504
45699
  await this.discardSession();
45505
45700
  await trace.measure("activation", () => this.activate(trace));
45506
45701
  try {
45507
- return await runPromptAttempt(prompt);
45702
+ return await runPromptAttempt(await activePrompt());
45508
45703
  } catch (retryError) {
45509
45704
  if (!isExpiredHarnessLoginError(retryError))
45510
45705
  throw retryError;
@@ -45536,9 +45731,7 @@ var MonolithCornerTurnLoop = class {
45536
45731
  await this.discardSession();
45537
45732
  await trace.measure("activation", () => this.activate(trace));
45538
45733
  }
45539
- result = await runPrompt(`${buildPrompt()}
45540
-
45541
- ${deviceGrantResumePrompt(grantedDevice, result.agentText)}`);
45734
+ result = await runPrompt(void 0, deviceGrantResumePrompt(grantedDevice, result.agentText));
45542
45735
  trace.promptSettled();
45543
45736
  }
45544
45737
  let explained = await this.explainEmpty(result);
@@ -45564,7 +45757,7 @@ ${deviceGrantResumePrompt(grantedDevice, result.agentText)}`);
45564
45757
  explained = await this.explainEmpty(result);
45565
45758
  }
45566
45759
  const checksTurn = isCornerChecksTurn(trigger, restates);
45567
- const deliveryState = !checksTurn && this.options.repository ? await cornerUndeliveredRepositoryState(this.options.worktreePath, this.options.repository.featureBranch, this.options.repository.targetBranch) : void 0;
45760
+ const deliveryState = !checksTurn && repositoryWork && this.options.repository ? await cornerUndeliveredRepositoryState(this.options.worktreePath, this.options.repository.featureBranch, this.options.repository.targetBranch) : void 0;
45568
45761
  const needsDeliveryNudge = deliveryState !== void 0 && deliveryState !== this.lastDeliveryNudgeState;
45569
45762
  let refreshedReviewerInstruction;
45570
45763
  let reviewerSecondPass = false;
@@ -45576,7 +45769,7 @@ ${deviceGrantResumePrompt(grantedDevice, result.agentText)}`);
45576
45769
  reviewerSecondPass = reviewerTargetChanged || !result.toolCalls.some((call) => /(?:^|[._:/-])(?:approve_merge|record_validation_stage)$/i.test(call.title ?? "") && isCompletedToolCall(call));
45577
45770
  }
45578
45771
  let replyBeforeNudge = "";
45579
- if (!explained && (needsDeliveryNudge || checksTurn && (this.reviewerInstructionInput ? reviewerSecondPass : this.yoloMode && !result.toolCalls.some((call) => /(?:^|[._:/-])pr_checks_status$/i.test(call.title ?? "") && isCompletedToolCall(call))))) {
45772
+ if (!explained && (needsDeliveryNudge || checksTurn && (this.reviewerInstructionInput ? reviewerSecondPass : repositoryWork && this.yoloMode && !result.toolCalls.some((call) => /(?:^|[._:/-])pr_checks_status$/i.test(call.title ?? "") && isCompletedToolCall(call))))) {
45580
45773
  if (needsDeliveryNudge)
45581
45774
  this.lastDeliveryNudgeState = deliveryState;
45582
45775
  await flushToolCalls(result.toolCalls, "");
@@ -46566,6 +46759,9 @@ async function assertCornerWorktreePublished(worktree) {
46566
46759
  throw new Error(`corner ${worktree.cornerId} changed during cleanup`);
46567
46760
  }
46568
46761
  }
46762
+ function hasUnpushedCommits(error) {
46763
+ return error instanceof Error && / has unpushed commits$/.test(error.message);
46764
+ }
46569
46765
  async function originContains(gitCommonDir, head) {
46570
46766
  const result = await execFileAsync8("git", [
46571
46767
  `--git-dir=${gitCommonDir}`,
@@ -46603,6 +46799,13 @@ var RoomRuntimeCoordinator = class {
46603
46799
  archiveCleanupFaults = /* @__PURE__ */ new Map();
46604
46800
  /** A refused local checkout is kept until a later Room listing proves membership again. */
46605
46801
  archiveCleanupAccessDenied = /* @__PURE__ */ new Set();
46802
+ /**
46803
+ * A corner whose branch cleanup found commits no origin ref contains. That
46804
+ * is never transient - nothing pushes them later - so retrying it every
46805
+ * reconciliation would only slow discovery down forever. Reported once,
46806
+ * then left alone; the checkout and its commits are kept, never deleted.
46807
+ */
46808
+ archiveCleanupAbandoned = /* @__PURE__ */ new Set();
46606
46809
  startingCorners = /* @__PURE__ */ new Set();
46607
46810
  /**
46608
46811
  * Rooms whose start is in flight. `running` is not set until the checkout
@@ -47500,13 +47703,21 @@ var RoomRuntimeCoordinator = class {
47500
47703
  for (const [cornerId, worktree] of [...this.pendingCornerReaps]) {
47501
47704
  if (desired.has(cornerId)) {
47502
47705
  this.pendingCornerReaps.delete(cornerId);
47706
+ this.archiveCleanupAbandoned.delete(cornerId);
47503
47707
  continue;
47504
47708
  }
47709
+ if (this.archiveCleanupAbandoned.has(cornerId))
47710
+ continue;
47505
47711
  if (!this.archiveCleanupDue(cornerId))
47506
47712
  continue;
47507
47713
  try {
47508
47714
  await this.reapCornerWorktree(worktree);
47509
47715
  } catch (error) {
47716
+ if (hasUnpushedCommits(error)) {
47717
+ this.archiveCleanupAbandoned.add(cornerId);
47718
+ console.error(`[thin-core] corner ${cornerId} branch cleanup abandoned; unpushed commits kept:`, error);
47719
+ continue;
47720
+ }
47510
47721
  this.deferArchiveCleanup(cornerId);
47511
47722
  console.error(`[thin-core] corner ${cornerId} branch cleanup retry failed:`, error);
47512
47723
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usebeeline",
3
- "version": "0.1.13",
3
+ "version": "0.1.16",
4
4
  "description": "Connect an AI coding agent to Beeline with one command.",
5
5
  "homepage": "https://usebeeline.app",
6
6
  "bugs": {