@algosuite/vo-mcp 0.2.0-beta.55 → 0.2.0-beta.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1178,8 +1178,8 @@ async function walkManagedRoots(ownership, options, onLink) {
1178
1178
  const normalized = normalizeOwnership(ownership);
1179
1179
  let scannedEntries = 0;
1180
1180
  for (const rootPath of normalized.managedRoots) {
1181
- const present = await assertManagedRoot(rootPath, normalized.worktreeRoot, fsApi);
1182
- if (!present) continue;
1181
+ const present2 = await assertManagedRoot(rootPath, normalized.worktreeRoot, fsApi);
1182
+ if (!present2) continue;
1183
1183
  const stack = [rootPath];
1184
1184
  while (stack.length > 0) {
1185
1185
  const current = stack.pop();
@@ -3497,8 +3497,9 @@ async function mergeVerifiedPrRequest(req, prNumber, automationContext, onUnauth
3497
3497
  actionReceiptId: typeof json.action_receipt_id === "string" ? json.action_receipt_id : null
3498
3498
  };
3499
3499
  }
3500
- if (res.status === 503 && json?.error === "verify_unavailable") {
3501
- return { status: "retry", reason: json.reason || "verification unavailable" };
3500
+ const captureStoreRetry = json?.action_status === "not_attempted" && (json?.error === "capture_preflight_unavailable" || json?.error === "decision_outcome_intent_failed");
3501
+ if (res.status === 503 && (json?.error === "verify_unavailable" || json?.error === "merge_unavailable" || captureStoreRetry)) {
3502
+ return { status: "retry", reason: json.reason || json.message || json.error || "verification unavailable" };
3502
3503
  }
3503
3504
  return {
3504
3505
  status: "blocked",
@@ -3604,6 +3605,64 @@ var init_claim_gate_notice = __esm({
3604
3605
  }
3605
3606
  });
3606
3607
 
3608
+ // ../../scripts/virtual-office/code-runner/control-plane-knowledge-context.mjs
3609
+ function isRetryableStatus(status) {
3610
+ return status >= 500 && status <= 599;
3611
+ }
3612
+ function defaultSleep(ms) {
3613
+ return new Promise((resolve3) => {
3614
+ setTimeout(resolve3, ms);
3615
+ });
3616
+ }
3617
+ async function getTaskKnowledgeContextRequest(req, taskId, { query } = {}, {
3618
+ taskRequestTimeoutMs,
3619
+ invalidateToken = () => {
3620
+ },
3621
+ sleep: sleep3 = defaultSleep,
3622
+ log: log2 = () => {
3623
+ }
3624
+ } = {}) {
3625
+ const body = {};
3626
+ if (typeof query === "string" && query.trim()) body.query = query;
3627
+ const path22 = `/api/v1/code-task/${encodeURIComponent(taskId)}/knowledge-context`;
3628
+ const timeoutMs = Math.max(Number(taskRequestTimeoutMs) || 0, MIN_KNOWLEDGE_CONTEXT_TIMEOUT_MS);
3629
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt += 1) {
3630
+ let res;
3631
+ let cause;
3632
+ try {
3633
+ res = await req("POST", path22, body, { timeoutMs });
3634
+ } catch (err) {
3635
+ cause = err;
3636
+ }
3637
+ if (!cause) {
3638
+ if (res.status === 401) {
3639
+ invalidateToken();
3640
+ throw new Error("knowledge-context unauthorized (401)");
3641
+ }
3642
+ if (res.status === 404) return null;
3643
+ if (res.ok) return res.json();
3644
+ if (!isRetryableStatus(res.status)) {
3645
+ throw new Error(`knowledge-context failed: HTTP ${res.status}`);
3646
+ }
3647
+ cause = new Error(`knowledge-context failed: HTTP ${res.status}`);
3648
+ }
3649
+ if (attempt === MAX_ATTEMPTS) throw cause;
3650
+ const delayMs = RETRY_DELAYS_MS[attempt - 1];
3651
+ log2(`knowledge-context attempt ${attempt}/${MAX_ATTEMPTS} failed (${cause.message}); retrying in ${delayMs}ms`);
3652
+ await sleep3(delayMs);
3653
+ }
3654
+ throw new Error("knowledge-context retry loop exited unexpectedly");
3655
+ }
3656
+ var MIN_KNOWLEDGE_CONTEXT_TIMEOUT_MS, RETRY_DELAYS_MS, MAX_ATTEMPTS;
3657
+ var init_control_plane_knowledge_context = __esm({
3658
+ "../../scripts/virtual-office/code-runner/control-plane-knowledge-context.mjs"() {
3659
+ "use strict";
3660
+ MIN_KNOWLEDGE_CONTEXT_TIMEOUT_MS = 15e3;
3661
+ RETRY_DELAYS_MS = [2e3, 6e3];
3662
+ MAX_ATTEMPTS = RETRY_DELAYS_MS.length + 1;
3663
+ }
3664
+ });
3665
+
3607
3666
  // src/runner/control-plane-auth-stub.mjs
3608
3667
  var control_plane_auth_stub_exports = {};
3609
3668
  __export(control_plane_auth_stub_exports, {
@@ -3647,7 +3706,8 @@ function createControlPlaneClient({
3647
3706
  6e4
3648
3707
  ),
3649
3708
  runnerId,
3650
- runnerInstanceId
3709
+ runnerInstanceId,
3710
+ sleep: sleep3
3651
3711
  } = {}) {
3652
3712
  const resolvedBaseUrl = baseUrl ?? env2.VO_CONTROL_PLANE_URL ?? "";
3653
3713
  if (!resolvedBaseUrl) throw new Error("VO_CONTROL_PLANE_URL is required for the code-runner daemon");
@@ -3828,17 +3888,16 @@ function createControlPlaneClient({
3828
3888
  if (!res.ok) throw new Error(`attachment download failed: HTTP ${res.status}`);
3829
3889
  return Buffer.from(await res.arrayBuffer());
3830
3890
  },
3891
+ // Raised per-attempt timeout (>=15s) + bounded retry — see control-plane-knowledge-context.mjs.
3831
3892
  async getTaskKnowledgeContext(taskId, { query } = {}) {
3832
- const body = {};
3833
- if (typeof query === "string" && query.trim()) body.query = query;
3834
- const res = await taskReq("POST", `/api/v1/code-task/${encodeURIComponent(taskId)}/knowledge-context`, body);
3835
- if (res.status === 401) {
3836
- cachedFirebaseToken = null;
3837
- throw new Error("knowledge-context unauthorized (401)");
3838
- }
3839
- if (res.status === 404) return null;
3840
- if (!res.ok) throw new Error(`knowledge-context failed: HTTP ${res.status}`);
3841
- return res.json();
3893
+ return getTaskKnowledgeContextRequest(req, taskId, { query }, {
3894
+ taskRequestTimeoutMs,
3895
+ invalidateToken: () => {
3896
+ cachedFirebaseToken = null;
3897
+ },
3898
+ sleep: sleep3,
3899
+ log: (m) => console.warn(`[code-runner ${(/* @__PURE__ */ new Date()).toISOString()}] ${m}`)
3900
+ });
3842
3901
  },
3843
3902
  /** Weekly Claude token usage report — see control-plane-weekly-tokens.mjs. */
3844
3903
  async postWeeklyTokens(report) {
@@ -3992,6 +4051,7 @@ var init_control_plane_client = __esm({
3992
4051
  init_control_plane_weekly_tokens();
3993
4052
  init_control_plane_telemetry_relay();
3994
4053
  init_claim_gate_notice();
4054
+ init_control_plane_knowledge_context();
3995
4055
  cachedFirebaseToken = null;
3996
4056
  ClaimAuthorityChangedError = class extends Error {
3997
4057
  constructor() {
@@ -8876,7 +8936,7 @@ function selectDueEntries({ entries = [], now, alreadyDispatched = /* @__PURE__
8876
8936
  if (!code_task_id) continue;
8877
8937
  if (seen.has(code_task_id)) continue;
8878
8938
  const stableAttempts = Number(attemptsByKey[stableTaskKey(e)] || 0);
8879
- if (Math.max(typeof attempts === "number" ? attempts : 0, stableAttempts) >= MAX_ATTEMPTS) {
8939
+ if (Math.max(typeof attempts === "number" ? attempts : 0, stableAttempts) >= MAX_ATTEMPTS2) {
8880
8940
  exhausted.push(e);
8881
8941
  continue;
8882
8942
  }
@@ -8912,11 +8972,11 @@ function reconcileQueue({ entries = [], dispatchedIds = /* @__PURE__ */ new Set(
8912
8972
  return true;
8913
8973
  });
8914
8974
  }
8915
- var MAX_ATTEMPTS, MAX_DISPATCH_PER_RUN, NULL_RESUME_AFTER_BACKOFF_MS;
8975
+ var MAX_ATTEMPTS2, MAX_DISPATCH_PER_RUN, NULL_RESUME_AFTER_BACKOFF_MS;
8916
8976
  var init_rate_limit_resume_scheduler_core = __esm({
8917
8977
  "../../scripts/virtual-office/code-runner/rate-limit-resume-scheduler-core.mjs"() {
8918
8978
  "use strict";
8919
- MAX_ATTEMPTS = 3;
8979
+ MAX_ATTEMPTS2 = 3;
8920
8980
  MAX_DISPATCH_PER_RUN = 10;
8921
8981
  NULL_RESUME_AFTER_BACKOFF_MS = 15 * 60 * 1e3;
8922
8982
  }
@@ -8943,7 +9003,7 @@ function bumpAttempts(store, key, sourceTaskId, nowIso) {
8943
9003
  store[key] = {
8944
9004
  count: (typeof prior.count === "number" ? prior.count : 0) + 1,
8945
9005
  lastSeen: nowIso,
8946
- resumedTaskIds: [...resumedTaskIds, sourceTaskId].slice(-MAX_ATTEMPTS)
9006
+ resumedTaskIds: [...resumedTaskIds, sourceTaskId].slice(-MAX_ATTEMPTS2)
8947
9007
  };
8948
9008
  }
8949
9009
  function pruneAttemptsStore(store, nowIso) {
@@ -8995,7 +9055,7 @@ async function runLockedScheduler({
8995
9055
  await writeResumeAttempts(attemptsPath, pruneAttemptsStore(attemptsStore, nowIso));
8996
9056
  await writeResumeQueue(queuePath, kept);
8997
9057
  if (exhausted.length > 0) {
8998
- log2(`WARN: ${exhausted.length} task(s) reached the ${MAX_ATTEMPTS}-continuation spend ceiling and remain queued for operator review: ${exhausted.map((entry) => entry.code_task_id).join(", ")}`);
9058
+ log2(`WARN: ${exhausted.length} task(s) reached the ${MAX_ATTEMPTS2}-continuation spend ceiling and remain queued for operator review: ${exhausted.map((entry) => entry.code_task_id).join(", ")}`);
8999
9059
  }
9000
9060
  if (due.length >= MAX_DISPATCH_PER_RUN) {
9001
9061
  log2(`WARN: hit the per-run dispatch cap (${MAX_DISPATCH_PER_RUN}); more tasks remain queued`);
@@ -10446,39 +10506,6 @@ var init_pr_watcher_failure_confirmation = __esm({
10446
10506
  }
10447
10507
  });
10448
10508
 
10449
- // ../../scripts/virtual-office/code-runner/superseded-pr-source.mjs
10450
- function supersededSourcePrNumber(prompt) {
10451
- const text = String(prompt || "");
10452
- if (text.includes(CI_FIX_MARKER)) {
10453
- const match = text.match(/\bPR:\s*#(\d+)\b/u);
10454
- return match ? Number(match[1]) : null;
10455
- }
10456
- const repairMatch = text.match(/^REPAIR MISSION:\s*PR\s+#(\d+)\b/iu);
10457
- if (repairMatch) return Number(repairMatch[1]);
10458
- const recoverySupersedeMatch = text.match(
10459
- /^VO_RECOVERY_FROM_CODE_TASK:\s*[0-9a-f-]{36}\n\nSupersede draft PR #(\d+) from a fresh current origin\/main branch\b/iu
10460
- );
10461
- if (recoverySupersedeMatch) return Number(recoverySupersedeMatch[1]);
10462
- const restoredContextMatch = text.match(
10463
- /^(?:The previous run reached its max-turn cap after opening a partial draft PR\.|The previous run left a draft PR\.)\nThe HQ runner will restore the existing draft PR context before you start \(draft PR https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/(\d+), PR #(\d+)\)\. Continue that work and finish it\./u
10464
- );
10465
- if (restoredContextMatch) {
10466
- return restoredContextMatch[1] === restoredContextMatch[2] ? Number(restoredContextMatch[1]) : null;
10467
- }
10468
- const continuationMatch = text.match(
10469
- /^(?:The previous run reached its max-turn cap after opening a partial draft PR\.|The previous run left a draft PR\.)\nContinue the work already started on the branch for PR #(\d+) \(draft PR https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/(\d+)\); check it out and finish it\./u
10470
- );
10471
- if (!continuationMatch || continuationMatch[1] !== continuationMatch[2]) return null;
10472
- return Number(continuationMatch[1]);
10473
- }
10474
- var CI_FIX_MARKER;
10475
- var init_superseded_pr_source = __esm({
10476
- "../../scripts/virtual-office/code-runner/superseded-pr-source.mjs"() {
10477
- "use strict";
10478
- CI_FIX_MARKER = "[VO-CI-FIX]";
10479
- }
10480
- });
10481
-
10482
10509
  // ../../scripts/virtual-office/code-runner/error-message.mjs
10483
10510
  function boundedErrorMessage(error, maxLength = 240) {
10484
10511
  try {
@@ -10513,9 +10540,52 @@ function mergeEnqueueActive(entry, nowMs = Date.now()) {
10513
10540
  if (!at) return true;
10514
10541
  return nowMs - at < MERGE_ENQUEUE_TTL_MS;
10515
10542
  }
10543
+ function migrateLegacyMergeState(entry, nowMs = Date.now()) {
10544
+ if (!entry) return false;
10545
+ if (entry.mergeEnqueued === true && !(Number(entry.mergeEnqueuedAt) > 0)) {
10546
+ entry.mergeEnqueuedAt = nowMs;
10547
+ return true;
10548
+ }
10549
+ const exhausted = entry.mergeRetryExhausted === true || (Number(entry.mergeAttempts) || 0) >= 3 || (Number(entry.mergeErrors) || 0) >= 3;
10550
+ if (entry.mergeTerminal === true || mergeEnqueueActive(entry, nowMs) || !exhausted) return false;
10551
+ entry.mergeRetryExhausted = true;
10552
+ entry.mergeTerminal = true;
10553
+ entry.mergeBlockReason ||= "Legacy watcher state exhausted 3 automatic merge attempts without a terminal result.";
10554
+ return true;
10555
+ }
10516
10556
  function isTerminalResumeRefusal(error) {
10517
10557
  return typeof error?.code === "string" && TERMINAL_RESUME_REFUSALS.includes(error.code);
10518
10558
  }
10559
+ async function reportPendingMergeExhaustion({
10560
+ entry,
10561
+ now,
10562
+ reportBlocker,
10563
+ taskId,
10564
+ prNumber,
10565
+ log: log2 = () => {
10566
+ }
10567
+ }) {
10568
+ if (!entry.mergeRetryExhausted || entry.mergeEscalatedAt || typeof reportBlocker !== "function") return false;
10569
+ const lastError = boundedErrorMessage(entry.mergeRetryReason || entry.mergeBlockReason);
10570
+ try {
10571
+ await reportBlocker({
10572
+ taskId,
10573
+ message: `PR #${prNumber} automatic merge verification exhausted its bounded retry budget; operator review is required. Last error: ${lastError}`
10574
+ });
10575
+ entry.mergeEscalatedAt = now();
10576
+ return true;
10577
+ } catch (reportError) {
10578
+ log2(`watch: pr #${prNumber} merge escalation failed; terminal state retained: ${boundedErrorMessage(reportError)}`);
10579
+ return false;
10580
+ }
10581
+ }
10582
+ async function scheduleMergeRetry(input) {
10583
+ await scheduleCoordinationRetry({ ...input, kind: "merge" });
10584
+ return {
10585
+ terminal: input.entry.mergeTerminal === true,
10586
+ escalated: await reportPendingMergeExhaustion(input)
10587
+ };
10588
+ }
10519
10589
  async function scheduleCoordinationRetry({
10520
10590
  entry,
10521
10591
  kind,
@@ -10534,10 +10604,21 @@ async function scheduleCoordinationRetry({
10534
10604
  log2(`watch: pr #${prNumber} automatic continuation refused by the plane (${error.code}); no retry \u2014 operator may resume explicitly with a larger cap`);
10535
10605
  return;
10536
10606
  }
10537
- const errorsKey = kind === "resume" ? "resumeErrors" : "enqueueErrors";
10538
- const attemptsKey = kind === "resume" ? "resumeAttempts" : "fixAttempts";
10607
+ const errorsKey = kind === "resume" ? "resumeErrors" : kind === "merge" ? "mergeErrors" : "enqueueErrors";
10608
+ const attemptsKey = kind === "resume" ? "resumeAttempts" : kind === "merge" ? "mergeAttempts" : "fixAttempts";
10539
10609
  entry[errorsKey] = (entry[errorsKey] || 0) + 1;
10540
- entry[attemptsKey] = Math.max(0, (entry[attemptsKey] || 1) - 1);
10610
+ if (kind === "merge") {
10611
+ entry.mergeRetryReason = boundedErrorMessage(error);
10612
+ if (entry[errorsKey] >= 3) {
10613
+ entry.mergeTerminal = true;
10614
+ entry.mergeRetryExhausted = true;
10615
+ entry.mergeBlockReason = `Automatic merge verification remained unavailable after 3 attempts: ${entry.mergeRetryReason}`;
10616
+ delete entry.nextRetryAt;
10617
+ log2(`watch: pr #${prNumber} merge retry cap reached \u2014 terminal and operator review required`);
10618
+ return;
10619
+ }
10620
+ }
10621
+ if (kind !== "merge") entry[attemptsKey] = Math.max(0, (entry[attemptsKey] || 1) - 1);
10541
10622
  const delay2 = Math.min(MAX_BACKOFF_MS2, 3e4 * 2 ** Math.min(7, entry[errorsKey] - 1));
10542
10623
  entry.nextRetryAt = now() + delay2;
10543
10624
  if (entry[errorsKey] >= 3 && !entry.coordinationEscalatedAt && typeof reportBlocker === "function") {
@@ -10646,16 +10727,87 @@ var init_watcher_state = __esm({
10646
10727
  function normalizedRepo(repo) {
10647
10728
  return String(repo || "").trim().toLowerCase();
10648
10729
  }
10730
+ function present(value) {
10731
+ return value !== null && value !== void 0 && value !== "";
10732
+ }
10649
10733
  function watcherKey(state, repo, prNumber) {
10734
+ const targetRepo = normalizedRepo(repo);
10735
+ for (const [key, entry] of Object.entries(state)) {
10736
+ if (watcherPrNumber(key, entry) !== Number(prNumber)) continue;
10737
+ if (normalizedRepo(entry?.repo) === targetRepo) return key;
10738
+ }
10650
10739
  const legacy = String(prNumber);
10651
10740
  const existing = state[legacy];
10652
- if (!existing || normalizedRepo(existing.repo) === normalizedRepo(repo)) return legacy;
10653
- return `${normalizedRepo(repo)}#${prNumber}`;
10741
+ if (!existing) return legacy;
10742
+ return `${targetRepo}#${prNumber}`;
10654
10743
  }
10655
10744
  function watcherPrNumber(stateKey, entry) {
10656
10745
  const value = Number(entry?.prNumber ?? String(stateKey).split("#").at(-1));
10657
10746
  return Number.isInteger(value) && value > 0 ? value : null;
10658
10747
  }
10748
+ function collapseDuplicateWatcherEntries(state) {
10749
+ const keeperBySubject = /* @__PURE__ */ new Map();
10750
+ let collapsed = 0;
10751
+ for (const [key, entry] of Object.entries(state)) {
10752
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
10753
+ const repo = normalizedRepo(entry.repo);
10754
+ const prNumber = watcherPrNumber(key, entry);
10755
+ if (!repo || !prNumber) continue;
10756
+ const subject = `${repo}#${prNumber}`;
10757
+ const keeperKey = keeperBySubject.get(subject);
10758
+ if (!keeperKey) {
10759
+ keeperBySubject.set(subject, key);
10760
+ continue;
10761
+ }
10762
+ const keeper = state[keeperKey];
10763
+ let identityConflict = false;
10764
+ for (const field of ["taskId", "operatorId", "tenantId"]) {
10765
+ const current = keeper[field];
10766
+ const duplicate = entry[field];
10767
+ if (!present(current) && present(duplicate)) keeper[field] = duplicate;
10768
+ else if (present(current) && present(duplicate) && current !== duplicate) identityConflict = true;
10769
+ }
10770
+ if (keeper.operatorId === "admin" && present(keeper.tenantId)) identityConflict = true;
10771
+ for (const field of ["fixAttempts", "mergeAttempts", "mergeErrors", "resumeAttempts"]) {
10772
+ const current = Number(keeper[field]);
10773
+ const duplicate = Number(entry[field]);
10774
+ const total = (Number.isSafeInteger(current) && current > 0 ? current : 0) + (Number.isSafeInteger(duplicate) && duplicate > 0 ? duplicate : 0);
10775
+ keeper[field] = Math.min(Number.MAX_SAFE_INTEGER, total);
10776
+ }
10777
+ for (const [field, value] of Object.entries(entry)) {
10778
+ if (["taskId", "operatorId", "tenantId"].includes(field)) continue;
10779
+ if (!present(keeper[field]) && present(value)) keeper[field] = value;
10780
+ }
10781
+ if (entry.allowFixDispatch === false) keeper.allowFixDispatch = false;
10782
+ const continuationExhausted = keeper.continuationExhausted === true || entry.continuationExhausted === true;
10783
+ keeper.continuationExhausted = continuationExhausted;
10784
+ keeper.needsContinuation = !continuationExhausted && (keeper.needsContinuation === true || entry.needsContinuation === true);
10785
+ if (entry.mergeEnqueued === true) {
10786
+ keeper.mergeEnqueued = true;
10787
+ if ((Number(entry.mergeEnqueuedAt) || 0) > (Number(keeper.mergeEnqueuedAt) || 0)) {
10788
+ keeper.mergeEnqueuedAt = entry.mergeEnqueuedAt;
10789
+ keeper.mergeActionReceiptId = entry.mergeActionReceiptId ?? null;
10790
+ }
10791
+ }
10792
+ if (entry.mergeTerminal === true && keeper.mergeTerminal !== true) {
10793
+ keeper.mergeTerminal = true;
10794
+ keeper.mergeBlockReason = entry.mergeBlockReason || "verification refused";
10795
+ }
10796
+ if ((Number(keeper.mergeErrors) || 0) >= 3) {
10797
+ keeper.mergeRetryExhausted = true;
10798
+ keeper.mergeTerminal = true;
10799
+ keeper.mergeBlockReason ||= `Duplicate watcher history exhausted ${keeper.mergeErrors} automatic merge retries.`;
10800
+ }
10801
+ if (identityConflict) {
10802
+ keeper.automationContextConflict = true;
10803
+ keeper.mergeTerminal = true;
10804
+ keeper.mergeBlockReason = "Duplicate watcher entries disagree on automation attribution.";
10805
+ }
10806
+ delete state[key];
10807
+ collapsed += 1;
10808
+ }
10809
+ return collapsed;
10810
+ }
10659
10811
  function deleteWatcherEntry(state, repo, prNumber) {
10660
10812
  const targetRepo = normalizedRepo(repo);
10661
10813
  for (const [key, entry] of Object.entries(state)) {
@@ -10670,6 +10822,46 @@ var init_watcher_key = __esm({
10670
10822
  }
10671
10823
  });
10672
10824
 
10825
+ // ../../scripts/virtual-office/code-runner/watcher-merge-authority.mjs
10826
+ function nonEmptyString(value) {
10827
+ return typeof value === "string" && value.length > 0;
10828
+ }
10829
+ function resolveWatcherMergeAuthority(entry = {}) {
10830
+ if (entry.automationContextConflict === true) {
10831
+ return {
10832
+ kind: "blocked",
10833
+ reason: "Watcher automation attribution conflicts with the durable code task."
10834
+ };
10835
+ }
10836
+ if (String(entry.repo || "").trim().toLowerCase() !== CANONICAL_WATCHER_MERGE_REPO) {
10837
+ return {
10838
+ kind: "blocked",
10839
+ reason: "Watcher merge target is not the canonical Nexus repository."
10840
+ };
10841
+ }
10842
+ const taskId = entry.taskId;
10843
+ const operatorId = entry.operatorId;
10844
+ const tenantId = entry.tenantId;
10845
+ if (nonEmptyString(taskId) && operatorId === "admin" && (tenantId === null || tenantId === void 0)) {
10846
+ return { kind: "legacy-admin", context: void 0 };
10847
+ }
10848
+ if (nonEmptyString(taskId) && nonEmptyString(operatorId) && nonEmptyString(tenantId)) {
10849
+ return { kind: "scoped", context: { taskId, operatorId, tenantId } };
10850
+ }
10851
+ return {
10852
+ kind: "blocked",
10853
+ reason: INCOMPLETE_WATCHER_AUTHORITY_REASON
10854
+ };
10855
+ }
10856
+ var INCOMPLETE_WATCHER_AUTHORITY_REASON, CANONICAL_WATCHER_MERGE_REPO;
10857
+ var init_watcher_merge_authority = __esm({
10858
+ "../../scripts/virtual-office/code-runner/watcher-merge-authority.mjs"() {
10859
+ "use strict";
10860
+ INCOMPLETE_WATCHER_AUTHORITY_REASON = "Watcher automation attribution is incomplete; merge was not attempted.";
10861
+ CANONICAL_WATCHER_MERGE_REPO = "algosuite-ai/nexus";
10862
+ }
10863
+ });
10864
+
10673
10865
  // ../../scripts/virtual-office/code-runner/watcher-adoption.mjs
10674
10866
  async function adoptPrOpenedTasks(tasks, {
10675
10867
  stateFile,
@@ -10690,7 +10882,38 @@ async function adoptPrOpenedTasks(tasks, {
10690
10882
  if (repos.size > 0 && !repos.has(task.repo.toLowerCase())) continue;
10691
10883
  if (operators.size > 0 && !operators.has(task.operator_id)) continue;
10692
10884
  const key = watcherKey(state, task.repo, task.pr_number);
10693
- if (state[key]) continue;
10885
+ const existing = state[key];
10886
+ if (existing) {
10887
+ let changed = false;
10888
+ let conflict = false;
10889
+ for (const [field, durable] of [
10890
+ ["taskId", task.code_task_id],
10891
+ ["operatorId", task.operator_id],
10892
+ ["tenantId", task.tenant_id]
10893
+ ]) {
10894
+ const comparable = durable === null || typeof durable === "string" && durable.length > 0;
10895
+ if (!comparable) continue;
10896
+ const current = existing[field];
10897
+ if ((current === null || current === void 0 || current === "") && durable !== null) {
10898
+ existing[field] = durable;
10899
+ changed = true;
10900
+ } else if (current !== null && current !== void 0 && current !== "" && current !== durable) {
10901
+ conflict = true;
10902
+ }
10903
+ }
10904
+ if (conflict && existing.automationContextConflict !== true) {
10905
+ existing.automationContextConflict = true;
10906
+ changed = true;
10907
+ }
10908
+ if (!conflict && existing.mergeTerminal === true && existing.mergeBlockReason === INCOMPLETE_WATCHER_AUTHORITY_REASON && resolveWatcherMergeAuthority(existing).kind !== "blocked") {
10909
+ delete existing.mergeTerminal;
10910
+ delete existing.mergeBlockReason;
10911
+ existing.mergeAttempts = 0;
10912
+ changed = true;
10913
+ }
10914
+ if (changed) adopted += 1;
10915
+ continue;
10916
+ }
10694
10917
  const partial = String(task.result || "").includes(PARTIAL_PR_CONTINUATION_MARKER);
10695
10918
  const repairChain = task.repair_chain ?? {
10696
10919
  root_pr_number: task.repair_pr_number ?? task.pr_number,
@@ -10726,6 +10949,7 @@ var init_watcher_adoption = __esm({
10726
10949
  init_watcher_state();
10727
10950
  init_partial_pr_continuation();
10728
10951
  init_watcher_key();
10952
+ init_watcher_merge_authority();
10729
10953
  }
10730
10954
  });
10731
10955
 
@@ -10757,6 +10981,39 @@ var init_watcher_github_token = __esm({
10757
10981
  }
10758
10982
  });
10759
10983
 
10984
+ // ../../scripts/virtual-office/code-runner/superseded-pr-source.mjs
10985
+ function supersededSourcePrNumber(prompt) {
10986
+ const text = String(prompt || "");
10987
+ if (text.includes(CI_FIX_MARKER)) {
10988
+ const match = text.match(/\bPR:\s*#(\d+)\b/u);
10989
+ return match ? Number(match[1]) : null;
10990
+ }
10991
+ const repairMatch = text.match(/^REPAIR MISSION:\s*PR\s+#(\d+)\b/iu);
10992
+ if (repairMatch) return Number(repairMatch[1]);
10993
+ const recoverySupersedeMatch = text.match(
10994
+ /^VO_RECOVERY_FROM_CODE_TASK:\s*[0-9a-f-]{36}\n\nSupersede draft PR #(\d+) from a fresh current origin\/main branch\b/iu
10995
+ );
10996
+ if (recoverySupersedeMatch) return Number(recoverySupersedeMatch[1]);
10997
+ const restoredContextMatch = text.match(
10998
+ /^(?:The previous run reached its max-turn cap after opening a partial draft PR\.|The previous run left a draft PR\.)\nThe HQ runner will restore the existing draft PR context before you start \(draft PR https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/(\d+), PR #(\d+)\)\. Continue that work and finish it\./u
10999
+ );
11000
+ if (restoredContextMatch) {
11001
+ return restoredContextMatch[1] === restoredContextMatch[2] ? Number(restoredContextMatch[1]) : null;
11002
+ }
11003
+ const continuationMatch = text.match(
11004
+ /^(?:The previous run reached its max-turn cap after opening a partial draft PR\.|The previous run left a draft PR\.)\nContinue the work already started on the branch for PR #(\d+) \(draft PR https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/(\d+)\); check it out and finish it\./u
11005
+ );
11006
+ if (!continuationMatch || continuationMatch[1] !== continuationMatch[2]) return null;
11007
+ return Number(continuationMatch[1]);
11008
+ }
11009
+ var CI_FIX_MARKER;
11010
+ var init_superseded_pr_source = __esm({
11011
+ "../../scripts/virtual-office/code-runner/superseded-pr-source.mjs"() {
11012
+ "use strict";
11013
+ CI_FIX_MARKER = "[VO-CI-FIX]";
11014
+ }
11015
+ });
11016
+
10760
11017
  // ../../scripts/virtual-office/code-runner/ci-fix-prompt.mjs
10761
11018
  function buildCiFixPrompt({ prNumber, repo, branch, headSha, failedChecks, prPatch, failedLogs }) {
10762
11019
  return [
@@ -11038,6 +11295,7 @@ async function runWatchCycleUnlocked({
11038
11295
  stateFile = DEFAULT_STATE_FILE
11039
11296
  }) {
11040
11297
  const state = await readWatcherState(stateFile);
11298
+ collapseDuplicateWatcherEntries(state);
11041
11299
  const prNumbers = Object.keys(state);
11042
11300
  let checked = 0;
11043
11301
  let fixed = 0;
@@ -11063,6 +11321,7 @@ async function runWatchCycleUnlocked({
11063
11321
  }
11064
11322
  checked += 1;
11065
11323
  const pr = parsePrCiStatus(view);
11324
+ migrateLegacyMergeState(entry, now());
11066
11325
  entry.lastCi = pr.ci;
11067
11326
  const confirmation = observeRepairFailure(entry, pr, now());
11068
11327
  const proposedAction = decideWatchAction(
@@ -11133,10 +11392,30 @@ async function runWatchCycleUnlocked({
11133
11392
  mergeBlocked += 1;
11134
11393
  log2(`watch: pr #${prNumber} verification blocked merge: ${entry.mergeBlockReason}`);
11135
11394
  } else {
11136
- log2(`watch: pr #${prNumber} verification unavailable; retry ${entry.mergeAttempts}/3`);
11395
+ const retry = await scheduleMergeRetry({
11396
+ entry,
11397
+ now,
11398
+ reportBlocker,
11399
+ taskId: entry.taskId,
11400
+ prNumber,
11401
+ error: new Error(outcome.reason || "verification unavailable"),
11402
+ log: log2
11403
+ });
11404
+ if (retry.terminal) mergeBlocked += 1;
11405
+ if (retry.escalated) escalated += 1;
11137
11406
  }
11138
11407
  } catch (err) {
11139
- log2(`watch: pr #${prNumber} gated merge failed (${entry.mergeAttempts}/3): ${err.message}`);
11408
+ const retry = await scheduleMergeRetry({
11409
+ entry,
11410
+ now,
11411
+ reportBlocker,
11412
+ taskId: entry.taskId,
11413
+ prNumber,
11414
+ error: err,
11415
+ log: log2
11416
+ });
11417
+ if (retry.terminal) mergeBlocked += 1;
11418
+ if (retry.escalated) escalated += 1;
11140
11419
  }
11141
11420
  } else if (action === "fix") {
11142
11421
  entry.fixAttempts = (entry.fixAttempts || 0) + 1;
@@ -11169,6 +11448,7 @@ async function runWatchCycleUnlocked({
11169
11448
  }
11170
11449
  } else {
11171
11450
  entry.lastCheckedAt = now();
11451
+ if (await reportPendingMergeExhaustion({ entry, now, reportBlocker, taskId: entry.taskId, prNumber, log: log2 })) escalated += 1;
11172
11452
  const repairCapped = pr.ci === "failing" && ((entry.fixAttempts || 0) >= maxFixAttempts || entry.allowFixDispatch === false);
11173
11453
  if ((repairCapped || entry.continuationExhausted) && !entry.escalatedAt) {
11174
11454
  try {
@@ -11226,7 +11506,7 @@ function makeWatchRunner({
11226
11506
  repairChainMax,
11227
11507
  repairBudgetUsd
11228
11508
  });
11229
- if (adopted > 0) log2(`watch: adopted ${adopted} open task PR(s) from the control plane`);
11509
+ if (adopted > 0) log2(`watch: adopted or reconciled ${adopted} open task PR(s) from the control plane`);
11230
11510
  return runWatchCycle({
11231
11511
  viewPr: watchView,
11232
11512
  enqueueFix: async ({ prNumber, repo, branch, headSha, failedChecks, failedCheckLinks, repairChain, operatorId }) => {
@@ -11268,13 +11548,10 @@ function makeWatchRunner({
11268
11548
  return result;
11269
11549
  },
11270
11550
  mergePr: (prNumber, entry) => {
11271
- const context = {
11272
- taskId: entry.taskId,
11273
- operatorId: entry.operatorId,
11274
- tenantId: entry.tenantId
11275
- };
11276
- const complete = Object.values(context).every((value) => typeof value === "string" && value.length > 0);
11277
- return client.mergeVerifiedPr(prNumber, complete ? context : void 0);
11551
+ const authority = resolveWatcherMergeAuthority(entry);
11552
+ if (authority.kind === "blocked")
11553
+ return Promise.resolve({ status: "blocked", reason: authority.reason });
11554
+ return client.mergeVerifiedPr(prNumber, authority.context);
11278
11555
  },
11279
11556
  log: log2,
11280
11557
  maxFixAttempts,
@@ -11291,7 +11568,6 @@ var init_pr_watcher = __esm({
11291
11568
  init_check_run_latest();
11292
11569
  init_ci_repair_evidence();
11293
11570
  init_pr_watcher_failure_confirmation();
11294
- init_superseded_pr_source();
11295
11571
  init_watcher_coordination();
11296
11572
  init_watcher_adoption();
11297
11573
  init_watcher_key();
@@ -11300,6 +11576,7 @@ var init_pr_watcher = __esm({
11300
11576
  init_pr_watcher_github();
11301
11577
  init_enqueue_autonomous_code_task();
11302
11578
  init_watcher_state();
11579
+ init_watcher_merge_authority();
11303
11580
  init_superseded_pr_source();
11304
11581
  init_ci_fix_prompt();
11305
11582
  DEFAULT_STATE_FILE = join14(homedir9(), ".vo", "dispatched-prs.json");
@@ -13110,9 +13387,16 @@ function makeSafeProgress(log2) {
13110
13387
  function runnerStagePatch(stage, message, extra = {}) {
13111
13388
  return { stage, message, ...extra };
13112
13389
  }
13390
+ function preservedReceiptLines(run, slicedBodyText) {
13391
+ const full = `${String(run?.summary || "")}
13392
+ ${String(run?.lastAgentMessage || "")}`;
13393
+ const found = [...new Set((full.match(RECEIPT_LINE_RE) || []).map((line) => line.trim()))];
13394
+ const missing = found.filter((line) => !String(slicedBodyText || "").includes(line));
13395
+ return missing.length ? ["", "### Consensus evidence (preserved past truncation)", "", ...missing.map((line) => redactSecrets(line))] : [];
13396
+ }
13113
13397
  function buildPrBody(task, run, files, { armAutoMerge = false } = {}) {
13114
13398
  const governedStakes = matchGovernedStakes({ prompt: String(task.prompt || "") });
13115
- return [
13399
+ const slicedSections = [
13116
13400
  "## AlgoHQ Command Center \u2014 Code-from-Anywhere task",
13117
13401
  "",
13118
13402
  `- **Task:** \`${task.code_task_id}\``,
@@ -13135,7 +13419,12 @@ function buildPrBody(task, run, files, { armAutoMerge = false } = {}) {
13135
13419
  "",
13136
13420
  // A budget/turn-capped run ends with no assistant text (summary = the bare
13137
13421
  // subtype); its LAST message is the honest report the operator needs.
13138
- ...run.lastAgentMessage ? ["### Last agent message before the cap", "", redactSecrets(String(run.lastAgentMessage)).slice(0, 2e3), ""] : [],
13422
+ ...run.lastAgentMessage ? ["### Last agent message before the cap", "", redactSecrets(String(run.lastAgentMessage)).slice(0, 2e3), ""] : []
13423
+ ];
13424
+ return [
13425
+ ...slicedSections,
13426
+ // Receipt lines the slices dropped — the gate reads only this body.
13427
+ ...preservedReceiptLines(run, slicedSections.join("\n")),
13139
13428
  "---",
13140
13429
  armAutoMerge ? "_Opened by the AlgoHQ code-runner daemon. After CI passes, the watcher must obtain a durable consensus receipt and merge the exact verified SHA._" : "_Opened by the AlgoHQ code-runner daemon. This PR awaits the verify-before-act gate / operator review \u2014 it is NOT auto-merged._"
13141
13430
  ].filter((l) => l !== "").join("\n");
@@ -13153,11 +13442,13 @@ async function mintRunnerGithubTokens({ client, taskId, log: log2, repo = null,
13153
13442
  if (!agentReadToken) log2(`task ${taskId}: no GitHub read access for the agent (${reason}); it cannot read a private repo`);
13154
13443
  return { publishToken, agentReadToken };
13155
13444
  }
13445
+ var RECEIPT_LINE_RE;
13156
13446
  var init_task_helpers = __esm({
13157
13447
  "../../scripts/virtual-office/code-runner/task-helpers.mjs"() {
13158
13448
  "use strict";
13159
13449
  init_redact_tokens();
13160
13450
  init_methodology_composer();
13451
+ RECEIPT_LINE_RE = /receipt id:\s*[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/giu;
13161
13452
  }
13162
13453
  });
13163
13454