@algosuite/vo-mcp 0.2.0-beta.20 → 0.2.0-beta.22

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.
@@ -2241,6 +2241,37 @@ var init_spend_cap_shim = __esm({
2241
2241
  }
2242
2242
  });
2243
2243
 
2244
+ // ../../scripts/virtual-office/code-runner/installation-token.mjs
2245
+ async function fetchInstallationToken({ req, required = false, readOnly = false, repo = null }) {
2246
+ const fail = (reason) => {
2247
+ if (required) throw new Error(`installation-token required: ${reason}`);
2248
+ return null;
2249
+ };
2250
+ try {
2251
+ const res = await req(
2252
+ "POST",
2253
+ "/api/v1/github/installation-token",
2254
+ readOnly ? { scope: "read", ...repo ? { repo } : {} } : {},
2255
+ readOnly ? { timeoutMs: READ_TOKEN_TIMEOUT_MS } : {}
2256
+ );
2257
+ if (!res.ok) return fail(`HTTP ${res.status}`);
2258
+ const json = await res.json();
2259
+ if (!json || !json.token) return fail("missing token");
2260
+ if (readOnly && json.scope !== "read") return fail("control plane did not confirm a read-only grant");
2261
+ return { token: json.token, expiresAt: json.expires_at || null };
2262
+ } catch (err) {
2263
+ if (required) throw err;
2264
+ return null;
2265
+ }
2266
+ }
2267
+ var READ_TOKEN_TIMEOUT_MS;
2268
+ var init_installation_token = __esm({
2269
+ "../../scripts/virtual-office/code-runner/installation-token.mjs"() {
2270
+ "use strict";
2271
+ READ_TOKEN_TIMEOUT_MS = 15e3;
2272
+ }
2273
+ });
2274
+
2244
2275
  // src/runner/control-plane-auth-stub.mjs
2245
2276
  var control_plane_auth_stub_exports = {};
