@algosuite/vo-mcp 0.2.0-beta.38 → 0.2.0-beta.39

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.
@@ -2566,6 +2566,44 @@ var init_control_plane_merge = __esm({
2566
2566
  }
2567
2567
  });
2568
2568
 
2569
+ // ../../scripts/virtual-office/code-runner/claim-gate-notice.mjs
2570
+ function describeClaimGate(gate) {
2571
+ if (!gate || gate.allowed !== false) return null;
2572
+ const reason = String(gate.reason || "denied");
2573
+ const floor = gate.floor_version ? ` (floor ${gate.floor_version})` : "";
2574
+ return `claim gate: DENIED \u2014 ${reason}${floor}: ${(Object.hasOwn(REASON_HELP, reason) ? REASON_HELP[reason] : null) ?? "the control plane refused this runner's claims"}`;
2575
+ }
2576
+ function makeClaimGateNotice({ log: log2 = () => {
2577
+ } } = {}) {
2578
+ let last = null;
2579
+ let current = null;
2580
+ return {
2581
+ current: () => current,
2582
+ observe(json) {
2583
+ const gate = json && typeof json === "object" ? json.claim_gate : null;
2584
+ const denied = gate && gate.allowed === false ? gate : null;
2585
+ current = denied ? { ...denied, observed_at: (/* @__PURE__ */ new Date()).toISOString() } : null;
2586
+ const signature = denied ? `${denied.reason}|${denied.floor_version ?? ""}` : null;
2587
+ if (signature === last) return;
2588
+ if (denied) log2(describeClaimGate(denied));
2589
+ else if (last !== null) log2("claim gate: allowed again \u2014 this runner may claim work");
2590
+ last = signature;
2591
+ }
2592
+ };
2593
+ }
2594
+ var REASON_HELP;
2595
+ var init_claim_gate_notice = __esm({
2596
+ "../../scripts/virtual-office/code-runner/claim-gate-notice.mjs"() {
2597
+ "use strict";
2598
+ REASON_HELP = {
2599
+ daemon_version_below_floor: "this daemon is older than the approved release target \u2014 it idles until the governed updater brings it current (operator override: VO_RUNNER_CLAIM_MIN_DAEMON_VERSION / VO_RUNNER_CLAIM_VERSION_GATE=off on the control plane)",
2600
+ daemon_version_unreported: "this daemon reports no parseable version in its heartbeat \u2014 too old for the updater to manage, so it may not claim work",
2601
+ no_fresh_heartbeat: "the control plane has no fresh heartbeat from this runner \u2014 claims resume once heartbeats land",
2602
+ runner_denylisted: "this runner id is on the operator quarantine list (VO_RUNNER_CLAIM_DENYLIST)"
2603
+ };
2604
+ }
2605
+ });
2606
+
2569
2607
  // src/runner/control-plane-auth-stub.mjs
2570
2608
  var control_plane_auth_stub_exports = {};
