usebeeline 0.1.13 → 0.1.14

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 +197 -54
  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) {
@@ -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.
@@ -41041,6 +41109,8 @@ function sessionConfigFingerprint(input) {
41041
41109
  input.yoloMode ?? false,
41042
41110
  mountedMcpSet(input.mcpServers)
41043
41111
  ];
41112
+ if (input.repositoryWork !== void 0)
41113
+ fingerprint.push({ repositoryWork: input.repositoryWork });
41044
41114
  if (input.reviewerHandle !== void 0)
41045
41115
  fingerprint.push(input.reviewerHandle);
41046
41116
  if (input.devices?.length)
@@ -43157,7 +43227,7 @@ var MonolithRoomTurnLoop = class {
43157
43227
  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
43228
  id: message.id,
43159
43229
  authorId: message.authorId,
43160
- line: transcriptMessagePrompt(names.get(message.authorId) ?? message.authorId.slice(0, 12), message.body, message.attachments, message.id)
43230
+ line: transcriptMessagePrompt(names.get(message.authorId) ?? message.authorId.slice(0, 12), message.body, message.attachments, message.id, renderReplyContext(message))
43161
43231
  }));
43162
43232
  const command = this.commandContext.current;
43163
43233
  const grantDecision = command?.action === "resume";
@@ -43190,6 +43260,7 @@ var MonolithRoomTurnLoop = class {
43190
43260
  closedCorners,
43191
43261
  task: {
43192
43262
  fromName: inboxItemAuthorName(item, names),
43263
+ reply: item,
43193
43264
  outsideEvent: this.commandContext.current?.source.systemEvent,
43194
43265
  ...item.cornerAskId ? { cornerAskId: item.cornerAskId } : {},
43195
43266
  body: roomMessagePrompt("", inboxItemPromptBody(item), item.attachments, delivered, this.acceptsImages(), item.type === "message" ? item.id : void 0)
@@ -43269,7 +43340,7 @@ var MonolithRoomTurnLoop = class {
43269
43340
  "The previous run was cancelled because its harness could not accept every live steer.",
43270
43341
  "Resume the same turn. Keep the original request and everything that happened before it was cancelled.",
43271
43342
  "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)),
43343
+ ...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
43344
  "Continue now and answer the updated request without erasing the earlier context."
43274
43345
  ].join("\n\n");
43275
43346
  }
@@ -43488,19 +43559,21 @@ function openCornerToolCall(calls) {
43488
43559
  function openedACorner(call) {
43489
43560
  return !!call && isCompletedToolCall(call);
43490
43561
  }
43491
- function transcriptMessagePrompt(author, body, attachments, messageId) {
43562
+ function transcriptMessagePrompt(author, body, attachments, messageId, replyContext = "") {
43492
43563
  return [
43493
43564
  `[message id: ${messageId}]`,
43494
43565
  `${author}: ${body.trim() || "(shared attachments)"}`,
43566
+ ...replyContext ? [replyContext] : [],
43495
43567
  ...attachmentMarkerLines(attachments)
43496
43568
  ].join("\n");
43497
43569
  }
43498
- function roomMessagePrompt(author, body, attachments, delivered, harnessAcceptsImages = true, messageId) {
43570
+ function roomMessagePrompt(author, body, attachments, delivered, harnessAcceptsImages = true, messageId, replyContext = "") {
43499
43571
  const message = body.trim() || "(shared attachments)";
43500
43572
  const rendered = author ? `${author}: ${message}` : message;
43501
43573
  return [
43502
43574
  ...messageId ? [`[message id: ${messageId}]`] : [],
43503
43575
  rendered,
43576
+ ...replyContext ? [replyContext] : [],
43504
43577
  ...attachmentPromptLines(attachments, delivered, harnessAcceptsImages)
43505
43578
  ].join("\n");
43506
43579
  }
@@ -44509,6 +44582,7 @@ var MonolithCornerTurnLoop = class {
44509
44582
  sessionPromptSectionIds = [];
44510
44583
  sessionSurface = "code-corner";
44511
44584
  sessionPromptContext;
44585
+ repositoryWork = true;
44512
44586
  /** Whether this corner's gh launcher uses a host binary or the REST fallback. */
44513
44587
  cornerGitHubCli = "host";
44514
44588
  busy = false;
@@ -44683,13 +44757,14 @@ var MonolithCornerTurnLoop = class {
44683
44757
  return await this.currentSessionFingerprint() === this.sessionFingerprint;
44684
44758
  }
44685
44759
  async currentSessionFingerprint() {
44686
- const [configuration, roster, grants] = await Promise.all([
44760
+ const [configuration, roster, grants, restored] = await Promise.all([
44687
44761
  this.options.api.execute("getAgentConfiguration", {
44688
44762
  agentId: this.agent.publicKey,
44689
44763
  roomId: this.options.cornerId
44690
44764
  }),
44691
44765
  this.roster(),
44692
- this.grantedResources()
44766
+ this.grantedResources(),
44767
+ this.options.api.execute("getCornerRestoreState", { cornerId: this.options.cornerId })
44693
44768
  ]);
44694
44769
  const { hostRoutes: grantedHostRoutes, devices: grantedDevices } = grants;
44695
44770
  const self = roster.members.find((member) => member.identityId === this.agent.publicKey);
@@ -44702,6 +44777,7 @@ var MonolithCornerTurnLoop = class {
44702
44777
  soul: configuration.soul ?? self?.soul,
44703
44778
  agentName: self?.name ?? this.agent.name,
44704
44779
  yoloMode: configuration.yoloMode,
44780
+ repositoryWork: cornerHasRepositoryWork(restored.brief),
44705
44781
  mcpServers: codegraphFingerprintServers(this.options.config, [
44706
44782
  ...expectedMountedImportedMcpServerNames({
44707
44783
  operatorHome: this.options.config.operatorHome,
@@ -44751,13 +44827,14 @@ var MonolithCornerTurnLoop = class {
44751
44827
  return this.sessionId;
44752
44828
  }
44753
44829
  trace?.noteActivation("cold");
44754
- const [configuration, roster, grants] = await Promise.all([
44830
+ const [configuration, roster, grants, restored] = await Promise.all([
44755
44831
  this.options.api.execute("getAgentConfiguration", {
44756
44832
  agentId: this.agent.publicKey,
44757
44833
  roomId: this.options.cornerId
44758
44834
  }),
44759
44835
  this.roster(),
44760
- this.grantedResources()
44836
+ this.grantedResources(),
44837
+ this.options.api.execute("getCornerRestoreState", { cornerId: this.options.cornerId })
44761
44838
  ]);
44762
44839
  const { hostRoutes: grantedHostRoutes, devices: grantedDevices } = grants;
44763
44840
  const self = roster.members.find((member) => member.identityId === this.agent.publicKey);
@@ -44772,7 +44849,9 @@ var MonolithCornerTurnLoop = class {
44772
44849
  this.reviewerInstructionInput = reviewerInstruction ? reviewerInput : void 0;
44773
44850
  const selfReviewerInstruction = cornerSelfReviewerInstruction(reviewerInput);
44774
44851
  this.sessionSurface = !this.options.repository ? "no-code-corner" : reviewerInstruction ? "review-corner" : "code-corner";
44852
+ this.repositoryWork = cornerHasRepositoryWork(restored.brief);
44775
44853
  this.sessionPromptContext = {
44854
+ brief: restored.brief,
44776
44855
  surface: this.sessionSurface,
44777
44856
  agentName: self?.name ?? this.agent.name,
44778
44857
  ...configuration.reviewerHandle ? { reviewerHandle: configuration.reviewerHandle } : {},
@@ -44952,6 +45031,7 @@ var MonolithCornerTurnLoop = class {
44952
45031
  soul: configuration.soul ?? self?.soul,
44953
45032
  agentName: self?.name ?? this.agent.name,
44954
45033
  yoloMode: configuration.yoloMode,
45034
+ repositoryWork: cornerHasRepositoryWork(restored.brief),
44955
45035
  mcpServers: codegraphFingerprintServers(this.options.config, [
44956
45036
  ...expectedMountedImportedMcpServerNames({
44957
45037
  operatorHome: this.options.config.operatorHome,
@@ -45285,20 +45365,29 @@ var MonolithCornerTurnLoop = class {
45285
45365
  if (institutionalContext.embeddingOutcome !== void 0) {
45286
45366
  trace.noteInstitutionalMemoryEmbedding(institutionalContext.embeddingOutcome, institutionalContext.embeddingMs ?? 0);
45287
45367
  }
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"));
45368
+ let repositoryWork = cornerHasRepositoryWork(restored.brief);
45369
+ let assignedBrief = restored.brief;
45370
+ const taskDelivered = this.attachmentDir && attachments.length ? await deliverAttachments(attachments, join20(this.attachmentDir, requestId.replace(/[^\w-]/g, "_")), this.options.fetchImpl) : [];
45371
+ let delivered = taskDelivered;
45372
+ let briefFileLines = [];
45373
+ let missingRequiredBriefFile = false;
45374
+ const deliverBrief = async () => {
45375
+ const briefAttachments = (assignedBrief?.attachments ?? []).map((file) => ({
45376
+ url: new URL(`/v1/media/${file.objectId}`, api.baseUrl).toString(),
45377
+ name: file.title,
45378
+ mimeType: file.mime,
45379
+ size: file.size
45380
+ }));
45381
+ const briefDelivered = this.attachmentDir && briefAttachments.length ? await deliverAttachments(briefAttachments, join20(this.attachmentDir, requestId.replace(/[^\w-]/g, "_"), "brief", String(assignedBrief.revision)), this.options.fetchImpl) : [];
45382
+ briefFileLines = await Promise.all((assignedBrief?.attachments ?? []).map(async (file, index) => {
45383
+ const entry = briefDelivered.find((item) => item.attachment === briefAttachments[index]);
45384
+ const verified = entry?.path && createHash14("sha256").update(await readFile29(entry.path)).digest("hex") === file.sha256;
45385
+ return `- ${file.title} (${file.purpose}; ${file.required ? "required" : "optional"}; sha256 ${file.sha256}): ${verified ? entry.path : "UNAVAILABLE OR CONTENT MISMATCH"}`;
45386
+ }));
45387
+ missingRequiredBriefFile = (assignedBrief?.attachments ?? []).some((file, index) => file.required && briefFileLines[index]?.includes("UNAVAILABLE OR CONTENT MISMATCH"));
45388
+ delivered = [...taskDelivered, ...briefDelivered];
45389
+ };
45390
+ await deliverBrief();
45302
45391
  const names = new Map(roster.members.map((member) => [member.identityId, member.name]));
45303
45392
  const requestedBy = requester && !requester.name && names.get(requester.pubkey) ? { ...requester, name: names.get(requester.pubkey) } : requester;
45304
45393
  if (requestedBy)
@@ -45309,24 +45398,54 @@ var MonolithCornerTurnLoop = class {
45309
45398
  line: [
45310
45399
  ...message.type === "message" ? [`[message id: ${message.id}]`] : [],
45311
45400
  `${names.get(message.authorId) ?? "Beeline"} [${message.type}]: ${message.body.trim() ? message.body : "(shared attachments)"}`,
45401
+ ...message.replyToMessageId ? [renderReplyContext(message)] : [],
45312
45402
  ...attachmentMarkerLines(message.attachments ?? [])
45313
45403
  ].join("\n")
45314
45404
  }));
45315
- const buildPrompt = () => {
45405
+ const buildPrompt = async (followup) => {
45406
+ for (; ; ) {
45407
+ const { revisions } = await api.execute("listCornerBriefRevisions", {
45408
+ cornerId,
45409
+ afterRevision: assignedBrief?.revision ?? 0,
45410
+ limit: 1
45411
+ });
45412
+ const latestBrief = revisions[0];
45413
+ if (!latestBrief || latestBrief.revision <= (assignedBrief?.revision ?? 0))
45414
+ break;
45415
+ assignedBrief = latestBrief;
45416
+ await deliverBrief();
45417
+ }
45418
+ repositoryWork = cornerHasRepositoryWork(assignedBrief);
45419
+ if (this.repositoryWork !== repositoryWork) {
45420
+ await this.discardSession();
45421
+ await trace.measure("activation", () => this.activate(trace));
45422
+ }
45423
+ if (followup === CORNER_DELIVERY_NUDGE && !repositoryWork)
45424
+ followup = "Deliver files with post_artifact; answer plain chat questions directly.";
45425
+ if (followup && !assignedBrief)
45426
+ return followup;
45427
+ const briefContext = {
45428
+ ...assignedBrief ? {
45429
+ brief: {
45430
+ brief: assignedBrief,
45431
+ fileLines: briefFileLines,
45432
+ missingRequiredFile: missingRequiredBriefFile
45433
+ }
45434
+ } : {}
45435
+ };
45316
45436
  const transcript = this.warmTranscript.select(this.sessionId, transcriptRows, this.agent.publicKey);
45317
- const assembled = assembleTurnPrompt({
45437
+ const assembled = assembleTurnPrompt(followup ? {
45438
+ surface: this.sessionSurface,
45439
+ modelContextTokens: this.modelContextTokens,
45440
+ ...briefContext,
45441
+ task: { body: followup }
45442
+ } : {
45318
45443
  surface: this.sessionSurface,
45319
45444
  modelContextTokens: this.modelContextTokens,
45320
45445
  sessionPrefix: this.turnSessionPrefix,
45321
45446
  objective: this.options.objective,
45322
45447
  ...restored.titleGenerated && restored.title ? { generatedTitle: restored.title } : {},
45323
- ...restored.brief ? {
45324
- brief: {
45325
- brief: restored.brief,
45326
- fileLines: briefFileLines,
45327
- missingRequiredFile: missingRequiredBriefFile
45328
- }
45329
- } : {},
45448
+ ...briefContext,
45330
45449
  transcript: {
45331
45450
  lines: transcript.rows.map((row) => row.line),
45332
45451
  sinceLastTurn: transcript.warm
@@ -45338,6 +45457,8 @@ var MonolithCornerTurnLoop = class {
45338
45457
  task: {
45339
45458
  ...sourceMessageId ? { reactionTargetId: sourceMessageId } : {},
45340
45459
  body: trigger,
45460
+ reply: this.commandContext.current?.source,
45461
+ commandReason: this.commandContext.current?.reason,
45341
45462
  outsideEvent: this.commandContext.current?.source.systemEvent,
45342
45463
  attachmentLines: attachmentPromptLines(attachments, delivered, this.acceptsImages())
45343
45464
  }
@@ -45478,9 +45599,15 @@ var MonolithCornerTurnLoop = class {
45478
45599
  }));
45479
45600
  };
45480
45601
  let loginRetryUsed = false;
45481
- const runPrompt = async (prompt = buildPrompt()) => {
45602
+ const runPrompt = async (prompt, continuation) => {
45603
+ const activePrompt = async () => {
45604
+ const assembled = await buildPrompt(prompt);
45605
+ return continuation ? `${assembled}
45606
+
45607
+ ${continuation}` : assembled;
45608
+ };
45482
45609
  try {
45483
- return await runPromptAttempt(prompt);
45610
+ return await runPromptAttempt(await activePrompt());
45484
45611
  } catch (error) {
45485
45612
  if (!isExpiredHarnessLoginError(error))
45486
45613
  throw error;
@@ -45504,7 +45631,7 @@ var MonolithCornerTurnLoop = class {
45504
45631
  await this.discardSession();
45505
45632
  await trace.measure("activation", () => this.activate(trace));
45506
45633
  try {
45507
- return await runPromptAttempt(prompt);
45634
+ return await runPromptAttempt(await activePrompt());
45508
45635
  } catch (retryError) {
45509
45636
  if (!isExpiredHarnessLoginError(retryError))
45510
45637
  throw retryError;
@@ -45536,9 +45663,7 @@ var MonolithCornerTurnLoop = class {
45536
45663
  await this.discardSession();
45537
45664
  await trace.measure("activation", () => this.activate(trace));
45538
45665
  }
45539
- result = await runPrompt(`${buildPrompt()}
45540
-
45541
- ${deviceGrantResumePrompt(grantedDevice, result.agentText)}`);
45666
+ result = await runPrompt(void 0, deviceGrantResumePrompt(grantedDevice, result.agentText));
45542
45667
  trace.promptSettled();
45543
45668
  }
45544
45669
  let explained = await this.explainEmpty(result);
@@ -45564,7 +45689,7 @@ ${deviceGrantResumePrompt(grantedDevice, result.agentText)}`);
45564
45689
  explained = await this.explainEmpty(result);
45565
45690
  }
45566
45691
  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;
45692
+ const deliveryState = !checksTurn && repositoryWork && this.options.repository ? await cornerUndeliveredRepositoryState(this.options.worktreePath, this.options.repository.featureBranch, this.options.repository.targetBranch) : void 0;
45568
45693
  const needsDeliveryNudge = deliveryState !== void 0 && deliveryState !== this.lastDeliveryNudgeState;
45569
45694
  let refreshedReviewerInstruction;
45570
45695
  let reviewerSecondPass = false;
@@ -45576,7 +45701,7 @@ ${deviceGrantResumePrompt(grantedDevice, result.agentText)}`);
45576
45701
  reviewerSecondPass = reviewerTargetChanged || !result.toolCalls.some((call) => /(?:^|[._:/-])(?:approve_merge|record_validation_stage)$/i.test(call.title ?? "") && isCompletedToolCall(call));
45577
45702
  }
45578
45703
  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))))) {
45704
+ if (!explained && (needsDeliveryNudge || checksTurn && (this.reviewerInstructionInput ? reviewerSecondPass : repositoryWork && this.yoloMode && !result.toolCalls.some((call) => /(?:^|[._:/-])pr_checks_status$/i.test(call.title ?? "") && isCompletedToolCall(call))))) {
45580
45705
  if (needsDeliveryNudge)
45581
45706
  this.lastDeliveryNudgeState = deliveryState;
45582
45707
  await flushToolCalls(result.toolCalls, "");
@@ -46566,6 +46691,9 @@ async function assertCornerWorktreePublished(worktree) {
46566
46691
  throw new Error(`corner ${worktree.cornerId} changed during cleanup`);
46567
46692
  }
46568
46693
  }
46694
+ function hasUnpushedCommits(error) {
46695
+ return error instanceof Error && / has unpushed commits$/.test(error.message);
46696
+ }
46569
46697
  async function originContains(gitCommonDir, head) {
46570
46698
  const result = await execFileAsync8("git", [
46571
46699
  `--git-dir=${gitCommonDir}`,
@@ -46603,6 +46731,13 @@ var RoomRuntimeCoordinator = class {
46603
46731
  archiveCleanupFaults = /* @__PURE__ */ new Map();
46604
46732
  /** A refused local checkout is kept until a later Room listing proves membership again. */
46605
46733
  archiveCleanupAccessDenied = /* @__PURE__ */ new Set();
46734
+ /**
46735
+ * A corner whose branch cleanup found commits no origin ref contains. That
46736
+ * is never transient - nothing pushes them later - so retrying it every
46737
+ * reconciliation would only slow discovery down forever. Reported once,
46738
+ * then left alone; the checkout and its commits are kept, never deleted.
46739
+ */
46740
+ archiveCleanupAbandoned = /* @__PURE__ */ new Set();
46606
46741
  startingCorners = /* @__PURE__ */ new Set();
46607
46742
  /**
46608
46743
  * Rooms whose start is in flight. `running` is not set until the checkout
@@ -47500,13 +47635,21 @@ var RoomRuntimeCoordinator = class {
47500
47635
  for (const [cornerId, worktree] of [...this.pendingCornerReaps]) {
47501
47636
  if (desired.has(cornerId)) {
47502
47637
  this.pendingCornerReaps.delete(cornerId);
47638
+ this.archiveCleanupAbandoned.delete(cornerId);
47503
47639
  continue;
47504
47640
  }
47641
+ if (this.archiveCleanupAbandoned.has(cornerId))
47642
+ continue;
47505
47643
  if (!this.archiveCleanupDue(cornerId))
47506
47644
  continue;
47507
47645
  try {
47508
47646
  await this.reapCornerWorktree(worktree);
47509
47647
  } catch (error) {
47648
+ if (hasUnpushedCommits(error)) {
47649
+ this.archiveCleanupAbandoned.add(cornerId);
47650
+ console.error(`[thin-core] corner ${cornerId} branch cleanup abandoned; unpushed commits kept:`, error);
47651
+ continue;
47652
+ }
47510
47653
  this.deferArchiveCleanup(cornerId);
47511
47654
  console.error(`[thin-core] corner ${cornerId} branch cleanup retry failed:`, error);
47512
47655
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usebeeline",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "Connect an AI coding agent to Beeline with one command.",
5
5
  "homepage": "https://usebeeline.app",
6
6
  "bugs": {