2246
2277
  __export(control_plane_auth_stub_exports, {
@@ -2477,7 +2508,7 @@ function createControlPlaneClient({
2477
2508
  * authenticated operator so the web shows a TRUE "runner online" signal.
2478
2509
  * Best-effort caller; throws on 401/non-ok so the daemon can log + retry.
2479
2510
  */
2480
- async postHeartbeat({ runnerId, runnerInstanceId, operatorId, uptimeSec, activeTasks, maxConcurrency, effectiveConcurrency, measuredTaskSlots, measuredCpuSlots, measuredMemorySlots, version, daemonVersion, defaultAgent, supervisorInstanceId, supervisorVersion, supervisorCapabilities, servedRepos, servedOperators, availableAgents, accountUsage }) {
2511
+ async postHeartbeat({ runnerId, runnerInstanceId, operatorId, uptimeSec, activeTasks, maxConcurrency, effectiveConcurrency, measuredTaskSlots, measuredCpuSlots, measuredMemorySlots, version, daemonVersion, defaultAgent, supervisorInstanceId, supervisorVersion, supervisorCapabilities, servedRepos, servedOperators, availableAgents, accountUsage, availableLocalModels }) {
2481
2512
  const body = { runner_id: runnerId };
2482
2513
  if (runnerInstanceId) body.runner_instance_id = runnerInstanceId;
2483
2514
  if (operatorId) body.operator_id = operatorId;
@@ -2506,6 +2537,9 @@ function createControlPlaneClient({
2506
2537
  if (Array.isArray(accountUsage) && accountUsage.length > 0) {
2507
2538
  body.account_usage = accountUsage;
2508
2539
  }
2540
+ if (Array.isArray(availableLocalModels) && availableLocalModels.length > 0) {
2541
+ body.available_local_models = availableLocalModels;
2542
+ }
2509
2543
  const res = await req("POST", "/api/v1/runner/heartbeat", body, {
2510
2544
  timeoutMs: heartbeatTimeoutMs
2511
2545
  });
@@ -2561,37 +2595,9 @@ function createControlPlaneClient({
2561
2595
  const json = await res.json();
2562
2596
  return json?.action || null;
2563
2597
  },
2564
- /**
2565
- * Mint a short-lived (~1h), repo-scoped GitHub App installation token for
2566
- * THIS runner's operator (M3). The control-plane keys the mint on the
2567
- * authenticated operator (ctx.operator_id), so the token covers only that
2568
- * operator's installation.
2569
- *
2570
- * Returns { token, expiresAt } on success. In legacy/admin mode it returns
2571
- * null on a miss so the caller may use ambient `gh`. In scoped-operator mode
2572
- * callers pass `{ required: true }`, which fails closed instead of letting a
2573
- * missing/mis-scoped installation fall through to the runner machine's `gh`.
2574
- *
2575
- * The minted token is only usable for push + PR if the GitHub App grants
2576
- * BOTH `Contents: write` (git push) AND `Pull requests: write` (gh pr
2577
- * create) — see docs/vo/github-app-setup-2026-06-18.md. A token missing
2578
- * either scope fails at push (→ ambient fallback) or at `gh pr create`.
2579
- */
2580
- async getInstallationToken({ required = false } = {}) {
2581
- const fail = (reason) => {
2582
- if (required) throw new Error(`installation-token required: ${reason}`);
2583
- return null;
2584
- };
2585
- try {
2586
- const res = await req("POST", "/api/v1/github/installation-token", {});
2587
- if (!res.ok) return fail(`HTTP ${res.status}`);
2588
- const json = await res.json();
2589
- if (!json || !json.token) return fail("missing token");
2590
- return { token: json.token, expiresAt: json.expires_at || null };
2591
- } catch (err) {
2592
- if (required) throw err;
2593
- return null;
2594
- }
2598
+ /** Mint a GitHub App installation token — see installation-token.mjs. */
2599
+ async getInstallationToken({ required = false, readOnly = false, repo = null } = {}) {
2600
+ return fetchInstallationToken({ req, required, readOnly, repo });
2595
2601
  },
2596
2602
  /**
2597
2603
  * Read the operator's dispatch-mode config (Fast→Ultracode effort setting).
@@ -2614,6 +2620,7 @@ var cachedFirebaseToken;
2614
2620
  var init_control_plane_client = __esm({
2615
2621
  "../../scripts/virtual-office/code-runner/control-plane-client.mjs"() {
2616
2622
  "use strict";
2623
+ init_installation_token();
2617
2624
  cachedFirebaseToken = null;
2618
2625
  }
2619
2626
  });
@@ -3752,7 +3759,10 @@ var init_agent_key_store = __esm({
3752
3759
  meta: ["MODEL_API_KEY"],
3753
3760
  // Generic OpenAI-compatible runner (bring-your-own model + endpoint): its key
3754
3761
  // is a dedicated var so it never collides with a real OpenAI/Codex key.
3755
- "oai-compat": ["VO_CODE_RUNNER_OAI_API_KEY"]
3762
+ "oai-compat": ["VO_CODE_RUNNER_OAI_API_KEY"],
3763
+ // Sovereign local inference (Ollama / LM Studio). The key is OPTIONAL — most
3764
+ // local servers need none — and exists for locally secured endpoints only.
3765
+ local: ["VO_CODE_RUNNER_LOCAL_API_KEY"]
3756
3766
  };
3757
3767
  PROVIDER_ALIAS = {
3758
3768
  claude: "anthropic",
@@ -3765,7 +3775,10 @@ var init_agent_key_store = __esm({
3765
3775
  spark: "meta",
3766
3776
  "muse-spark": "meta",
3767
3777
  oai: "oai-compat",
3768
- "oai-compat": "oai-compat"
3778
+ "oai-compat": "oai-compat",
3779
+ local: "local",
3780
+ ollama: "local",
3781
+ lmstudio: "local"
3769
3782
  };
3770
3783
  _loadTried2 = false;
3771
3784
  }
@@ -3835,6 +3848,11 @@ function itemText(item) {
3835
3848
  }
3836
3849
  return "";
3837
3850
  }
3851
+ function parseCodexVersion(stdout) {
3852
+ if (typeof stdout !== "string") return null;
3853
+ const match = stdout.match(/\b(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)\b/u);
3854
+ return match ? match[1] : null;
3855
+ }
3838
3856
  function parseCodexEvent(line) {
3839
3857
  const trimmed = String(line || "").trim();
3840
3858
  if (!trimmed) return null;
@@ -3929,7 +3947,7 @@ var init_codex_runner = __esm({
3929
3947
  ...this.getSpawnOptions({ bin }),
3930
3948
  windowsHide: true,
3931
3949
  timeout: 3e3,
3932
- stdio: "ignore"
3950
+ encoding: "utf8"
3933
3951
  });
3934
3952
  if (version.error) {
3935
3953
  return { installed: false, authenticated: false, message: `codex not found on PATH: ${version.error.message}` };
@@ -3937,6 +3955,8 @@ var init_codex_runner = __esm({
3937
3955
  if (version.status !== 0) {
3938
3956
  return { installed: true, authenticated: false, message: "codex exists but --version failed (auth unclear)" };
3939
3957
  }
3958
+ const cliVersion = parseCodexVersion(version.stdout);
3959
+ const versionField = cliVersion ? { version: cliVersion } : {};
3940
3960
  const login = this.spawn(bin, ["login", "status"], {
3941
3961
  ...this.getSpawnOptions({ bin }),
3942
3962
  windowsHide: true,
@@ -3951,18 +3971,21 @@ ${login.stderr || ""}`.trim();
3951
3971
  return {
3952
3972
  installed: true,
3953
3973
  authenticated: true,
3974
+ ...versionField,
3954
3975
  message: "codex API key available (no persisted ChatGPT login)"
3955
3976
  };
3956
3977
  }
3957
3978
  return {
3958
3979
  installed: true,
3959
3980
  authenticated: false,
3981
+ ...versionField,
3960
3982
  message: output || login.error?.message || "codex is installed but not logged in"
3961
3983
  };
3962
3984
  }
3963
3985
  return {
3964
3986
  installed: true,
3965
3987
  authenticated: true,
3988
+ ...versionField,
3966
3989
  message: output || "codex login status succeeded"
3967
3990
  };
3968
3991
  } catch (err) {
@@ -4096,6 +4119,200 @@ var init_cursor_runner = __esm({
4096
4119
  }
4097
4120
  });
4098
4121
 
4122
+ // ../../scripts/virtual-office/code-runner/local-model-runner.mjs
4123
+ function resolveLocalProvider(env2 = process.env) {
4124
+ return String(env2.VO_CODE_RUNNER_LOCAL_PROVIDER || "").trim().toLowerCase() || DEFAULT_LOCAL_PROVIDER;
4125
+ }
4126
+ function setRemoteDesiredLocalModel(model) {
4127
+ const value = typeof model === "string" ? model.trim() : "";
4128
+ remoteDesiredLocalModel = value && isValidLocalModel(value) ? value : "";
4129
+ }
4130
+ function resolveLocalModel(env2 = process.env) {
4131
+ return String(env2.VO_CODE_RUNNER_LOCAL_MODEL || "").trim() || remoteDesiredLocalModel;
4132
+ }
4133
+ function resolveLocalBaseUrl(env2 = process.env) {
4134
+ return String(env2.VO_CODE_RUNNER_LOCAL_BASE_URL || "").trim();
4135
+ }
4136
+ function isValidLocalModel(model) {
4137
+ return LOCAL_MODEL_RE.test(String(model || ""));
4138
+ }
4139
+ function isLoopbackBaseUrl(url) {
4140
+ const raw = String(url || "").trim();
4141
+ if (!/^https?:\/\/[^\s"'`\\]+$/.test(raw)) return false;
4142
+ let parsed;
4143
+ try {
4144
+ parsed = new URL(raw);
4145
+ } catch {
4146
+ return false;
4147
+ }
4148
+ const host = parsed.hostname.toLowerCase();
4149
+ return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
4150
+ }
4151
+ function buildLocalArgs(opts = {}, env2 = process.env) {
4152
+ const provider = resolveLocalProvider(env2);
4153
+ if (!LOCAL_PROVIDERS.includes(provider)) {
4154
+ throw new Error(
4155
+ `local-model runner: unknown VO_CODE_RUNNER_LOCAL_PROVIDER "${provider}" (supported: ${LOCAL_PROVIDERS.join(", ")}).`
4156
+ );
4157
+ }
4158
+ const model = resolveLocalModel(env2);
4159
+ if (!model) {
4160
+ throw new Error(
4161
+ "local-model runner: set VO_CODE_RUNNER_LOCAL_MODEL to a model your local server already has (recommended: gpt-oss:20b \u2014 codex drives its tool loop reliably; generic chat models often cannot edit files agentically), or pick an already-pulled model from the web runner settings. Refusing to run with no explicit model (fail-closed)."
4162
+ );
4163
+ }
4164
+ if (!isValidLocalModel(model)) {
4165
+ throw new Error(`local-model runner: "${model}" is not a valid local model id.`);
4166
+ }
4167
+ const baseUrl = resolveLocalBaseUrl(env2);
4168
+ if (baseUrl && !isLoopbackBaseUrl(baseUrl)) {
4169
+ throw new Error(
4170
+ "local-model runner: 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 for hosted providers."
4171
+ );
4172
+ }
4173
+ const args = [
4174
+ "exec",
4175
+ "--json",
4176
+ "-c",
4177
+ 'approval_policy="never"',
4178
+ "--sandbox",
4179
+ "workspace-write",
4180
+ "--skip-git-repo-check",
4181
+ "--oss",
4182
+ "--local-provider",
4183
+ provider,
4184
+ "--model",
4185
+ model
4186
+ ];
4187
+ if (opts.effort) {
4188
+ args.push("-c", `model_reasoning_effort="${String(opts.effort)}"`);
4189
+ }
4190
+ args.push("-");
4191
+ return args;
4192
+ }
4193
+ function applyLocalAuthEnv(baseEnv = process.env, configEnv = process.env) {
4194
+ const out = withAgentKey("local", baseEnv);
4195
+ const baseUrl = resolveLocalBaseUrl(configEnv);
4196
+ if (baseUrl && isLoopbackBaseUrl(baseUrl) && resolveLocalProvider(configEnv) === "ollama" && !String(out.OLLAMA_HOST || "").trim()) {
4197
+ out.OLLAMA_HOST = baseUrl.replace(/\/+$/, "");
4198
+ }
4199
+ return out;
4200
+ }
4201
+ var LOCAL_API_KEY_ENV, LOCAL_PROVIDERS, DEFAULT_LOCAL_PROVIDER, LOCAL_PROBE_URLS, remoteDesiredLocalModel, LOCAL_MODEL_RE, LocalModelRunner, localModelRunner;
4202
+ var init_local_model_runner = __esm({
4203
+ "../../scripts/virtual-office/code-runner/local-model-runner.mjs"() {
4204
+ "use strict";
4205
+ init_codex_runner();
4206
+ init_agent_key_store();
4207
+ LOCAL_API_KEY_ENV = "VO_CODE_RUNNER_LOCAL_API_KEY";
4208
+ LOCAL_PROVIDERS = ["ollama", "lmstudio"];
4209
+ DEFAULT_LOCAL_PROVIDER = "ollama";
4210
+ LOCAL_PROBE_URLS = {
4211
+ ollama: "http://127.0.0.1:11434/api/version",
4212
+ lmstudio: "http://127.0.0.1:1234/v1/models"
4213
+ };
4214
+ remoteDesiredLocalModel = "";
4215
+ LOCAL_MODEL_RE = new RegExp("^[A-Za-z0-9][A-Za-z0-9._/:-]{0,127}$");
4216
+ LocalModelRunner = class {
4217
+ constructor({
4218
+ spawn: spawn5 = null,
4219
+ resolveBinary = resolveCodexBinary,
4220
+ env: env2 = process.env,
4221
+ fetchImpl = globalThis.fetch
4222
+ } = {}) {
4223
+ this.spawn = spawn5;
4224
+ this.resolveBinary = resolveBinary;
4225
+ this.env = env2;
4226
+ this.fetchImpl = fetchImpl;
4227
+ }
4228
+ /** Codex is the transport binary; the model/endpoint are the user's. */
4229
+ get binary() {
4230
+ return this.resolveBinary();
4231
+ }
4232
+ buildArgs(opts = {}) {
4233
+ return buildLocalArgs(opts, this.env);
4234
+ }
4235
+ /** Codex JSONL events map identically → reuse the proven parser. */
4236
+ parseEvent(line) {
4237
+ return parseCodexEvent(line);
4238
+ }
4239
+ // SECURITY: never shell — see no-shell-spawn.test.mjs. The model id is
4240
+ // control-plane-influenced and validated, but shell:false is the hard floor.
4241
+ getSpawnOptions() {
4242
+ return {
4243
+ shell: false,
4244
+ windowsHide: true,
4245
+ windowsVerbatimArguments: false
4246
+ };
4247
+ }
4248
+ applyAuthEnv(env2 = process.env) {
4249
+ return applyLocalAuthEnv(env2, this.env);
4250
+ }
4251
+ describeAuth(env2 = process.env) {
4252
+ const authEnv = this.applyAuthEnv(env2);
4253
+ const provider = resolveLocalProvider(this.env);
4254
+ const model = resolveLocalModel(this.env) || "<unset>";
4255
+ const hasKey = Boolean(String(authEnv[LOCAL_API_KEY_ENV] || "").trim());
4256
+ return `local provider=${provider} model=${model} key=${hasKey ? "set" : "none (optional)"}`;
4257
+ }
4258
+ /**
4259
+ * Best-effort: codex transport present AND the local inference endpoint
4260
+ * answers. Never throws, never spends tokens; the endpoint probe is bounded
4261
+ * to 1.5s so a stopped Ollama can't hang availability checks.
4262
+ */
4263
+ async checkAuth() {
4264
+ const provider = resolveLocalProvider(this.env);
4265
+ if (!LOCAL_PROVIDERS.includes(provider)) {
4266
+ return {
4267
+ installed: false,
4268
+ authenticated: false,
4269
+ message: `unknown local provider "${provider}" (supported: ${LOCAL_PROVIDERS.join(", ")})`
4270
+ };
4271
+ }
4272
+ const model = resolveLocalModel(this.env);
4273
+ const override = resolveLocalBaseUrl(this.env);
4274
+ if (override && !isLoopbackBaseUrl(override)) {
4275
+ return {
4276
+ installed: true,
4277
+ authenticated: false,
4278
+ message: "VO_CODE_RUNNER_LOCAL_BASE_URL is not loopback \u2014 refused (fail-closed)"
4279
+ };
4280
+ }
4281
+ const probeUrl = provider === "ollama" && override ? `${override.replace(/\/+$/, "")}/api/version` : LOCAL_PROBE_URLS[provider];
4282
+ let endpointUp = false;
4283
+ let probeNote = "";
4284
+ try {
4285
+ const res = await this.fetchImpl(probeUrl, { signal: AbortSignal.timeout(1500) });
4286
+ endpointUp = Boolean(res?.ok);
4287
+ if (!endpointUp) probeNote = `endpoint ${probeUrl} answered HTTP ${res?.status}`;
4288
+ } catch {
4289
+ probeNote = `no local inference server answering at ${probeUrl}`;
4290
+ }
4291
+ if (!endpointUp) {
4292
+ return {
4293
+ installed: true,
4294
+ authenticated: false,
4295
+ message: `${probeNote} \u2014 start ${provider === "ollama" ? "Ollama" : "LM Studio"} first`
4296
+ };
4297
+ }
4298
+ if (!model) {
4299
+ return {
4300
+ installed: true,
4301
+ authenticated: false,
4302
+ message: `${provider} is running but VO_CODE_RUNNER_LOCAL_MODEL is not set`
4303
+ };
4304
+ }
4305
+ return {
4306
+ installed: true,
4307
+ authenticated: true,
4308
+ message: `${provider} reachable; model "${model}" configured (local-only, no cloud spend)`
4309
+ };
4310
+ }
4311
+ };
4312
+ localModelRunner = new LocalModelRunner();
4313
+ }
4314
+ });
4315
+
4099
4316
  // ../../scripts/virtual-office/code-runner/meta-runner.mjs
4100
4317
  function applyMetaAuthEnv(baseEnv = process.env) {
4101
4318
  const out = withAgentKey("meta", baseEnv);
@@ -4311,6 +4528,7 @@ var init_resolve_runner = __esm({
4311
4528
  init_claude_runner();
4312
4529
  init_codex_runner();
4313
4530
  init_cursor_runner();
4531
+ init_local_model_runner();
4314
4532
  init_meta_runner();
4315
4533
  init_openai_compatible_runner();
4316
4534
  init_agent_runner_interface();
@@ -4319,6 +4537,9 @@ var init_resolve_runner = __esm({
4319
4537
  claude: claudeRunner,
4320
4538
  codex: codexRunner,
4321
4539
  cursor: cursorRunner,
4540
+ // `local` = sovereign local inference (Ollama / LM Studio; Mistral/Llama-class
4541
+ // models) — free tier of the pricing pivot; nothing leaves the user's machine.
4542
+ local: localModelRunner,
4322
4543
  meta: metaRunner,
4323
4544
  oai: openaiCompatibleRunner
4324
4545
  };
@@ -5983,6 +6204,12 @@ function makeLoopTicks({
5983
6204
  applyCapacity: () => false,
5984
6205
  heartbeatFields: () => ({})
5985
6206
  },
6207
+ // Track 1: served-local-model reporting + desired-model echo application
6208
+ // (local-model-remote-config.mjs). No-op defaults keep old callers working.
6209
+ localModelController = {
6210
+ applyRemoteConfig: () => false,
6211
+ heartbeatFields: () => ({})
6212
+ },
5986
6213
  // Cached agent-availability provider (agent-availability.mjs); returns null
5987
6214
  // until the first probe completes — the heartbeat simply omits the field.
5988
6215
  getAgentAvailability = () => null,
@@ -6020,7 +6247,10 @@ function makeLoopTicks({
6020
6247
  } catch (error) {
6021
6248
  request = Promise.reject(error);
6022
6249
  }
6023
- request.then((response) => capacityController.applyCapacity(response?.capacity, nextPayload.operatorId)).catch((e) => log3(`heartbeat failed: ${e.message}`)).finally(() => {
6250
+ request.then((response) => {
6251
+ capacityController.applyCapacity(response?.capacity, nextPayload.operatorId);
6252
+ localModelController.applyRemoteConfig(response?.local_model, nextPayload.operatorId);
6253
+ }).catch((e) => log3(`heartbeat failed: ${e.message}`)).finally(() => {
6024
6254
  for (const done of waiters) done();
6025
6255
  if (state.pending) {
6026
6256
  const pending = state.pending;
@@ -6062,6 +6292,7 @@ function makeLoopTicks({
6062
6292
  const supervisorVersion = String(env2.VO_RUNNER_SUPERVISOR_VERSION || "").trim().slice(0, 40);
6063
6293
  const supervisorCapabilities = String(env2.VO_RUNNER_SUPERVISOR_CAPABILITIES || "").split(",").map((value) => value.trim()).filter(Boolean).slice(0, 8);
6064
6294
  const capacityFields = capacityController.heartbeatFields();
6295
+ const localModelFields = localModelController.heartbeatFields();
6065
6296
  const baseHeartbeat = {
6066
6297
  runnerId: cfg.runnerId,
6067
6298
  ...runnerInstanceId ? { runnerInstanceId } : {},
@@ -6078,7 +6309,8 @@ function makeLoopTicks({
6078
6309
  uptimeSec: Math.floor(process.uptime()),
6079
6310
  activeTasks: getActive(),
6080
6311
  maxConcurrency: cfg.maxConcurrency,
6081
- ...capacityFields
6312
+ ...capacityFields,
6313
+ ...localModelFields
6082
6314
  };
6083
6315
  const operatorIds = servedOperators.length > 0 ? servedOperators : [void 0];
6084
6316
  for (const operatorId of operatorIds) {
@@ -6204,7 +6436,12 @@ async function collectAgentAvailability({
6204
6436
  const probes = agents.map(async (agent) => {
6205
6437
  try {
6206
6438
  const r = await runnerFor(agent).checkAuth();
6207
- return { agent, installed: Boolean(r?.installed), authenticated: Boolean(r?.authenticated) };
6439
+ return {
6440
+ agent,
6441
+ installed: Boolean(r?.installed),
6442
+ authenticated: Boolean(r?.authenticated),
6443
+ ...typeof r?.version === "string" && r.version ? { version: r.version } : {}
6444
+ };
6208
6445
  } catch {
6209
6446
  return { agent, installed: false, authenticated: false };
6210
6447
  }
@@ -6245,6 +6482,127 @@ var init_agent_availability = __esm({
6245
6482
  }
6246
6483
  });
6247
6484
 
6485
+ // ../../scripts/virtual-office/code-runner/local-model-remote-config.mjs
6486
+ async function listServedLocalModels({
6487
+ env: env2 = process.env,
6488
+ fetchImpl = globalThis.fetch,
6489
+ timeoutMs = 1500
6490
+ } = {}) {
6491
+ const provider = resolveLocalProvider(env2);
6492
+ if (!LOCAL_PROVIDERS.includes(provider)) return null;
6493
+ const override = resolveLocalBaseUrl(env2);
6494
+ if (override && !isLoopbackBaseUrl(override)) return null;
6495
+ const url = provider === "ollama" && override ? `${override.replace(/\/+$/, "")}/api/tags` : SERVED_MODELS_PROBE_URLS[provider];
6496
+ try {
6497
+ const res = await fetchImpl(url, { signal: AbortSignal.timeout(timeoutMs) });
6498
+ if (!res?.ok) return null;
6499
+ const json = await res.json();
6500
+ const names = provider === "ollama" ? Array.isArray(json?.models) ? json.models.map((m) => m?.name) : null : Array.isArray(json?.data) ? json.data.map((m) => m?.id) : null;
6501
+ if (!names) return null;
6502
+ return names.filter((name) => typeof name === "string" && isValidLocalModel(name)).slice(0, MAX_REPORTED_MODELS);
6503
+ } catch {
6504
+ return null;
6505
+ }
6506
+ }
6507
+ function createLocalModelRemoteController({
6508
+ env: env2 = process.env,
6509
+ fetchImpl = globalThis.fetch,
6510
+ log: log3 = () => {
6511
+ },
6512
+ probeIntervalMs = 6e4,
6513
+ now = () => Date.now(),
6514
+ apply = setRemoteDesiredLocalModel,
6515
+ listModels = listServedLocalModels
6516
+ } = {}) {
6517
+ let served = null;
6518
+ const byOperator = /* @__PURE__ */ new Map();
6519
+ let probing = false;
6520
+ let lastProbeAt = 0;
6521
+ let warnedUnserved = "";
6522
+ let warnedConflict = "";
6523
+ function desiredModel() {
6524
+ const models = [...new Set(
6525
+ [...byOperator.values()].map((s) => s.desired).filter((m) => typeof m === "string" && m)
6526
+ )];
6527
+ if (models.length > 1) {
6528
+ const key = models.slice().sort().join(",");
6529
+ if (warnedConflict !== key) {
6530
+ warnedConflict = key;
6531
+ log3(`local-model remote config: conflicting desired models across served operators (${key}) \u2014 applying none`);
6532
+ }
6533
+ return null;
6534
+ }
6535
+ return models[0] ?? null;
6536
+ }
6537
+ function syncEffective() {
6538
+ const desired = desiredModel();
6539
+ const effective = desired && Array.isArray(served) && served.includes(desired) ? desired : "";
6540
+ apply(effective);
6541
+ if (desired && !effective && warnedUnserved !== desired) {
6542
+ warnedUnserved = desired;
6543
+ log3(
6544
+ `local-model remote config: "${desired}" is not served by the local inference server \u2014 ignored (never auto-pull; pull it locally first)`
6545
+ );
6546
+ }
6547
+ return effective;
6548
+ }
6549
+ function refreshServedModels() {
6550
+ if (probing || now() - lastProbeAt < probeIntervalMs) return;
6551
+ probing = true;
6552
+ lastProbeAt = now();
6553
+ Promise.resolve(listModels({ env: env2, fetchImpl })).then((models) => {
6554
+ if (Array.isArray(models)) served = models;
6555
+ syncEffective();
6556
+ }).catch(() => {
6557
+ }).finally(() => {
6558
+ probing = false;
6559
+ });
6560
+ }
6561
+ return {
6562
+ /** Heartbeat payload extras; also kicks the throttled background probe. */
6563
+ heartbeatFields() {
6564
+ refreshServedModels();
6565
+ return Array.isArray(served) && served.length > 0 ? { availableLocalModels: served } : {};
6566
+ },
6567
+ /**
6568
+ * Apply one operator's heartbeat-response echo
6569
+ * { schema_version, desired_local_model, revision }. Revision ordering is
6570
+ * PER OPERATOR — counters are independent across operator scopes.
6571
+ */
6572
+ applyRemoteConfig(echo, operatorId = "") {
6573
+ const scope = String(operatorId || "");
6574
+ const previous = byOperator.get(scope);
6575
+ if (!echo || echo.schema_version !== 1 || !Number.isInteger(echo.revision) || previous && echo.revision < previous.revision) {
6576
+ return false;
6577
+ }
6578
+ const model = echo.desired_local_model;
6579
+ if (model !== null && (typeof model !== "string" || !isValidLocalModel(model))) {
6580
+ return false;
6581
+ }
6582
+ byOperator.set(scope, { revision: echo.revision, desired: model });
6583
+ syncEffective();
6584
+ return true;
6585
+ },
6586
+ snapshot: () => ({
6587
+ served,
6588
+ desired: desiredModel(),
6589
+ operators: Object.fromEntries(byOperator)
6590
+ })
6591
+ };
6592
+ }
6593
+ var SERVED_MODELS_PROBE_URLS, MAX_REPORTED_MODELS;
6594
+ var init_local_model_remote_config = __esm({
6595
+ "../../scripts/virtual-office/code-runner/local-model-remote-config.mjs"() {
6596
+ "use strict";
6597
+ init_local_model_runner();
6598
+ SERVED_MODELS_PROBE_URLS = {
6599
+ ollama: "http://127.0.0.1:11434/api/tags",
6600
+ lmstudio: "http://127.0.0.1:1234/v1/models"
6601
+ };
6602
+ MAX_REPORTED_MODELS = 50;
6603
+ }
6604
+ });
6605
+
6248
6606
  // ../../scripts/virtual-office/code-runner/account-usage.mjs
6249
6607
  import { spawn as spawn4 } from "node:child_process";
6250
6608
  import fs6 from "node:fs";
@@ -6854,7 +7212,8 @@ function makeWatchRunner({
6854
7212
  maxFixAttempts,
6855
7213
  repairEvidence = readCiRepairEvidence,
6856
7214
  stateFile = DEFAULT_STATE_FILE,
6857
- autoMergeEnabled = process.env.VO_CODE_RUNNER_ARM_AUTOMERGE === "1"
7215
+ // Default ON (operator directive 2026-07-24); VO_CODE_RUNNER_ARM_AUTOMERGE=0 opts out.
7216
+ autoMergeEnabled = process.env.VO_CODE_RUNNER_ARM_AUTOMERGE !== "0"
6858
7217
  }) {
6859
7218
  return () => runWatchCycle({
6860
7219
  viewPr,
@@ -7511,7 +7870,7 @@ var init_model_router = __esm({
7511
7870
  "use strict";
7512
7871
  init_model_registry();
7513
7872
  init_meta_model_catalog();
7514
- TASK_MODEL_AGENTS = ["claude", "codex", "cursor", "meta"];
7873
+ TASK_MODEL_AGENTS = ["claude", "codex", "cursor", "local", "meta"];
7515
7874
  DEFAULT_AGENT2 = "claude";
7516
7875
  AGENT_TIER_FAMILIES = {
7517
7876
  claude: {
@@ -7533,6 +7892,13 @@ var init_model_router = __esm({
7533
7892
  mid: null,
7534
7893
  best: null
7535
7894
  },
7895
+ // Local models are machine-specific pulls (Ollama / LM Studio); the runner's
7896
+ // VO_CODE_RUNNER_LOCAL_MODEL is the source of truth, not the registry.
7897
+ local: {
7898
+ cheap: null,
7899
+ mid: null,
7900
+ best: null
7901
+ },
7536
7902
  meta: {
7537
7903
  cheap: null,
7538
7904
  mid: null,
@@ -7555,6 +7921,11 @@ var init_model_router = __esm({
7555
7921
  mid: null,
7556
7922
  best: null
7557
7923
  },
7924
+ local: {
7925
+ cheap: null,
7926
+ mid: null,
7927
+ best: null
7928
+ },
7558
7929
  meta: {
7559
7930
  cheap: resolveMetaModelForTier("cheap"),
7560
7931
  mid: resolveMetaModelForTier("mid"),
@@ -7573,6 +7944,16 @@ var init_model_router = __esm({
7573
7944
  // cursor-runner. Restrict to the shape a model id actually has so a payload
7574
7945
  // like `x & powershell -enc ...` is rejected before it reaches a spawn.
7575
7946
  cursor: (model) => /^[A-Za-z0-9][A-Za-z0-9._:@\[\]-]{0,79}$/.test(String(model || "")),
7947
+ // SECURITY: the local lane accepts NO remote model pins — return false for
7948
+ // EVERY value so control-plane `task.model` can never influence what runs on
7949
+ // the user's machine. codex `--oss` AUTO-PULLS missing models (live-verified
7950
+ // 2026-07-24: a single --model argument downloaded 397MB unprompted), so an
7951
+ // honored pin like "llama3.1:405b" (231GB, valid id shape) would let any
7952
+ // tenant member remotely fill a BYO runner owner's disk. The machine-local
7953
+ // env (VO_CODE_RUNNER_LOCAL_MODEL — the owner's own choice in the app) is
7954
+ // the ONLY model authority, mirroring the runner-release-target principle
7955
+ // that browser input is never local-execution authority.
7956
+ local: () => false,
7576
7957
  meta: (model) => /^muse-spark-[A-Za-z0-9._:@\[\]-]{0,79}$/i.test(String(model || ""))
7577
7958
  };
7578
7959
  }
@@ -8461,11 +8842,41 @@ var init_reconnect_backoff = __esm({
8461
8842
  }
8462
8843
  });
8463
8844
 
8845
+ // ../../scripts/virtual-office/code-runner/redact-tokens.mjs
8846
+ function redactSecrets(text) {
8847
+ if (typeof text !== "string" || text.length === 0) return text;
8848
+ let out = text;
8849
+ for (const [re, mask] of TOKEN_PATTERNS) out = out.replace(re, mask);
8850
+ return out;
8851
+ }
8852
+ function redactPatch(patch) {
8853
+ if (!patch || typeof patch !== "object") return patch;
8854
+ const out = Array.isArray(patch) ? [...patch] : { ...patch };
8855
+ for (const [k, v] of Object.entries(out)) {
8856
+ if (typeof v === "string") out[k] = redactSecrets(v);
8857
+ else if (v && typeof v === "object") out[k] = redactPatch(v);
8858
+ }
8859
+ return out;
8860
+ }
8861
+ var TOKEN_PATTERNS;
8862
+ var init_redact_tokens = __esm({
8863
+ "../../scripts/virtual-office/code-runner/redact-tokens.mjs"() {
8864
+ "use strict";
8865
+ TOKEN_PATTERNS = [
8866
+ [/\b(?:gh[oprsu]|vocred|npm)_[A-Za-z0-9._-]{10,}\b/gu, "[REDACTED]"],
8867
+ [/\bgithub_pat_[A-Za-z0-9_]{10,}\b/gu, "[REDACTED]"],
8868
+ // Keep the scheme so the line still reads as an auth header, matching
8869
+ // sanitizeMaintenanceDiagnostic's behaviour.
8870
+ [/\bBearer\s+[A-Za-z0-9._~+/-]{10,}=*/giu, "Bearer [REDACTED]"]
8871
+ ];
8872
+ }
8873
+ });
8874
+
8464
8875
  // ../../scripts/virtual-office/code-runner/task-helpers.mjs
8465
8876
  function makeSafeProgress(log3) {
8466
8877
  return async (client, id, patch) => {
8467
8878
  try {
8468
- const r = await client.postProgress(id, patch);
8879
+ const r = await client.postProgress(id, redactPatch(patch));
8469
8880
  if (r && r.terminal) log3(`task ${id} is terminal server-side; stopping updates`);
8470
8881
  return r;
8471
8882
  } catch (err) {
@@ -8491,20 +8902,34 @@ function buildPrBody(task, run, files, { armAutoMerge = false } = {}) {
8491
8902
  "### Prompt",
8492
8903
  "",
8493
8904
  "```",
8494
- String(task.prompt).slice(0, 2e3),
8905
+ redactSecrets(String(task.prompt)).slice(0, 2e3),
8495
8906
  "```",
8496
8907
  "",
8497
8908
  "### Agent summary",
8498
8909
  "",
8499
- String(run.summary || "").slice(0, 2e3),
8910
+ redactSecrets(String(run.summary || "")).slice(0, 2e3),
8500
8911
  "",
8501
8912
  "---",
8502
8913
  armAutoMerge ? "_Opened by the AlgoHQ code-runner daemon. After CI passes, the watcher must obtain a durable consensus receipt and merge the exact verified SHA._" : "_Opened by the AlgoHQ code-runner daemon. This PR awaits the verify-before-act gate / operator review \u2014 it is NOT auto-merged._"
8503
8914
  ].filter((l) => l !== "").join("\n");
8504
8915
  }
8916
+ async function mintRunnerGithubTokens({ client, taskId, log: log3, repo = null, requirePublish = false }) {
8917
+ const publishToken = (await client.getInstallationToken({ required: requirePublish }))?.token ?? null;
8918
+ let agentReadToken = null;
8919
+ let reason = null;
8920
+ try {
8921
+ agentReadToken = (await client.getInstallationToken({ readOnly: true, repo }))?.token ?? null;
8922
+ if (!agentReadToken) reason = "control plane returned no confirmed read-only grant";
8923
+ } catch (err) {
8924
+ reason = err?.message ?? String(err);
8925
+ }
8926
+ if (!agentReadToken) log3(`task ${taskId}: no GitHub read access for the agent (${reason}); it cannot read a private repo`);
8927
+ return { publishToken, agentReadToken };
8928
+ }
8505
8929
  var init_task_helpers = __esm({
8506
8930
  "../../scripts/virtual-office/code-runner/task-helpers.mjs"() {
8507
8931
  "use strict";
8932
+ init_redact_tokens();
8508
8933
  }
8509
8934
  });
8510
8935
 
@@ -8520,8 +8945,12 @@ function safeBaseEnv(env2 = {}) {
8520
8945
  }
8521
8946
  return result;
8522
8947
  }
8523
- function buildAgentProcessEnv(env2, { agent = "agent", runnerId = "vo-runner", taskId = "task" } = {}) {
8948
+ function buildAgentProcessEnv(env2, { agent = "agent", runnerId = "vo-runner", taskId = "task", githubReadToken = null } = {}) {
8524
8949
  const base = safeBaseEnv(env2);
8950
+ if (typeof githubReadToken === "string" && githubReadToken) {
8951
+ base.GH_TOKEN = githubReadToken;
8952
+ base.GITHUB_TOKEN = githubReadToken;
8953
+ }
8525
8954
  if (String(env2?.AGENT_ID || "").trim()) return { ...base, AGENT_ID: env2.AGENT_ID };
8526
8955
  const generated = [
8527
8956
  "vo",
@@ -8569,6 +8998,198 @@ var init_agent_process_env = __esm({
8569
8998
  }
8570
8999
  });
8571
9000
 
9001
+ // ../../scripts/virtual-office/code-runner/inference-executor.mjs
9002
+ function buildChatBody({ messages, prompt, model, maxTokens, temperature } = {}) {
9003
+ const msgs = Array.isArray(messages) && messages.length > 0 ? messages : [{ role: "user", content: String(prompt ?? "") }];
9004
+ if (msgs.length > MAX_INFERENCE_MESSAGES) {
9005
+ throw new Error(`inference: too many messages (${msgs.length} > ${MAX_INFERENCE_MESSAGES})`);
9006
+ }
9007
+ const totalChars = msgs.reduce((n, m) => n + String(m?.content ?? "").length, 0);
9008
+ if (totalChars > MAX_INFERENCE_PROMPT_CHARS) {
9009
+ throw new Error(`inference: prompt too large (${totalChars} > ${MAX_INFERENCE_PROMPT_CHARS} chars)`);
9010
+ }
9011
+ if (!model || typeof model !== "string") {
9012
+ throw new Error("inference: a model id is required");
9013
+ }
9014
+ const body = {
9015
+ model,
9016
+ messages: msgs.map((m) => ({ role: String(m.role || "user"), content: String(m.content ?? "") })),
9017
+ stream: false,
9018
+ max_tokens: Number.isInteger(maxTokens) && maxTokens > 0 ? maxTokens : DEFAULT_MAX_OUTPUT_TOKENS
9019
+ };
9020
+ if (Number.isFinite(temperature)) body.temperature = temperature;
9021
+ return body;
9022
+ }
9023
+ function resolveInferenceEndpoint({ providerMode, baseUrl } = {}) {
9024
+ const mode = String(providerMode || "local").toLowerCase();
9025
+ if (mode !== "local") {
9026
+ throw new Error(`inference: unsupported provider mode "${mode}" (this slice is local-only)`);
9027
+ }
9028
+ const url = String(baseUrl || DEFAULT_LOCAL_INFERENCE_BASE_URL).replace(/\/+$/, "");
9029
+ if (!isLoopbackBaseUrl(url)) {
9030
+ throw new Error(`inference: local base_url must be loopback (localhost/127.0.0.1/[::1]); got "${url}"`);
9031
+ }
9032
+ return url;
9033
+ }
9034
+ function extractCompletion(json) {
9035
+ const choice = json?.choices?.[0];
9036
+ const text = choice?.message?.content ?? choice?.text ?? "";
9037
+ if (typeof text !== "string" || text.length === 0) {
9038
+ throw new Error("inference: provider returned no completion text");
9039
+ }
9040
+ const usage = json?.usage ?? null;
9041
+ return {
9042
+ text,
9043
+ usage: usage ? {
9044
+ input_tokens: Number(usage.prompt_tokens) || 0,
9045
+ output_tokens: Number(usage.completion_tokens) || 0,
9046
+ total_tokens: Number(usage.total_tokens) || 0
9047
+ } : null,
9048
+ finish_reason: choice?.finish_reason ?? null
9049
+ };
9050
+ }
9051
+ async function runInference({ providerMode = "local", baseUrl, model, prompt, messages, maxTokens, temperature, timeoutMs = 12e4 } = {}, { fetchImpl = globalThis.fetch } = {}) {
9052
+ const endpoint = resolveInferenceEndpoint({ providerMode, baseUrl });
9053
+ const body = buildChatBody({ messages, prompt, model, maxTokens, temperature });
9054
+ const res = await fetchImpl(`${endpoint}/chat/completions`, {
9055
+ method: "POST",
9056
+ headers: { "Content-Type": "application/json" },
9057
+ body: JSON.stringify(body),
9058
+ signal: AbortSignal.timeout(timeoutMs)
9059
+ });
9060
+ if (!res?.ok) {
9061
+ const status = res?.status ?? "ERR";
9062
+ let detail = "";
9063
+ try {
9064
+ detail = (await res.text()).slice(0, 300);
9065
+ } catch {
9066
+ }
9067
+ throw new Error(`inference: endpoint returned HTTP ${status}${detail ? ` \u2014 ${detail}` : ""}`);
9068
+ }
9069
+ const json = await res.json();
9070
+ const { text, usage, finish_reason } = extractCompletion(json);
9071
+ return { text, usage, finish_reason, model, endpoint };
9072
+ }
9073
+ var DEFAULT_LOCAL_INFERENCE_BASE_URL, MAX_INFERENCE_MESSAGES, MAX_INFERENCE_PROMPT_CHARS, DEFAULT_MAX_OUTPUT_TOKENS;
9074
+ var init_inference_executor = __esm({
9075
+ "../../scripts/virtual-office/code-runner/inference-executor.mjs"() {
9076
+ "use strict";
9077
+ init_local_model_runner();
9078
+ DEFAULT_LOCAL_INFERENCE_BASE_URL = "http://127.0.0.1:11434/v1";
9079
+ MAX_INFERENCE_MESSAGES = 200;
9080
+ MAX_INFERENCE_PROMPT_CHARS = 1e5;
9081
+ DEFAULT_MAX_OUTPUT_TOKENS = 1024;
9082
+ }
9083
+ });
9084
+
9085
+ // ../../scripts/virtual-office/code-runner/inference-task-handler.mjs
9086
+ async function handleInferenceTask(task, { runInferenceImpl = runInference, now = () => Date.now() } = {}) {
9087
+ const started = now();
9088
+ try {
9089
+ const result = await runInferenceImpl({
9090
+ providerMode: "local",
9091
+ baseUrl: task.base_url,
9092
+ model: task.model,
9093
+ prompt: task.prompt,
9094
+ messages: task.messages,
9095
+ maxTokens: task.max_output_tokens,
9096
+ temperature: task.temperature
9097
+ });
9098
+ const text = String(result.text || "").slice(0, MAX_RESULT_CHARS);
9099
+ return {
9100
+ ok: true,
9101
+ text,
9102
+ usage: result.usage ?? null,
9103
+ finish_reason: result.finish_reason ?? null,
9104
+ provider_mode: "local",
9105
+ // Structural $0: no AlgoSuite provider call happened, so nothing to charge.
9106
+ billable: false,
9107
+ duration_ms: now() - started
9108
+ };
9109
+ } catch (err) {
9110
+ return {
9111
+ ok: false,
9112
+ provider_mode: "local",
9113
+ error: err instanceof Error ? err.message : String(err),
9114
+ duration_ms: now() - started
9115
+ };
9116
+ }
9117
+ }
9118
+ var MAX_RESULT_CHARS;
9119
+ var init_inference_task_handler = __esm({
9120
+ "../../scripts/virtual-office/code-runner/inference-task-handler.mjs"() {
9121
+ "use strict";
9122
+ init_inference_executor();
9123
+ MAX_RESULT_CHARS = 1e5;
9124
+ }
9125
+ });
9126
+
9127
+ // ../../scripts/virtual-office/code-runner/inference-task-runner.mjs
9128
+ async function processInferenceTask(client, task, cfg, {
9129
+ env: env2 = process.env,
9130
+ safeProgress: safeProgress2,
9131
+ runnerStagePatch: runnerStagePatch2,
9132
+ handle = handleInferenceTask,
9133
+ log: log3 = () => {
9134
+ }
9135
+ } = {}) {
9136
+ const id = task.code_task_id;
9137
+ await safeProgress2(
9138
+ client,
9139
+ id,
9140
+ runnerStagePatch2("starting_agent", `${cfg.runnerId} running local inference for this task`, {
9141
+ status: "running"
9142
+ })
9143
+ );
9144
+ const model = resolveLocalModel(env2);
9145
+ if (!model) {
9146
+ await safeProgress2(client, id, {
9147
+ status: "failed",
9148
+ message: "inference: this runner has no local model configured",
9149
+ result: "This computer is not configured with a local model. Set VO_CODE_RUNNER_LOCAL_MODEL (a model your Ollama / LM Studio already has), then retry."
9150
+ });
9151
+ return;
9152
+ }
9153
+ const out = await handle(
9154
+ {
9155
+ provider_mode: "local",
9156
+ model,
9157
+ prompt: task.prompt,
9158
+ base_url: resolveLocalBaseUrl(env2) || void 0
9159
+ },
9160
+ { now: () => Date.now() }
9161
+ );
9162
+ if (out.ok) {
9163
+ log3(`inference task ${id} completed on ${model} (${out.usage?.output_tokens ?? "?"} output tokens)`);
9164
+ await safeProgress2(client, id, {
9165
+ status: INFERENCE_SUCCESS_STATUS,
9166
+ message: "inference complete",
9167
+ result: out.text,
9168
+ // $0 by construction — the completion was produced on the user's machine,
9169
+ // AlgoSuite made no provider call, so there is nothing to bill.
9170
+ cost_usd: 0,
9171
+ num_turns: 1
9172
+ });
9173
+ return;
9174
+ }
9175
+ log3(`inference task ${id} failed: ${out.error}`);
9176
+ await safeProgress2(client, id, {
9177
+ status: "failed",
9178
+ message: "inference failed",
9179
+ result: out.error,
9180
+ cost_usd: 0
9181
+ });
9182
+ }
9183
+ var INFERENCE_SUCCESS_STATUS;
9184
+ var init_inference_task_runner = __esm({
9185
+ "../../scripts/virtual-office/code-runner/inference-task-runner.mjs"() {
9186
+ "use strict";
9187
+ init_local_model_runner();
9188
+ init_inference_task_handler();
9189
+ INFERENCE_SUCCESS_STATUS = "no_changes_needed";
9190
+ }
9191
+ });
9192
+
8572
9193
  // ../../scripts/virtual-office/code-runner/isolation-audit.mjs
8573
9194
  import fs8 from "node:fs";
8574
9195
  import fsp9 from "node:fs/promises";
@@ -9001,7 +9622,8 @@ function loadConfig(env2 = process.env) {
9001
9622
  watchEnabled: env2.VO_CODE_RUNNER_WATCH !== "0",
9002
9623
  watchMaxFix: Math.max(0, Number(env2.VO_CODE_RUNNER_WATCH_MAX_FIX ?? 1) || 0),
9003
9624
  watchIntervalSec: Math.max(30, Number(env2.VO_CODE_RUNNER_WATCH_SEC ?? 60) || 60),
9004
- armAutoMerge: env2.VO_CODE_RUNNER_ARM_AUTOMERGE === "1",
9625
+ armAutoMerge: env2.VO_CODE_RUNNER_ARM_AUTOMERGE !== "0",
9626
+ // default ON (operator directive 2026-07-24); =0 opts out; receipt-gated consensus merge stays the mechanism
9005
9627
  // In-product runner control. Off: VO_CODE_RUNNER_CONTROL=0.
9006
9628
  controlEnabled: env2.VO_CODE_RUNNER_CONTROL !== "0",
9007
9629
  controlPort: Math.max(1, Number(env2.VO_CODE_RUNNER_CONTROL_PORT ?? 7787) || 7787),
@@ -9019,7 +9641,7 @@ async function processOneTask(client, task, cfg) {
9019
9641
  const wt = await Promise.resolve(createFixWorktree("code-task", { source: id.slice(0, 8), repo: task.repo }));
9020
9642
  worktreeName = wt.worktreeName;
9021
9643
  if (!worktreeName || !wt.worktreeDir) throw new Error("worktree isolation failure \u2014 refusing to run in the main tree");
9022
- const githubToken = (await client.getInstallationToken({ required: cfg.requireGithubAppAuth }))?.token ?? null;
9644
+ const { publishToken: githubToken, agentReadToken: agentGithubReadToken } = await mintRunnerGithubTokens({ client, taskId: id, log: log2, repo: task.repo, requirePublish: cfg.requireGithubAppAuth });
9023
9645
  const parentTask = task.resumed_from ? await client.getTask(task.resumed_from).catch(() => null) : null;
9024
9646
  const continuationRestore = await prepareContinuationBranch(wt.worktreeDir, {
9025
9647
  task,
@@ -9054,7 +9676,7 @@ async function processOneTask(client, task, cfg) {
9054
9676
  model,
9055
9677
  effort: effectiveEffort,
9056
9678
  maxBudgetUsd: effectiveMaxBudgetUsd,
9057
- env: buildAgentProcessEnv(process.env, { agent: cfg.agent, runnerId: cfg.runnerId, taskId: id }),
9679
+ env: buildAgentProcessEnv(process.env, { agent: cfg.agent, runnerId: cfg.runnerId, taskId: id, githubReadToken: agentGithubReadToken }),
9058
9680
  onProgress: (text) => {
9059
9681
  void safeProgress(client, id, runnerStagePatch("agent_working", text));
9060
9682
  },
@@ -9231,7 +9853,7 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
9231
9853
  );
9232
9854
  for (const line of describeClaimScoping(cfg, env2)) log2(line);
9233
9855
  log2(
9234
- cfg.armAutoMerge ? "PR auto-merge arming ON for complete PRs (VO_CODE_RUNNER_ARM_AUTOMERGE=1)" : "PR auto-merge arming OFF (set VO_CODE_RUNNER_ARM_AUTOMERGE=1 for internal dogfood autonomy)"
9856
+ cfg.armAutoMerge ? "PR auto-merge arming ON for complete PRs (default; VO_CODE_RUNNER_ARM_AUTOMERGE=0 to disable)" : "PR auto-merge arming OFF (VO_CODE_RUNNER_ARM_AUTOMERGE=0)"
9235
9857
  );
9236
9858
  log2(
9237
9859
  watchCyclesEnabled ? `PR watcher ON \u2014 auto-fix ${cfg.watchMaxFix}/PR; ${cfg.armAutoMerge ? "receipt-gated merge enabled" : "merge disabled"}; every ${cfg.watchIntervalSec}s (VO_CODE_RUNNER_WATCH=0 to disable)` : once2 ? "PR watcher bypassed for --once; dispatched PRs remain tracked" : "PR watcher OFF (VO_CODE_RUNNER_WATCH=0)"
@@ -9240,7 +9862,7 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
9240
9862
  const watchCoordinator = makeWatchCycleCoordinator({ runWatch, log: log2, intervalMs: cfg.watchIntervalSec * 1e3 });
9241
9863
  const agentAvailability = makeAgentAvailabilityProvider({ onError: (e) => log2(`agent probe failed: ${e.message}`) });
9242
9864
  const accountUsage = makeAccountUsageProvider();
9243
- const loopTick = makeLoopTicks({ client, cfg, env: env2, log: log2, getActive: () => active, runnerInstanceId, capacityController, getAgentAvailability: () => agentAvailability.get(), getAccountUsage: () => accountUsage.get() });
9865
+ const loopTick = makeLoopTicks({ client, cfg, env: env2, log: log2, getActive: () => active, runnerInstanceId, capacityController, localModelController: createLocalModelRemoteController({ env: env2, log: log2 }), getAgentAvailability: () => agentAvailability.get(), getAccountUsage: () => accountUsage.get() });
9244
9866
  const backoff = makeReconnectBackoff({ baseMs: cfg.pollSec * 1e3, log: log2 });
9245
9867
  while (!stopping) {
9246
9868
  const heartbeatCompletion = loopTick();
@@ -9283,7 +9905,8 @@ async function main({ env: env2 = process.env, once: once2 = false } = {}) {
9283
9905
  }
9284
9906
  log2(`claimed task ${task.code_task_id} (${task.repo})`);
9285
9907
  active += 1;
9286
- const done = processOneTask(client, task, cfg).finally(() => {
9908
+ const runTask = task.kind === "inference" ? processInferenceTask(client, task, cfg, { safeProgress, runnerStagePatch, log: log2 }) : processOneTask(client, task, cfg);
9909
+ const done = runTask.finally(() => {
9287
9910
  active -= 1;
9288
9911
  });
9289
9912
  if (once2) {
@@ -9316,6 +9939,7 @@ var init_code_runner_daemon = __esm({
9316
9939
  init_loop_ticks();
9317
9940
  init_runner_capacity();
9318
9941
  init_agent_availability();
9942
+ init_local_model_remote_config();
9319
9943
  init_account_usage();
9320
9944
  init_pr_watcher();
9321
9945
  init_existing_pr_target();
@@ -9326,6 +9950,7 @@ var init_code_runner_daemon = __esm({
9326
9950
  init_reconnect_backoff();
9327
9951
  init_task_helpers();
9328
9952
  init_agent_process_env();
9953
+ init_inference_task_runner();
9329
9954
  init_isolation_audit();
9330
9955
  init_recovery_ledger();
9331
9956
  init_no_changes_terminal_status();