@bivy/bivy 0.10.1-staging.239 → 0.10.1-staging.240
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/github-tasks.js +32 -1
- package/dist/server.js +8 -4
- package/package.json +1 -1
package/dist/github-tasks.js
CHANGED
|
@@ -288,6 +288,35 @@ export async function ensureTaskLabels(cfg) {
|
|
|
288
288
|
export async function commentIssue(cfg, issueNumber, body) {
|
|
289
289
|
await gh(cfg, "POST", `/issues/${issueNumber}/comments`, { body });
|
|
290
290
|
}
|
|
291
|
+
/** A hidden HTML-comment marker (invisible in rendered Markdown) that makes a
|
|
292
|
+
* Bivy issue comment idempotent across process restarts and lease reclaims: the
|
|
293
|
+
* same logical comment carries the same marker, so a duplicate delivery or a
|
|
294
|
+
* reclaim on a fresh node can detect that it was already posted. */
|
|
295
|
+
export function bivyCommentMarker(key) {
|
|
296
|
+
return `<!-- bivy:comment:${key} -->`;
|
|
297
|
+
}
|
|
298
|
+
/** Whether the issue already carries a Bivy comment with the given key. Scans a
|
|
299
|
+
* single bounded page (newest issues have few comments); an API failure returns
|
|
300
|
+
* false so we prefer re-posting the signal over silently dropping it. */
|
|
301
|
+
export async function issueHasCommentMarker(cfg, issueNumber, key) {
|
|
302
|
+
const marker = bivyCommentMarker(key);
|
|
303
|
+
const res = await gh(cfg, "GET", `/issues/${issueNumber}/comments?per_page=100`);
|
|
304
|
+
if (!res.ok)
|
|
305
|
+
return false;
|
|
306
|
+
const raw = (await res.json().catch(() => []));
|
|
307
|
+
return Array.isArray(raw) && raw.some((c) => typeof c?.body === "string" && c.body.includes(marker));
|
|
308
|
+
}
|
|
309
|
+
/** Post an issue comment at most once per `(issue, key)`. Returns true when it
|
|
310
|
+
* posted, false when an identically-keyed comment already existed. This is the
|
|
311
|
+
* external-effect idempotency guard: a retry or reclaim of the same Run does not
|
|
312
|
+
* duplicate a pickup or outcome comment. The marker is appended to the body so
|
|
313
|
+
* the check above can see it. */
|
|
314
|
+
export async function commentIssueOnce(cfg, issueNumber, body, key) {
|
|
315
|
+
if (await issueHasCommentMarker(cfg, issueNumber, key))
|
|
316
|
+
return false;
|
|
317
|
+
await commentIssue(cfg, issueNumber, `${body}\n\n${bivyCommentMarker(key)}`);
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
291
320
|
/**
|
|
292
321
|
* The comment posted on an issue the moment Bivy picks it up — the "visibly
|
|
293
322
|
* signal pickup" half of the on-issue lifecycle (the other half is the label
|
|
@@ -318,7 +347,9 @@ export async function announcePickup(cfg, issueNumber, nodeName) {
|
|
|
318
347
|
if (cfg.label && cfg.label !== cfg.claimLabel) {
|
|
319
348
|
await removeLabel(cfg, issueNumber, cfg.label).catch((error) => warn(`remove routing label "${cfg.label}"`, error));
|
|
320
349
|
}
|
|
321
|
-
|
|
350
|
+
// Idempotent across reclaims: a Machine that reclaims this issue after another
|
|
351
|
+
// lost its lease must not post a second pickup comment.
|
|
352
|
+
await commentIssueOnce(cfg, issueNumber, pickupMessage(nodeName), "pickup").catch((error) => warn("post pickup comment", error));
|
|
322
353
|
}
|
|
323
354
|
export async function defaultBranch(cfg) {
|
|
324
355
|
const res = await gh(cfg, "GET", "");
|
package/dist/server.js
CHANGED
|
@@ -78,7 +78,7 @@ import { setConfiguredAutoAttachToolImages } from "./harness/tool-image-attachme
|
|
|
78
78
|
import { injectMcpProxyForSession, injectBivyToolsForSession } from "./harness/mcp-inject.js";
|
|
79
79
|
import { parseRepo, isGitHubSlugPart, inferGitHubRepoFromWorkspace, isSharedCloneRoot, resolveGitHubToken, ghCliInstalled, cloneOrUpdateRepo, resolveDefaultBaseRef, resolveBranchBaseRef, resolveAdoptBaseRef, fetchOrigin } from "./repo-workspace.js";
|
|
80
80
|
import { configureGitAuth, writeGitCredentialEndpoint } from "./git-auth.js";
|
|
81
|
-
import { GitHubTaskPoller, resolveGitHubTaskConfig, buildTaskPrompt, buildResumePrompt, buildInteractiveResumePrompt, DEFAULT_ISSUE_INSTRUCTIONS, parseBivyDirectives, commitAll, pushBranch, mergeBaseIntoBranch, completeMerge, abortMerge, findOpenPullRequestForBranch, findPullRequestsForBranch, findMergedPullRequestForBranch, issueBranchName, getPullRequest,
|
|
81
|
+
import { GitHubTaskPoller, resolveGitHubTaskConfig, buildTaskPrompt, buildResumePrompt, buildInteractiveResumePrompt, DEFAULT_ISSUE_INSTRUCTIONS, parseBivyDirectives, commitAll, pushBranch, mergeBaseIntoBranch, completeMerge, abortMerge, findOpenPullRequestForBranch, findPullRequestsForBranch, findMergedPullRequestForBranch, issueBranchName, getPullRequest, commentIssueOnce, listOpenLabelledIssues, selectActionableIssues, getIssue, getIssueCommentBody, addLabel, removeLabel, announcePickup, } from "./github-tasks.js";
|
|
82
82
|
import { buildLinearTaskPrompt, getLinearIssue, linearBranchName } from "./linear-tasks.js";
|
|
83
83
|
import { PairingStore } from "./device-registry.js";
|
|
84
84
|
import { IntegrationManager } from "./integrations/index.js";
|
|
@@ -4185,7 +4185,9 @@ async function reportIssueOutcome(cfg, issue, record, emit, opts) {
|
|
|
4185
4185
|
await prDetection.maybeDetectPullRequest(record);
|
|
4186
4186
|
if (record.prUrl) {
|
|
4187
4187
|
emit(record, "pr_opened", `Pull request ready for issue #${issue.number}.`, { prUrl: record.prUrl });
|
|
4188
|
-
|
|
4188
|
+
// Keyed by the PR URL so a reclaim/retry that lands on the same PR does not
|
|
4189
|
+
// post a second link, while a genuinely new PR still gets its own comment.
|
|
4190
|
+
await commentIssueOnce(cfg, issue.number, `🤖 ${record.prUrl}`, `pr:${record.prUrl}`).catch(() => { });
|
|
4189
4191
|
// Clean up the claim label now that the PR itself is the live "in progress"
|
|
4190
4192
|
// signal on the issue (linked in the timeline + the comment above) — keeping
|
|
4191
4193
|
// `bivy:in-progress` around after a PR exists is stale and, per the issue's
|
|
@@ -4199,7 +4201,9 @@ async function reportIssueOutcome(cfg, issue, record, emit, opts) {
|
|
|
4199
4201
|
? "Bivy handled the follow-up but produced no file changes."
|
|
4200
4202
|
: "Bivy ran on this issue but produced no file changes.";
|
|
4201
4203
|
emit(record, "no_changes", message);
|
|
4202
|
-
|
|
4204
|
+
// Keyed by the deterministic issue branch so a reclaim doesn't repeat the
|
|
4205
|
+
// no-changes note for the same attempt cycle.
|
|
4206
|
+
await commentIssueOnce(cfg, issue.number, message, `no-changes:${wt.branch}`).catch(() => { });
|
|
4203
4207
|
// The run is finished with nothing in progress — drop the claim label so it
|
|
4204
4208
|
// doesn't linger on the issue. A stale `bivy/<node>:in-progress` label was
|
|
4205
4209
|
// also mis-routing follow-up mentions before pickRoutingLabel was hardened;
|
|
@@ -4208,7 +4212,7 @@ async function reportIssueOutcome(cfg, issue, record, emit, opts) {
|
|
|
4208
4212
|
return;
|
|
4209
4213
|
}
|
|
4210
4214
|
emit(record, "pushed", `Pushed ${wt.branch} for issue #${issue.number}; no pull request yet.`);
|
|
4211
|
-
await
|
|
4215
|
+
await commentIssueOnce(cfg, issue.number, `🤖 Pushed \`${wt.branch}\` but didn't open a pull request. Comment \`@bivy\` again to continue, or open one from the session's chat (\`/pr\`).`, `pushed:${wt.branch}`).catch(() => { });
|
|
4212
4216
|
}
|
|
4213
4217
|
/** The live open session for an issue (by source tag), if we still have one. */
|
|
4214
4218
|
function findIssueSession(source) {
|
package/package.json
CHANGED