@algosuite/vo-mcp 0.2.0-beta.30 → 0.2.0-beta.34

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.
@@ -2582,6 +2582,11 @@ var init_control_plane_auth_stub = __esm({
2582
2582
  });
2583
2583
 
2584
2584
  // ../../scripts/virtual-office/code-runner/control-plane-client.mjs
2585
+ var control_plane_client_exports = {};
2586
+ __export(control_plane_client_exports, {
2587
+ ClaimAuthorityChangedError: () => ClaimAuthorityChangedError,
2588
+ createControlPlaneClient: () => createControlPlaneClient
2589
+ });
2585
2590
  async function resolveBearer(env2) {
2586
2591
  const adminToken = env2.VO_CONTROL_PLANE_ADMIN_TOKEN;
2587
2592
  if (adminToken) return adminToken;
@@ -2614,11 +2619,11 @@ function createControlPlaneClient({
2614
2619
  const resolvedBaseUrl = baseUrl ?? env2.VO_CONTROL_PLANE_URL ?? "";
2615
2620
  if (!resolvedBaseUrl) throw new Error("VO_CONTROL_PLANE_URL is required for the code-runner daemon");
2616
2621
  const root = resolvedBaseUrl.replace(/\/+$/, "");
2617
- async function req(method, path20, body, { timeoutMs } = {}) {
2622
+ async function req(method, path23, body, { timeoutMs } = {}) {
2618
2623
  const bearer = await resolveBearer(env2);
2619
2624
  const controller = timeoutMs ? new AbortController() : null;
2620
2625
  let timeoutId;
2621
- const request = Promise.resolve(fetchImpl(`${root}${path20}`, {
2626
+ const request = Promise.resolve(fetchImpl(`${root}${path23}`, {
2622
2627
  method,
2623
2628
  headers: {
2624
2629
  "content-type": "application/json",
@@ -2631,7 +2636,7 @@ function createControlPlaneClient({
2631
2636
  const timeout = new Promise((_, reject) => {
2632
2637
  timeoutId = setTimeout(() => {
2633
2638
  controller.abort();
2634
- reject(new Error(`control-plane ${path20} timed out after ${timeoutMs}ms`));
2639
+ reject(new Error(`control-plane ${path23} timed out after ${timeoutMs}ms`));
2635
2640
  }, timeoutMs);
2636
2641
  });
2637
2642
  try {
@@ -2640,7 +2645,7 @@ function createControlPlaneClient({
2640
2645
  clearTimeout(timeoutId);
2641
2646
  }
2642
2647
  }
2643
- const taskReq = (method, path20, body, options = {}) => req(method, path20, body, { timeoutMs: taskRequestTimeoutMs, ...options });
2648
+ const taskReq = (method, path23, body, options = {}) => req(method, path23, body, { timeoutMs: taskRequestTimeoutMs, ...options });
2644
2649
  return {
2645
2650
  ...makeAutonomousDispatchAdmissionClient(
2646
2651
  req,
@@ -2763,8 +2768,8 @@ function createControlPlaneClient({
2763
2768
  return listAllPrOpenedTasks(taskReq);
2764
2769
  },
2765
2770
  async downloadTaskAttachment(taskId, attachmentId) {
2766
- const path20 = `/api/v1/code-task/${encodeURIComponent(taskId)}/attachment/${encodeURIComponent(attachmentId)}`;
2767
- const res = await taskReq("GET", path20);
2771
+ const path23 = `/api/v1/code-task/${encodeURIComponent(taskId)}/attachment/${encodeURIComponent(attachmentId)}`;
2772
+ const res = await taskReq("GET", path23);
2768
2773
  if (res.status === 401) cachedFirebaseToken = null;
2769
2774
  if (!res.ok) throw new Error(`attachment download failed: HTTP ${res.status}`);
2770
2775
  return Buffer.from(await res.arrayBuffer());
@@ -4870,6 +4875,188 @@ var init_cursor_runner = __esm({
4870
4875
  }
4871
4876
  });
4872
4877
 
4878
+ // ../../scripts/virtual-office/code-runner/ollama-agent-tools.mjs
4879
+ var MAX_READ_BYTES, MAX_WRITE_BYTES, TOOL_DEFS, TOOL_NAMES, READ_ONLY_TOOL_NAMES, READ_ONLY_TOOL_DEFS;
4880
+ var init_ollama_agent_tools = __esm({
4881
+ "../../scripts/virtual-office/code-runner/ollama-agent-tools.mjs"() {
4882
+ "use strict";
4883
+ MAX_READ_BYTES = 64 * 1024;
4884
+ MAX_WRITE_BYTES = 512 * 1024;
4885
+ TOOL_DEFS = [
4886
+ {
4887
+ type: "function",
4888
+ function: {
4889
+ name: "read_file",
4890
+ description: "Read a UTF-8 text file inside the working directory. Returns up to 64 KiB.",
4891
+ parameters: {
4892
+ type: "object",
4893
+ properties: {
4894
+ path: { type: "string", description: "File path relative to the working directory." }
4895
+ },
4896
+ required: ["path"]
4897
+ }
4898
+ }
4899
+ },
4900
+ {
4901
+ type: "function",
4902
+ function: {
4903
+ name: "list_files",
4904
+ description: "List entries in a directory inside the working directory (files and subdirs).",
4905
+ parameters: {
4906
+ type: "object",
4907
+ properties: {
4908
+ path: { type: "string", description: 'Directory path relative to the working directory. Default ".".' }
4909
+ }
4910
+ }
4911
+ }
4912
+ },
4913
+ {
4914
+ type: "function",
4915
+ function: {
4916
+ name: "write_file",
4917
+ description: "Create or overwrite a UTF-8 text file inside the working directory. Parent dirs are created.",
4918
+ parameters: {
4919
+ type: "object",
4920
+ properties: {
4921
+ path: { type: "string", description: "File path relative to the working directory." },
4922
+ content: { type: "string", description: "Full new file contents." }
4923
+ },
4924
+ required: ["path", "content"]
4925
+ }
4926
+ }
4927
+ }
4928
+ ];
4929
+ TOOL_NAMES = TOOL_DEFS.map((t) => t.function.name);
4930
+ READ_ONLY_TOOL_NAMES = Object.freeze(["read_file", "list_files"]);
4931
+ READ_ONLY_TOOL_DEFS = Object.freeze(
4932
+ TOOL_DEFS.filter((tool) => READ_ONLY_TOOL_NAMES.includes(tool.function.name))
4933
+ );
4934
+ }
4935
+ });
4936
+
4937
+ // ../../scripts/virtual-office/code-runner/ollama-agent-core.mjs
4938
+ function parseOllamaAgentEvent(line) {
4939
+ const trimmed = String(line || "").trim();
4940
+ if (!trimmed) return null;
4941
+ let evt;
4942
+ try {
4943
+ evt = JSON.parse(trimmed);
4944
+ } catch {
4945
+ return null;
4946
+ }
4947
+ if (!evt || typeof evt !== "object") return null;
4948
+ if (evt.type === "progress") {
4949
+ const text = String(evt.text || "").trim();
4950
+ return text ? { kind: "progress", text } : null;
4951
+ }
4952
+ if (evt.type === "tool") {
4953
+ const via = evt.recovered ? " (recovered from text)" : "";
4954
+ const label = `${evt.ok === false ? "tool failed" : "tool"}: ${evt.name}${evt.path ? ` ${evt.path}` : ""}${via}`;
4955
+ return { kind: "progress", text: label };
4956
+ }
4957
+ if (evt.type === "result") {
4958
+ const usage = evt.usage || null;
4959
+ const tokenUsage = usage ? { inputTokens: usage.inputTokens ?? null, outputTokens: usage.outputTokens ?? null, totalTokens: usage.totalTokens ?? null } : void 0;
4960
+ return {
4961
+ kind: "result",
4962
+ isError: Boolean(evt.isError),
4963
+ costUsd: 0,
4964
+ // sovereign local inference is free — no meter.
4965
+ summary: String(evt.summary || (evt.isError ? "local run failed" : "completed")),
4966
+ numTurns: Number.isInteger(evt.numTurns) ? evt.numTurns : null,
4967
+ ...tokenUsage ? { tokenUsage } : {},
4968
+ // Pass the sovereign receipt through to the daemon. Omitted entirely when
4969
+ // absent so the event shape is unchanged for every other transport.
4970
+ ...evt.receipt ? { receipt: evt.receipt } : {}
4971
+ };
4972
+ }
4973
+ return null;
4974
+ }
4975
+ var MAX_TURNS_DEFAULT, NUM_CTX_DEFAULT;
4976
+ var init_ollama_agent_core = __esm({
4977
+ "../../scripts/virtual-office/code-runner/ollama-agent-core.mjs"() {
4978
+ "use strict";
4979
+ init_ollama_agent_tools();
4980
+ init_ollama_agent_tools();
4981
+ MAX_TURNS_DEFAULT = 20;
4982
+ NUM_CTX_DEFAULT = 16384;
4983
+ }
4984
+ });
4985
+
4986
+ // ../../scripts/virtual-office/code-runner/ollama-native-transport.mjs
4987
+ import { fileURLToPath } from "node:url";
4988
+ import { dirname as dirname2, join as join2 } from "node:path";
4989
+ function resolveLocalNativeProfile(env2 = process.env) {
4990
+ const profile = String(env2.VO_CODE_RUNNER_LOCAL_PROFILE || "").trim().toLowerCase() || DEFAULT_LOCAL_NATIVE_PROFILE;
4991
+ if (!LOCAL_NATIVE_PROFILES.includes(profile)) {
4992
+ throw new Error(`local-model runner (native): unknown profile "${profile}" (coding|verification).`);
4993
+ }
4994
+ return profile;
4995
+ }
4996
+ function resolveLocalTransport(env2 = process.env) {
4997
+ return String(env2.VO_CODE_RUNNER_LOCAL_TRANSPORT || "").trim().toLowerCase() === "native" ? "native" : DEFAULT_LOCAL_TRANSPORT;
4998
+ }
4999
+ function ollamaAgentScriptPath() {
5000
+ return join2(dirname2(fileURLToPath(import.meta.url)), "ollama-agent.mjs");
5001
+ }
5002
+ function posIntOr(raw, fallback) {
5003
+ const n = Number(String(raw ?? "").trim());
5004
+ return Number.isInteger(n) && n > 0 ? n : fallback;
5005
+ }
5006
+ function buildOllamaAgentArgs({ model, numCtx, maxTurns, profile = DEFAULT_LOCAL_NATIVE_PROFILE } = {}) {
5007
+ return [
5008
+ ollamaAgentScriptPath(),
5009
+ "--model",
5010
+ String(model),
5011
+ "--profile",
5012
+ String(profile),
5013
+ "--num-ctx",
5014
+ String(posIntOr(numCtx, NUM_CTX_DEFAULT)),
5015
+ "--max-turns",
5016
+ String(posIntOr(maxTurns, MAX_TURNS_DEFAULT))
5017
+ ];
5018
+ }
5019
+ function buildLocalNativeArgs(opts = {}, env2 = process.env) {
5020
+ const provider = resolveLocalProvider(env2);
5021
+ if (provider !== "ollama") {
5022
+ throw new Error(
5023
+ `local-model runner (native): the native tool-loop executor speaks Ollama's /api/chat; provider "${provider}" is not supported on native transport. Set VO_CODE_RUNNER_LOCAL_PROVIDER=ollama, or use VO_CODE_RUNNER_LOCAL_TRANSPORT=codex for LM Studio.`
5024
+ );
5025
+ }
5026
+ const model = resolveLocalModel(env2);
5027
+ if (!model) {
5028
+ throw new Error(
5029
+ "local-model runner (native): set VO_CODE_RUNNER_LOCAL_MODEL to a coding model your Ollama server already has (e.g. qwen2.5-coder:7b). Refusing to run with no explicit model (fail-closed)."
5030
+ );
5031
+ }
5032
+ if (!isValidLocalModel(model)) {
5033
+ throw new Error(`local-model runner (native): "${model}" is not a valid local model id.`);
5034
+ }
5035
+ const baseUrl = resolveLocalBaseUrl(env2);
5036
+ if (baseUrl && !isLoopbackBaseUrl(baseUrl)) {
5037
+ throw new Error(
5038
+ "local-model runner (native): VO_CODE_RUNNER_LOCAL_BASE_URL must be a loopback http(s) URL (localhost / 127.0.0.1 / [::1]). Remote endpoints are refused \u2014 use the Model Firewall lanes."
5039
+ );
5040
+ }
5041
+ return buildOllamaAgentArgs({
5042
+ model,
5043
+ profile: resolveLocalNativeProfile(env2),
5044
+ numCtx: env2.VO_CODE_RUNNER_LOCAL_NUM_CTX,
5045
+ maxTurns: env2.VO_CODE_RUNNER_LOCAL_MAX_TURNS
5046
+ });
5047
+ }
5048
+ var DEFAULT_LOCAL_TRANSPORT, LOCAL_NATIVE_PROFILES, DEFAULT_LOCAL_NATIVE_PROFILE;
5049
+ var init_ollama_native_transport = __esm({
5050
+ "../../scripts/virtual-office/code-runner/ollama-native-transport.mjs"() {
5051
+ "use strict";
5052
+ init_ollama_agent_core();
5053
+ init_local_model_runner();
5054
+ DEFAULT_LOCAL_TRANSPORT = "codex";
5055
+ LOCAL_NATIVE_PROFILES = Object.freeze(["coding", "verification"]);
5056
+ DEFAULT_LOCAL_NATIVE_PROFILE = "coding";
5057
+ }
5058
+ });
5059
+
4873
5060
  // ../../scripts/virtual-office/code-runner/local-model-runner.mjs
4874
5061
  function resolveLocalProvider(env2 = process.env) {
4875
5062
  return String(env2.VO_CODE_RUNNER_LOCAL_PROVIDER || "").trim().toLowerCase() || DEFAULT_LOCAL_PROVIDER;
@@ -4943,20 +5130,37 @@ function buildLocalArgs(opts = {}, env2 = process.env) {
4943
5130
  }
4944
5131
  function applyLocalAuthEnv(baseEnv = process.env, configEnv = process.env) {
4945
5132
  const out = withAgentKey("local", baseEnv);
5133
+ for (const key of Object.keys(out)) {
5134
+ if (FORBIDDEN_LOCAL_CHILD_CREDENTIALS.has(key.toUpperCase())) delete out[key];
5135
+ }
4946
5136
  const baseUrl = resolveLocalBaseUrl(configEnv);
4947
5137
  if (baseUrl && isLoopbackBaseUrl(baseUrl) && resolveLocalProvider(configEnv) === "ollama" && !String(out.OLLAMA_HOST || "").trim()) {
4948
5138
  out.OLLAMA_HOST = baseUrl.replace(/\/+$/, "");
4949
5139
  }
4950
5140
  return out;
4951
5141
  }
4952
- var LOCAL_API_KEY_ENV, LOCAL_PROVIDERS, DEFAULT_LOCAL_PROVIDER, LOCAL_PROBE_URLS, remoteDesiredLocalModel, LOCAL_MODEL_RE, LocalModelRunner, localModelRunner;
5142
+ var LOCAL_API_KEY_ENV, FORBIDDEN_LOCAL_CHILD_CREDENTIALS, LOCAL_PROVIDERS, DEFAULT_LOCAL_PROVIDER, LOCAL_PROBE_URLS, remoteDesiredLocalModel, LOCAL_MODEL_RE, LocalModelRunner, localModelRunner;
4953
5143
  var init_local_model_runner = __esm({
4954
5144
  "../../scripts/virtual-office/code-runner/local-model-runner.mjs"() {
4955
5145
  "use strict";
4956
5146
  init_codex_runner();
4957
5147
  init_agent_key_store();
4958
5148
  init_agent_auth_tier();
5149
+ init_ollama_agent_core();
5150
+ init_ollama_native_transport();
4959
5151
  LOCAL_API_KEY_ENV = "VO_CODE_RUNNER_LOCAL_API_KEY";
5152
+ FORBIDDEN_LOCAL_CHILD_CREDENTIALS = /* @__PURE__ */ new Set([
5153
+ "ANTHROPIC_API_KEY",
5154
+ "AWS_ACCESS_KEY_ID",
5155
+ "AWS_SECRET_ACCESS_KEY",
5156
+ "AWS_SESSION_TOKEN",
5157
+ "FIREBASE_TOKEN",
5158
+ "GH_TOKEN",
5159
+ "GITHUB_TOKEN",
5160
+ "GOOGLE_API_KEY",
5161
+ "GOOGLE_APPLICATION_CREDENTIALS",
5162
+ "OPENAI_API_KEY"
5163
+ ]);
4960
5164
  LOCAL_PROVIDERS = ["ollama", "lmstudio"];
4961
5165
  DEFAULT_LOCAL_PROVIDER = "ollama";
4962
5166
  LOCAL_PROBE_URLS = {
@@ -4977,19 +5181,25 @@ var init_local_model_runner = __esm({
4977
5181
  this.env = env2;
4978
5182
  this.fetchImpl = fetchImpl;
4979
5183
  }
4980
- /** Codex is the transport binary; the model/endpoint are the user's. */
5184
+ /**
5185
+ * Transport binary. codex transport → the codex CLI. native transport →
5186
+ * this daemon's own node (process.execPath), which runs ollama-agent.mjs; no
5187
+ * external CLI is involved on the native path.
5188
+ */
4981
5189
  get binary() {
4982
- return this.resolveBinary();
5190
+ return resolveLocalTransport(this.env) === "native" ? process.execPath : this.resolveBinary();
4983
5191
  }
4984
5192
  buildArgs(opts = {}) {
4985
- return buildLocalArgs(opts, this.env);
5193
+ return resolveLocalTransport(this.env) === "native" ? buildLocalNativeArgs(opts, this.env) : buildLocalArgs(opts, this.env);
4986
5194
  }
4987
5195
  /**
4988
- * Codex JSONL events map identically, except a sovereign local model has no
4989
- * vendor bill. Codex omits total_cost_usd for OSS runs; turn that known fact
4990
- * into a measured zero at the producer so readers never have to guess.
5196
+ * native transport parse the executor's own JSONL contract. codex transport
5197
+ * codex JSONL maps identically, except a sovereign local model has no vendor
5198
+ * bill: codex omits total_cost_usd for OSS runs, so turn that known fact into a
5199
+ * measured zero at the producer. (The native parser already stamps costUsd:0.)
4991
5200
  */
4992
5201
  parseEvent(line) {
5202
+ if (resolveLocalTransport(this.env) === "native") return parseOllamaAgentEvent(line);
4993
5203
  const event = parseCodexEvent(line);
4994
5204
  return event?.kind === "result" ? { ...event, costUsd: 0 } : event;
4995
5205
  }
@@ -5488,9 +5698,9 @@ var init_rate_limit_resume_state = __esm({
5488
5698
 
5489
5699
  // ../../scripts/virtual-office/code-runner/rate-limit-resume.mjs
5490
5700
  import { homedir as homedir3 } from "node:os";
5491
- import { join as join2 } from "node:path";
5701
+ import { join as join3 } from "node:path";
5492
5702
  function resumeQueuePath() {
5493
- return join2(homedir3(), ".claude", "resume-queue.jsonl");
5703
+ return join3(homedir3(), ".claude", "resume-queue.jsonl");
5494
5704
  }
5495
5705
  function buildResumeEntry({ task = {}, resumeAfter = null, summary = "", at } = {}) {
5496
5706
  return {
@@ -5692,7 +5902,7 @@ var init_auto_merge = __esm({
5692
5902
  // ../../scripts/virtual-office/code-runner/pr-overlap-gate.mjs
5693
5903
  import { spawnSync as spawnSync8 } from "node:child_process";
5694
5904
  import { existsSync as existsSync6 } from "node:fs";
5695
- import { fileURLToPath } from "node:url";
5905
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
5696
5906
  function stripCredentials(env2 = process.env) {
5697
5907
  const safe = { ...env2 };
5698
5908
  for (const key of CREDENTIAL_ENV_KEYS) delete safe[key];
@@ -5718,7 +5928,7 @@ var init_pr_overlap_gate = __esm({
5718
5928
  TRUSTED_OVERLAP_CANDIDATES = [
5719
5929
  new URL("../../ci/check-local-pr-overlap.mjs", import.meta.url),
5720
5930
  new URL("./ci/check-local-pr-overlap.js", import.meta.url)
5721
- ].map((candidate) => fileURLToPath(candidate));
5931
+ ].map((candidate) => fileURLToPath2(candidate));
5722
5932
  CREDENTIAL_ENV_KEYS = Object.freeze([
5723
5933
  "GH_TOKEN",
5724
5934
  "GITHUB_TOKEN",
@@ -5771,14 +5981,14 @@ function parsePorcelainZ(out) {
5771
5981
  for (let i = 0; i < tokens.length; i += 1) {
5772
5982
  const token2 = tokens[i];
5773
5983
  if (!token2) continue;
5774
- const path20 = token2.slice(3);
5775
- if (path20) files.push(path20);
5984
+ const path23 = token2.slice(3);
5985
+ if (path23) files.push(path23);
5776
5986
  if (token2[0] === "R" || token2[0] === "C") i += 1;
5777
5987
  }
5778
5988
  return files;
5779
5989
  }
5780
- function isAgentScratch(path20) {
5781
- const normalized = String(path20 || "");
5990
+ function isAgentScratch(path23) {
5991
+ const normalized = String(path23 || "");
5782
5992
  return SCRATCH_PATTERNS.some((pattern) => pattern.test(normalized));
5783
5993
  }
5784
5994
  var SCRATCH_PATTERNS;
@@ -5845,6 +6055,540 @@ var init_publish = __esm({
5845
6055
  }
5846
6056
  });
5847
6057
 
6058
+ // ../../scripts/virtual-office/test-gen/auto-tier.mjs
6059
+ function lower(s) {
6060
+ return typeof s === "string" ? s.toLowerCase() : "";
6061
+ }
6062
+ function isAlgoProduct(product) {
6063
+ return /^algo/i.test(String(product || ""));
6064
+ }
6065
+ function isAdminRoute(route) {
6066
+ return /(^|\/)admin(\/|$)/i.test(String(route || ""));
6067
+ }
6068
+ function importsMatch(imports, re) {
6069
+ return Array.isArray(imports) && imports.some((i) => re.test(String(i || "")));
6070
+ }
6071
+ function classifyTier(contract = {}) {
6072
+ const product = lower(contract.product);
6073
+ const callable = String(contract.callable || "");
6074
+ const route = String(contract.route || "");
6075
+ const imports = contract.imports;
6076
+ const ov = contract.tier_override;
6077
+ if (ov === 1 || ov === 2 || ov === 3 || ov === 4) {
6078
+ return { tier: ov, rationale: `operator override \u2192 Tier ${ov}`, tier_override: true };
6079
+ }
6080
+ const algo = isAlgoProduct(product);
6081
+ let tier;
6082
+ let why;
6083
+ const legalDomain = LEGAL_PRODUCTS.has(product);
6084
+ const hasGovernedSource = Boolean(contract.authoritative_source_url) || importsMatch(imports, LEGAL_IMPORT_RE);
6085
+ if (legalDomain && hasGovernedSource) {
6086
+ tier = 4;
6087
+ why = `legal-correctness domain (${product}) with a governed source \u2192 source-grounded Tier 4`;
6088
+ } else if (isAdminRoute(route) || MONEY_OR_SAFETY_RE.test(callable)) {
6089
+ tier = 3;
6090
+ why = isAdminRoute(route) ? "admin/safety-critical route \u2192 real-time monitor Tier 3" : `money-moving or grading callable (${callable}) \u2192 real-time monitor Tier 3`;
6091
+ } else if (importsMatch(imports, GENERATIVE_RE) || GENERATIVE_CALLABLE_RE.test(callable)) {
6092
+ const long = Number(contract.workflow_seconds) > 30;
6093
+ tier = long ? 3 : 2;
6094
+ why = `generative/AI feature \u2192 E2E with verified results${long ? " + monitor (long workflow) Tier 3" : " Tier 2"}`;
6095
+ } else if (callable || route) {
6096
+ tier = 2;
6097
+ why = "standard business logic \u2192 E2E with verified results (read-back) Tier 2";
6098
+ } else {
6099
+ tier = 1;
6100
+ why = "pure navigation/surface, no backend effect \u2192 surface smoke Tier 1";
6101
+ }
6102
+ if (algo && tier < 2) {
6103
+ return {
6104
+ tier: 2,
6105
+ rationale: `${why}; raised to Tier 2 (E2E floor: Algo* apps never auto-pick surface-smoke-only)`,
6106
+ tier_override: false
6107
+ };
6108
+ }
6109
+ return { tier, rationale: why, tier_override: false };
6110
+ }
6111
+ function tierLabel(tier) {
6112
+ return {
6113
+ 1: "Tier 1 \u2014 surface & navigation smoke",
6114
+ 2: "Tier 2 \u2014 E2E with verified results",
6115
+ 3: "Tier 3 \u2014 real-time monitor / sentinel",
6116
+ 4: "Tier 4 \u2014 source-grounded governed-fact verification"
6117
+ }[tier] || `Tier ${tier}`;
6118
+ }
6119
+ var LEGAL_PRODUCTS, LEGAL_IMPORT_RE, GENERATIVE_RE, GENERATIVE_CALLABLE_RE, MONEY_OR_SAFETY_RE;
6120
+ var init_auto_tier = __esm({
6121
+ "../../scripts/virtual-office/test-gen/auto-tier.mjs"() {
6122
+ "use strict";
6123
+ LEGAL_PRODUCTS = /* @__PURE__ */ new Set(["algotax", "algolaw", "algoteach", "algolegal"]);
6124
+ LEGAL_IMPORT_RE = /functions-core-(tax|law)|cornell|irs|\bstatute\b/i;
6125
+ GENERATIVE_RE = /@google\/generative-ai|@anthropic|anthropic|openai|@google\/genai|consensus|\bllm\b/i;
6126
+ GENERATIVE_CALLABLE_RE = /generate|summari[sz]e|\bai\b|consensus|draft|classify/i;
6127
+ MONEY_OR_SAFETY_RE = /grade|payment|transfer|trade|charge|refund|payout|disburse|withdraw|remit/i;
6128
+ }
6129
+ });
6130
+
6131
+ // ../../scripts/virtual-office/test-gen/dispatch.mjs
6132
+ import { spawnSync as spawnSync10 } from "node:child_process";
6133
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
6134
+ import path14 from "node:path";
6135
+ function buildGenerationPrompt({ contract = {}, tier }) {
6136
+ const label = tierLabel(tier);
6137
+ const target = contract.callable ? `the \`${contract.callable}\` callable` : contract.route ? `the \`${contract.route}\` route` : contract.feature_name || "the feature";
6138
+ return [
6139
+ `Write a ${label} test for ${target} in product "${contract.product || "unknown"}".`,
6140
+ contract.source_file ? `Source: ${contract.source_file}.` : "",
6141
+ contract.expected_behavior ? `Expected behavior: ${contract.expected_behavior}.` : "",
6142
+ "",
6143
+ `TIER REQUIREMENT \u2014 ${TIER_GUIDANCE[tier] || TIER_GUIDANCE[2]}`,
6144
+ "",
6145
+ "Follow the AlgoSuite test-honesty standard: NO fake-green (no bare toBeTruthy, no broad",
6146
+ "try/catch that swallows failures, no treating INVALID_ARGUMENT / empty / null / SKIP as a",
6147
+ "pass). Use a real fixture (smoke@algosuite.ai) and real data shapes.",
6148
+ "",
6149
+ "After writing the test, it will be GATED server-side: deterministic ratchets, then",
6150
+ "multi-model consensus that it proves the behavior with VERIFIED-CORRECT expected values.",
6151
+ "A test that does not pass BOTH gates will NOT ship \u2014 so make the assertions real and the",
6152
+ "expected values known-correct. Leave the test file UNCOMMITTED; the runner opens the PR."
6153
+ ].filter((l) => l !== "").join("\n");
6154
+ }
6155
+ function planTestGenDispatch({ contract = {} }) {
6156
+ const { tier, rationale } = classifyTier(contract);
6157
+ return {
6158
+ contract,
6159
+ tier,
6160
+ tier_label: tierLabel(tier),
6161
+ tier_rationale: rationale,
6162
+ generation_prompt: buildGenerationPrompt({ contract, tier })
6163
+ };
6164
+ }
6165
+ function buildTestGenTaskPrompt(dispatch = {}) {
6166
+ const contractJson = JSON.stringify(dispatch.contract ?? {});
6167
+ return `${TEST_GEN_MARKER} ${contractJson}
6168
+
6169
+ ${dispatch.generation_prompt ?? ""}`;
6170
+ }
6171
+ function pickNextTarget({ gaps = [] }) {
6172
+ if (!Array.isArray(gaps) || gaps.length === 0) return null;
6173
+ const ranked = [...gaps].sort((a, b) => {
6174
+ const al = LEGAL.has(String(a.product)) ? 0 : 1;
6175
+ const bl = LEGAL.has(String(b.product)) ? 0 : 1;
6176
+ return al - bl;
6177
+ });
6178
+ return ranked[0];
6179
+ }
6180
+ function resolveRepo(argv, env2) {
6181
+ const repoArg = argv.indexOf("--repo");
6182
+ if (repoArg >= 0 && argv[repoArg + 1]) return argv[repoArg + 1];
6183
+ if (env2.VO_TEST_GEN_REPO) return env2.VO_TEST_GEN_REPO;
6184
+ const remote = spawnSync10("git", ["remote", "get-url", "origin"], { encoding: "utf8" });
6185
+ if (remote.status === 0) {
6186
+ const m = String(remote.stdout).trim().match(/[:/]([^/]+\/[^/]+?)(?:\.git)?$/);
6187
+ if (m) return m[1];
6188
+ }
6189
+ return null;
6190
+ }
6191
+ async function main(argv = process.argv.slice(2)) {
6192
+ const countArg = argv.indexOf("--count");
6193
+ const count3 = countArg >= 0 ? Math.max(1, Number(argv[countArg + 1]) || 1) : 1;
6194
+ const enqueue = argv.includes("--enqueue");
6195
+ const here = path14.dirname(fileURLToPath3(import.meta.url));
6196
+ const scan = spawnSync10("node", [path14.join(here, "coverage-scan.mjs"), "--limit", String(count3 * 8)], {
6197
+ encoding: "utf8",
6198
+ maxBuffer: 64 * 1024 * 1024
6199
+ });
6200
+ if (scan.status !== 0) {
6201
+ console.error(`[dispatch] coverage-scan failed: ${scan.stderr || scan.stdout}`);
6202
+ process.exit(1);
6203
+ }
6204
+ let gaps = [];
6205
+ try {
6206
+ gaps = JSON.parse(scan.stdout).gaps || [];
6207
+ } catch (err) {
6208
+ console.error(`[dispatch] could not parse scan output: ${err.message}`);
6209
+ process.exit(1);
6210
+ }
6211
+ const dispatches = [];
6212
+ const seen = /* @__PURE__ */ new Set();
6213
+ let pool = gaps;
6214
+ while (dispatches.length < count3 && pool.length > 0) {
6215
+ const target = pickNextTarget({ gaps: pool });
6216
+ if (!target) break;
6217
+ const key = `${target.product}:${target.callable}`;
6218
+ if (!seen.has(key)) {
6219
+ seen.add(key);
6220
+ dispatches.push(planTestGenDispatch({ contract: target }));
6221
+ }
6222
+ pool = pool.filter((g) => `${g.product}:${g.callable}` !== key);
6223
+ }
6224
+ if (!enqueue) {
6225
+ console.log(JSON.stringify({ requested: count3, emitted: dispatches.length, dispatches }, null, 2));
6226
+ return;
6227
+ }
6228
+ const repo = resolveRepo(argv, process.env);
6229
+ if (!repo) {
6230
+ console.error("[dispatch] --enqueue needs a repo: pass --repo owner/name or set VO_TEST_GEN_REPO (could not derive from git origin)");
6231
+ process.exit(1);
6232
+ }
6233
+ const { createControlPlaneClient: createControlPlaneClient2 } = await Promise.resolve().then(() => (init_control_plane_client(), control_plane_client_exports));
6234
+ const client = createControlPlaneClient2({ env: process.env });
6235
+ const enqueued = [];
6236
+ for (const d of dispatches) {
6237
+ const task = await client.enqueueCodeTask({
6238
+ repo,
6239
+ prompt: buildTestGenTaskPrompt(d),
6240
+ max_turns: 40
6241
+ });
6242
+ const taskId = task && task.code_task_id ? task.code_task_id : "(unknown)";
6243
+ enqueued.push({ task_id: taskId, product: d.contract.product, callable: d.contract.callable, tier: d.tier_label });
6244
+ console.error(`[dispatch] enqueued ${d.tier_label} test-gen for ${d.contract.product}:${d.contract.callable} \u2192 task ${taskId}`);
6245
+ }
6246
+ console.log(JSON.stringify({ requested: count3, enqueued: enqueued.length, repo, tasks: enqueued }, null, 2));
6247
+ }
6248
+ var TEST_GEN_MARKER, TIER_GUIDANCE, LEGAL, invokedDirectly;
6249
+ var init_dispatch = __esm({
6250
+ "../../scripts/virtual-office/test-gen/dispatch.mjs"() {
6251
+ "use strict";
6252
+ init_auto_tier();
6253
+ TEST_GEN_MARKER = "[VO-TEST-GEN]";
6254
+ TIER_GUIDANCE = {
6255
+ 1: "Surface/navigation smoke: assert the surface renders and the key controls are present. No backend effect needed.",
6256
+ 2: "E2E with VERIFIED RESULTS: drive the real user/callable workflow, then READ BACK the produced artifact and assert KNOWN-CORRECT expected values (not just a 200 / truthiness).",
6257
+ 3: "Real-time monitor / sentinel: drive the workflow AND verify the state transitions + safety rails (no money moved / graded / admin action without the guard). Assert the post-conditions, not just the response.",
6258
+ 4: "Source-grounded governed-fact: assert expected values that are GROUNDED in the cited authoritative source (the contract's authoritative_source_url). The fixture and the expected number/string must trace to that source."
6259
+ };
6260
+ LEGAL = /* @__PURE__ */ new Set(["algotax", "algolaw", "algoteach", "algolegal"]);
6261
+ invokedDirectly = process.argv[1] && fileURLToPath3(import.meta.url) === path14.resolve(process.argv[1]);
6262
+ if (invokedDirectly) {
6263
+ main().catch((err) => {
6264
+ console.error(`[dispatch] ${err.message}`);
6265
+ process.exit(1);
6266
+ });
6267
+ }
6268
+ }
6269
+ });
6270
+
6271
+ // ../../scripts/virtual-office/test-gen/executor.mjs
6272
+ function consensusQuestion(tier) {
6273
+ const tierAsk = tier >= 4 ? " For this governed-fact (Tier 4) test, the expected values MUST be grounded in the cited authoritative source." : tier >= 3 ? " For this safety-critical (Tier 3) test, it must verify state transitions and the safety rails, not just a 200." : "";
6274
+ return "Does this test PROVE the stated behavior with VERIFIED-CORRECT expected values (not fake-green)? It must exercise the real product path (no stub/mock that bypasses the feature) and assert known-correct values, not placeholders." + tierAsk;
6275
+ }
6276
+ function buildContractSummary(contract, tier) {
6277
+ const parts = [
6278
+ `Feature: ${contract.feature_name || contract.callable || contract.route || "unnamed"}`,
6279
+ `Product: ${contract.product || "unknown"}`,
6280
+ contract.route ? `Route: ${contract.route}` : "",
6281
+ contract.callable ? `Callable: ${contract.callable}` : "",
6282
+ `Auto-selected tier: ${tierLabel(tier)}`,
6283
+ contract.expected_behavior ? `Expected behavior: ${contract.expected_behavior}` : "",
6284
+ contract.authoritative_source_url ? `Authoritative source: ${contract.authoritative_source_url}` : ""
6285
+ ];
6286
+ return parts.filter(Boolean).join("\n");
6287
+ }
6288
+ async function gateGeneratedTest({ contract = {}, testSource = "", verify, taskId = "autotest" }) {
6289
+ if (typeof verify !== "function") throw new Error("gateGeneratedTest requires a `verify` transport");
6290
+ const { tier, rationale } = classifyTier(contract);
6291
+ const label = tierLabel(tier);
6292
+ const filename = typeof contract.test_filename === "string" ? contract.test_filename : void 0;
6293
+ const ratchets = await verify({
6294
+ task_id: taskId,
6295
+ gate_type: "ratchets",
6296
+ excerpt: testSource,
6297
+ ...filename ? { context: { filename } } : {}
6298
+ });
6299
+ if (!ratchets || ratchets.approved !== true) {
6300
+ return {
6301
+ ship: false,
6302
+ tier,
6303
+ tierLabel: label,
6304
+ stage: "ratchets",
6305
+ reason: ratchets?.reason || "ratchets gate did not approve",
6306
+ ratchets: ratchets || null,
6307
+ consensus: null
6308
+ };
6309
+ }
6310
+ const consensus = await verify({
6311
+ task_id: taskId,
6312
+ gate_type: CONSENSUS_GATE_TYPE,
6313
+ excerpt: `${buildContractSummary(contract, tier)}
6314
+
6315
+ --- TEST ---
6316
+ ${testSource}`,
6317
+ question: consensusQuestion(tier)
6318
+ });
6319
+ const consensusBlocks = consensus?.available === true && consensus?.approved !== true;
6320
+ if (consensusBlocks) {
6321
+ return {
6322
+ ship: false,
6323
+ tier,
6324
+ tierLabel: label,
6325
+ stage: "consensus",
6326
+ reason: consensus?.reason || "consensus did not confirm verified-correct expected values",
6327
+ ratchets,
6328
+ consensus
6329
+ };
6330
+ }
6331
+ return {
6332
+ ship: true,
6333
+ tier,
6334
+ tierLabel: label,
6335
+ stage: "passed",
6336
+ reason: consensus?.available === true ? `Passed ratchets + consensus (${label}). ${rationale}` : `Passed ratchets; consensus unavailable so not blocking (${label}). ${rationale}`,
6337
+ ratchets,
6338
+ consensus
6339
+ };
6340
+ }
6341
+ function makeHttpVerify({ baseUrl, token: token2, fetchImpl = fetch }) {
6342
+ if (!baseUrl) throw new Error("makeHttpVerify requires a baseUrl");
6343
+ const url = `${baseUrl.replace(/\/$/, "")}/api/v1/verify`;
6344
+ return async function httpVerify(req) {
6345
+ let res;
6346
+ try {
6347
+ res = await fetchImpl(url, {
6348
+ method: "POST",
6349
+ headers: { "Content-Type": "application/json", ...token2 ? { Authorization: `Bearer ${token2}` } : {} },
6350
+ body: JSON.stringify(req)
6351
+ });
6352
+ } catch (err) {
6353
+ return { ok: false, available: false, approved: false, reason: `transport error: ${err?.message || err}` };
6354
+ }
6355
+ let body = {};
6356
+ try {
6357
+ body = await res.json();
6358
+ } catch {
6359
+ }
6360
+ if (!res.ok) {
6361
+ return { ok: false, available: false, approved: false, reason: body?.error || `http ${res.status}` };
6362
+ }
6363
+ return body;
6364
+ };
6365
+ }
6366
+ var CONSENSUS_GATE_TYPE;
6367
+ var init_executor = __esm({
6368
+ "../../scripts/virtual-office/test-gen/executor.mjs"() {
6369
+ "use strict";
6370
+ init_auto_tier();
6371
+ CONSENSUS_GATE_TYPE = "test_correctness_consensus";
6372
+ }
6373
+ });
6374
+
6375
+ // ../../scripts/virtual-office/code-runner/test-gen-gate.mjs
6376
+ import fs7 from "node:fs";
6377
+ import path15 from "node:path";
6378
+ async function postFailed(client, id, message, result) {
6379
+ try {
6380
+ await client.postProgress(id, {
6381
+ status: "failed",
6382
+ message: String(message).slice(0, 1500),
6383
+ result
6384
+ });
6385
+ } catch {
6386
+ }
6387
+ }
6388
+ async function gateTestGenTaskOrFail({ client, id, task, files, worktreeDir, env: env2 = process.env, log: log2 = () => {
6389
+ }, verify: verifyInjected } = {}) {
6390
+ const prompt = String(task && task.prompt || "");
6391
+ if (!prompt.startsWith(TEST_GEN_MARKER)) return false;
6392
+ let contract;
6393
+ try {
6394
+ const after = prompt.slice(TEST_GEN_MARKER.length).trimStart();
6395
+ contract = JSON.parse(after.split("\n\n")[0]);
6396
+ } catch (err) {
6397
+ log2(`task ${id}: test-gen contract parse failed: ${err.message}`);
6398
+ await postFailed(client, id, `test-gen contract parse failed: ${err.message}`, "gate_contract_unparseable");
6399
+ return true;
6400
+ }
6401
+ const testFile = (files || []).find((f) => TEST_FILE_RE.test(f));
6402
+ if (!testFile) {
6403
+ await postFailed(client, id, "test-gen task produced no *.test.* file", "gate_no_test_file");
6404
+ return true;
6405
+ }
6406
+ const baseUrl = env2.VO_MOAT_PLANE_URL;
6407
+ if (!verifyInjected && !baseUrl) {
6408
+ log2(`task ${id}: VO_MOAT_PLANE_URL unset \u2014 skipping test-gen gate (fail-safe, ADR-002 opt-out)`);
6409
+ return false;
6410
+ }
6411
+ let testSource = "";
6412
+ try {
6413
+ testSource = fs7.readFileSync(path15.join(worktreeDir, testFile), "utf8");
6414
+ } catch (err) {
6415
+ await postFailed(client, id, `could not read generated test ${testFile}: ${err.message}`, "gate_test_unreadable");
6416
+ return true;
6417
+ }
6418
+ const verify = verifyInjected || makeHttpVerify({ baseUrl: String(baseUrl).replace(/\/$/, ""), token: env2.VO_MOAT_PLANE_TOKEN || "" });
6419
+ let verdict;
6420
+ try {
6421
+ verdict = await gateGeneratedTest({
6422
+ contract: { ...contract, test_filename: testFile },
6423
+ testSource,
6424
+ verify,
6425
+ taskId: id
6426
+ });
6427
+ } catch (err) {
6428
+ log2(`task ${id}: test-gen gate could not run: ${err.message}`);
6429
+ await postFailed(client, id, `test-gen gate could not run (${err.message}) \u2014 not shipping un-gated`, "gate_transport_error");
6430
+ return true;
6431
+ }
6432
+ if (!verdict.ship) {
6433
+ log2(`task ${id}: test-gen gate REJECTED at ${verdict.stage}`);
6434
+ await postFailed(
6435
+ client,
6436
+ id,
6437
+ `test-gen gate rejected (${verdict.stage}): ${verdict.reason || "did not pass ratchets + consensus"}`,
6438
+ "gate_rejected"
6439
+ );
6440
+ return true;
6441
+ }
6442
+ try {
6443
+ await client.postProgress(id, {
6444
+ message: `test-gen gate PASSED (${verdict.tierLabel || "tier"}): ${verdict.reason || "ratchets + consensus"}`
6445
+ });
6446
+ } catch {
6447
+ }
6448
+ return false;
6449
+ }
6450
+ var TEST_FILE_RE;
6451
+ var init_test_gen_gate = __esm({
6452
+ "../../scripts/virtual-office/code-runner/test-gen-gate.mjs"() {
6453
+ "use strict";
6454
+ init_dispatch();
6455
+ init_executor();
6456
+ TEST_FILE_RE = /\.(test|spec)\.(ts|tsx|mjs|js)$/;
6457
+ }
6458
+ });
6459
+
6460
+ // ../../scripts/virtual-office/code-runner/completion-gate.mjs
6461
+ import { execFile } from "node:child_process";
6462
+ import fs8 from "node:fs";
6463
+ import path16 from "node:path";
6464
+ function resolveCompletionGate(task) {
6465
+ const raw = task?.completion_gate;
6466
+ if (raw === void 0 || raw === null) return null;
6467
+ if (typeof raw !== "string" || raw.trim() === "") {
6468
+ return { invalid: true, reason: "completion_gate must be a non-empty string" };
6469
+ }
6470
+ const trimmed = raw.trim();
6471
+ if (trimmed.length > 500) {
6472
+ return { invalid: true, reason: "completion_gate exceeds 500 characters" };
6473
+ }
6474
+ if (SHELL_METACHARACTERS.test(trimmed)) {
6475
+ return { invalid: true, reason: "completion_gate contains shell metacharacters (no-shell contract: plain argv only, no pipes/redirects/quotes)" };
6476
+ }
6477
+ const [command, ...args] = trimmed.split(/\s+/);
6478
+ const allowedCommand = ALLOWED_GATE_COMMANDS.get(command);
6479
+ if (allowedCommand === void 0) {
6480
+ const shown = command.length > 40 ? `${command.slice(0, 40)}...` : command;
6481
+ return {
6482
+ invalid: true,
6483
+ reason: `completion_gate command ${shown} is not an allowed gate runner (${[...ALLOWED_GATE_COMMANDS.keys()].join(", ")})`
6484
+ };
6485
+ }
6486
+ return { argv: [allowedCommand, ...args] };
6487
+ }
6488
+ function boundedTail(text) {
6489
+ const s = String(text ?? "");
6490
+ return s.length <= COMPLETION_GATE_OUTPUT_CAP ? s : s.slice(s.length - COMPLETION_GATE_OUTPUT_CAP);
6491
+ }
6492
+ function workspaceFingerprint(worktreeDir, execFileImpl = execFile) {
6493
+ return new Promise((resolve2) => {
6494
+ execFileImpl("git", ["rev-parse", "HEAD^{tree}"], { cwd: worktreeDir }, (err, stdout) => {
6495
+ resolve2(err ? null : String(stdout).trim() || null);
6496
+ });
6497
+ });
6498
+ }
6499
+ function readState(worktreeDir) {
6500
+ try {
6501
+ return JSON.parse(fs8.readFileSync(path16.join(worktreeDir, COMPLETION_GATE_STATE_FILE), "utf8"));
6502
+ } catch {
6503
+ return null;
6504
+ }
6505
+ }
6506
+ function writeState(worktreeDir, state) {
6507
+ try {
6508
+ fs8.writeFileSync(path16.join(worktreeDir, COMPLETION_GATE_STATE_FILE), `${JSON.stringify(state)}
6509
+ `, "utf8");
6510
+ } catch {
6511
+ }
6512
+ }
6513
+ function runGateCommand({ argv, worktreeDir, timeoutMs = COMPLETION_GATE_TIMEOUT_MS, execFileImpl = execFile }) {
6514
+ return new Promise((resolve2) => {
6515
+ const command = ALLOWED_GATE_COMMANDS.get(argv?.[0]);
6516
+ if (command === void 0) {
6517
+ return resolve2({
6518
+ exitCode: 1,
6519
+ output: `completion_gate executable is not an allowed gate runner (${[...ALLOWED_GATE_COMMANDS.keys()].join(", ")})`,
6520
+ timedOut: false
6521
+ });
6522
+ }
6523
+ execFileImpl(
6524
+ command,
6525
+ argv.slice(1),
6526
+ { cwd: worktreeDir, timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024, windowsHide: true },
6527
+ (err, stdout, stderr) => {
6528
+ const output = boundedTail(`${stdout ?? ""}
6529
+ ${stderr ?? ""}`.trim());
6530
+ if (!err) return resolve2({ exitCode: 0, output, timedOut: false });
6531
+ const timedOut = err.killed === true || err.signal === "SIGTERM";
6532
+ const exitCode = typeof err.code === "number" ? err.code : 1;
6533
+ resolve2({ exitCode, output: output || boundedTail(err.message), timedOut });
6534
+ }
6535
+ );
6536
+ });
6537
+ }
6538
+ async function postFailed2(client, id, message, result) {
6539
+ try {
6540
+ await client.postProgress(id, { status: "failed", message: String(message).slice(0, 1500), result });
6541
+ } catch {
6542
+ }
6543
+ }
6544
+ function messageExcerpt(output) {
6545
+ const s = String(output ?? "");
6546
+ return s.length > 1200 ? `...${s.slice(-1200)}` : s;
6547
+ }
6548
+ async function enforceCompletionGateOrFail({ client, id, task, worktreeDir, log: log2 = () => {
6549
+ }, execFileImpl = execFile } = {}) {
6550
+ const resolved = resolveCompletionGate(task);
6551
+ if (resolved === null) return false;
6552
+ if (resolved.invalid) {
6553
+ log2(`task ${id}: completion_gate invalid \u2014 ${resolved.reason}`);
6554
+ await postFailed2(client, id, `completion_gate invalid (${resolved.reason}) \u2014 failing closed, not publishing`, "completion_gate_invalid");
6555
+ return true;
6556
+ }
6557
+ const fingerprint = await workspaceFingerprint(worktreeDir, execFileImpl);
6558
+ const cached2 = readState(worktreeDir);
6559
+ if (fingerprint && cached2 && cached2.fingerprint === fingerprint && cached2.exitCode !== 0) {
6560
+ log2(`task ${id}: completion gate skip-on-unchanged (tree ${fingerprint.slice(0, 12)}) \u2014 reusing recorded failure exit ${cached2.exitCode}`);
6561
+ await postFailed2(client, id, `completion gate '${task.completion_gate}' previously failed (exit ${cached2.exitCode}) and the workspace is unchanged:
6562
+ ${messageExcerpt(cached2.output)}`, "completion_gate_failed");
6563
+ return true;
6564
+ }
6565
+ log2(`task ${id}: running completion gate: ${resolved.argv.join(" ")}`);
6566
+ const outcome = await runGateCommand({ argv: resolved.argv, worktreeDir, execFileImpl });
6567
+ if (fingerprint) writeState(worktreeDir, { fingerprint, exitCode: outcome.exitCode, output: outcome.output, at: (/* @__PURE__ */ new Date()).toISOString() });
6568
+ if (outcome.exitCode === 0) {
6569
+ log2(`task ${id}: completion gate PASSED`);
6570
+ return false;
6571
+ }
6572
+ const kind = outcome.timedOut ? `timed out after ${COMPLETION_GATE_TIMEOUT_MS}ms` : `exited ${outcome.exitCode}`;
6573
+ log2(`task ${id}: completion gate FAILED (${kind}) \u2014 not publishing`);
6574
+ await postFailed2(client, id, `completion gate '${task.completion_gate}' ${kind} \u2014 task may not claim completion:
6575
+ ${messageExcerpt(outcome.output)}`, "completion_gate_failed");
6576
+ return true;
6577
+ }
6578
+ var COMPLETION_GATE_TIMEOUT_MS, COMPLETION_GATE_OUTPUT_CAP, COMPLETION_GATE_STATE_FILE, SHELL_METACHARACTERS, ALLOWED_GATE_COMMANDS;
6579
+ var init_completion_gate = __esm({
6580
+ "../../scripts/virtual-office/code-runner/completion-gate.mjs"() {
6581
+ "use strict";
6582
+ COMPLETION_GATE_TIMEOUT_MS = 5 * 6e4;
6583
+ COMPLETION_GATE_OUTPUT_CAP = 8 * 1024;
6584
+ COMPLETION_GATE_STATE_FILE = ".vo-completion-gate-state.json";
6585
+ SHELL_METACHARACTERS = /[|&;<>$`(){}[\]*?~#\\'"]/;
6586
+ ALLOWED_GATE_COMMANDS = new Map(
6587
+ ["pnpm", "npm", "yarn", "node", "git", "make", "python", "python3", "cargo", "go"].map((name) => [name, name])
6588
+ );
6589
+ }
6590
+ });
6591
+
5848
6592
  // ../../scripts/virtual-office/code-runner/process-runner.mjs
5849
6593
  import { spawn as spawn3 } from "node:child_process";
5850
6594
  function buildStepLabel(cmd, args = []) {
@@ -6338,8 +7082,8 @@ var init_publish_async = __esm({
6338
7082
 
6339
7083
  // ../../scripts/virtual-office/code-runner/skill-catalog.mjs
6340
7084
  import { readdirSync as readdirSync2, readFileSync as readFileSync3, statSync } from "node:fs";
6341
- import { dirname as dirname2, join as join3 } from "node:path";
6342
- import { fileURLToPath as fileURLToPath2 } from "node:url";
7085
+ import { dirname as dirname3, join as join4 } from "node:path";
7086
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
6343
7087
  function parseFrontmatterNameDescription(raw) {
6344
7088
  const text = String(raw).replace(/\r\n/g, "\n");
6345
7089
  if (!text.startsWith("---\n")) return null;
@@ -6358,15 +7102,15 @@ function parseFrontmatterNameDescription(raw) {
6358
7102
  return name && description ? { name, description } : null;
6359
7103
  }
6360
7104
  function resolveDefaultRepoRoot() {
6361
- const starts = [dirname2(fileURLToPath2(import.meta.url)), process.cwd()];
7105
+ const starts = [dirname3(fileURLToPath4(import.meta.url)), process.cwd()];
6362
7106
  for (const start of starts) {
6363
7107
  let dir = start;
6364
7108
  for (let i = 0; i < 8; i += 1) {
6365
7109
  try {
6366
- if (statSync(join3(dir, ".claude", "skills")).isDirectory()) return dir;
7110
+ if (statSync(join4(dir, ".claude", "skills")).isDirectory()) return dir;
6367
7111
  } catch {
6368
7112
  }
6369
- const parent = dirname2(dir);
7113
+ const parent = dirname3(dir);
6370
7114
  if (parent === dir) break;
6371
7115
  dir = parent;
6372
7116
  }
@@ -6375,14 +7119,14 @@ function resolveDefaultRepoRoot() {
6375
7119
  }
6376
7120
  function loadSkillCatalog({ repoRoot: repoRoot2 = resolveDefaultRepoRoot() } = {}) {
6377
7121
  try {
6378
- const skillsDir = join3(repoRoot2, ".claude", "skills");
7122
+ const skillsDir = join4(repoRoot2, ".claude", "skills");
6379
7123
  const catalog = [];
6380
7124
  for (const entry of readdirSync2(skillsDir)) {
6381
- const dir = join3(skillsDir, entry);
7125
+ const dir = join4(skillsDir, entry);
6382
7126
  try {
6383
7127
  if (!statSync(dir).isDirectory()) continue;
6384
7128
  const parsed = parseFrontmatterNameDescription(
6385
- readFileSync3(join3(dir, "SKILL.md"), "utf8")
7129
+ readFileSync3(join4(dir, "SKILL.md"), "utf8")
6386
7130
  );
6387
7131
  if (parsed) catalog.push(parsed);
6388
7132
  } catch {
@@ -6614,7 +7358,7 @@ var init_task_prompt = __esm({
6614
7358
  import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
6615
7359
  import { chmod, mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
6616
7360
  import os2 from "node:os";
6617
- import path14 from "node:path";
7361
+ import path17 from "node:path";
6618
7362
  function safeTaskToken(taskId) {
6619
7363
  return String(taskId || "task").replace(/[^0-9A-Za-z_-]/gu, "_").slice(0, 48) || "task";
6620
7364
  }
@@ -6624,25 +7368,25 @@ function sanitizeTaskAttachmentName(name, index = 0) {
6624
7368
  return `${String(index + 1).padStart(2, "0")}-${normalized}`;
6625
7369
  }
6626
7370
  function assertGeneratedDirectory(directory, tempRoot) {
6627
- const resolvedDirectory = path14.resolve(directory);
6628
- const resolvedRoot = path14.resolve(tempRoot);
6629
- if (path14.dirname(resolvedDirectory) !== resolvedRoot || !path14.basename(resolvedDirectory).startsWith(DIRECTORY_PREFIX)) {
7371
+ const resolvedDirectory = path17.resolve(directory);
7372
+ const resolvedRoot = path17.resolve(tempRoot);
7373
+ if (path17.dirname(resolvedDirectory) !== resolvedRoot || !path17.basename(resolvedDirectory).startsWith(DIRECTORY_PREFIX)) {
6630
7374
  throw new Error("refusing to clean an unverified task-attachment directory");
6631
7375
  }
6632
7376
  return resolvedDirectory;
6633
7377
  }
6634
7378
  async function createAttachmentDirectory(taskId, tempRoot) {
6635
- const root = path14.resolve(tempRoot);
7379
+ const root = path17.resolve(tempRoot);
6636
7380
  await mkdir(root, { recursive: true });
6637
- const directory = await mkdtemp(path14.join(root, `${DIRECTORY_PREFIX}${safeTaskToken(taskId)}-`));
6638
- const marker = JSON.stringify({ owner: MARKER_OWNER, token: randomUUID2(), directory: path14.basename(directory), created_at: (/* @__PURE__ */ new Date()).toISOString() });
6639
- await writeFile(path14.join(directory, MARKER_FILE), marker, { encoding: "utf8", mode: 384 });
7381
+ const directory = await mkdtemp(path17.join(root, `${DIRECTORY_PREFIX}${safeTaskToken(taskId)}-`));
7382
+ const marker = JSON.stringify({ owner: MARKER_OWNER, token: randomUUID2(), directory: path17.basename(directory), created_at: (/* @__PURE__ */ new Date()).toISOString() });
7383
+ await writeFile(path17.join(directory, MARKER_FILE), marker, { encoding: "utf8", mode: 384 });
6640
7384
  return { directory, marker, tempRoot: root };
6641
7385
  }
6642
7386
  async function cleanupGeneratedDirectory(state) {
6643
7387
  if (!state || state.cleaned) return;
6644
7388
  const directory = assertGeneratedDirectory(state.directory, state.tempRoot);
6645
- const marker = await readFile(path14.join(directory, MARKER_FILE), "utf8").catch(() => "");
7389
+ const marker = await readFile(path17.join(directory, MARKER_FILE), "utf8").catch(() => "");
6646
7390
  if (marker !== state.marker) throw new Error("refusing to clean a task-attachment directory without its exact marker");
6647
7391
  await rm(directory, { recursive: true, force: true });
6648
7392
  state.cleaned = true;
@@ -6661,7 +7405,7 @@ async function sweepStaleTaskAttachmentDirectories({
6661
7405
  now = Date.now(),
6662
7406
  maxAgeMs = DEFAULT_STALE_AGE_MS
6663
7407
  } = {}) {
6664
- const root = path14.resolve(tempRoot);
7408
+ const root = path17.resolve(tempRoot);
6665
7409
  if (!Number.isFinite(maxAgeMs) || maxAgeMs <= 0) throw new Error("stale attachment age must be positive");
6666
7410
  const entries = await readdir(root, { withFileTypes: true }).catch((error) => {
6667
7411
  if (error?.code === "ENOENT") return [];
@@ -6670,8 +7414,8 @@ async function sweepStaleTaskAttachmentDirectories({
6670
7414
  let removed = 0;
6671
7415
  for (const entry of entries) {
6672
7416
  if (!entry.isDirectory() || !entry.name.startsWith(DIRECTORY_PREFIX)) continue;
6673
- const directory = assertGeneratedDirectory(path14.join(root, entry.name), root);
6674
- const markerRaw = await readFile(path14.join(directory, MARKER_FILE), "utf8").catch(() => "");
7417
+ const directory = assertGeneratedDirectory(path17.join(root, entry.name), root);
7418
+ const markerRaw = await readFile(path17.join(directory, MARKER_FILE), "utf8").catch(() => "");
6675
7419
  const marker = parseOwnedMarker(markerRaw, entry.name);
6676
7420
  if (!marker) continue;
6677
7421
  const directoryStat = await stat(directory);
@@ -6714,10 +7458,10 @@ async function materializeTaskAttachments(client, task, { tempRoot = os2.tmpdir(
6714
7458
  const sha256 = createHash3("sha256").update(content).digest("hex");
6715
7459
  if (sha256 !== ref.sha256) throw new Error(`attachment ${ref.attachment_id} sha256 mismatch`);
6716
7460
  const name = sanitizeTaskAttachmentName(ref.name, index);
6717
- const filePath = path14.join(state.directory, name);
7461
+ const filePath = path17.join(state.directory, name);
6718
7462
  await writeFile(filePath, content, { flag: "wx", mode: 384 });
6719
7463
  await chmod(filePath, 384);
6720
- files.push({ attachmentId: ref.attachment_id, name, mime: ref.mime, sizeBytes: ref.size_bytes, sha256, path: path14.resolve(filePath) });
7464
+ files.push({ attachmentId: ref.attachment_id, name, mime: ref.mime, sizeBytes: ref.size_bytes, sha256, path: path17.resolve(filePath) });
6721
7465
  }
6722
7466
  return { directory: state.directory, files, manifestMarkdown: buildManifest(files), cleanup: () => cleanupGeneratedDirectory(state) };
6723
7467
  } catch (error) {
@@ -6740,7 +7484,7 @@ var init_task_attachments = __esm({
6740
7484
 
6741
7485
  // ../../scripts/virtual-office/code-runner/session-spool-forwarder.mjs
6742
7486
  import { homedir as homedir4 } from "node:os";
6743
- import { join as join4 } from "node:path";
7487
+ import { join as join5 } from "node:path";
6744
7488
  import { readdir as readdir2, readFile as readFile2, unlink, writeFile as writeFile2 } from "node:fs/promises";
6745
7489
  import { createHash as createHash4 } from "node:crypto";
6746
7490
  function deriveUuid(seed) {
@@ -6772,18 +7516,18 @@ async function readSpool(spoolDir = SPOOL_DIR) {
6772
7516
  for (const f of files) {
6773
7517
  if (!f.endsWith(".json")) continue;
6774
7518
  try {
6775
- const record = JSON.parse(await readFile2(join4(spoolDir, f), "utf8"));
7519
+ const record = JSON.parse(await readFile2(join5(spoolDir, f), "utf8"));
6776
7520
  if (record && typeof record.session_key === "string") {
6777
- out.push({ full: join4(spoolDir, f), record });
7521
+ out.push({ full: join5(spoolDir, f), record });
6778
7522
  }
6779
7523
  } catch {
6780
7524
  }
6781
7525
  }
6782
7526
  return out;
6783
7527
  }
6784
- async function readCloudMap(path20) {
7528
+ async function readCloudMap(path23) {
6785
7529
  try {
6786
- return JSON.parse(await readFile2(path20, "utf8"));
7530
+ return JSON.parse(await readFile2(path23, "utf8"));
6787
7531
  } catch {
6788
7532
  return {};
6789
7533
  }
@@ -6856,8 +7600,8 @@ var SPOOL_DIR, CLOUD_MAP_FILE, STALE_MS, ACTIVE_SILENCE_MS;
6856
7600
  var init_session_spool_forwarder = __esm({
6857
7601
  "../../scripts/virtual-office/code-runner/session-spool-forwarder.mjs"() {
6858
7602
  "use strict";
6859
- SPOOL_DIR = join4(homedir4(), ".vo", "session-spool");
6860
- CLOUD_MAP_FILE = join4(homedir4(), ".vo", "session-cloud-map.json");
7603
+ SPOOL_DIR = join5(homedir4(), ".vo", "session-spool");
7604
+ CLOUD_MAP_FILE = join5(homedir4(), ".vo", "session-cloud-map.json");
6861
7605
  STALE_MS = 60 * 60 * 1e3;
6862
7606
  ACTIVE_SILENCE_MS = 10 * 60 * 1e3;
6863
7607
  }
@@ -6928,7 +7672,7 @@ var init_rate_limit_resume_scheduler_core = __esm({
6928
7672
  });
6929
7673
 
6930
7674
  // ../../scripts/virtual-office/code-runner/rate-limit-resume-scheduler.mjs
6931
- import { dirname as dirname3, join as join5, resolve } from "node:path";
7675
+ import { dirname as dirname4, join as join6, resolve } from "node:path";
6932
7676
  function defaultLog(message) {
6933
7677
  console.log(`[rate-limit-scheduler ${(/* @__PURE__ */ new Date()).toISOString()}] ${message}`);
6934
7678
  }
@@ -7010,7 +7754,7 @@ async function runLockedScheduler({
7010
7754
  async function runScheduler({
7011
7755
  env: env2 = process.env,
7012
7756
  queuePath = resumeQueuePath(),
7013
- attemptsPath = join5(dirname3(queuePath), "resume-attempts.json"),
7757
+ attemptsPath = join6(dirname4(queuePath), "resume-attempts.json"),
7014
7758
  client,
7015
7759
  now,
7016
7760
  log: log2 = defaultLog
@@ -7073,6 +7817,10 @@ function makeLoopTicks({
7073
7817
  applyRemoteConfig: () => false,
7074
7818
  heartbeatFields: () => ({})
7075
7819
  },
7820
+ // Host version awareness: reads `update_status` off the heartbeat ACK and logs
7821
+ // ONE line per drift change (daemon-update-status.mjs). No-op default keeps
7822
+ // old callers working; absent update_status reads as unknown, never current.
7823
+ updateStatusTracker = { applyHeartbeatResponse: () => false },
7076
7824
  // Cached agent-availability provider (agent-availability.mjs); returns null
7077
7825
  // until the first probe completes — the heartbeat simply omits the field.
7078
7826
  getAgentAvailability = () => null,
@@ -7113,6 +7861,7 @@ function makeLoopTicks({
7113
7861
  request.then((response) => {
7114
7862
  capacityController.applyCapacity(response?.capacity, nextPayload.operatorId);
7115
7863
  localModelController.applyRemoteConfig(response?.local_model, nextPayload.operatorId);
7864
+ updateStatusTracker.applyHeartbeatResponse(response);
7116
7865
  }).catch((e) => log2(`heartbeat failed: ${e.message}`)).finally(() => {
7117
7866
  for (const done of waiters) done();
7118
7867
  if (state.pending) {
@@ -7302,7 +8051,7 @@ var init_runner_capacity = __esm({
7302
8051
  });
7303
8052
 
7304
8053
  // ../../scripts/virtual-office/code-runner/agent-auth-probe-process.mjs
7305
- import { fileURLToPath as fileURLToPath3 } from "node:url";
8054
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
7306
8055
  async function probeAgentInChild(agent, timeoutMs) {
7307
8056
  const stdout = await runProcess2(process.execPath, [probeCli, agent], {
7308
8057
  timeout: timeoutMs,
@@ -7315,7 +8064,7 @@ var init_agent_auth_probe_process = __esm({
7315
8064
  "../../scripts/virtual-office/code-runner/agent-auth-probe-process.mjs"() {
7316
8065
  "use strict";
7317
8066
  init_process_runner2();
7318
- probeCli = fileURLToPath3(new URL("./agent-auth-probe-cli.mjs", import.meta.url));
8067
+ probeCli = fileURLToPath5(new URL("./agent-auth-probe-cli.mjs", import.meta.url));
7319
8068
  }
7320
8069
  });
7321
8070
 
@@ -7540,7 +8289,7 @@ var init_local_model_remote_config = __esm({
7540
8289
 
7541
8290
  // ../../scripts/virtual-office/code-runner/account-usage/shared.mjs
7542
8291
  import crypto from "node:crypto";
7543
- import fs7 from "node:fs";
8292
+ import fs9 from "node:fs";
7544
8293
  function accountKey(agent, rawId) {
7545
8294
  const id = typeof rawId === "string" ? rawId.trim() : "";
7546
8295
  if (!id) return null;
@@ -7604,7 +8353,7 @@ var init_shared = __esm({
7604
8353
  };
7605
8354
  readJson = (p) => {
7606
8355
  try {
7607
- return JSON.parse(fs7.readFileSync(p, "utf8"));
8356
+ return JSON.parse(fs9.readFileSync(p, "utf8"));
7608
8357
  } catch {
7609
8358
  return null;
7610
8359
  }
@@ -7614,9 +8363,9 @@ var init_shared = __esm({
7614
8363
  });
7615
8364
 
7616
8365
  // ../../scripts/virtual-office/code-runner/account-usage/claude.mjs
7617
- import fs8 from "node:fs";
8366
+ import fs10 from "node:fs";
7618
8367
  import os3 from "node:os";
7619
- import path15 from "node:path";
8368
+ import path18 from "node:path";
7620
8369
  function fileCaptureTime(filePath, explicit, statFn) {
7621
8370
  if (typeof explicit === "string" && explicit) return explicit;
7622
8371
  try {
@@ -7630,7 +8379,7 @@ function usageBaseUrl(env2 = process.env) {
7630
8379
  return String(raw).replace(/\/+$/, "");
7631
8380
  }
7632
8381
  function readOAuthToken({ homeDir = os3.homedir(), read = readJson, now = Date.now() } = {}) {
7633
- const creds = read(path15.join(homeDir, ".claude", ".credentials.json"));
8382
+ const creds = read(path18.join(homeDir, ".claude", ".credentials.json"));
7634
8383
  const oauth = creds && typeof creds === "object" ? creds.claudeAiOauth : null;
7635
8384
  if (!oauth || typeof oauth !== "object") return null;
7636
8385
  const token2 = typeof oauth.accessToken === "string" ? oauth.accessToken.trim() : "";
@@ -7640,7 +8389,7 @@ function readOAuthToken({ homeDir = os3.homedir(), read = readJson, now = Date.n
7640
8389
  return token2;
7641
8390
  }
7642
8391
  function readAccountId({ homeDir = os3.homedir(), read = readJson } = {}) {
7643
- const cfg = read(path15.join(homeDir, ".claude.json"));
8392
+ const cfg = read(path18.join(homeDir, ".claude.json"));
7644
8393
  const account = cfg && typeof cfg === "object" ? cfg.oauthAccount : null;
7645
8394
  return account && typeof account.accountUuid === "string" ? account.accountUuid : null;
7646
8395
  }
@@ -7733,7 +8482,7 @@ async function readClaudeOAuthUsage({
7733
8482
  function readClaudeFileUsage({
7734
8483
  homeDir = os3.homedir(),
7735
8484
  read: rawRead = readJson,
7736
- statFn = fs8.statSync,
8485
+ statFn = fs10.statSync,
7737
8486
  now = () => Date.now()
7738
8487
  } = {}) {
7739
8488
  const read = (p) => {
@@ -7750,7 +8499,7 @@ function readClaudeFileUsage({
7750
8499
  if (age === null || age > MAX_FILE_AGE_MS) return null;
7751
8500
  return row;
7752
8501
  };
7753
- const statusPath = path15.join(homeDir, ".claude", "claude-usage.json");
8502
+ const statusPath = path18.join(homeDir, ".claude", "claude-usage.json");
7754
8503
  const status = read(statusPath);
7755
8504
  if (status && (status.seven_day || status.five_hour)) {
7756
8505
  const row = fresh(makeUsageRow({
@@ -7765,7 +8514,7 @@ function readClaudeFileUsage({
7765
8514
  }));
7766
8515
  if (row) return row;
7767
8516
  }
7768
- const weeklyPath = path15.join(homeDir, ".claude", "claude-weekly-usage.json");
8517
+ const weeklyPath = path18.join(homeDir, ".claude", "claude-weekly-usage.json");
7769
8518
  const weekly = read(weeklyPath);
7770
8519
  if (weekly) {
7771
8520
  const row = fresh(makeUsageRow({
@@ -8206,7 +8955,7 @@ var init_watcher_coordination = __esm({
8206
8955
  // ../../scripts/virtual-office/code-runner/watcher-state.mjs
8207
8956
  import { randomUUID as randomUUID3 } from "node:crypto";
8208
8957
  import { mkdir as mkdir2, open, readFile as readFile3, rename, unlink as unlink2 } from "node:fs/promises";
8209
- import { dirname as dirname4 } from "node:path";
8958
+ import { dirname as dirname5 } from "node:path";
8210
8959
  async function readWatcherState(stateFile) {
8211
8960
  let raw;
8212
8961
  try {
@@ -8222,7 +8971,7 @@ async function readWatcherState(stateFile) {
8222
8971
  return parsed;
8223
8972
  }
8224
8973
  async function writeWatcherState(stateFile, state) {
8225
- const directory = dirname4(stateFile);
8974
+ const directory = dirname5(stateFile);
8226
8975
  await mkdir2(directory, { recursive: true });
8227
8976
  const temp = `${stateFile}.${process.pid}.${randomUUID3()}.tmp`;
8228
8977
  let handle;
@@ -8468,7 +9217,7 @@ var init_enqueue_autonomous_code_task = __esm({
8468
9217
 
8469
9218
  // ../../scripts/virtual-office/code-runner/pr-watcher.mjs
8470
9219
  import { homedir as homedir5 } from "node:os";
8471
- import { join as join6 } from "node:path";
9220
+ import { join as join7 } from "node:path";
8472
9221
  function parsePrCiStatus(view) {
8473
9222
  const state = (view && typeof view.state === "string" ? view.state : "UNKNOWN").toUpperCase();
8474
9223
  const rollup = view && Array.isArray(view.statusCheckRollup) ? view.statusCheckRollup : [];
@@ -8811,7 +9560,7 @@ var init_pr_watcher = __esm({
8811
9560
  init_watcher_state();
8812
9561
  init_superseded_pr_source();
8813
9562
  init_ci_fix_prompt();
8814
- DEFAULT_STATE_FILE = join6(homedir5(), ".vo", "dispatched-prs.json");
9563
+ DEFAULT_STATE_FILE = join7(homedir5(), ".vo", "dispatched-prs.json");
8815
9564
  FAIL_CONCLUSIONS = /* @__PURE__ */ new Set([
8816
9565
  "FAILURE",
8817
9566
  "TIMED_OUT",
@@ -9041,9 +9790,9 @@ function buildControlHandler({ getStatus, requestStop, allowedOrigin }) {
9041
9790
  res.end();
9042
9791
  return;
9043
9792
  }
9044
- const path20 = String(req.url || "").split("?")[0];
9793
+ const path23 = String(req.url || "").split("?")[0];
9045
9794
  res.setHeader("content-type", "application/json");
9046
- if (req.method === "GET" && path20 === "/status") {
9795
+ if (req.method === "GET" && path23 === "/status") {
9047
9796
  let status;
9048
9797
  try {
9049
9798
  status = getStatus();
@@ -9054,7 +9803,7 @@ function buildControlHandler({ getStatus, requestStop, allowedOrigin }) {
9054
9803
  res.end(JSON.stringify({ ok: true, ...status }));
9055
9804
  return;
9056
9805
  }
9057
- if (req.method === "POST" && path20 === "/stop") {
9806
+ if (req.method === "POST" && path23 === "/stop") {
9058
9807
  if (!isControlOriginAllowed(req.headers.origin, allowedOrigin) || !req.headers["x-vo-control"]) {
9059
9808
  res.statusCode = 403;
9060
9809
  res.end(JSON.stringify({ ok: false, error: "forbidden" }));
@@ -9115,7 +9864,7 @@ function startControlServer({ port, getStatus, requestStop, allowedOrigin, log:
9115
9864
  return server;
9116
9865
  }
9117
9866
  function startDaemonControl({ cfg, runnerInstanceId, requestStop, getActiveCount, isRunning, startedAt, log: log2 = () => {
9118
- }, onDuplicate = null }) {
9867
+ }, onDuplicate = null, getUpdateStatus = () => null }) {
9119
9868
  if (!cfg.controlEnabled) return null;
9120
9869
  return startControlServer({
9121
9870
  port: cfg.controlPort,
@@ -9132,7 +9881,9 @@ function startDaemonControl({ cfg, runnerInstanceId, requestStop, getActiveCount
9132
9881
  watchEnabled: cfg.watchEnabled,
9133
9882
  activeTasks: getActiveCount(),
9134
9883
  startedAt: new Date(startedAt).toISOString(),
9135
- uptimeSec: Math.round((Date.now() - startedAt) / 1e3)
9884
+ uptimeSec: Math.round((Date.now() - startedAt) / 1e3),
9885
+ // Host version awareness — the app + `runner --status` read drift from here.
9886
+ updateStatus: getUpdateStatus()
9136
9887
  }),
9137
9888
  log: log2
9138
9889
  });
@@ -9217,18 +9968,35 @@ var init_effort_mode_config = __esm({
9217
9968
  });
9218
9969
 
9219
9970
  // ../../scripts/virtual-office/model-registry.mjs
9220
- import fs9 from "node:fs";
9221
- import path16 from "node:path";
9222
- import { fileURLToPath as fileURLToPath4 } from "node:url";
9971
+ import { randomUUID as randomUUID5 } from "node:crypto";
9972
+ import fs11 from "node:fs";
9973
+ import os4 from "node:os";
9974
+ import path19 from "node:path";
9975
+ import { fileURLToPath as fileURLToPath6 } from "node:url";
9976
+ function userCacheRoot() {
9977
+ try {
9978
+ const home = os4.homedir();
9979
+ if (home) return path19.join(home, ".claude");
9980
+ } catch {
9981
+ }
9982
+ return path19.join(os4.tmpdir(), `vo-model-registry-${randomUUID5()}`);
9983
+ }
9984
+ function resolveCacheBaseDir(env2 = process.env, moduleDir = __dirname) {
9985
+ if (env2.VO_MODEL_REGISTRY_CACHE_DIR) return env2.VO_MODEL_REGISTRY_CACHE_DIR;
9986
+ if (env2.VO_RUNNER_RUNTIME_ROOT) return env2.VO_RUNNER_RUNTIME_ROOT;
9987
+ const segments = moduleDir.split(path19.sep);
9988
+ const isRepoCheckout = segments.at(-1) === "virtual-office" && segments.at(-2) === "scripts";
9989
+ return isRepoCheckout ? path19.resolve(moduleDir, "..", "..") : userCacheRoot();
9990
+ }
9223
9991
  function uniqueModels(models = []) {
9224
9992
  return [...new Set(models.map((model) => String(model || "").trim()).filter(Boolean))];
9225
9993
  }
9226
9994
  function normalizeProvider(value = "") {
9227
- const lower = String(value || "").trim().toLowerCase();
9228
- if (lower.includes("anthropic")) return "anthropic";
9229
- if (lower.includes("openai")) return "openai";
9230
- if (lower.includes("google") || lower.includes("gemini")) return "google";
9231
- return lower;
9995
+ const lower2 = String(value || "").trim().toLowerCase();
9996
+ if (lower2.includes("anthropic")) return "anthropic";
9997
+ if (lower2.includes("openai")) return "openai";
9998
+ if (lower2.includes("google") || lower2.includes("gemini")) return "google";
9999
+ return lower2;
9232
10000
  }
9233
10001
  function stripProviderPrefix(id = "") {
9234
10002
  const raw = String(id || "").trim();
@@ -9270,15 +10038,15 @@ function normalizeCatalogModel(model = {}) {
9270
10038
  };
9271
10039
  }
9272
10040
  function parseVersionScore(id = "") {
9273
- const lower = String(id || "").toLowerCase();
9274
- const numbers = [...lower.matchAll(/\d+/g)].map((match) => Number(match[0])).filter(Number.isFinite);
10041
+ const lower2 = String(id || "").toLowerCase();
10042
+ const numbers = [...lower2.matchAll(/\d+/g)].map((match) => Number(match[0])).filter(Number.isFinite);
9275
10043
  let score = 0;
9276
10044
  for (let i = 0; i < numbers.length; i++) score += numbers[i] / Math.pow(1e3, i);
9277
- if (/opus|pro|flagship/.test(lower)) score += 10;
9278
- if (/sonnet/.test(lower)) score += 5;
9279
- if (/preview|latest/.test(lower)) score += 0.25;
9280
- if (/\[1m\]|\(1m\)/i.test(lower)) score += 1;
9281
- if (/mini|nano|haiku|lite/.test(lower)) score -= 20;
10045
+ if (/opus|pro|flagship/.test(lower2)) score += 10;
10046
+ if (/sonnet/.test(lower2)) score += 5;
10047
+ if (/preview|latest/.test(lower2)) score += 0.25;
10048
+ if (/\[1m\]|\(1m\)/i.test(lower2)) score += 1;
10049
+ if (/mini|nano|haiku|lite/.test(lower2)) score -= 20;
9282
10050
  return score;
9283
10051
  }
9284
10052
  function familyMatches(model, family) {
@@ -9330,9 +10098,9 @@ async function fetchGoogleModels(fetchImpl, env2 = process.env) {
9330
10098
  return (data?.models || []).map((model) => normalizeCatalogModel({ ...model, provider: "google", source: "google" })).filter(Boolean);
9331
10099
  }
9332
10100
  function readCache(cacheFile = DEFAULT_CACHE_FILE, nowMs = Date.now(), ttlMs = DEFAULT_TTL_MS3) {
9333
- if (!fs9.existsSync(cacheFile)) return null;
10101
+ if (!fs11.existsSync(cacheFile)) return null;
9334
10102
  try {
9335
- const parsed = JSON.parse(fs9.readFileSync(cacheFile, "utf-8"));
10103
+ const parsed = JSON.parse(fs11.readFileSync(cacheFile, "utf-8"));
9336
10104
  if (nowMs - Number(parsed.checkedAtMs || 0) > ttlMs) return null;
9337
10105
  if (!Array.isArray(parsed.models)) return null;
9338
10106
  return parsed;
@@ -9341,8 +10109,8 @@ function readCache(cacheFile = DEFAULT_CACHE_FILE, nowMs = Date.now(), ttlMs = D
9341
10109
  }
9342
10110
  }
9343
10111
  function writeCache(cacheFile = DEFAULT_CACHE_FILE, payload) {
9344
- fs9.mkdirSync(path16.dirname(cacheFile), { recursive: true });
9345
- fs9.writeFileSync(cacheFile, JSON.stringify(payload, null, 2));
10112
+ fs11.mkdirSync(path19.dirname(cacheFile), { recursive: true });
10113
+ fs11.writeFileSync(cacheFile, JSON.stringify(payload, null, 2));
9346
10114
  }
9347
10115
  async function fetchRegistryCatalog({
9348
10116
  fetchImpl = fetch,
@@ -9395,14 +10163,17 @@ async function resolveModelFamily(family, options = {}) {
9395
10163
  const resolved = selectBestFamilyModel(catalog.models || [], family);
9396
10164
  return resolved || def.fallbacks[0];
9397
10165
  }
9398
- var __dirname, ROOT, DEFAULT_CACHE_DIR, DEFAULT_CACHE_FILE, DEFAULT_TTL_MS3, ANTHROPIC_API_VERSION, FAMILY_DEFINITIONS, memoryCache;
10166
+ var __dirname, DEFAULT_CACHE_DIR, DEFAULT_CACHE_FILE, DEFAULT_TTL_MS3, ANTHROPIC_API_VERSION, FAMILY_DEFINITIONS, memoryCache;
9399
10167
  var init_model_registry = __esm({
9400
10168
  "../../scripts/virtual-office/model-registry.mjs"() {
9401
10169
  "use strict";
9402
- __dirname = path16.dirname(fileURLToPath4(import.meta.url));
9403
- ROOT = path16.resolve(__dirname, "..", "..");
9404
- DEFAULT_CACHE_DIR = path16.join(ROOT, ".virtual-office-cache", "model-registry");
9405
- DEFAULT_CACHE_FILE = path16.join(DEFAULT_CACHE_DIR, "catalog.json");
10170
+ __dirname = path19.dirname(fileURLToPath6(import.meta.url));
10171
+ DEFAULT_CACHE_DIR = path19.join(
10172
+ resolveCacheBaseDir(),
10173
+ ".virtual-office-cache",
10174
+ "model-registry"
10175
+ );
10176
+ DEFAULT_CACHE_FILE = path19.join(DEFAULT_CACHE_DIR, "catalog.json");
9406
10177
  DEFAULT_TTL_MS3 = 60 * 60 * 1e3;
9407
10178
  ANTHROPIC_API_VERSION = "2023-06-01";
9408
10179
  FAMILY_DEFINITIONS = {
@@ -9507,17 +10278,17 @@ function modelCompatibleWithAgent(agent, model) {
9507
10278
  if (!model) return true;
9508
10279
  return AGENT_MODEL_COMPATIBILITY[agent]?.(model) ?? false;
9509
10280
  }
9510
- function classifyTier(prompt) {
10281
+ function classifyTier2(prompt) {
9511
10282
  const text = String(prompt || "").trim();
9512
10283
  if (!text) return "mid";
9513
- const lower = text.toLowerCase();
10284
+ const lower2 = text.toLowerCase();
9514
10285
  if (/lint|format|typo|missing import|update deps|chore|maintenance|runner|daemon/.test(
9515
- lower
10286
+ lower2
9516
10287
  )) {
9517
10288
  return "cheap";
9518
10289
  }
9519
10290
  if (/generate roadmap|new feature|implement .* feature|major refactor|strategic/.test(
9520
- lower
10291
+ lower2
9521
10292
  ) || text.length > 1500) {
9522
10293
  return "best";
9523
10294
  }
@@ -9538,7 +10309,7 @@ async function resolveModelForTier(tier, { agent = DEFAULT_AGENT2, resolveModelF
9538
10309
  return fallbacks[effectiveTier];
9539
10310
  }
9540
10311
  async function resolveTaskModel(task, { agent = DEFAULT_AGENT2 } = {}) {
9541
- const tier = task.tier && task.tier !== "auto" ? task.tier : classifyTier(task.prompt);
10312
+ const tier = task.tier && task.tier !== "auto" ? task.tier : classifyTier2(task.prompt);
9542
10313
  const pinned = typeof task.model === "string" ? task.model.trim() : "";
9543
10314
  if (pinned && modelCompatibleWithAgent(normalizeAgent(agent), pinned)) {
9544
10315
  return { tier, model: pinned };
@@ -10011,7 +10782,7 @@ var init_classify_task = __esm({
10011
10782
  // ../../scripts/virtual-office/code-runner/auto-router/effort-policy.mjs
10012
10783
  import { readFileSync as readFileSync4 } from "node:fs";
10013
10784
  import { homedir as homedir6 } from "node:os";
10014
- import { join as join7 } from "node:path";
10785
+ import { join as join8 } from "node:path";
10015
10786
  function difficultyToRung(difficulty, thresholds) {
10016
10787
  const b = thresholds.rungBounds;
10017
10788
  if (difficulty >= b.R5) return "R5";
@@ -10036,9 +10807,9 @@ function operatorTierToRung(tier, difficulty, thresholds) {
10036
10807
  if (tier === "best" && difficulty >= thresholds.rungBounds.R5) return "R5";
10037
10808
  return base;
10038
10809
  }
10039
- function readCodexModelsCache({ path: path20 = DEFAULT_CODEX_MODELS_CACHE, read = readFileSync4 } = {}) {
10810
+ function readCodexModelsCache({ path: path23 = DEFAULT_CODEX_MODELS_CACHE, read = readFileSync4 } = {}) {
10040
10811
  try {
10041
- const parsed = JSON.parse(read(path20, "utf8"));
10812
+ const parsed = JSON.parse(read(path23, "utf8"));
10042
10813
  return Array.isArray(parsed?.models) ? parsed : null;
10043
10814
  } catch {
10044
10815
  return null;
@@ -10088,7 +10859,7 @@ var init_effort_policy = __esm({
10088
10859
  init_meta_model_catalog();
10089
10860
  RUNG_ORDER = ["R1", "R2", "R3", "R4", "R5"];
10090
10861
  rungIndex = (rung) => RUNG_ORDER.indexOf(rung);
10091
- DEFAULT_CODEX_MODELS_CACHE = join7(homedir6(), ".codex", "models_cache.json");
10862
+ DEFAULT_CODEX_MODELS_CACHE = join8(homedir6(), ".codex", "models_cache.json");
10092
10863
  }
10093
10864
  });
10094
10865
 
@@ -10220,16 +10991,16 @@ var init_role_cost_shadow = __esm({
10220
10991
  // ../../scripts/virtual-office/code-runner/auto-router/auto-router.mjs
10221
10992
  import { readFileSync as readFileSync5, appendFileSync, mkdirSync as mkdirSync4 } from "node:fs";
10222
10993
  import { homedir as homedir7 } from "node:os";
10223
- import { join as join8, dirname as dirname5 } from "node:path";
10224
- import { fileURLToPath as fileURLToPath5 } from "node:url";
10994
+ import { join as join9, dirname as dirname6 } from "node:path";
10995
+ import { fileURLToPath as fileURLToPath7 } from "node:url";
10225
10996
  function getAutoRouterMode(env2 = process.env) {
10226
10997
  const raw = String(env2.VO_CODE_RUNNER_AUTO_ROUTER || "").trim().toLowerCase();
10227
10998
  return MODES.has(raw) ? raw : "off";
10228
10999
  }
10229
11000
  function loadThresholds() {
10230
11001
  if (!cachedThresholds) {
10231
- const here = dirname5(fileURLToPath5(import.meta.url));
10232
- cachedThresholds = JSON.parse(readFileSync5(join8(here, "thresholds.json"), "utf8"));
11002
+ const here = dirname6(fileURLToPath7(import.meta.url));
11003
+ cachedThresholds = JSON.parse(readFileSync5(join9(here, "thresholds.json"), "utf8"));
10233
11004
  }
10234
11005
  return cachedThresholds;
10235
11006
  }
@@ -10295,15 +11066,15 @@ function formatDecisionReason(decision, maxLen = 480) {
10295
11066
  const s = `[${decision.routerVersion}] ${decision.taskClass} d=${decision.difficulty} c=${decision.confidence} \u2192 ${decision.rung}/${decision.tier}${decision.effort ? ` effort=${decision.effort}` : ""} turns=${decision.maxTurns} $${decision.maxBudgetUsd}${decision.flags.length ? ` [${decision.flags.join(",")}]` : ""} :: ${decision.reasons.join("; ")}`;
10296
11067
  return s.length > maxLen ? `${s.slice(0, maxLen - 1)}\u2026` : s;
10297
11068
  }
10298
- function appendDecisionFallback(decision, { path: path20 = DECISION_FALLBACK_PATH, append = appendFileSync, mkdir: mkdir4 = mkdirSync4, task, thresholds, roleCostInputs } = {}) {
11069
+ function appendDecisionFallback(decision, { path: path23 = DECISION_FALLBACK_PATH, append = appendFileSync, mkdir: mkdir4 = mkdirSync4, task, thresholds, roleCostInputs } = {}) {
10299
11070
  try {
10300
- mkdir4(dirname5(path20), { recursive: true });
10301
- append(path20, `${JSON.stringify(decision)}
11071
+ mkdir4(dirname6(path23), { recursive: true });
11072
+ append(path23, `${JSON.stringify(decision)}
10302
11073
  `, "utf8");
10303
11074
  if (isRouterDecision(decision)) {
10304
11075
  try {
10305
11076
  const records = buildShadowRecords({ decision, task, thresholds: thresholds || loadThresholds(), roleCostInputs });
10306
- for (const record of records) append(path20, `${JSON.stringify(record)}
11077
+ for (const record of records) append(path23, `${JSON.stringify(record)}
10307
11078
  `, "utf8");
10308
11079
  } catch {
10309
11080
  }
@@ -10321,7 +11092,7 @@ var init_auto_router = __esm({
10321
11092
  init_effort_policy();
10322
11093
  init_role_cost_shadow();
10323
11094
  ROUTER_VERSION = "0.1.0";
10324
- DECISION_FALLBACK_PATH = join8(homedir7(), ".claude", "vo-auto-router-decisions.jsonl");
11095
+ DECISION_FALLBACK_PATH = join9(homedir7(), ".claude", "vo-auto-router-decisions.jsonl");
10325
11096
  MODES = /* @__PURE__ */ new Set(["off", "shadow", "on"]);
10326
11097
  cachedThresholds = null;
10327
11098
  isRouterDecision = (d) => Boolean(d && typeof d === "object" && typeof d.taskClass === "string" && typeof d.confidence === "number");
@@ -11294,9 +12065,9 @@ var init_inference_task_runner = __esm({
11294
12065
  });
11295
12066
 
11296
12067
  // ../../scripts/virtual-office/code-runner/isolation-audit.mjs
11297
- import fs10 from "node:fs";
12068
+ import fs12 from "node:fs";
11298
12069
  import fsp11 from "node:fs/promises";
11299
- import path17 from "node:path";
12070
+ import path20 from "node:path";
11300
12071
  async function defaultRun(command, args, cwd, options = {}) {
11301
12072
  return runProcess2(command, args, { cwd, timeout: 6e4, ...options });
11302
12073
  }
@@ -11309,7 +12080,7 @@ async function canonicalRootForWorktree(worktreeDir, run) {
11309
12080
  "--path-format=absolute",
11310
12081
  "--git-common-dir"
11311
12082
  ])).trim();
11312
- const root = path17.dirname(commonDir);
12083
+ const root = path20.dirname(commonDir);
11313
12084
  return samePath2(root, worktreeDir) ? null : root;
11314
12085
  }
11315
12086
  async function snapshot(root, run) {
@@ -11351,21 +12122,21 @@ async function changedPaths(root, run) {
11351
12122
  }
11352
12123
  async function quarantineCanonicalWrites({ baseline, worktreeDir, taskId, run, now }) {
11353
12124
  const paths = await changedPaths(baseline.root, run);
11354
- const quarantineDir = path17.join(
11355
- path17.dirname(worktreeDir),
12125
+ const quarantineDir = path20.join(
12126
+ path20.dirname(worktreeDir),
11356
12127
  ".canonical-recovery",
11357
12128
  `${String(taskId || "unknown").replace(/[^a-z0-9-]/gi, "-")}-${now().toISOString().replace(/[:.]/g, "-")}`
11358
12129
  );
11359
12130
  await fsp11.mkdir(quarantineDir, { recursive: true });
11360
12131
  const patch = await git2(run, baseline.root, ["diff", "--binary", "HEAD"], { raw: true });
11361
- await fsp11.writeFile(path17.join(quarantineDir, "tracked.patch"), patch, "utf8");
12132
+ await fsp11.writeFile(path20.join(quarantineDir, "tracked.patch"), patch, "utf8");
11362
12133
  for (const relative of paths.untracked) {
11363
- const source = path17.join(baseline.root, relative);
11364
- const target = path17.join(quarantineDir, "untracked", relative);
11365
- await fsp11.mkdir(path17.dirname(target), { recursive: true });
12134
+ const source = path20.join(baseline.root, relative);
12135
+ const target = path20.join(quarantineDir, "untracked", relative);
12136
+ await fsp11.mkdir(path20.dirname(target), { recursive: true });
11366
12137
  await fsp11.copyFile(source, target);
11367
12138
  }
11368
- await fsp11.writeFile(path17.join(quarantineDir, "manifest.json"), `${JSON.stringify({
12139
+ await fsp11.writeFile(path20.join(quarantineDir, "manifest.json"), `${JSON.stringify({
11369
12140
  taskId,
11370
12141
  canonicalRoot: baseline.root,
11371
12142
  canonicalHead: baseline.head,
@@ -11387,9 +12158,9 @@ async function restoreExactCanonicalPaths(baseline, evidence, run) {
11387
12158
  ]);
11388
12159
  }
11389
12160
  for (const relative of evidence.untracked) {
11390
- const target = path17.resolve(baseline.root, relative);
11391
- const prefix = `${path17.resolve(baseline.root)}${path17.sep}`;
11392
- if (!target.startsWith(prefix) || !fs10.existsSync(target)) continue;
12161
+ const target = path20.resolve(baseline.root, relative);
12162
+ const prefix = `${path20.resolve(baseline.root)}${path20.sep}`;
12163
+ if (!target.startsWith(prefix) || !fs12.existsSync(target)) continue;
11393
12164
  await fsp11.rm(target, { force: true });
11394
12165
  }
11395
12166
  }
@@ -11425,7 +12196,7 @@ var init_isolation_audit = __esm({
11425
12196
  init_process_runner2();
11426
12197
  splitZ2 = (value) => String(value || "").split("\0").map((item) => item.trim()).filter(Boolean);
11427
12198
  samePath2 = (left, right) => {
11428
- const [a, b] = [left, right].map((value) => path17.resolve(value));
12199
+ const [a, b] = [left, right].map((value) => path20.resolve(value));
11429
12200
  return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
11430
12201
  };
11431
12202
  }
@@ -11785,7 +12556,7 @@ var init_publication_outcome = __esm({
11785
12556
 
11786
12557
  // ../../scripts/virtual-office/code-runner/committed-scratch-cleanup.mjs
11787
12558
  import fsp12 from "node:fs/promises";
11788
- import path18 from "node:path";
12559
+ import path21 from "node:path";
11789
12560
  function defaultRun2(command, args, cwd, options = {}) {
11790
12561
  return runProcess2(command, args, { cwd, ...options });
11791
12562
  }
@@ -11793,13 +12564,13 @@ async function resolveSafeScratchTarget(worktreeDir, file) {
11793
12564
  if (!isAgentScratch(file)) {
11794
12565
  throw new Error(`refusing to remove non-scratch publication path: ${file}`);
11795
12566
  }
11796
- const root = path18.resolve(worktreeDir);
11797
- const target = path18.resolve(root, file);
11798
- const relative = path18.relative(root, target);
11799
- if (!relative || relative.startsWith(`..${path18.sep}`) || path18.isAbsolute(relative)) {
12567
+ const root = path21.resolve(worktreeDir);
12568
+ const target = path21.resolve(root, file);
12569
+ const relative = path21.relative(root, target);
12570
+ if (!relative || relative.startsWith(`..${path21.sep}`) || path21.isAbsolute(relative)) {
11800
12571
  throw new Error(`refusing to remove publication scratch outside worktree: ${file}`);
11801
12572
  }
11802
- for (let cursor = target; cursor !== root; cursor = path18.dirname(cursor)) {
12573
+ for (let cursor = target; cursor !== root; cursor = path21.dirname(cursor)) {
11803
12574
  try {
11804
12575
  if ((await fsp12.lstat(cursor)).isSymbolicLink()) {
11805
12576
  throw new Error(`refusing to follow symlink while removing publication scratch: ${file}`);
@@ -11919,9 +12690,9 @@ var init_publication_scope = __esm({
11919
12690
  });
11920
12691
 
11921
12692
  // ../../scripts/virtual-office/code-runner/recovery-ledger.mjs
11922
- import fs11 from "node:fs";
12693
+ import fs13 from "node:fs";
11923
12694
  import fsp13 from "node:fs/promises";
11924
- import path19 from "node:path";
12695
+ import path22 from "node:path";
11925
12696
  function recoveryTaskId(prompt) {
11926
12697
  const match = String(prompt || "").match(/VO_RECOVERY_FROM_CODE_TASK:\s*([0-9a-f-]{36})/i);
11927
12698
  return match ? match[1].toLowerCase() : null;
@@ -11935,10 +12706,10 @@ function cloneLeaf(repo) {
11935
12706
  function recoveryLedgerCandidates(repo, clonesRoot2) {
11936
12707
  const leaf = cloneLeaf(repo);
11937
12708
  if (!leaf || !clonesRoot2) return [];
11938
- const canonical = path19.join(clonesRoot2, leaf);
12709
+ const canonical = path22.join(clonesRoot2, leaf);
11939
12710
  return [
11940
- path19.join(clonesRoot2, ".agent-worktrees", leaf, "recovery-ledger.jsonl"),
11941
- path19.join(canonical, ".agent-worktrees", "recovery-ledger.jsonl")
12711
+ path22.join(clonesRoot2, ".agent-worktrees", leaf, "recovery-ledger.jsonl"),
12712
+ path22.join(canonical, ".agent-worktrees", "recovery-ledger.jsonl")
11942
12713
  ];
11943
12714
  }
11944
12715
  async function readLedger(file, readFile5) {
@@ -11957,7 +12728,7 @@ async function readLedger(file, readFile5) {
11957
12728
  async function findPreservedRecovery(task, {
11958
12729
  clonesRoot: clonesRoot2 = process.env.VO_CODE_RUNNER_CLONES_ROOT || "",
11959
12730
  readFile: readFile5 = fsp13.readFile,
11960
- exists = fs11.existsSync
12731
+ exists = fs13.existsSync
11961
12732
  } = {}) {
11962
12733
  const resumedFrom = /^[0-9a-f-]{36}$/iu.test(String(task.resumed_from || "")) ? String(task.resumed_from).toLowerCase() : null;
11963
12734
  const originalTaskId = recoveryTaskId(task.prompt) ?? resumedFrom;
@@ -12272,7 +13043,7 @@ var init_cancellation_probe = __esm({
12272
13043
 
12273
13044
  // ../../scripts/virtual-office/code-runner/detached-economics-spool.mjs
12274
13045
  import { homedir as homedir8 } from "node:os";
12275
- import { dirname as dirname6, join as join9 } from "node:path";
13046
+ import { dirname as dirname7, join as join10 } from "node:path";
12276
13047
  import { mkdir as mkdir3, readFile as readFile4, rename as rename2, writeFile as writeFile3 } from "node:fs/promises";
12277
13048
  function withLock(operation) {
12278
13049
  const result = serialized.then(operation, operation);
@@ -12290,7 +13061,7 @@ async function readEntries(file) {
12290
13061
  }
12291
13062
  }
12292
13063
  async function writeEntries(file, entries) {
12293
- await mkdir3(dirname6(file), { recursive: true });
13064
+ await mkdir3(dirname7(file), { recursive: true });
12294
13065
  const temp = `${file}.${process.pid}.tmp`;
12295
13066
  await writeFile3(temp, `${JSON.stringify(entries)}
12296
13067
  `, "utf8");
@@ -12336,13 +13107,13 @@ var DEFAULT_FILE, serialized;
12336
13107
  var init_detached_economics_spool = __esm({
12337
13108
  "../../scripts/virtual-office/code-runner/detached-economics-spool.mjs"() {
12338
13109
  "use strict";
12339
- DEFAULT_FILE = join9(homedir8(), ".vo", "detached-run-economics.json");
13110
+ DEFAULT_FILE = join10(homedir8(), ".vo", "detached-run-economics.json");
12340
13111
  serialized = Promise.resolve();
12341
13112
  }
12342
13113
  });
12343
13114
 
12344
13115
  // ../../scripts/virtual-office/code-runner/killed-run-outcome.mjs
12345
- import { randomUUID as randomUUID5 } from "node:crypto";
13116
+ import { randomUUID as randomUUID6 } from "node:crypto";
12346
13117
  async function handleKilledRun({
12347
13118
  client,
12348
13119
  id,
@@ -12379,7 +13150,7 @@ async function handleKilledRun({
12379
13150
  };
12380
13151
  }
12381
13152
  if (reason === "claim_authority_changed") {
12382
- const occurrenceId = randomUUID5();
13153
+ const occurrenceId = randomUUID6();
12383
13154
  const economics = {
12384
13155
  occurrence_id: occurrenceId,
12385
13156
  runner_id: runnerId,
@@ -12479,13 +13250,13 @@ var init_runner_runtime_limits = __esm({
12479
13250
  });
12480
13251
 
12481
13252
  // ../../scripts/virtual-office/code-runner/daemon-config.mjs
12482
- import os4 from "node:os";
13253
+ import os5 from "node:os";
12483
13254
  function loadCodeRunnerConfig(env2 = process.env, { log: log2 = () => {
12484
13255
  } } = {}) {
12485
13256
  const servedOperators = parseList(env2.VO_CODE_RUNNER_OPERATOR_IDS);
12486
13257
  const allowAmbientGithub = env2.VO_CODE_RUNNER_ALLOW_AMBIENT_GH === "1";
12487
13258
  return {
12488
- runnerId: env2.VO_CODE_RUNNER_ID || `vo-code-runner-${os4.hostname()}`,
13259
+ runnerId: env2.VO_CODE_RUNNER_ID || `vo-code-runner-${os5.hostname()}`,
12489
13260
  ...resolveRunner(env2, { warn: (message) => log2(`agent-select: ${message}`) }),
12490
13261
  permissionMode: env2.VO_CODE_RUNNER_PERMISSION_MODE || "acceptEdits",
12491
13262
  maxConcurrency: Math.max(1, Number(env2.VO_CODE_TASK_MAX_CONCURRENCY || 2) || 2),
@@ -12495,7 +13266,7 @@ function loadCodeRunnerConfig(env2 = process.env, { log: log2 = () => {
12495
13266
  requireGithubAppAuth: !allowAmbientGithub && servedOperators.length > 0,
12496
13267
  allowAmbientGithub,
12497
13268
  sessionForwardSec: Math.max(0, Number(env2.VO_SESSION_FORWARD_SEC ?? 30) || 0),
12498
- operatorSeed: env2.VO_LOCAL_OPERATOR_SEED || env2.VO_CODE_RUNNER_ID || `local-${os4.hostname()}`,
13269
+ operatorSeed: env2.VO_LOCAL_OPERATOR_SEED || env2.VO_CODE_RUNNER_ID || `local-${os5.hostname()}`,
12499
13270
  cancelPollMs: Math.max(1e3, Number(env2.VO_CODE_RUNNER_CANCEL_POLL_MS || 2500) || 2500),
12500
13271
  maxWallClockMs: resolveMaxWallClockMs(env2.VO_CODE_RUNNER_MAX_WALL_CLOCK_MS),
12501
13272
  watchEnabled: env2.VO_CODE_RUNNER_WATCH !== "0",
@@ -12674,10 +13445,10 @@ var init_task_worktree_preparation = __esm({
12674
13445
  // ../../scripts/virtual-office/code-runner-daemon.mjs
12675
13446
  var code_runner_daemon_exports = {};
12676
13447
  __export(code_runner_daemon_exports, {
12677
- main: () => main
13448
+ main: () => main2
12678
13449
  });
12679
- import { randomUUID as randomUUID6 } from "node:crypto";
12680
- import { fileURLToPath as fileURLToPath6 } from "node:url";
13450
+ import { randomUUID as randomUUID7 } from "node:crypto";
13451
+ import { fileURLToPath as fileURLToPath8 } from "node:url";
12681
13452
  function log(msg) {
12682
13453
  console.log(`[code-runner ${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}`);
12683
13454
  }
@@ -12819,16 +13590,12 @@ async function processOneTask(client, task, cfg, runnerInstanceId, swarmAdmissio
12819
13590
  return;
12820
13591
  }
12821
13592
  if (await taskWasCancelled({ client, id, run, safeProgress, log })) return;
13593
+ if (await gateTestGenTaskOrFail({ client, id, task, files, worktreeDir: wt.worktreeDir, log })) return;
13594
+ if (await enforceCompletionGateOrFail({ client, id, task, worktreeDir: wt.worktreeDir, log })) return;
12822
13595
  const publicationTarget = await resolvePublicationTarget({ task, continuationRestore, worktreeDir: wt.worktreeDir, githubToken, allowAmbientGithubFallback: cfg.allowAmbientGithub });
12823
13596
  const localBranch = await resolveOrCreateBranchAsync(wt.worktreeDir, "vo/code-task");
12824
13597
  const publicationBranch = publicationTarget.targetBranch || localBranch;
12825
- if (!await recordPublicationIntent({
12826
- client,
12827
- id,
12828
- branch: publicationBranch,
12829
- safeProgress,
12830
- log
12831
- })) {
13598
+ if (!await recordPublicationIntent({ client, id, branch: publicationBranch, safeProgress, log })) {
12832
13599
  await reportCancelledRun({ client, id, run, safeProgress, log });
12833
13600
  return;
12834
13601
  }
@@ -12903,10 +13670,10 @@ Closes #${publicationTarget.supersedesPrNumber}` : "";
12903
13670
  }
12904
13671
  }
12905
13672
  }
12906
- async function main({ env: env2 = process.env, once: once2 = false } = {}) {
13673
+ async function main2({ env: env2 = process.env, once: once2 = false } = {}) {
12907
13674
  const cfg = loadCodeRunnerConfig(env2, { log });
12908
13675
  await sweepStaleTaskAttachmentDirectories().catch((error) => log(`stale attachment cleanup failed: ${error.message}`));
12909
- const runnerInstanceId = randomUUID6();
13676
+ const runnerInstanceId = randomUUID7();
12910
13677
  const client = createControlPlaneClient({
12911
13678
  env: env2,
12912
13679
  runnerId: cfg.runnerId,
@@ -13049,7 +13816,7 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
13049
13816
  if (controlServer) controlServer.close();
13050
13817
  log("stopped");
13051
13818
  }
13052
- var RATE_LIMIT_RESUME_ENABLED, sleep2, safeProgress, invokedDirectly;
13819
+ var RATE_LIMIT_RESUME_ENABLED, sleep2, safeProgress, invokedDirectly2;
13053
13820
  var init_code_runner_daemon = __esm({
13054
13821
  "../../scripts/virtual-office/code-runner-daemon.mjs"() {
13055
13822
  "use strict";
@@ -13060,6 +13827,8 @@ var init_code_runner_daemon = __esm({
13060
13827
  init_resolve_runner();
13061
13828
  init_rate_limit_resume();
13062
13829
  init_publish();
13830
+ init_test_gen_gate();
13831
+ init_completion_gate();
13063
13832
  init_orphan_agent_reaper();
13064
13833
  init_publish_async();
13065
13834
  init_task_prompt();
@@ -13097,11 +13866,11 @@ var init_code_runner_daemon = __esm({
13097
13866
  RATE_LIMIT_RESUME_ENABLED = process.env.VO_RATE_LIMIT_RESUME !== "0";
13098
13867
  sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
13099
13868
  safeProgress = makeSafeProgress(log);
13100
- invokedDirectly = process.argv[1] && fileURLToPath6(import.meta.url) === process.argv[1] && // Bundle-safe: self-start only when THIS file is the real entry (not inlined into vo-mcp's runner-cli.js ⇒ double-claim).
13869
+ invokedDirectly2 = process.argv[1] && fileURLToPath8(import.meta.url) === process.argv[1] && // Bundle-safe: self-start only when THIS file is the real entry (not inlined into vo-mcp's runner-cli.js ⇒ double-claim).
13101
13870
  import.meta.url.endsWith("code-runner-daemon.mjs");
13102
- if (invokedDirectly) {
13871
+ if (invokedDirectly2) {
13103
13872
  const once2 = process.argv.includes("--once");
13104
- main({ once: once2 }).catch((err) => {
13873
+ main2({ once: once2 }).catch((err) => {
13105
13874
  console.error("[code-runner] fatal:", err);
13106
13875
  process.exit(1);
13107
13876
  });
@@ -13468,8 +14237,8 @@ var env = {
13468
14237
  ...pairedOperatorId ? { VO_CODE_RUNNER_OPERATOR_IDS: pairedOperatorId } : {}
13469
14238
  };
13470
14239
  var once = process.argv.includes("--once");
13471
- var { main: main2 } = await Promise.resolve().then(() => (init_code_runner_daemon(), code_runner_daemon_exports));
13472
- main2({ env, once }).catch((err) => {
14240
+ var { main: main3 } = await Promise.resolve().then(() => (init_code_runner_daemon(), code_runner_daemon_exports));
14241
+ main3({ env, once }).catch((err) => {
13473
14242
  console.error("[vo-mcp runner] fatal:", err);
13474
14243
  process.exit(1);
13475
14244
  });