@algosuite/vo-mcp 0.2.0-beta.68 → 0.2.0-beta.69

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.
@@ -1699,14 +1699,18 @@ async function addWorktreeWithRetry({
1699
1699
  sleep: sleep3 = sleepMs,
1700
1700
  removeDir = async (target) => {
1701
1701
  await fsp2.rm(target, { recursive: true, force: true });
1702
- }
1702
+ },
1703
+ baseRef = "origin/main"
1703
1704
  }) {
1705
+ if (baseRef !== "origin/main" && !/^[0-9a-f]{40}$/u.test(baseRef)) {
1706
+ throw new Error("worktree base ref must be origin/main or an exact lowercase SHA");
1707
+ }
1704
1708
  const maxAttempts = Math.max(1, Math.floor(attempts));
1705
1709
  let result = null;
1706
1710
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
1707
1711
  result = await runner(
1708
1712
  "git",
1709
- ["worktree", "add", "-b", branchName, worktreeDir, "origin/main"],
1713
+ ["worktree", "add", "-b", branchName, worktreeDir, baseRef],
1710
1714
  { cwd: root, timeoutMs }
1711
1715
  );
1712
1716
  if (result.status === 0 && await pathExistsFn(worktreeDir)) {
@@ -2892,6 +2896,18 @@ async function alignCanonicalClone(root, options = {}) {
2892
2896
  }
2893
2897
  return { updated: true, headSha, originSha };
2894
2898
  }
2899
+ async function fetchExactTaskBase(root, baseSha, options = {}) {
2900
+ if (!/^[0-9a-f]{40}$/u.test(baseSha)) {
2901
+ throw new Error("comparative base SHA must be exact lowercase hex");
2902
+ }
2903
+ const fetched = await git(root, ["fetch", "origin", baseSha], options);
2904
+ if (fetched.status !== 0) {
2905
+ throw new Error(`exact comparative source is unavailable: ${summarizeProcessFailure(fetched)}`);
2906
+ }
2907
+ const resolved = await gitText(root, ["rev-parse", `${baseSha}^{commit}`], options);
2908
+ if (resolved !== baseSha) throw new Error("exact comparative source did not resolve to the requested commit");
2909
+ return resolved;
2910
+ }
2895
2911
  function shouldIgnoreManagedEntry(entryName) {
2896
2912
  return IGNORED_MANAGED_ENTRIES.has(entryName) || entryName.endsWith(".lock") || entryName.startsWith("runner-");
2897
2913
  }
@@ -2932,6 +2948,9 @@ async function prepareTaskRoot(root, options = {}) {
2932
2948
  try {
2933
2949
  await reportLegacyResiduals(root, options);
2934
2950
  const alignment = await alignCanonicalClone(root, options);
2951
+ if (options.baseSha) {
2952
+ await fetchExactTaskBase(root, options.baseSha, options);
2953
+ }
2935
2954
  const hydration = await ensurePnpmHydration(root, {
2936
2955
  logger: options.logger,
2937
2956
  runner: options.runner || runProcess
@@ -3206,10 +3225,16 @@ async function createFixWorktree(kind, error = {}, options = {}) {
3206
3225
  const dependencyOwnership = createDependencyOwnershipTracker(worktreeDir);
3207
3226
  const prep = await prepare(root, {
3208
3227
  recoverDirtyCanonical: multiRepo,
3209
- managedPool: path11.dirname(worktreeDir)
3228
+ managedPool: path11.dirname(worktreeDir),
3229
+ baseSha: options.baseSha || null
3210
3230
  });
3211
3231
  await processRunner("git", ["config", "core.longpaths", "true"], { cwd: root, timeoutMs: 3e4 });
3212
- const add = await addWorktree({ root, branchName, worktreeDir });
3232
+ const add = await addWorktree({
3233
+ root,
3234
+ branchName,
3235
+ worktreeDir,
3236
+ baseRef: options.baseSha || "origin/main"
3237
+ });
3213
3238
  if (!add.ok) {
3214
3239
  const detail = describeGitFailure(add.result);
3215
3240
  if (multiRepo) {
@@ -4694,6 +4719,7 @@ function finalizeAgentTaskResult({
4694
4719
  result,
4695
4720
  bin,
4696
4721
  stderrTail,
4722
+ classifyStderr = null,
4697
4723
  timedOut,
4698
4724
  killed,
4699
4725
  cancelReason,
@@ -4724,8 +4750,16 @@ function finalizeAgentTaskResult({
4724
4750
  if (code !== 0 || signal) summary = fallback || `${bin} exited ${signal || code}`;
4725
4751
  else summary = fallback || `${bin} exited without terminal result`;
4726
4752
  }
4753
+ let failure = result.failure ?? null;
4754
+ if (!failure && !sawTerminalResult && (code !== 0 || signal) && typeof classifyStderr === "function") {
4755
+ try {
4756
+ failure = classifyStderr(stderrTail);
4757
+ } catch {
4758
+ }
4759
+ }
4727
4760
  return {
4728
4761
  ...result,
4762
+ ...failure ? { failure } : {},
4729
4763
  ok: sawTerminalResult && result.ok && (code === 0 || forcedAfterResult),
4730
4764
  summary: augmentSummary(summary)
4731
4765
  };
@@ -5650,7 +5684,8 @@ function runAgentTask({
5650
5684
  // parser would work, the runner would report null, and the feature
5651
5685
  // would measure nothing while every test passed.
5652
5686
  tokenUsage: evt.tokenUsage ?? result.tokenUsage ?? null,
5653
- modelUsage: evt.modelUsage ?? result.modelUsage ?? null
5687
+ modelUsage: evt.modelUsage ?? result.modelUsage ?? null,
5688
+ failure: evt.failure ?? result.failure ?? null
5654
5689
  };
5655
5690
  terminalCleanupTimer ??= armTerminalCleanup({
5656
5691
  child,
@@ -5679,6 +5714,7 @@ function runAgentTask({
5679
5714
  result,
5680
5715
  bin,
5681
5716
  stderrTail,
5717
+ classifyStderr: runner.classifyStderr,
5682
5718
  timedOut,
5683
5719
  killed,
5684
5720
  cancelReason,
@@ -5803,26 +5839,15 @@ var init_claude_runner = __esm({
5803
5839
  if (process.platform !== "win32") return { bin, args, spawnOptions: spawnOptions ?? this.getSpawnOptions() };
5804
5840
  return buildWindowsClaudeLaunch({ bin, args, env: env2 });
5805
5841
  }
5806
- /**
5807
- * Fill ANTHROPIC_API_KEY from the OS keychain when not already set (M4 BYO),
5808
- * so a friend who ran `vo-mcp set-key` authenticates without an env var.
5809
- * Explicit env wins; no key stored → unchanged (Claude Code login as before).
5810
- */
5811
5842
  applyAuthEnv(env2 = process.env) {
5812
5843
  return withAnthropicKey(env2);
5813
5844
  }
5814
5845
  costBasis(env2 = process.env) {
5815
5846
  return claudeCostBasis(env2);
5816
5847
  }
5817
- /** Describe which Anthropic auth source the spawn will use (for runner logs). */
5818
5848
  describeAuth(env2 = process.env) {
5819
5849
  return describeAnthropicAuthSource(env2);
5820
5850
  }
5821
- /**
5822
- * Best-effort auth check: is `claude` on PATH and can we verify login?
5823
- * Never throws. If we can't cheaply detect auth, we return installed:true
5824
- * and let the real spawn fail with a clearer error from the CLI itself.
5825
- */
5826
5851
  async checkAuth() {
5827
5852
  return checkClaudeAuth();
5828
5853
  }
@@ -5948,6 +5973,66 @@ var init_flat_token_usage = __esm({
5948
5973
  }
5949
5974
  });
5950
5975
 
5976
+ // ../../scripts/virtual-office/code-runner/error-message.mjs
5977
+ function boundedErrorMessage(error, maxLength = 240) {
5978
+ try {
5979
+ return String(error?.message ?? error ?? "unknown error").slice(0, maxLength);
5980
+ } catch {
5981
+ return "unknown error";
5982
+ }
5983
+ }
5984
+ function codexFailure(code, fields = {}) {
5985
+ const definition = FAILURE_DEFINITIONS[code];
5986
+ return {
5987
+ schema: FAILURE_SCHEMA,
5988
+ agent: "codex",
5989
+ source: definition.source,
5990
+ code,
5991
+ ...fields,
5992
+ operator_next_action: definition.operator_next_action
5993
+ };
5994
+ }
5995
+ function classifyCodexTerminalFailure(evt = {}) {
5996
+ const error = evt && typeof evt.error === "object" && evt.error !== null ? evt.error : null;
5997
+ if (!/^The ['"][^'"]+['"] model is not supported when using Codex with a ChatGPT account\.$/u.test(
5998
+ typeof error?.message === "string" ? error.message : ""
5999
+ )) return null;
6000
+ const status = Number(evt.status ?? error.status);
6001
+ return codexFailure("model_unsupported_for_account", Number.isInteger(status) ? { provider_status: status } : {});
6002
+ }
6003
+ function classifyCodexStderr(stderr = "") {
6004
+ if (!/(?:^|\n)\s*windows sandbox(?: failed)?: runner error:.*\bCreateProcessAsUserW failed:\s*1312\b/imu.test(String(stderr))) return null;
6005
+ return codexFailure("codex_runtime_launch_logon_session", { win32_error: 1312 });
6006
+ }
6007
+ function serializeRunnerFailure(record, maxLength = 2e3) {
6008
+ const definition = record && FAILURE_DEFINITIONS[record.code];
6009
+ if (!definition || record.schema !== FAILURE_SCHEMA || record.source !== definition.source) return null;
6010
+ const value = codexFailure(record.code, {
6011
+ ...record.code === "model_unsupported_for_account" && Number.isInteger(record.provider_status) ? { provider_status: record.provider_status } : {},
6012
+ ...definition.win32_error ? { win32_error: definition.win32_error } : {}
6013
+ });
6014
+ const serialized2 = JSON.stringify(value);
6015
+ return serialized2.length <= Math.max(0, maxLength) ? serialized2 : null;
6016
+ }
6017
+ var FAILURE_SCHEMA, FAILURE_DEFINITIONS;
6018
+ var init_error_message = __esm({
6019
+ "../../scripts/virtual-office/code-runner/error-message.mjs"() {
6020
+ "use strict";
6021
+ FAILURE_SCHEMA = "vo.runner_failure.v1";
6022
+ FAILURE_DEFINITIONS = {
6023
+ model_unsupported_for_account: {
6024
+ source: "terminal_event",
6025
+ operator_next_action: "Create a new owner-reviewed task with an account-supported Codex model; keep the preserved draft PR for review rather than resuming this task."
6026
+ },
6027
+ codex_runtime_launch_logon_session: {
6028
+ source: "stderr",
6029
+ win32_error: 1312,
6030
+ operator_next_action: "Repair the Codex Windows logon/session configuration on the serving host, then rerun the runner."
6031
+ }
6032
+ };
6033
+ }
6034
+ });
6035
+
5951
6036
  // ../../scripts/virtual-office/code-runner/codex-runner.mjs
5952
6037
  import { spawnSync as spawnSync6 } from "node:child_process";
5953
6038
  import { existsSync as existsSync10 } from "node:fs";
@@ -6048,7 +6133,8 @@ function parseCodexEvent(line) {
6048
6133
  }
6049
6134
  if (type === "turn.failed" || type === "error") {
6050
6135
  const msg = evt.error && (evt.error.message || evt.error) || evt.message || "codex run failed";
6051
- return { kind: "result", isError: true, costUsd: null, summary: String(msg), numTurns: null };
6136
+ const failure = classifyCodexTerminalFailure(evt);
6137
+ return { kind: "result", isError: true, costUsd: null, summary: String(msg), numTurns: null, ...failure ? { failure } : {} };
6052
6138
  }
6053
6139
  return null;
6054
6140
  }
@@ -6059,6 +6145,7 @@ var init_codex_runner = __esm({
6059
6145
  init_agent_key_store();
6060
6146
  init_agent_auth_tier();
6061
6147
  init_flat_token_usage();
6148
+ init_error_message();
6062
6149
  CODEX_PREFER_LOGIN_ENV = "VO_RUNNER_CODEX_PREFER_LOGIN";
6063
6150
  LEGACY_PREFER_LOGIN_ENV = "VO_RUNNER_PREFER_LOGIN";
6064
6151
  CodexRunner = class {
@@ -6076,6 +6163,9 @@ var init_codex_runner = __esm({
6076
6163
  parseEvent(line) {
6077
6164
  return parseCodexEvent(line);
6078
6165
  }
6166
+ classifyStderr(stderr) {
6167
+ return classifyCodexStderr(stderr);
6168
+ }
6079
6169
  /**
6080
6170
  * SECURITY: never `shell: true` — same RCE class as cursor-runner. The old
6081
6171
  * `shell: win32 && !/\.exe$/` fell back to shell mode whenever
@@ -6097,12 +6187,6 @@ var init_codex_runner = __esm({
6097
6187
  windowsVerbatimArguments: false
6098
6188
  };
6099
6189
  }
6100
- /**
6101
- * Fill the OpenAI credential env var(s) (OPENAI_API_KEY / CODEX_API_KEY) from
6102
- * the OS keychain when not already set, so a BYO friend who ran
6103
- * `vo-mcp set-key --provider codex` authenticates without an env var. Explicit
6104
- * env wins; no key stored → unchanged (a prior `codex login` still works).
6105
- */
6106
6190
  applyAuthEnv(env2 = process.env) {
6107
6191
  if (isTruthyFlag2(env2[CODEX_PREFER_LOGIN_ENV]) || isTruthyFlag2(env2[LEGACY_PREFER_LOGIN_ENV])) {
6108
6192
  const out = { ...env2 };
@@ -6115,20 +6199,9 @@ var init_codex_runner = __esm({
6115
6199
  costBasis(env2 = process.env) {
6116
6200
  return String(env2.OPENAI_API_KEY || env2.CODEX_API_KEY || "").trim() ? "vendor_billed" : "subscription_api_equivalent";
6117
6201
  }
6118
- /**
6119
- * Dispatch-time billing tier for the NEXT codex spawn, from the SAME facts
6120
- * checkAuth() already gathered — no extra subprocess. applyAuthEnv() is a
6121
- * keychain read in this process (@napi-rs/keyring), not a spawn.
6122
- *
6123
- * Codex is the agent where this signal was already computed and then thrown
6124
- * away: checkAuth() distinguished "API key available (no persisted ChatGPT
6125
- * login)" from a real login, but only inside a `message` string that the
6126
- * heartbeat schema strips before storage. This gives that fact a typed home.
6127
- */
6128
6202
  authTier(env2 = this.env) {
6129
6203
  return safeAuthTier(() => authTierFromCostBasis(this.costBasis(this.applyAuthEnv(env2))));
6130
6204
  }
6131
- /** Best-effort binary + persisted-login probe. Never throws or spends tokens. */
6132
6205
  async checkAuth() {
6133
6206
  try {
6134
6207
  const bin = this.binary;
@@ -7228,6 +7301,17 @@ async function classifyFailureForResume({
7228
7301
  record = recordRateLimited,
7229
7302
  deferRecord = false
7230
7303
  } = {}) {
7304
+ const structuredFailure = serializeRunnerFailure(run.failure);
7305
+ if (structuredFailure) {
7306
+ return {
7307
+ rateLimited: false,
7308
+ progress: {
7309
+ status: "failed",
7310
+ message: `runner blocker: ${run.failure.code}; operator review required`,
7311
+ result: structuredFailure
7312
+ }
7313
+ };
7314
+ }
7231
7315
  if (enabled) {
7232
7316
  const rl = detect(run.summary, { now });
7233
7317
  if (rl.rateLimited) {
@@ -7302,6 +7386,7 @@ var init_rate_limit_resume = __esm({
7302
7386
  "use strict";
7303
7387
  init_rate_limit_detector_core();
7304
7388
  init_rate_limit_resume_state();
7389
+ init_error_message();
7305
7390
  }
7306
7391
  });
7307
7392
 
@@ -8648,16 +8733,28 @@ var init_repair_publication_strategy = __esm({
8648
8733
 
8649
8734
  // ../../scripts/virtual-office/code-runner/partial-pr-continuation.mjs
8650
8735
  function partialPrContinuationResult(run = {}, maxLength = 2e3, reason = null) {
8736
+ const limit = Math.max(0, Number(maxLength) || 0);
8651
8737
  const summary = String(run.summary || "agent stopped before completing the task").trim();
8652
8738
  const reasonMarker = reason === "rate_limited" ? `
8653
8739
  ${RATE_LIMITED_CONTINUATION_MARKER}` : "";
8654
- return `${PARTIAL_PR_CONTINUATION_MARKER}${reasonMarker}
8655
- ${summary}`.slice(0, Math.max(0, maxLength));
8740
+ const blockedMarker = "ALGOSUITE_TASK_OUTCOME: BLOCKED";
8741
+ const blockedPrefix = `${blockedMarker}${reasonMarker}
8742
+ `;
8743
+ const serializedFailure = serializeRunnerFailure(run.failure, Math.max(0, limit - blockedPrefix.length - 1));
8744
+ const outcomeMarker = serializedFailure ? blockedMarker : PARTIAL_PR_CONTINUATION_MARKER;
8745
+ const prefix = `${outcomeMarker}${reasonMarker}
8746
+ `;
8747
+ const failure = serializedFailure && outcomeMarker === blockedMarker ? serializedFailure : null;
8748
+ const suffix = failure ? `
8749
+ ${failure}` : "";
8750
+ const summaryRoom = Math.max(0, limit - prefix.length - suffix.length);
8751
+ return `${prefix}${summary.slice(0, summaryRoom)}${suffix}`.slice(0, limit);
8656
8752
  }
8657
8753
  var PARTIAL_PR_CONTINUATION_MARKER, RATE_LIMITED_CONTINUATION_MARKER;
8658
8754
  var init_partial_pr_continuation = __esm({
8659
8755
  "../../scripts/virtual-office/code-runner/partial-pr-continuation.mjs"() {
8660
8756
  "use strict";
8757
+ init_error_message();
8661
8758
  PARTIAL_PR_CONTINUATION_MARKER = "ALGOSUITE_TASK_OUTCOME: NEEDS_CONTINUATION";
8662
8759
  RATE_LIMITED_CONTINUATION_MARKER = "ALGOSUITE_CONTINUATION_REASON: RATE_LIMITED";
8663
8760
  }
@@ -12015,20 +12112,6 @@ var init_pr_watcher_failure_confirmation = __esm({
12015
12112
  }
12016
12113
  });
12017
12114
 
12018
- // ../../scripts/virtual-office/code-runner/error-message.mjs
12019
- function boundedErrorMessage(error, maxLength = 240) {
12020
- try {
12021
- return String(error?.message ?? error ?? "unknown error").slice(0, maxLength);
12022
- } catch {
12023
- return "unknown error";
12024
- }
12025
- }
12026
- var init_error_message = __esm({
12027
- "../../scripts/virtual-office/code-runner/error-message.mjs"() {
12028
- "use strict";
12029
- }
12030
- });
12031
-
12032
12115
  // ../../scripts/virtual-office/code-runner/watcher-coordination.mjs
12033
12116
  import { createHash as createHash7 } from "node:crypto";
12034
12117
  function ciFixOccurrenceKey({ repo, prNumber, headSha, repairAttempt }) {
@@ -12524,7 +12607,49 @@ var init_superseded_pr_source = __esm({
12524
12607
  });
12525
12608
 
12526
12609
  // ../../scripts/virtual-office/code-runner/ci-fix-prompt.mjs
12527
- function buildCiFixPrompt({ prNumber, repo, branch, headSha, failedChecks, prPatch, failedLogs }) {
12610
+ function fairSplit(aLen, bLen, budget) {
12611
+ if (aLen + bLen <= budget) return { a: aLen, b: bLen };
12612
+ const half = Math.floor(budget / 2);
12613
+ if (aLen <= half) return { a: aLen, b: budget - aLen };
12614
+ if (bLen <= half) return { a: budget - bLen, b: bLen };
12615
+ return { a: half, b: budget - half };
12616
+ }
12617
+ function boundHead(content, allocated, label) {
12618
+ if (content.length <= allocated) return content;
12619
+ const marker = `
12620
+ [${label} truncated to the first ${allocated} characters available in this dispatch]`;
12621
+ if (marker.length >= allocated) return marker.slice(0, Math.max(0, allocated));
12622
+ return `${content.slice(0, allocated - marker.length)}${marker}`;
12623
+ }
12624
+ function boundTail(content, allocated, label) {
12625
+ if (content.length <= allocated) return content;
12626
+ const marker = `[${label} truncated to the final ${allocated} characters available in this dispatch]
12627
+ `;
12628
+ if (marker.length >= allocated) return marker.slice(0, Math.max(0, allocated));
12629
+ return `${marker}${content.slice(-(allocated - marker.length))}`;
12630
+ }
12631
+ function boundedEvidenceShares({
12632
+ prPatch,
12633
+ failedLogs,
12634
+ fixedOverheadChars,
12635
+ prNumber,
12636
+ maxTotalChars = CI_FIX_PROMPT_MAX_CHARS
12637
+ }) {
12638
+ const patchWanted = String(prPatch || "").trim() || NO_PATCH_EVIDENCE;
12639
+ const logsWanted = String(failedLogs || "").trim() || NO_LOG_EVIDENCE;
12640
+ const available = maxTotalChars - fixedOverheadChars;
12641
+ if (available < NO_PATCH_EVIDENCE.length + NO_LOG_EVIDENCE.length) {
12642
+ throw new Error(
12643
+ `CI-fix prompt for PR #${prNumber ?? "?"} cannot fit its mandatory instructions and identity (${fixedOverheadChars} chars) plus even an empty-evidence notice inside the ${maxTotalChars}-character dispatch limit \u2014 refusing to send a malformed or instructions-truncated request`
12644
+ );
12645
+ }
12646
+ const { a: patchShare, b: logsShare } = fairSplit(patchWanted.length, logsWanted.length, available);
12647
+ return {
12648
+ patchBody: boundHead(patchWanted, patchShare, "PR patch"),
12649
+ logsBody: boundTail(logsWanted, logsShare, "failed-job evidence")
12650
+ };
12651
+ }
12652
+ function renderHeader({ prNumber, repo, branch, headSha, failedChecks }) {
12528
12653
  return [
12529
12654
  `${CI_FIX_MARKER} An AlgoHQ-dispatched pull request has FAILING CI and needs a fix.`,
12530
12655
  "",
@@ -12543,24 +12668,45 @@ function buildCiFixPrompt({ prNumber, repo, branch, headSha, failedChecks, prPat
12543
12668
  "VO-ALLOW-PR-OVERLAP marker. Your work ships: finish the fix completely, leave UNCOMMITTED",
12544
12669
  "edits, and the runner publishes for you. A denied git/gh is EXPECTED; do NOT retry it.",
12545
12670
  "",
12546
- HEADLESS_EXECUTION_CONTRACT,
12671
+ HEADLESS_EXECUTION_CONTRACT
12672
+ ].join("\n");
12673
+ }
12674
+ function assemblePrompt(header, patchBody, logsBody) {
12675
+ return [
12676
+ header,
12547
12677
  "",
12548
12678
  "## Bounded source PR patch excerpt",
12549
12679
  "```diff",
12550
- prPatch || "[No excerpt available; the complete exact source is still materialized in the worktree.]",
12680
+ patchBody,
12551
12681
  "```",
12552
12682
  "",
12553
12683
  "## Failed-job evidence",
12554
12684
  "```text",
12555
- failedLogs || "[No failed-job log was available; use the named checks and patch.]",
12685
+ logsBody,
12556
12686
  "```"
12557
12687
  ].join("\n");
12558
12688
  }
12689
+ function buildCiFixPrompt({ prNumber, repo, branch, headSha, failedChecks, prPatch, failedLogs }) {
12690
+ const header = renderHeader({ prNumber, repo, branch, headSha, failedChecks });
12691
+ const fixedOverheadChars = assemblePrompt(header, "", "").length;
12692
+ const { patchBody, logsBody } = boundedEvidenceShares({
12693
+ prPatch,
12694
+ failedLogs,
12695
+ fixedOverheadChars,
12696
+ prNumber,
12697
+ maxTotalChars: CI_FIX_PROMPT_MAX_CHARS
12698
+ });
12699
+ return assemblePrompt(header, patchBody, logsBody);
12700
+ }
12701
+ var CI_FIX_PROMPT_MAX_CHARS, NO_PATCH_EVIDENCE, NO_LOG_EVIDENCE;
12559
12702
  var init_ci_fix_prompt = __esm({
12560
12703
  "../../scripts/virtual-office/code-runner/ci-fix-prompt.mjs"() {
12561
12704
  "use strict";
12562
12705
  init_superseded_pr_source();
12563
12706
  init_headless_execution_contract();
12707
+ CI_FIX_PROMPT_MAX_CHARS = 9900;
12708
+ NO_PATCH_EVIDENCE = "[No excerpt available; the complete exact source is still materialized in the worktree.]";
12709
+ NO_LOG_EVIDENCE = "[No failed-job log was available; use the named checks and patch.]";
12564
12710
  }
12565
12711
  });
12566
12712
 
@@ -16067,7 +16213,11 @@ async function finalizePublishedPr({
16067
16213
  const overlapBlocked = pr.overlapDraft === true;
16068
16214
  const blockedRefs = Array.isArray(pr.overlapBlockedBy) && pr.overlapBlockedBy.length > 0 ? pr.overlapBlockedBy.map((n) => `#${n}`).join(", ") : "unresolved (see PR body for the gate report)";
16069
16215
  const overlapNote = overlapBlocked ? ` as DRAFT \u2014 overlap-blocked by ${blockedRefs} (finished work preserved; mark ready after the overlap is resolved)` : "";
16070
- const fixDispatchGuard = overlapBlocked ? { allowFixDispatch: false } : {};
16216
+ const serializedRunnerFailure = serializeRunnerFailure(run?.failure);
16217
+ const runnerFailure = Boolean(serializedRunnerFailure);
16218
+ const runnerFailureRecord = serializedRunnerFailure ? JSON.parse(serializedRunnerFailure) : null;
16219
+ const runnerFailureMessage = runnerFailureRecord ? `blocked by ${runnerFailureRecord.code}; operator review required: ${runnerFailureRecord.operator_next_action}` : "";
16220
+ const fixDispatchGuard = overlapBlocked || runnerFailure ? { allowFixDispatch: false } : {};
16071
16221
  let resumeQueued = false;
16072
16222
  if (cfg.watchEnabled) {
16073
16223
  try {
@@ -16078,7 +16228,7 @@ async function finalizePublishedPr({
16078
16228
  taskId: id,
16079
16229
  operatorId: task.operator_id,
16080
16230
  tenantId: task.tenant_id,
16081
- needsContinuation: partial && !rateLimitResume && (task.continuation_attempt ?? 0) < (task.continuation_max_attempts ?? 3),
16231
+ needsContinuation: partial && !rateLimitResume && !runnerFailure && (task.continuation_attempt ?? 0) < (task.continuation_max_attempts ?? 3),
16082
16232
  continuationExhausted: partial && (task.continuation_attempt ?? 0) >= (task.continuation_max_attempts ?? 3),
16083
16233
  repairChain: task.repair_chain ?? {
16084
16234
  root_pr_number: pr.prNumber,
@@ -16135,15 +16285,15 @@ async function finalizePublishedPr({
16135
16285
  safeProgress: safeProgress2,
16136
16286
  log: log2,
16137
16287
  patch: {
16138
- status: "pr_opened",
16139
- message: `opened ${pr.prUrl}${overlapNote}${pr.newCommit === false ? " (no new commits \u2014 the branch already held every change)" : ""}`,
16288
+ status: runnerFailure ? "failed" : "pr_opened",
16289
+ message: runnerFailure ? `${runnerFailureMessage}; preserved PR ${pr.prUrl} for owner review` : `opened ${pr.prUrl}${overlapNote}${pr.newCommit === false ? " (no new commits \u2014 the branch already held every change)" : ""}`,
16140
16290
  pr_url: pr.prUrl,
16141
16291
  pr_number: pr.prNumber,
16142
16292
  pr_branch: pr.branch,
16143
16293
  result: (() => {
16144
16294
  const prefix = overlapBlocked ? `[VO-PUBLISH-OVERLAP-BLOCKED: ${blockedRefs}] ` : "";
16145
16295
  const room = 2e3 - prefix.length;
16146
- return `${prefix}${partial ? partialPrContinuationResult(run, room, rateLimitResume ? "rate_limited" : null) : String(run.summary).slice(0, room)}`;
16296
+ return `${prefix}${partial || runnerFailure ? partialPrContinuationResult(run, room, rateLimitResume ? "rate_limited" : null) : String(run.summary).slice(0, room)}`;
16147
16297
  })(),
16148
16298
  ...runOutcomePatch(run),
16149
16299
  ...terminalLedgerPatch(run)
@@ -16187,6 +16337,7 @@ var init_publication_outcome = __esm({
16187
16337
  init_outcome_commit();
16188
16338
  init_terminal_delivery();
16189
16339
  init_rate_limit_resume();
16340
+ init_error_message();
16190
16341
  defaultRunCommand4 = (cmd, args, cwd, opts = {}) => runProcess2(cmd, args, { cwd, ...opts });
16191
16342
  }
16192
16343
  });
@@ -16524,7 +16675,16 @@ function isMaxTurnExhaustion(run = {}, maxTurns) {
16524
16675
  return Number.isInteger(maxTurns) && maxTurns > 0 && Number.isInteger(run.numTurns) && run.numTurns > maxTurns;
16525
16676
  }
16526
16677
  function decideNoChangesTerminalStatus({ partial, run = {}, maxTurns } = {}) {
16678
+ const structuredFailure = serializeRunnerFailure(run.failure);
16527
16679
  if (!partial) {
16680
+ if (structuredFailure) {
16681
+ const failure = JSON.parse(structuredFailure);
16682
+ return {
16683
+ status: "failed",
16684
+ message: `agent reported a runner blocker with no file changes \u2014 ${failure.code}; ${failure.operator_next_action}`,
16685
+ result: partialPrContinuationResult(run, RESULT_LIMIT)
16686
+ };
16687
+ }
16528
16688
  if (reportsBlocker(run.summary)) {
16529
16689
  return {
16530
16690
  status: "failed",
@@ -16548,8 +16708,8 @@ function decideNoChangesTerminalStatus({ partial, run = {}, maxTurns } = {}) {
16548
16708
  const cause = String(run.summary || "").trim();
16549
16709
  return {
16550
16710
  status: "failed",
16551
- message: cause ? `agent made no file changes \u2014 ${cause.slice(0, 200)}` : "agent made no file changes",
16552
- result: (cause || "no_changes").slice(0, RESULT_LIMIT)
16711
+ message: structuredFailure ? `agent made no file changes \u2014 ${JSON.parse(structuredFailure).code}; ${JSON.parse(structuredFailure).operator_next_action}` : cause ? `agent made no file changes \u2014 ${cause.slice(0, 200)}` : "agent made no file changes",
16712
+ result: structuredFailure ? partialPrContinuationResult(run, RESULT_LIMIT) : (cause || "no_changes").slice(0, RESULT_LIMIT)
16553
16713
  };
16554
16714
  }
16555
16715
  async function closeSupersededSourceOnNoChanges({
@@ -16682,6 +16842,8 @@ var init_no_changes_terminal_status = __esm({
16682
16842
  init_terminal_ledger_patch();
16683
16843
  init_outcome_commit();
16684
16844
  init_terminal_delivery();
16845
+ init_error_message();
16846
+ init_partial_pr_continuation();
16685
16847
  RESULT_LIMIT = 2e3;
16686
16848
  }
16687
16849
  });
@@ -16999,6 +17161,50 @@ var init_daemon_config = __esm({
16999
17161
  });
17000
17162
 
17001
17163
  // ../../scripts/virtual-office/code-runner/task-worktree-preparation.mjs
17164
+ async function git3(worktreeDir, args, runCommand = runProcess2) {
17165
+ const result = await runCommand("git", args, { cwd: worktreeDir, timeoutMs: 6e4 });
17166
+ if (result.status !== 0) throw new Error(`source binding git ${args[0]} failed before model execution`);
17167
+ return String(result.stdout || "").trim();
17168
+ }
17169
+ function hasAffirmativePreSpawnNoSpendEvidence(parentTask) {
17170
+ return Boolean(parentTask && parentTask.status === "failed" && parentTask.completed_at && parentTask.execution_started_at === null && parentTask.usage_reported_at && parentTask.cost_usd === 0 && parentTask.cost_basis === "no_agent_spawned" && typeof parentTask.claimed_by === "string" && parentTask.claimed_by.length > 0 && RUNNER_INSTANCE_ID.test(parentTask.runner_instance_id ?? "") && parentTask.usage_runner_instance_id === parentTask.runner_instance_id && !parentTask.token_usage && !parentTask.model_usage && !parentTask.detached_run_economics?.length && !parentTask.resumed_from && !parentTask.pr_branch && !parentTask.pr_url && !parentTask.pr_number);
17171
+ }
17172
+ async function bindTaskExecutionSource({
17173
+ client,
17174
+ task,
17175
+ parentTask,
17176
+ worktreeDir,
17177
+ continuationRestore,
17178
+ runCommand = runProcess2
17179
+ }) {
17180
+ const baseSha = task.comparative_base_sha;
17181
+ if (!baseSha) return null;
17182
+ if (!EXACT_SHA.test(baseSha)) throw new Error("comparative base SHA is invalid");
17183
+ const actualSha = await git3(worktreeDir, ["rev-parse", "HEAD"], runCommand);
17184
+ if (!EXACT_SHA.test(actualSha)) throw new Error("worktree HEAD is not an exact commit SHA");
17185
+ if (!task.resumed_from) {
17186
+ if (actualSha !== baseSha) throw new Error("initial comparative worktree HEAD does not match requested base");
17187
+ } else if (continuationRestore) {
17188
+ if (task.pr_head_sha_at_enqueue && actualSha !== task.pr_head_sha_at_enqueue) {
17189
+ throw new Error("continuation worktree HEAD does not match its expected continuation head");
17190
+ }
17191
+ await git3(worktreeDir, ["merge-base", "--is-ancestor", baseSha, actualSha], runCommand);
17192
+ } else {
17193
+ if (!hasAffirmativePreSpawnNoSpendEvidence(parentTask)) {
17194
+ throw new Error("continuation branch is missing and prior execution state is unknown; recovery review required");
17195
+ }
17196
+ if (actualSha !== baseSha) throw new Error("safe pre-spawn continuation did not rematerialize the original base");
17197
+ }
17198
+ const acknowledgement = await client.postProgress(task.code_task_id, {
17199
+ execution_start_sha: actualSha,
17200
+ stage: "preparing_worktree",
17201
+ message: `Exact execution source ${actualSha} verified before model start`
17202
+ });
17203
+ if (acknowledgement?.terminal || acknowledgement?.task?.execution_start_sha !== actualSha) {
17204
+ throw new Error("control plane did not durably acknowledge execution_start_sha");
17205
+ }
17206
+ return { baseSha, executionStartSha: actualSha };
17207
+ }
17002
17208
  async function prepareTaskWorktree({ client, task, cfg, safeProgress: safeProgress2, log: log2 }) {
17003
17209
  const id = task.code_task_id;
17004
17210
  await safeProgress2(client, id, runnerStagePatch(
@@ -17015,7 +17221,7 @@ async function prepareTaskWorktree({ client, task, cfg, safeProgress: safeProgre
17015
17221
  const wt = await createFixWorktree(
17016
17222
  "code-task",
17017
17223
  { source: id.slice(0, 8), repo: task.repo },
17018
- { githubToken }
17224
+ { githubToken, baseSha: task.comparative_base_sha ?? null }
17019
17225
  );
17020
17226
  if (!wt.worktreeName || !wt.worktreeDir) {
17021
17227
  throw new Error("worktree isolation failure \u2014 refusing to run in the main tree");
@@ -17045,8 +17251,16 @@ async function prepareTaskWorktree({ client, task, cfg, safeProgress: safeProgre
17045
17251
  });
17046
17252
  log2(repairPlan.strategy === "in-place" ? `task ${id}: repairing ${task.repo}#${task.repair_pr_number}@${repairPlan.headSha} IN PLACE on ${repairPlan.remoteBranch}` : `task ${id}: materialized full repair source ${task.repo}#${task.repair_pr_number}@${repairPlan.headSha} for supersede (${repairPlan.reason})${repairPlan.conflicted ? " with conflicts for the agent to resolve" : ""}`);
17047
17253
  }
17048
- return { wt, githubToken, agentGithubReadToken, continuationRestore, repairPlan };
17254
+ const sourceBinding = await bindTaskExecutionSource({
17255
+ client,
17256
+ task,
17257
+ parentTask,
17258
+ worktreeDir: wt.worktreeDir,
17259
+ continuationRestore
17260
+ });
17261
+ return { wt, githubToken, agentGithubReadToken, continuationRestore, repairPlan, sourceBinding };
17049
17262
  }
17263
+ var EXACT_SHA, RUNNER_INSTANCE_ID;
17050
17264
  var init_task_worktree_preparation = __esm({
17051
17265
  "../../scripts/virtual-office/code-runner/task-worktree-preparation.mjs"() {
17052
17266
  "use strict";
@@ -17054,6 +17268,9 @@ var init_task_worktree_preparation = __esm({
17054
17268
  init_resume_branch();
17055
17269
  init_repair_publication_strategy();
17056
17270
  init_task_helpers();
17271
+ init_process_runner2();
17272
+ EXACT_SHA = /^[0-9a-f]{40}$/u;
17273
+ RUNNER_INSTANCE_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
17057
17274
  }
17058
17275
  });
17059
17276