@algosuite/vo-mcp 0.2.0-beta.40 → 0.2.0-beta.42

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.
@@ -2416,7 +2416,7 @@ async function fetchInstallationToken({ req, required = false, readOnly = false,
2416
2416
  const json = await res.json();
2417
2417
  if (!json || !json.token) return fail("missing token");
2418
2418
  if (readOnly && json.scope !== "read") return fail("control plane did not confirm a read-only grant");
2419
- return { token: json.token, expiresAt: json.expires_at || null };
2419
+ return { token: json.token, expiresAt: json.expires_at || null, ...typeof json.ci_readable === "boolean" ? { ciReadable: json.ci_readable } : {} };
2420
2420
  } catch (err) {
2421
2421
  if (required) throw err;
2422
2422
  return null;
@@ -2479,7 +2479,18 @@ async function resumeCodeTaskRequest(req, taskId, { automaticRateLimit = false,
2479
2479
  onUnauthorized();
2480
2480
  throw new Error("resume unauthorized (401)");
2481
2481
  }
2482
- if (!res.ok) throw new Error(`resume failed: HTTP ${res.status}`);
2482
+ if (!res.ok) {
2483
+ let code = null;
2484
+ try {
2485
+ const body = await res.json();
2486
+ code = typeof body?.error === "string" ? body.error : null;
2487
+ } catch {
2488
+ }
2489
+ const err = new Error(`resume failed: HTTP ${res.status}${code ? ` (${code})` : ""}`);
2490
+ err.status = res.status;
2491
+ err.code = code;
2492
+ throw err;
2493
+ }
2483
2494
  const json = await res.json();
2484
2495
  return json && json.task ? json.task : null;
2485
2496
  }
@@ -3921,6 +3932,12 @@ var init_agent_token_usage = __esm({
3921
3932
  });
3922
3933
 
3923
3934
  // ../../scripts/virtual-office/code-runner/claude-result-event.mjs
3935
+ function cappedRunLastMessage(evt, lastProgress) {
3936
+ const summary = String(evt?.summary || "").trim();
3937
+ const salvage = String(lastProgress || "").trim();
3938
+ if (!salvage || !CAPPED_RESULT_SUBTYPES.includes(summary)) return null;
3939
+ return salvage;
3940
+ }
3924
3941
  function buildResultEvent(evt) {
3925
3942
  const isError = Boolean(evt.is_error) || evt.subtype === "error_max_turns" || evt.subtype === "error_during_execution";
3926
3943
  return {
@@ -3933,10 +3950,12 @@ function buildResultEvent(evt) {
3933
3950
  modelUsage: extractModelUsage(evt)
3934
3951
  };
3935
3952
  }
3953
+ var CAPPED_RESULT_SUBTYPES;
3936
3954
  var init_claude_result_event = __esm({
3937
3955
  "../../scripts/virtual-office/code-runner/claude-result-event.mjs"() {
3938
3956
  "use strict";
3939
3957
  init_agent_token_usage();
3958
+ CAPPED_RESULT_SUBTYPES = Object.freeze(["error_max_budget_usd", "error_max_turns"]);
3940
3959
  }
3941
3960
  });
3942
3961
 
@@ -4250,7 +4269,7 @@ function runAgentTask({
4250
4269
  } catch {
4251
4270
  }
4252
4271
  let buffer = "";
4253
- let result = { ok: false, costUsd: null, costBasis, summary: "", numTurns: null, tokenUsage: null, modelUsage: null, executionStarted: false, killed: false };
4272
+ let result = { ok: false, costUsd: null, costBasis, summary: "", lastAgentMessage: null, numTurns: null, tokenUsage: null, modelUsage: null, executionStarted: false, killed: false };
4254
4273
  child.once("spawn", () => {
4255
4274
  result = { ...result, executionStarted: true };
4256
4275
  Promise.resolve(onSpawn()).catch(() => {
@@ -4285,6 +4304,9 @@ function runAgentTask({
4285
4304
  costUsd: evt.costUsd,
4286
4305
  costBasis: result.costBasis,
4287
4306
  summary,
4307
+ // A budget/turn-capped run's honest last message, kept OUT of summary (see
4308
+ // claude-result-event.cappedRunLastMessage) and surfaced in the PR body.
4309
+ lastAgentMessage: cappedRunLastMessage(evt, lastProgress),
4288
4310
  numTurns: evt.numTurns,
4289
4311
  // MUST be listed explicitly. This assignment spreads the PREVIOUS
4290
4312
  // result and then names each field it carries forward, so anything
@@ -4423,6 +4445,7 @@ var init_claude_runner = __esm({
4423
4445
  init_orphan_agent_reaper();
4424
4446
  init_agent_token_usage();
4425
4447
  init_claude_stream_event();
4448
+ init_claude_result_event();
4426
4449
  init_claude_auth_check();
4427
4450
  ClaudeRunner = class {
4428
4451
  get enforcesBudgetCap() {
@@ -4527,7 +4550,14 @@ var init_agent_key_store = __esm({
4527
4550
  "oai-compat": ["VO_CODE_RUNNER_OAI_API_KEY"],
4528
4551
  // Sovereign local inference (Ollama / LM Studio). The key is OPTIONAL — most
4529
4552
  // local servers need none — and exists for locally secured endpoints only.
4530
- local: ["VO_CODE_RUNNER_LOCAL_API_KEY"]
4553
+ local: ["VO_CODE_RUNNER_LOCAL_API_KEY"],
4554
+ // AlgoHQ cloud consensus (ADR-002 moat plane) entitlement token. Not a model
4555
+ // key: it authorises the runner's `vo_consensus_judgment` / `vo_verify_answer`
4556
+ // tools against the moat. On an npm-installed runner the local consensus
4557
+ // engine is never present (it is a workspace-only package), so WITHOUT this
4558
+ // token every consensus call answers `unimplemented /
4559
+ // consensus-engine-package-not-installed` (2026-08-16 finding, task fbd8659b).
4560
+ moat: ["VO_ENTITLEMENT_TOKEN"]
4531
4561
  };
4532
4562
  PROVIDER_ALIAS = {
4533
4563
  claude: "anthropic",
@@ -4543,7 +4573,10 @@ var init_agent_key_store = __esm({
4543
4573
  "oai-compat": "oai-compat",
4544
4574
  local: "local",
4545
4575
  ollama: "local",
4546
- lmstudio: "local"
4576
+ lmstudio: "local",
4577
+ moat: "moat",
4578
+ entitlement: "moat",
4579
+ consensus: "moat"
4547
4580
  };
4548
4581
  _loadTried2 = false;
4549
4582
  }
@@ -9051,6 +9084,9 @@ function ciFixOccurrenceKey({ repo, prNumber, headSha, repairAttempt }) {
9051
9084
  function coordinationRetryDue(entry, nowMs) {
9052
9085
  return !entry.nextRetryAt || nowMs >= entry.nextRetryAt;
9053
9086
  }
9087
+ function isTerminalResumeRefusal(error) {
9088
+ return typeof error?.code === "string" && TERMINAL_RESUME_REFUSALS.includes(error.code);
9089
+ }
9054
9090
  async function scheduleCoordinationRetry({
9055
9091
  entry,
9056
9092
  kind,
@@ -9061,6 +9097,14 @@ async function scheduleCoordinationRetry({
9061
9097
  error,
9062
9098
  log: log2
9063
9099
  }) {
9100
+ if (kind === "resume" && isTerminalResumeRefusal(error)) {
9101
+ entry.needsContinuation = false;
9102
+ entry.continuationExhausted = true;
9103
+ entry.continuationRefusal = error.code;
9104
+ entry.continuationRefusedAt = now();
9105
+ log2(`watch: pr #${prNumber} automatic continuation refused by the plane (${error.code}); no retry \u2014 operator may resume explicitly with a larger cap`);
9106
+ return;
9107
+ }
9064
9108
  const errorsKey = kind === "resume" ? "resumeErrors" : "enqueueErrors";
9065
9109
  const attemptsKey = kind === "resume" ? "resumeAttempts" : "fixAttempts";
9066
9110
  entry[errorsKey] = (entry[errorsKey] || 0) + 1;
@@ -9080,12 +9124,23 @@ async function scheduleCoordinationRetry({
9080
9124
  }
9081
9125
  log2(`watch: pr #${prNumber} ${kind} coordination failed ${entry[errorsKey]}x; retry in ${Math.round(delay2 / 1e3)}s: ${boundedErrorMessage(error)}`);
9082
9126
  }
9083
- var MAX_BACKOFF_MS;
9127
+ var MAX_BACKOFF_MS, TERMINAL_RESUME_REFUSALS;
9084
9128
  var init_watcher_coordination = __esm({
9085
9129
  "../../scripts/virtual-office/code-runner/watcher-coordination.mjs"() {
9086
9130
  "use strict";
9087
9131
  init_error_message();
9088
9132
  MAX_BACKOFF_MS = 60 * 60 * 1e3;
9133
+ TERMINAL_RESUME_REFUSALS = Object.freeze([
9134
+ "automatic_continuation_budget_too_small",
9135
+ "automatic_continuation_budget_required",
9136
+ "continuation_spend_ceiling_reached",
9137
+ "continuation_budget_exhausted",
9138
+ "continuation_spend_unmeasured",
9139
+ "continuation_lineage_incomplete",
9140
+ "continuation_lineage_invalid",
9141
+ "cancelled_not_automatically_resumable",
9142
+ "not_resumable"
9143
+ ]);
9089
9144
  }
9090
9145
  });
9091
9146
 
@@ -9201,7 +9256,7 @@ async function adoptPrOpenedTasks(tasks, {
9201
9256
  if (operators.size > 0 && !operators.has(task.operator_id)) continue;
9202
9257
  const key = watcherKey(state, task.repo, task.pr_number);
9203
9258
  if (state[key]) continue;
9204
- const partial = String(task.result || "").startsWith(PARTIAL_PR_CONTINUATION_MARKER);
9259
+ const partial = String(task.result || "").includes(PARTIAL_PR_CONTINUATION_MARKER);
9205
9260
  const repairChain = task.repair_chain ?? {
9206
9261
  root_pr_number: task.repair_pr_number ?? task.pr_number,
9207
9262
  attempt: task.repair_pr_number ? 1 : 0,
@@ -9240,8 +9295,10 @@ var init_watcher_adoption = __esm({
9240
9295
  });
9241
9296
 
9242
9297
  // ../../scripts/virtual-office/code-runner/watcher-github-token.mjs
9243
- function makeWatcherTokenProvider(client, { required = true, allowAmbient = false, now = () => Date.now() } = {}) {
9298
+ function makeWatcherTokenProvider(client, { required = true, allowAmbient = false, now = () => Date.now(), log: log2 = () => {
9299
+ } } = {}) {
9244
9300
  const cache = /* @__PURE__ */ new Map();
9301
+ let ciUnreadableLoggedAt = null;
9245
9302
  return async (repo) => {
9246
9303
  const key = String(repo).toLowerCase();
9247
9304
  const prior = cache.get(key);
@@ -9251,6 +9308,10 @@ function makeWatcherTokenProvider(client, { required = true, allowAmbient = fals
9251
9308
  if (allowAmbient) return null;
9252
9309
  throw new Error(`GitHub App read token unavailable for ${repo}`);
9253
9310
  }
9311
+ if (result.ciReadable === false && (ciUnreadableLoggedAt === null || now() - ciUnreadableLoggedAt > 6 * 60 * 60 * 1e3)) {
9312
+ ciUnreadableLoggedAt = now();
9313
+ log2(`watch: the plane minted a read token for ${repo} WITHOUT CI read (ci_readable=false \u2014 the GitHub App installation has not accepted checks:read/statuses:read); PR CI stays unreadable until the operator accepts the App permission update`);
9314
+ }
9254
9315
  cache.set(key, { token: result.token, at: now() });
9255
9316
  return result.token;
9256
9317
  };
@@ -9298,25 +9359,47 @@ var init_ci_fix_prompt = __esm({
9298
9359
  });
9299
9360
 
9300
9361
  // ../../scripts/virtual-office/code-runner/pr-watcher-github.mjs
9301
- async function ghViewPr(prNumber, repo, { githubToken } = {}) {
9302
- const stdout = await runProcess2("gh", [
9362
+ function isCiUnreadableError(err) {
9363
+ const text = `${err?.message || ""}
9364
+ ${err?.stderr || ""}`;
9365
+ return /Resource not accessible by integration/i.test(text) && /statusCheckRollup/i.test(text);
9366
+ }
9367
+ function noteCiUnreadable(log2) {
9368
+ const now = Date.now();
9369
+ if (now - lastDiagnosticAt < DIAGNOSTIC_INTERVAL_MS) return;
9370
+ lastDiagnosticAt = now;
9371
+ log2("watch: CI status is UNREADABLE with the GitHub App read token (the installation has not granted checks:read/statuses:read \u2014 accept the App permission update on the installation). Auto-fix and merge stay dormant; untrack/resume keep working on state alone.");
9372
+ }
9373
+ async function ghViewPr(prNumber, repo, { githubToken, log: log2 = (m) => console.error(`[vo-runner] ${m}`), run = runProcess2 } = {}) {
9374
+ const env2 = githubToken ? { ...process.env, GH_TOKEN: githubToken } : process.env;
9375
+ const view = async (fields) => JSON.parse(await run("gh", [
9303
9376
  "pr",
9304
9377
  "view",
9305
9378
  String(prNumber),
9306
9379
  "-R",
9307
9380
  repo,
9308
9381
  "--json",
9309
- "state,statusCheckRollup,headRefName,headRefOid,url,isDraft,mergeStateStatus"
9310
- ], {
9311
- timeout: 3e4,
9312
- env: githubToken ? { ...process.env, GH_TOKEN: githubToken } : process.env
9313
- });
9314
- return JSON.parse(stdout || "{}");
9382
+ fields
9383
+ ], { timeout: 3e4, env: env2 }) || "{}");
9384
+ try {
9385
+ return await view(VIEW_FIELDS_WITH_CI);
9386
+ } catch (err) {
9387
+ if (!isCiUnreadableError(err)) throw err;
9388
+ noteCiUnreadable(log2);
9389
+ const withoutCi = await view(VIEW_FIELDS_WITHOUT_CI);
9390
+ return { ...withoutCi, statusCheckRollup: null, ciUnreadable: true, ciUnreadableReason: CI_UNREADABLE_REASON };
9391
+ }
9315
9392
  }
9393
+ var VIEW_FIELDS_WITH_CI, VIEW_FIELDS_WITHOUT_CI, CI_UNREADABLE_REASON, DIAGNOSTIC_INTERVAL_MS, lastDiagnosticAt;
9316
9394
  var init_pr_watcher_github = __esm({
9317
9395
  "../../scripts/virtual-office/code-runner/pr-watcher-github.mjs"() {
9318
9396
  "use strict";
9319
9397
  init_process_runner2();
9398
+ VIEW_FIELDS_WITH_CI = "state,statusCheckRollup,headRefName,headRefOid,url,isDraft,mergeStateStatus";
9399
+ VIEW_FIELDS_WITHOUT_CI = "state,headRefName,headRefOid,url,isDraft,mergeStateStatus";
9400
+ CI_UNREADABLE_REASON = "app_token_missing_checks_read";
9401
+ DIAGNOSTIC_INTERVAL_MS = 30 * 60 * 1e3;
9402
+ lastDiagnosticAt = 0;
9320
9403
  }
9321
9404
  });
9322
9405
 
@@ -9377,7 +9460,7 @@ function parsePrCiStatus(view) {
9377
9460
  else if (st !== "SUCCESS") pending = true;
9378
9461
  }
9379
9462
  }
9380
- const ci = failedChecks.length > 0 ? "failing" : pending ? "pending" : "passing";
9463
+ const ci = view?.ciUnreadable ? "unknown" : failedChecks.length > 0 ? "failing" : pending ? "pending" : "passing";
9381
9464
  return {
9382
9465
  state,
9383
9466
  ci,
@@ -9576,7 +9659,7 @@ async function runWatchCycleUnlocked({
9576
9659
  try {
9577
9660
  if (typeof reportBlocker === "function") await reportBlocker({
9578
9661
  taskId: entry.taskId,
9579
- message: entry.continuationExhausted ? `PR #${prNumber} remains a partial draft after its bounded automatic continuation; watcher is still monitoring and operator review is required before more spend.` : `PR #${prNumber} remains failing after ${entry.fixAttempts || 0} bounded repair attempt(s); watcher is still monitoring and operator review is required.`
9662
+ message: entry.continuationExhausted ? `PR #${prNumber} remains a partial draft after its bounded automatic continuation${entry.continuationRefusal ? ` (plane refused: ${entry.continuationRefusal})` : ""}; watcher is still monitoring and operator review is required before more spend.` : `PR #${prNumber} remains failing after ${entry.fixAttempts || 0} bounded repair attempt(s); watcher is still monitoring and operator review is required.`
9580
9663
  });
9581
9664
  entry.escalatedAt = now();
9582
9665
  escalated += 1;
@@ -9611,9 +9694,10 @@ function makeWatchRunner({
9611
9694
  }) {
9612
9695
  const tokenForRepo = makeWatcherTokenProvider(client, {
9613
9696
  required: !allowAmbientGithub,
9614
- allowAmbient: allowAmbientGithub
9697
+ allowAmbient: allowAmbientGithub,
9698
+ log: log2
9615
9699
  });
9616
- const watchView = viewPr === ghViewPr ? async (prNumber, repo) => ghViewPr(prNumber, repo, { githubToken: await tokenForRepo(repo) }) : viewPr;
9700
+ const watchView = viewPr === ghViewPr ? async (prNumber, repo) => ghViewPr(prNumber, repo, { githubToken: await tokenForRepo(repo), log: log2 }) : viewPr;
9617
9701
  return async () => {
9618
9702
  const openTasks = typeof client.listPrOpenedTasks === "function" ? await client.listPrOpenedTasks() : [];
9619
9703
  const adopted = await adoptPrOpenedTasks(openTasks, {
@@ -11526,6 +11610,9 @@ function buildPrBody(task, run, files, { armAutoMerge = false } = {}) {
11526
11610
  "",
11527
11611
  redactSecrets(String(run.summary || "")).slice(0, 2e3),
11528
11612
  "",
11613
+ // A budget/turn-capped run ends with no assistant text (summary = the bare
11614
+ // subtype); its LAST message is the honest report the operator needs.
11615
+ ...run.lastAgentMessage ? ["### Last agent message before the cap", "", redactSecrets(String(run.lastAgentMessage)).slice(0, 2e3), ""] : [],
11529
11616
  "---",
11530
11617
  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._"
11531
11618
  ].filter((l) => l !== "").join("\n");
@@ -13101,7 +13188,7 @@ function reportsBlocker(summary) {
13101
13188
  return /\btask remains\s+(?:\*\*)?BLOCKED\b/i.test(text) || /(?:^|\n)\s*(?:#{1,6}\s*)?(?:\*\*)?(?:host-recovery\s+)?(?:result|outcome|status)\s*:\s*(?:\*\*)?BLOCKED\b/im.test(text);
13102
13189
  }
13103
13190
  function isMaxTurnExhaustion(run = {}, maxTurns) {
13104
- const summary = String(run.summary || "").trim().toLowerCase();
13191
+ const summary = String(run.summary || "").trim().toLowerCase().split("\n")[0].trim();
13105
13192
  if (summary === "error_max_turns" || summary === "inconclusive_max_turns") return true;
13106
13193
  return Number.isInteger(maxTurns) && maxTurns > 0 && Number.isInteger(run.numTurns) && run.numTurns > maxTurns;
13107
13194
  }