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.
- package/dist/usebeeline.mjs +266 -55
- package/package.json +1 -1
package/dist/usebeeline.mjs
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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
|
-
|
|
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)"}
|
|
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.
|
|
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
|
-
|
|
45289
|
-
|
|
45290
|
-
|
|
45291
|
-
|
|
45292
|
-
|
|
45293
|
-
|
|
45294
|
-
const
|
|
45295
|
-
|
|
45296
|
-
|
|
45297
|
-
|
|
45298
|
-
|
|
45299
|
-
|
|
45300
|
-
|
|
45301
|
-
|
|
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
|
-
...
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
}
|