2571
2609
  __export(control_plane_auth_stub_exports, {
@@ -2641,7 +2679,10 @@ function createControlPlaneClient({
2641
2679
  }
2642
2680
  }
2643
2681
  const taskReq = (method, path22, body, options = {}) => req(method, path22, body, { timeoutMs: taskRequestTimeoutMs, ...options });
2682
+ const claimGate = makeClaimGateNotice({ log: (m) => console.warn(`[code-runner ${(/* @__PURE__ */ new Date()).toISOString()}] ${m}`) });
2644
2683
  return {
2684
+ getClaimGate: () => claimGate.current(),
2685
+ // last DENIED claim-gate verdict (null when allowed) — for /status + tests
2645
2686
  ...makeAutonomousDispatchAdmissionClient(
2646
2687
  req,
2647
2688
  taskRequestTimeoutMs,
@@ -2676,6 +2717,7 @@ function createControlPlaneClient({
2676
2717
  }
2677
2718
  if (!res.ok) throw new Error(`claim failed: HTTP ${res.status}`);
2678
2719
  const json = await res.json();
2720
+ claimGate.observe(json);
2679
2721
  return json && json.task ? json.task : null;
2680
2722
  },
2681
2723
  /**
@@ -2946,6 +2988,7 @@ var init_control_plane_client = __esm({
2946
2988
  init_control_plane_resume();
2947
2989
  init_control_plane_autonomous_admission();
2948
2990
  init_control_plane_merge();
2991
+ init_claim_gate_notice();
2949
2992
  cachedFirebaseToken = null;
2950
2993
  ClaimAuthorityChangedError = class extends Error {
2951
2994
  constructor() {
@@ -3445,11 +3488,14 @@ function normalizeClaudePermissionMode(value) {
3445
3488
  }
3446
3489
  return normalized;
3447
3490
  }
3448
- function buildClaudeArgs({ permissionMode = DEFAULT_PERMISSION_MODE, maxTurns, model, effort, maxBudgetUsd, env: env2 = process.env } = {}) {
3491
+ function buildClaudeArgs({ permissionMode = DEFAULT_PERMISSION_MODE, maxTurns, model, effort, maxBudgetUsd, researchHarness = false, env: env2 = process.env } = {}) {
3449
3492
  const effectivePermissionMode = normalizeClaudePermissionMode(permissionMode);
3450
- const research = String(env2?.VO_CODE_RUNNER_NO_WEB ?? "").trim() === "1" ? [] : VO_RESEARCH_TOOLS;
3493
+ const noWeb = String(env2?.VO_CODE_RUNNER_NO_WEB ?? "").trim() === "1";
3494
+ const research = noWeb ? [] : VO_RESEARCH_TOOLS;
3495
+ const noWorkflow = noWeb || String(env2?.VO_CODE_RUNNER_NO_WORKFLOW ?? "").trim() === "1";
3496
+ const workflow = researchHarness === true && !noWorkflow ? VO_WORKFLOW_TOOLS : [];
3451
3497
  const baseTools = effectivePermissionMode === DEFAULT_PERMISSION_MODE ? [VO_SESSION_STATE_TOOL, VO_HEADLESS_PNPM_TOOL, VO_HEADLESS_PNPM_FROM_DIR_TOOL] : [VO_SESSION_STATE_TOOL];
3452
- const allowedTools = [...baseTools, ...research].join(",");
3498
+ const allowedTools = [...baseTools, ...research, ...workflow].join(",");
3453
3499
  const args = [
3454
3500
  "-p",
3455
3501
  "--output-format",
@@ -3475,7 +3521,7 @@ function buildClaudeArgs({ permissionMode = DEFAULT_PERMISSION_MODE, maxTurns, m
3475
3521
  args.push(...context7McpArgs(env2));
3476
3522
  return args;
3477
3523
  }
3478
- var DEFAULT_PERMISSION_MODE, VO_SESSION_STATE_TOOL, VO_HEADLESS_PNPM_TOOL, VO_HEADLESS_PNPM_FROM_DIR_TOOL, VO_RESEARCH_TOOLS, SAFE_PERMISSION_MODES;
3524
+ var DEFAULT_PERMISSION_MODE, VO_SESSION_STATE_TOOL, VO_HEADLESS_PNPM_TOOL, VO_HEADLESS_PNPM_FROM_DIR_TOOL, VO_RESEARCH_TOOLS, VO_WORKFLOW_TOOLS, SAFE_PERMISSION_MODES;
3479
3525
  var init_claude_args = __esm({
3480
3526
  "../../scripts/virtual-office/code-runner/claude-args.mjs"() {
3481
3527
  "use strict";
@@ -3485,6 +3531,7 @@ var init_claude_args = __esm({
3485
3531
  VO_HEADLESS_PNPM_TOOL = "Bash(pnpm *)";
3486
3532
  VO_HEADLESS_PNPM_FROM_DIR_TOOL = "Bash(pnpm --dir *)";
3487
3533
  VO_RESEARCH_TOOLS = ["WebFetch", "WebSearch"];
3534
+ VO_WORKFLOW_TOOLS = ["Workflow"];
3488
3535
  SAFE_PERMISSION_MODES = /* @__PURE__ */ new Set(["acceptEdits", "plan", "default", "dontAsk", "delegate"]);
3489
3536
  }
3490
3537
  });
@@ -3857,14 +3904,15 @@ function runOutcomePatch(run) {
3857
3904
  if (run?.executionStarted === true) patch.execution_started = true;
3858
3905
  return { ...patch, ...tokenUsagePatch(run) };
3859
3906
  }
3860
- var MAX_TOKEN_COUNT, MAX_COST_USD, MAX_TURNS, COST_BASES, MAX_MODELS;
3907
+ var MAX_TOKEN_COUNT, MAX_COST_USD, MAX_TURNS, COST_BASES, NO_AGENT_SPAWNED_ECONOMICS, MAX_MODELS;
3861
3908
  var init_agent_token_usage = __esm({
3862
3909
  "../../scripts/virtual-office/code-runner/agent-token-usage.mjs"() {
3863
3910
  "use strict";
3864
3911
  MAX_TOKEN_COUNT = 1e9;
3865
3912
  MAX_COST_USD = 1e4;
3866
3913
  MAX_TURNS = 1e4;
3867
- COST_BASES = /* @__PURE__ */ new Set(["vendor_billed", "subscription_api_equivalent", "local_zero", "unknown"]);
3914
+ COST_BASES = /* @__PURE__ */ new Set(["vendor_billed", "subscription_api_equivalent", "local_zero", "unknown", "no_agent_spawned"]);
3915
+ NO_AGENT_SPAWNED_ECONOMICS = Object.freeze({ cost_usd: 0, cost_basis: "no_agent_spawned" });
3868
3916
  MAX_MODELS = 20;
3869
3917
  }
3870
3918
  });
@@ -4136,6 +4184,7 @@ function runAgentTask({
4136
4184
  model,
4137
4185
  effort = null,
4138
4186
  maxBudgetUsd = null,
4187
+ researchHarness = false,
4139
4188
  env: env2 = process.env,
4140
4189
  onProgress = () => {
4141
4190
  },
@@ -4153,7 +4202,7 @@ function runAgentTask({
4153
4202
  sandbox = null
4154
4203
  }) {
4155
4204
  return new Promise((resolve2) => {
4156
- const args = runner.buildArgs({ permissionMode, maxTurns, model, effort, maxBudgetUsd, prompt });
4205
+ const args = runner.buildArgs({ permissionMode, maxTurns, model, effort, maxBudgetUsd, researchHarness, prompt });
4157
4206
  const spawnEnv = typeof runner.applyAuthEnv === "function" ? runner.applyAuthEnv(env2) : env2;
4158
4207
  const costBasis = typeof runner.costBasis === "function" ? runner.costBasis(spawnEnv) : "unknown";
4159
4208
  if (costBasis === "vendor_billed" && runner.enforcesBudgetCap !== true && env2.VO_CODE_RUNNER_ALLOW_UNCAPPED_VENDOR_BILLED !== "1") {
@@ -4379,8 +4428,8 @@ var init_claude_runner = __esm({
4379
4428
  get binary() {
4380
4429
  return "claude";
4381
4430
  }
4382
- buildArgs({ permissionMode, maxTurns, model, effort, maxBudgetUsd } = {}) {
4383
- return buildClaudeArgs({ permissionMode, maxTurns, model, effort, maxBudgetUsd });
4431
+ buildArgs({ permissionMode, maxTurns, model, effort, maxBudgetUsd, researchHarness } = {}) {
4432
+ return buildClaudeArgs({ permissionMode, maxTurns, model, effort, maxBudgetUsd, researchHarness });
4384
4433
  }
4385
4434
  parseEvent(line) {
4386
4435
  return parseStreamEvent(line);
@@ -7189,12 +7238,12 @@ function composeMethodologyBlock(task) {
7189
7238
  return { shape, stakes, block: lines.join("\n") };
7190
7239
  }
7191
7240
  function withMethodology(prompt, task) {
7192
- const { shape, block } = composeMethodologyBlock(task);
7193
- return { shape, prompt: `${prompt ?? ""}
7241
+ const { shape, stakes, block } = composeMethodologyBlock(task);
7242
+ return { shape, stakes, prompt: `${prompt ?? ""}
7194
7243
 
7195
7244
  ${block}` };
7196
7245
  }
7197
- var SHAPE_RULES, GOVERNED_STAKES_PATTERN, CONSENSUS_DIRECTIVES, UNIVERSAL_DIRECTIVES, SHAPE_DIRECTIVES;
7246
+ var SHAPE_RULES, GOVERNED_STAKES_PATTERN, RESEARCH_WORKFLOW_DIRECTIVE, CONSENSUS_DIRECTIVES, UNIVERSAL_DIRECTIVES, SHAPE_DIRECTIVES;
7198
7247
  var init_methodology_composer = __esm({
7199
7248
  "../../scripts/virtual-office/code-runner/methodology-composer.mjs"() {
7200
7249
  "use strict";
@@ -7229,15 +7278,16 @@ var init_methodology_composer = __esm({
7229
7278
  }
7230
7279
  ];
7231
7280
  GOVERNED_STAKES_PATTERN = /\b(FERPA|IDOR|HIPAA|PII|privacy|security|authz|authorization|access[- ]control|permission[- ]denied|IRS|tax|§\s?\d|payroll|1099|W-2|MACRS|depreciation|billing|payment|refund|ledger|journal entr|reconcil|compliance|IEP\b|§?504\b|safeguard|governed fact)\b/iu;
7281
+ RESEARCH_WORKFLOW_DIRECTIVE = "For a genuinely open research question (not a single-file or single-log lookup \u2014 those need no harness), use the office research harness rather than ad-hoc browsing: for an open-web question run the workflow at ~/.claude/workflows/storm-deep-research-budget.mjs (Workflow tool, scriptPath, the question as args); for a question about THIS repo run ~/.claude/workflows/deep-research-internal-budget.mjs. Use ONLY the -budget variants (sonnet investigators, one opus synthesis) and stay inside this task budget. If the Workflow tool or those scripts are unavailable on this host, say so in the report and run the same four stages yourself \u2014 perspectives, WebSearch/WebFetch investigation, an adversarial pass that tries to REFUTE each key claim, then a cited write-up \u2014 with at most cheap-tier subagents; never a Fable/Opus fan-out.";
7232
7282
  CONSENSUS_DIRECTIVES = [
7233
- "This task touches governed or high-stakes facts. BEFORE building tests around your central domain claim, run a multi-model consensus check on that claim (vo-mcp: vo_consensus_judgment or vo_verify_answer) and paste the verdict + receipt id into the PR body. A wrong governed fact caught at the claim stage costs one panel call; caught at the PR stage it costs the whole task; caught in production it costs a user.",
7283
+ "This task touches governed or high-stakes facts. BEFORE building tests around your central domain claim, run a multi-model consensus check on that claim (vo-mcp: vo_consensus_judgment or vo_verify_answer) and paste the verdict AND the tool result's receipt_id (a UUID; present when the cloud moat verified) into the PR body as `receipt id: <uuid>` \u2014 never invent one, and if the result has no receipt_id say so. A wrong governed fact caught at the claim stage costs one panel call; caught at the PR stage it costs the whole task; caught in production it costs a user.",
7234
7284
  "If the consensus tools are not available in this session, say exactly that in the PR body instead of silently skipping \u2014 an unverified governed claim must be visible, never implied."
7235
7285
  ];
7236
7286
  UNIVERSAL_DIRECTIVES = [
7237
7287
  "Verification is a stage, not a vibe: before publishing, run the tests/build your change touches and cite their actual output. A claim without execution evidence is not done.",
7238
7288
  "Work to completion or end with an explicit failure reason. Do not stop because time has passed; stop when the evidence says the work is done \u2014 or state exactly what is blocking.",
7239
7289
  "Default to doing the work yourself in this session. Spawn parallel subagents ONLY for pieces that are genuinely independent and independently verifiable \u2014 and verify their results yourself before integrating; never let unreviewed parallel output merge into shared files.",
7240
- 'If you must stop for an operator decision, NEVER post a bare "blocked \u2014 needs your call". Post the decision as 2-4 concrete lettered options, each one line with its tradeoff, name the recommended default, and state what you will safely do (or leave untouched) if no answer arrives. An escalation the operator cannot answer with one word is an unfinished escalation.'
7290
+ 'If you must stop for an operator decision, NEVER post a bare "blocked \u2014 needs your call". Post the decision as 2-4 concrete lettered options, each one line with its tradeoff, name the recommended default, and state what you will safely do (or leave untouched) if no answer arrives. An escalation the operator cannot answer with one word is an unfinished escalation. ALSO emit the same decision as a fenced code block whose info string is vo-decision-request, containing ONE JSON object with keys question, options (2-4 items, each {key A-D, label, tradeoff}), recommended_key and safe_default \u2014 real text in every field (a placeholder like "..." is dropped), so Command Center can render one-click buttons.'
7241
7291
  ];
7242
7292
  SHAPE_DIRECTIVES = {
7243
7293
  "bug-fix": [
@@ -7245,7 +7295,8 @@ var init_methodology_composer = __esm({
7245
7295
  ],
7246
7296
  research: [
7247
7297
  "Every claim needs its source AND exact attribution \u2014 which file:line, which benchmark, which baseline, which version. Verify attribution, not just that a source exists; misattributed real facts are the dominant research failure mode.",
7248
- "Deliver findings as a repo artifact (docs/) with the evidence inline, not only as chat output."
7298
+ "Deliver findings as a repo artifact (docs/) with the evidence inline, not only as chat output.",
7299
+ RESEARCH_WORKFLOW_DIRECTIVE
7249
7300
  ],
7250
7301
  "roadmap-advance": [
7251
7302
  "Update the roadmap doc status, regenerate the roadmap board if the doc changed, and add the roadmap-log fragment IN THIS SAME PR \u2014 a roadmap task that does not move the roadmap did not happen.",
@@ -7313,16 +7364,25 @@ function withAttachmentManifest(prompt, markdown) {
7313
7364
 
7314
7365
  ${manifest}` : prompt;
7315
7366
  }
7316
- function withComposedMethodology(prompt, task, log2, taskId) {
7317
- const { shape, prompt: composed } = withMethodology(prompt, task);
7318
- log2(`task ${taskId || "unknown-task"}: methodology shape=${shape}`);
7367
+ function withComposedMethodology(prompt, task, log2, taskId, onMethodology) {
7368
+ const { shape, stakes, prompt: composed } = withMethodology(prompt, task);
7369
+ log2(`task ${taskId || "unknown-task"}: methodology shape=${shape}${stakes ? ` governed-stakes=${stakes}` : ""}`);
7370
+ try {
7371
+ onMethodology?.({ shape, stakes: stakes ?? null });
7372
+ } catch {
7373
+ }
7319
7374
  return composed;
7320
7375
  }
7376
+ function methodologyLedgerFields(methodology) {
7377
+ const shape = String(methodology?.shape ?? "").trim().slice(0, 40);
7378
+ const stakes = String(methodology?.stakes ?? "").trim().slice(0, 60);
7379
+ return { ...shape ? { methodology_shape: shape } : {}, ...stakes ? { governed_stakes: stakes } : {} };
7380
+ }
7321
7381
  async function composeCodeTaskPrompt(client, task, { log: log2 = () => {
7322
- }, allowMissingKnowledgeContext = false, attachmentManifestMarkdown = "" } = {}) {
7382
+ }, allowMissingKnowledgeContext = false, attachmentManifestMarkdown = "", onMethodology } = {}) {
7323
7383
  const taskId = task?.code_task_id;
7324
7384
  if (!taskId) {
7325
- return composeDispatchPrompt(withComposedMethodology(withAttachmentManifest(task?.prompt, attachmentManifestMarkdown), task, log2, taskId), {
7385
+ return composeDispatchPrompt(withComposedMethodology(withAttachmentManifest(task?.prompt, attachmentManifestMarkdown), task, log2, taskId, onMethodology), {
7326
7386
  repo: task?.repo,
7327
7387
  knowledgeContextMarkdown: handleMissingKnowledgeContext(taskId, "missing code_task_id on the claimed task", {
7328
7388
  allowMissingKnowledgeContext,
@@ -7331,7 +7391,7 @@ async function composeCodeTaskPrompt(client, task, { log: log2 = () => {
7331
7391
  });
7332
7392
  }
7333
7393
  if (typeof client?.getTaskKnowledgeContext !== "function") {
7334
- return composeDispatchPrompt(withComposedMethodology(withAttachmentManifest(task?.prompt, attachmentManifestMarkdown), task, log2, taskId), {
7394
+ return composeDispatchPrompt(withComposedMethodology(withAttachmentManifest(task?.prompt, attachmentManifestMarkdown), task, log2, taskId, onMethodology), {
7335
7395
  repo: task?.repo,
7336
7396
  knowledgeContextMarkdown: handleMissingKnowledgeContext(taskId, "control-plane client cannot fetch knowledge context", {
7337
7397
  allowMissingKnowledgeContext,
@@ -7360,7 +7420,7 @@ async function composeCodeTaskPrompt(client, task, { log: log2 = () => {
7360
7420
  const prompt = operatorInstructions ? `${task?.prompt ?? ""}
7361
7421
 
7362
7422
  ${operatorInstructions}` : task?.prompt;
7363
- return composeDispatchPrompt(withComposedMethodology(withAttachmentManifest(prompt, attachmentManifestMarkdown), task, log2, taskId), {
7423
+ return composeDispatchPrompt(withComposedMethodology(withAttachmentManifest(prompt, attachmentManifestMarkdown), task, log2, taskId, onMethodology), {
7364
7424
  repo: task?.repo,
7365
7425
  knowledgeContextMarkdown
7366
7426
  });
@@ -9885,7 +9945,7 @@ function startControlServer({ port, getStatus, requestStop, allowedOrigin, log:
9885
9945
  return server;
9886
9946
  }
9887
9947
  function startDaemonControl({ cfg, runnerInstanceId, requestStop, getActiveCount, isRunning, startedAt, log: log2 = () => {
9888
- }, onDuplicate = null, getUpdateStatus = () => null }) {
9948
+ }, onDuplicate = null, getUpdateStatus = () => null, getClaimGate = () => null }) {
9889
9949
  if (!cfg.controlEnabled) return null;
9890
9950
  return startControlServer({
9891
9951
  port: cfg.controlPort,
@@ -9904,7 +9964,10 @@ function startDaemonControl({ cfg, runnerInstanceId, requestStop, getActiveCount
9904
9964
  startedAt: new Date(startedAt).toISOString(),
9905
9965
  uptimeSec: Math.round((Date.now() - startedAt) / 1e3),
9906
9966
  // Host version awareness — the app + `runner --status` read drift from here.
9907
- updateStatus: getUpdateStatus()
9967
+ updateStatus: getUpdateStatus(),
9968
+ // Last claim-gate verdict from the control plane (null = allowed / never denied):
9969
+ // a below-floor runner idles on a benign empty queue; this is where the host says why.
9970
+ claimGate: getClaimGate()
9908
9971
  }),
9909
9972
  log: log2
9910
9973
  });
@@ -12248,6 +12311,78 @@ var init_isolation_audit = __esm({
12248
12311
  }
12249
12312
  });
12250
12313
 
12314
+ // ../../scripts/virtual-office/code-runner/terminal-ledger-patch.mjs
12315
+ function clip(value, max) {
12316
+ if (typeof value !== "string") return "";
12317
+ const text = value.trim();
12318
+ if (text.length === 0 || PLACEHOLDER_RE.test(text)) return "";
12319
+ return text.slice(0, max);
12320
+ }
12321
+ function parseDecisionRequest(text) {
12322
+ const source = String(text ?? "");
12323
+ let match = null;
12324
+ for (const candidate of source.matchAll(new RegExp(FENCE_RE.source, "giu"))) match = candidate;
12325
+ if (!match) return null;
12326
+ let raw;
12327
+ try {
12328
+ raw = JSON.parse(match[1]);
12329
+ } catch {
12330
+ return null;
12331
+ }
12332
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
12333
+ const question = clip(raw.question, 1e3);
12334
+ const safeDefault = clip(raw.safe_default ?? raw.safeDefault, 500);
12335
+ const options = Array.isArray(raw.options) ? raw.options : [];
12336
+ const seen = /* @__PURE__ */ new Set();
12337
+ const cleaned = [];
12338
+ for (const option of options) {
12339
+ if (!option || typeof option !== "object") return null;
12340
+ const key = typeof option.key === "string" ? option.key.trim().replace(/[).:\-\s]+$/u, "").toUpperCase() : "";
12341
+ const label = clip(option.label, 200);
12342
+ const tradeoff = clip(option.tradeoff, 300);
12343
+ if (!KEY_RE.test(key) || !label || !tradeoff || seen.has(key)) return null;
12344
+ seen.add(key);
12345
+ cleaned.push({ key, label, tradeoff });
12346
+ }
12347
+ const recommendedRaw = raw.recommended_key ?? raw.recommendedKey ?? raw.recommended;
12348
+ const recommended = typeof recommendedRaw === "string" ? recommendedRaw.trim().replace(/[).:\-\s]+$/u, "").toUpperCase() : "";
12349
+ if (!question || !safeDefault || cleaned.length < 2 || cleaned.length > 4 || !seen.has(recommended)) return null;
12350
+ return { question, options: cleaned, recommended_key: recommended, safe_default: safeDefault };
12351
+ }
12352
+ function consensusReceiptIdFrom(text) {
12353
+ const source = String(text ?? "");
12354
+ for (const match of source.matchAll(RECEIPT_RE)) {
12355
+ const token2 = match[1];
12356
+ if (UUID_RE.test(token2)) return token2.toLowerCase();
12357
+ const end = match.index + match[0].length;
12358
+ const probe = `${token2.replace(/[_-]+/gu, " ")} ${source.slice(end, end + 40)}`;
12359
+ if (RECEIPT_NEGATIVE_RE.test(probe)) continue;
12360
+ return token2.slice(0, 120);
12361
+ }
12362
+ return null;
12363
+ }
12364
+ function terminalLedgerPatch(run, nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
12365
+ const patch = {};
12366
+ const text = run?.summary;
12367
+ const decision = parseDecisionRequest(text);
12368
+ if (decision) patch.decision_request = { ...decision, requested_at: nowIso };
12369
+ const receipt = consensusReceiptIdFrom(text);
12370
+ if (receipt) patch.consensus_receipt_id = receipt;
12371
+ return patch;
12372
+ }
12373
+ var FENCE_RE, KEY_RE, UUID_RE, RECEIPT_RE, RECEIPT_NEGATIVE_RE, PLACEHOLDER_RE;
12374
+ var init_terminal_ledger_patch = __esm({
12375
+ "../../scripts/virtual-office/code-runner/terminal-ledger-patch.mjs"() {
12376
+ "use strict";
12377
+ FENCE_RE = /```vo-decision-request\s*\n([\s\S]*?)```/iu;
12378
+ KEY_RE = /^[A-D]$/u;
12379
+ UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
12380
+ RECEIPT_RE = /(?<![A-Za-z0-9_-])(?:consensus[ _-]?)?receipt(?:[ _-]?id)?[`"'*]*\s*[:=#\-\u2013\u2014]\s*[`"'*]*\s*([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|(?:rcpt|cons)[_-][A-Za-z0-9._:-]{4,115})[`"'*]?/giu;
12381
+ RECEIPT_NEGATIVE_RE = /\b(?:not[_-]?available|unavailable|todo|n\/a|none|pending|missing|skipped)\b/iu;
12382
+ PLACEHOLDER_RE = /^[\s.\u2026"'`\-_]*$/u;
12383
+ }
12384
+ });
12385
+
12251
12386
  // ../../scripts/virtual-office/code-runner/outcome-commit.mjs
12252
12387
  async function beginOutcomeCommit({
12253
12388
  client,
@@ -12557,7 +12692,9 @@ async function finalizePublishedPr({
12557
12692
  pr_number: pr.prNumber,
12558
12693
  pr_branch: pr.branch,
12559
12694
  result: partial ? partialPrContinuationResult(run, 2e3, rateLimitResume ? "rate_limited" : null) : String(run.summary).slice(0, 2e3),
12560
- ...runOutcomePatch(run)
12695
+ ...runOutcomePatch(run),
12696
+ ...terminalLedgerPatch(run)
12697
+ // decision_request / consensus_receipt_id parsed from the agent's final text (2026-08-15)
12561
12698
  }
12562
12699
  });
12563
12700
  if (!posted.accepted) {
@@ -12592,6 +12729,7 @@ var init_publication_outcome = __esm({
12592
12729
  init_publish_async();
12593
12730
  init_pr_watcher();
12594
12731
  init_cancelled_run_report();
12732
+ init_terminal_ledger_patch();
12595
12733
  init_process_runner2();
12596
12734
  init_outcome_commit();
12597
12735
  init_terminal_delivery();
@@ -12820,8 +12958,8 @@ async function recoverPreservedCodeTask({
12820
12958
  const token2 = (await client.getInstallationToken({ required: cfg.requireGithubAppAuth }))?.token ?? null;
12821
12959
  const run = {
12822
12960
  summary: `Recovered preserved work from task ${recovery.originalTaskId}.`,
12823
- costUsd: null,
12824
- costBasis: "unknown",
12961
+ costUsd: 0,
12962
+ costBasis: "no_agent_spawned",
12825
12963
  executionStarted: false
12826
12964
  };
12827
12965
  const supersedesPrNumber = supersededSourcePrNumber(recovery.preserved.prompt || task.prompt);
@@ -13003,7 +13141,9 @@ async function finalizeNoChangesOutcome({
13003
13141
  // Owns FOUR terminal outcomes: no_changes_needed plus three `failed`
13004
13142
  // variants. `failed` is a WASTED_STATUSES member, so usage MUST ride along
13005
13143
  // or the waste bucket stays unexplainable.
13006
- ...runOutcomePatch(run)
13144
+ ...runOutcomePatch(run),
13145
+ ...terminalLedgerPatch(run)
13146
+ // decision_request / consensus_receipt_id parsed from the agent's final text (2026-08-15)
13007
13147
  }
13008
13148
  });
13009
13149
  if (!persisted.accepted) return terminal;
@@ -13038,6 +13178,7 @@ var init_no_changes_terminal_status = __esm({
13038
13178
  init_publish();
13039
13179
  init_superseded_pr_source();
13040
13180
  init_cancelled_run_report();
13181
+ init_terminal_ledger_patch();
13041
13182
  init_outcome_commit();
13042
13183
  init_terminal_delivery();
13043
13184
  RESULT_LIMIT = 2e3;
@@ -13504,6 +13645,7 @@ async function processOneTask(client, task, cfg, runnerInstanceId, swarmAdmissio
13504
13645
  let preserveReason = null;
13505
13646
  let attachmentBundle = null;
13506
13647
  let run = null;
13648
+ let methodology = null;
13507
13649
  let rateLimitResume = null;
13508
13650
  try {
13509
13651
  if (await recoverPreservedCodeTask({ task, cfg, client, log })) return;
@@ -13521,6 +13663,9 @@ async function processOneTask(client, task, cfg, runnerInstanceId, swarmAdmissio
13521
13663
  const attemptTask = { ...task, max_budget_usd: attemptBudgetUsd };
13522
13664
  const { dispatchMode, routerMode, tier, model, permissionMode: effectivePermissionMode, maxTurns: effectiveMaxTurns, effort: effectiveEffort, maxBudgetUsd: effectiveMaxBudgetUsd, prompt: effortPrompt, routerDecision } = await resolveEffortDispatch({ client, task: attemptTask, agent: sel.agent, env: process.env, basePrompt: await composeCodeTaskPrompt(client, task, {
13523
13665
  log,
13666
+ onMethodology: (m) => {
13667
+ methodology = m;
13668
+ },
13524
13669
  allowMissingKnowledgeContext: process.env.VO_CODE_RUNNER_ALLOW_MISSING_KNOWLEDGE_CONTEXT === "1",
13525
13670
  attachmentManifestMarkdown: attachmentBundle.manifestMarkdown
13526
13671
  }) });
@@ -13535,7 +13680,7 @@ async function processOneTask(client, task, cfg, runnerInstanceId, swarmAdmissio
13535
13680
  await safeProgress(client, id, runnerStagePatch(
13536
13681
  "starting_agent",
13537
13682
  `${cfg.runnerId} spawning ${sel.agent}:${model || "default"} (${tier}, effort ${dispatchMode}; ${sel.agent === "claude" ? typeof effectiveMaxBudgetUsd === "number" && effectiveMaxBudgetUsd > 0 ? `$${effectiveMaxBudgetUsd} hard API-equivalent cap` : `no dollar cap, ${effectiveMaxTurns}-turn ceiling` : "20m wall-clock cap"}${routerMode !== "off" && routerDecision ? `; auto-router ${routerMode}: ${routerDecision.rung}${routerDecision.effort ? ` effort=${routerDecision.effort}` : ""}` : ""})`,
13538
- routerDecision ? { router_decision: routerDecision } : {}
13683
+ { ...routerDecision ? { router_decision: routerDecision } : {}, ...methodologyLedgerFields(methodology) }
13539
13684
  ));
13540
13685
  const cap = typeof attemptBudgetUsd === "number" ? attemptBudgetUsd : resolveCodeDispatchCapUsd();
13541
13686
  run = await runAgentTask({
@@ -13548,6 +13693,8 @@ async function processOneTask(client, task, cfg, runnerInstanceId, swarmAdmissio
13548
13693
  model,
13549
13694
  effort: effectiveEffort,
13550
13695
  maxBudgetUsd: sel.agent === "claude" ? effectiveMaxBudgetUsd : void 0,
13696
+ researchHarness: methodology?.shape === "research",
13697
+ // Workflow grant only for research-shaped tasks
13551
13698
  env: buildAgentProcessEnv(process.env, { agent: sel.agent, runnerId: cfg.runnerId, taskId: id, githubReadToken: agentGithubReadToken, swarmAdmission }),
13552
13699
  // swarmAdmission mints VO_SWARM_TIER_BINDING: ONE tier decision for this task's whole agent tree
13553
13700
  sandbox,
@@ -13701,7 +13848,8 @@ Closes #${publicationTarget.supersedesPrNumber}` : "";
13701
13848
  status: "failed",
13702
13849
  message: `runner error: ${msg}`.slice(0, 1500),
13703
13850
  result: msg.slice(0, 2e3),
13704
- ...runOutcomePatch(run)
13851
+ ...run ? { ...runOutcomePatch(run), ...terminalLedgerPatch(run) } : NO_AGENT_SPAWNED_ECONOMICS
13852
+ // no agent ever spawned ⇒ structural $0, never an unmeasured null; a run that did emit a decision block keeps it
13705
13853
  } });
13706
13854
  } finally {
13707
13855
  try {
@@ -13746,6 +13894,8 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
13746
13894
  isRunning: () => !stopping,
13747
13895
  startedAt,
13748
13896
  log,
13897
+ getClaimGate: () => client.getClaimGate?.() ?? null,
13898
+ // claim-gate verdict on /status (deny-site visibility)
13749
13899
  // Single-instance guard: the control-port bind detects an already-serving
13750
13900
  // daemon on this machine (see decideAddrInUseAction). Exit 0 so launcher
13751
13901
  // respawn loops treat it as a clean stop, not a crash to retry hard.
@@ -13899,6 +14049,7 @@ var init_code_runner_daemon = __esm({
13899
14049
  init_recovery_ledger();
13900
14050
  init_no_changes_terminal_status();
13901
14051
  init_cancelled_run_report();
14052
+ init_terminal_ledger_patch();
13902
14053
  init_publication_outcome();
13903
14054
  init_outcome_commit();
13904
14055
  init_cancellation_probe();