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

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
  }
@@ -7260,6 +7293,11 @@ var init_dispatch_onboarding = __esm({
7260
7293
  });
7261
7294
 
7262
7295
  // ../../scripts/virtual-office/code-runner/methodology-composer.mjs
7296
+ function openingClause(prompt) {
7297
+ const text = String(prompt || "").replace(UI_ROADMAP_DISPATCH_MARKER, "").trimStart();
7298
+ const firstSentence = text.split(/(?<=[.!?])\s|\n/u)[0] || "";
7299
+ return firstSentence.slice(0, 240);
7300
+ }
7263
7301
  function classifyTaskShape(task) {
7264
7302
  const prompt = String(task?.prompt || "");
7265
7303
  for (const rule of SHAPE_RULES) {
@@ -7318,7 +7356,11 @@ var init_methodology_composer = __esm({
7318
7356
  },
7319
7357
  {
7320
7358
  shape: "research",
7321
- matches: (_task, prompt) => /\b(investigate|research|root[- ]cause|audit|diagnose|find out why|explain why)\b/iu.test(prompt)
7359
+ // Live-test finding 2026-08-16 (task cbed1006): an IMPLEMENT task whose prompt
7360
+ // cites an audit doc ("…exactly as recommended in docs/audits/…; per the audit")
7361
+ // classified as research from a keyword deep in the body. Research is decided by
7362
+ // the OPENING clause — the same rule the design shape already applies.
7363
+ matches: (_task, prompt) => /\b(investigate|research|root[- ]cause|audit|diagnose|find out why|explain why)\b/iu.test(openingClause(prompt))
7322
7364
  },
7323
7365
  {
7324
7366
  shape: "design",
@@ -9051,6 +9093,9 @@ function ciFixOccurrenceKey({ repo, prNumber, headSha, repairAttempt }) {
9051
9093
  function coordinationRetryDue(entry, nowMs) {
9052
9094
  return !entry.nextRetryAt || nowMs >= entry.nextRetryAt;
9053
9095
  }
9096
+ function isTerminalResumeRefusal(error) {
9097
+ return typeof error?.code === "string" && TERMINAL_RESUME_REFUSALS.includes(error.code);
9098
+ }
9054
9099
  async function scheduleCoordinationRetry({
9055
9100
  entry,
9056
9101
  kind,
@@ -9061,6 +9106,14 @@ async function scheduleCoordinationRetry({
9061
9106
  error,
9062
9107
  log: log2
9063
9108
  }) {
9109
+ if (kind === "resume" && isTerminalResumeRefusal(error)) {
9110
+ entry.needsContinuation = false;
9111
+ entry.continuationExhausted = true;
9112
+ entry.continuationRefusal = error.code;
9113
+ entry.continuationRefusedAt = now();
9114
+ log2(`watch: pr #${prNumber} automatic continuation refused by the plane (${error.code}); no retry \u2014 operator may resume explicitly with a larger cap`);
9115
+ return;
9116
+ }
9064
9117
  const errorsKey = kind === "resume" ? "resumeErrors" : "enqueueErrors";
9065
9118
  const attemptsKey = kind === "resume" ? "resumeAttempts" : "fixAttempts";
9066
9119
  entry[errorsKey] = (entry[errorsKey] || 0) + 1;
@@ -9080,12 +9133,23 @@ async function scheduleCoordinationRetry({
9080
9133
  }
9081
9134
  log2(`watch: pr #${prNumber} ${kind} coordination failed ${entry[errorsKey]}x; retry in ${Math.round(delay2 / 1e3)}s: ${boundedErrorMessage(error)}`);
9082
9135
  }
9083
- var MAX_BACKOFF_MS;
9136
+ var MAX_BACKOFF_MS, TERMINAL_RESUME_REFUSALS;
9084
9137
  var init_watcher_coordination = __esm({
9085
9138
  "../../scripts/virtual-office/code-runner/watcher-coordination.mjs"() {
9086
9139
  "use strict";
9087
9140
  init_error_message();
9088
9141
  MAX_BACKOFF_MS = 60 * 60 * 1e3;
9142
+ TERMINAL_RESUME_REFUSALS = Object.freeze([
9143
+ "automatic_continuation_budget_too_small",
9144
+ "automatic_continuation_budget_required",
9145
+ "continuation_spend_ceiling_reached",
9146
+ "continuation_budget_exhausted",
9147
+ "continuation_spend_unmeasured",
9148
+ "continuation_lineage_incomplete",
9149
+ "continuation_lineage_invalid",
9150
+ "cancelled_not_automatically_resumable",
9151
+ "not_resumable"
9152
+ ]);
9089
9153
  }
9090
9154
  });
9091
9155
 
@@ -9201,7 +9265,7 @@ async function adoptPrOpenedTasks(tasks, {
9201
9265
  if (operators.size > 0 && !operators.has(task.operator_id)) continue;
9202
9266
  const key = watcherKey(state, task.repo, task.pr_number);
9203
9267
  if (state[key]) continue;
9204
- const partial = String(task.result || "").startsWith(PARTIAL_PR_CONTINUATION_MARKER);
9268
+ const partial = String(task.result || "").includes(PARTIAL_PR_CONTINUATION_MARKER);
9205
9269
  const repairChain = task.repair_chain ?? {
9206
9270
  root_pr_number: task.repair_pr_number ?? task.pr_number,
9207
9271
  attempt: task.repair_pr_number ? 1 : 0,
@@ -9240,8 +9304,10 @@ var init_watcher_adoption = __esm({
9240
9304
  });
9241
9305
 
9242
9306
  // ../../scripts/virtual-office/code-runner/watcher-github-token.mjs
9243
- function makeWatcherTokenProvider(client, { required = true, allowAmbient = false, now = () => Date.now() } = {}) {
9307
+ function makeWatcherTokenProvider(client, { required = true, allowAmbient = false, now = () => Date.now(), log: log2 = () => {
9308
+ } } = {}) {
9244
9309
  const cache = /* @__PURE__ */ new Map();
9310
+ let ciUnreadableLoggedAt = null;
9245
9311
  return async (repo) => {
9246
9312
  const key = String(repo).toLowerCase();
9247
9313
  const prior = cache.get(key);
@@ -9251,6 +9317,10 @@ function makeWatcherTokenProvider(client, { required = true, allowAmbient = fals
9251
9317
  if (allowAmbient) return null;
9252
9318
  throw new Error(`GitHub App read token unavailable for ${repo}`);
9253
9319
  }
9320
+ if (result.ciReadable === false && (ciUnreadableLoggedAt === null || now() - ciUnreadableLoggedAt > 6 * 60 * 60 * 1e3)) {
9321
+ ciUnreadableLoggedAt = now();
9322
+ 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`);
9323
+ }
9254
9324
  cache.set(key, { token: result.token, at: now() });
9255
9325
  return result.token;
9256
9326
  };
@@ -9298,25 +9368,47 @@ var init_ci_fix_prompt = __esm({
9298
9368
  });
9299
9369
 
9300
9370
  // ../../scripts/virtual-office/code-runner/pr-watcher-github.mjs
9301
- async function ghViewPr(prNumber, repo, { githubToken } = {}) {
9302
- const stdout = await runProcess2("gh", [
9371
+ function isCiUnreadableError(err) {
9372
+ const text = `${err?.message || ""}
9373
+ ${err?.stderr || ""}`;
9374
+ return /Resource not accessible by integration/i.test(text) && /statusCheckRollup/i.test(text);
9375
+ }
9376
+ function noteCiUnreadable(log2) {
9377
+ const now = Date.now();
9378
+ if (now - lastDiagnosticAt < DIAGNOSTIC_INTERVAL_MS) return;
9379
+ lastDiagnosticAt = now;
9380
+ 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.");
9381
+ }
9382
+ async function ghViewPr(prNumber, repo, { githubToken, log: log2 = (m) => console.error(`[vo-runner] ${m}`), run = runProcess2 } = {}) {
9383
+ const env2 = githubToken ? { ...process.env, GH_TOKEN: githubToken } : process.env;
9384
+ const view = async (fields) => JSON.parse(await run("gh", [
9303
9385
  "pr",
9304
9386
  "view",
9305
9387
  String(prNumber),
9306
9388
  "-R",
9307
9389
  repo,
9308
9390
  "--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 || "{}");
9391
+ fields
9392
+ ], { timeout: 3e4, env: env2 }) || "{}");
9393
+ try {
9394
+ return await view(VIEW_FIELDS_WITH_CI);
9395
+ } catch (err) {
9396
+ if (!isCiUnreadableError(err)) throw err;
9397
+ noteCiUnreadable(log2);
9398
+ const withoutCi = await view(VIEW_FIELDS_WITHOUT_CI);
9399
+ return { ...withoutCi, statusCheckRollup: null, ciUnreadable: true, ciUnreadableReason: CI_UNREADABLE_REASON };
9400
+ }
9315
9401
  }
9402
+ var VIEW_FIELDS_WITH_CI, VIEW_FIELDS_WITHOUT_CI, CI_UNREADABLE_REASON, DIAGNOSTIC_INTERVAL_MS, lastDiagnosticAt;
9316
9403
  var init_pr_watcher_github = __esm({
9317
9404
  "../../scripts/virtual-office/code-runner/pr-watcher-github.mjs"() {
9318
9405
  "use strict";
9319
9406
  init_process_runner2();
9407
+ VIEW_FIELDS_WITH_CI = "state,statusCheckRollup,headRefName,headRefOid,url,isDraft,mergeStateStatus";
9408
+ VIEW_FIELDS_WITHOUT_CI = "state,headRefName,headRefOid,url,isDraft,mergeStateStatus";
9409
+ CI_UNREADABLE_REASON = "app_token_missing_checks_read";
9410
+ DIAGNOSTIC_INTERVAL_MS = 30 * 60 * 1e3;
9411
+ lastDiagnosticAt = 0;
9320
9412
  }
9321
9413
  });
9322
9414
 
@@ -9377,7 +9469,7 @@ function parsePrCiStatus(view) {
9377
9469
  else if (st !== "SUCCESS") pending = true;
9378
9470
  }
9379
9471
  }
9380
- const ci = failedChecks.length > 0 ? "failing" : pending ? "pending" : "passing";
9472
+ const ci = view?.ciUnreadable ? "unknown" : failedChecks.length > 0 ? "failing" : pending ? "pending" : "passing";
9381
9473
  return {
9382
9474
  state,
9383
9475
  ci,
@@ -9576,7 +9668,7 @@ async function runWatchCycleUnlocked({
9576
9668
  try {
9577
9669
  if (typeof reportBlocker === "function") await reportBlocker({
9578
9670
  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.`
9671
+ 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
9672
  });
9581
9673
  entry.escalatedAt = now();
9582
9674
  escalated += 1;
@@ -9611,9 +9703,10 @@ function makeWatchRunner({
9611
9703
  }) {
9612
9704
  const tokenForRepo = makeWatcherTokenProvider(client, {
9613
9705
  required: !allowAmbientGithub,
9614
- allowAmbient: allowAmbientGithub
9706
+ allowAmbient: allowAmbientGithub,
9707
+ log: log2
9615
9708
  });
9616
- const watchView = viewPr === ghViewPr ? async (prNumber, repo) => ghViewPr(prNumber, repo, { githubToken: await tokenForRepo(repo) }) : viewPr;
9709
+ const watchView = viewPr === ghViewPr ? async (prNumber, repo) => ghViewPr(prNumber, repo, { githubToken: await tokenForRepo(repo), log: log2 }) : viewPr;
9617
9710
  return async () => {
9618
9711
  const openTasks = typeof client.listPrOpenedTasks === "function" ? await client.listPrOpenedTasks() : [];
9619
9712
  const adopted = await adoptPrOpenedTasks(openTasks, {
@@ -11526,6 +11619,9 @@ function buildPrBody(task, run, files, { armAutoMerge = false } = {}) {
11526
11619
  "",
11527
11620
  redactSecrets(String(run.summary || "")).slice(0, 2e3),
11528
11621
  "",
11622
+ // A budget/turn-capped run ends with no assistant text (summary = the bare
11623
+ // subtype); its LAST message is the honest report the operator needs.
11624
+ ...run.lastAgentMessage ? ["### Last agent message before the cap", "", redactSecrets(String(run.lastAgentMessage)).slice(0, 2e3), ""] : [],
11529
11625
  "---",
11530
11626
  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
11627
  ].filter((l) => l !== "").join("\n");
@@ -12986,6 +13082,7 @@ async function findPreservedRecovery(task, {
12986
13082
  const resumedFrom = /^[0-9a-f-]{36}$/iu.test(String(task.resumed_from || "")) ? String(task.resumed_from).toLowerCase() : null;
12987
13083
  const originalTaskId = recoveryTaskId(task.prompt) ?? resumedFrom;
12988
13084
  if (!originalTaskId) return null;
13085
+ if (task.pr_branch && !recoveryTaskId(task.prompt)) return null;
12989
13086
  for (const ledgerPath of recoveryLedgerCandidates(task.repo, clonesRoot2)) {
12990
13087
  const entries = await readLedger(ledgerPath, readFile5);
12991
13088
  const resolved = entries.some((entry) => RESOLVED_RECOVERY_TYPES.has(entry.type) && entry.taskId === originalTaskId);
@@ -13101,7 +13198,7 @@ function reportsBlocker(summary) {
13101
13198
  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
13199
  }
13103
13200
  function isMaxTurnExhaustion(run = {}, maxTurns) {
13104
- const summary = String(run.summary || "").trim().toLowerCase();
13201
+ const summary = String(run.summary || "").trim().toLowerCase().split("\n")[0].trim();
13105
13202
  if (summary === "error_max_turns" || summary === "inconclusive_max_turns") return true;
13106
13203
  return Number.isInteger(maxTurns) && maxTurns > 0 && Number.isInteger(run.numTurns) && run.numTurns > maxTurns;
13107
13204
  }