@algosuite/vo-mcp 0.2.0-beta.7 → 0.2.0-beta.70

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.
@@ -0,0 +1,1731 @@
1
+ import { createRequire as __cr } from 'module'; const require = __cr(import.meta.url);
2
+
3
+ // ../../scripts/virtual-office/code-runner/claude-runner.mjs
4
+ import { spawn } from "node:child_process";
5
+
6
+ // ../../scripts/virtual-office/code-runner/anthropic-key-store.mjs
7
+ import { createRequire } from "node:module";
8
+ import { spawnSync as spawnSync2 } from "node:child_process";
9
+
10
+ // ../../scripts/virtual-office/code-runner/windows-claude-launch.mjs
11
+ import { existsSync, realpathSync } from "node:fs";
12
+ import { win32 as path } from "node:path";
13
+ import { spawnSync } from "node:child_process";
14
+ var NATIVE_CLAUDE_PARTS = [
15
+ "node_modules",
16
+ "@anthropic-ai",
17
+ "claude-code",
18
+ "bin",
19
+ "claude.exe"
20
+ ];
21
+ function pathValue(env) {
22
+ for (const key of ["Path", "PATH", "path"]) {
23
+ if (typeof env?.[key] === "string") return env[key];
24
+ }
25
+ return "";
26
+ }
27
+ function cleanPathSegment(value) {
28
+ const trimmed = String(value || "").trim();
29
+ return trimmed.startsWith('"') && trimmed.endsWith('"') ? trimmed.slice(1, -1) : trimmed;
30
+ }
31
+ function envValue(env, name) {
32
+ const exact = env?.[name];
33
+ if (typeof exact === "string") return exact.trim();
34
+ const key = Object.keys(env || {}).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
35
+ return typeof env?.[key] === "string" ? env[key].trim() : "";
36
+ }
37
+ function userClaudeCandidates(bin, env) {
38
+ if (!/^claude(?:\.(?:exe|cmd|ps1))?$/iu.test(bin)) return [];
39
+ const userProfile = envValue(env, "USERPROFILE");
40
+ const appData = envValue(env, "APPDATA") || (userProfile ? path.join(userProfile, "AppData", "Roaming") : "");
41
+ const localAppData = envValue(env, "LOCALAPPDATA") || (userProfile ? path.join(userProfile, "AppData", "Local") : "");
42
+ const candidates = [];
43
+ if (appData) {
44
+ const npmBin = path.join(appData, "npm");
45
+ candidates.push(
46
+ path.join(npmBin, "claude.exe"),
47
+ path.join(npmBin, "claude.cmd"),
48
+ path.join(npmBin, "claude.ps1"),
49
+ path.join(npmBin, "claude"),
50
+ path.join(npmBin, ...NATIVE_CLAUDE_PARTS)
51
+ );
52
+ }
53
+ if (userProfile) candidates.push(path.join(userProfile, ".local", "bin", "claude.exe"));
54
+ if (localAppData) {
55
+ candidates.push(
56
+ path.join(localAppData, "Microsoft", "WinGet", "Links", "claude.exe"),
57
+ path.join(localAppData, "Microsoft", "WindowsApps", "claude.exe")
58
+ );
59
+ }
60
+ return candidates;
61
+ }
62
+ function pathCandidates(bin, env) {
63
+ if (path.isAbsolute(bin) || /[\\/]/u.test(bin)) {
64
+ return [path.resolve(bin)];
65
+ }
66
+ const extension = path.extname(bin);
67
+ const fromPath = pathValue(env).split(";").map(cleanPathSegment).filter(Boolean).flatMap((directory) => extension ? [path.join(directory, bin)] : [
68
+ path.join(directory, `${bin}.exe`),
69
+ path.join(directory, `${bin}.cmd`),
70
+ path.join(directory, `${bin}.ps1`),
71
+ path.join(directory, bin)
72
+ ]);
73
+ const seen = /* @__PURE__ */ new Set();
74
+ return [...fromPath, ...userClaudeCandidates(bin, env)].filter((candidate) => {
75
+ const key = candidate.toLowerCase();
76
+ if (seen.has(key)) return false;
77
+ seen.add(key);
78
+ return true;
79
+ });
80
+ }
81
+ function canonicalExistingPath(candidate, exists, canonicalize) {
82
+ if (!exists(candidate)) return null;
83
+ try {
84
+ return canonicalize(candidate);
85
+ } catch {
86
+ return null;
87
+ }
88
+ }
89
+ function resolveWindowsClaudeExecutable({
90
+ bin = "claude",
91
+ env = process.env,
92
+ exists = existsSync,
93
+ canonicalize = realpathSync
94
+ } = {}) {
95
+ const requested = String(bin || "").trim();
96
+ if (!requested || requested.includes("\0")) {
97
+ throw new TypeError("Claude executable must be a non-empty path without NUL bytes");
98
+ }
99
+ for (const candidate of pathCandidates(requested, env)) {
100
+ const found = canonicalExistingPath(candidate, exists, canonicalize);
101
+ if (!found) continue;
102
+ if (path.extname(found).toLowerCase() === ".exe") return found;
103
+ const native = path.join(path.dirname(found), ...NATIVE_CLAUDE_PARTS);
104
+ const resolvedNative = canonicalExistingPath(native, exists, canonicalize);
105
+ if (resolvedNative) return resolvedNative;
106
+ }
107
+ const error = new Error(
108
+ `Could not resolve a native claude.exe for "${requested}". Install or update Claude Code with the native Windows installer (recommended) or npm install -g @anthropic-ai/claude-code; the HQ runner will not execute a shell-only .cmd/.ps1 shim.`
109
+ );
110
+ error.code = "ENOENT";
111
+ throw error;
112
+ }
113
+ function buildWindowsClaudeLaunch({
114
+ bin = "claude",
115
+ args = [],
116
+ env = process.env
117
+ } = {}) {
118
+ return {
119
+ bin: resolveWindowsClaudeExecutable({ bin, env }),
120
+ args: Array.from(args, (value) => String(value)),
121
+ spawnOptions: {
122
+ shell: false,
123
+ windowsHide: true,
124
+ windowsVerbatimArguments: false
125
+ }
126
+ };
127
+ }
128
+ function spawnClaudeSync(args = [], options = {}) {
129
+ if (process.platform !== "win32") {
130
+ return spawnSync("claude", args, { windowsHide: true, ...options });
131
+ }
132
+ try {
133
+ const launch = buildWindowsClaudeLaunch({
134
+ bin: "claude",
135
+ args,
136
+ env: options.env || process.env
137
+ });
138
+ return spawnSync(launch.bin, launch.args, {
139
+ ...options,
140
+ ...launch.spawnOptions
141
+ });
142
+ } catch (error) {
143
+ return {
144
+ error,
145
+ status: null,
146
+ signal: null,
147
+ output: null,
148
+ stdout: null,
149
+ stderr: null
150
+ };
151
+ }
152
+ }
153
+
154
+ // ../../scripts/virtual-office/code-runner/claude-credential-choice.mjs
155
+ var PREFER_LOGIN_ENV = "VO_RUNNER_PREFER_LOGIN";
156
+ var CLAUDE_PREFER_LOGIN_ENV = "VO_RUNNER_CLAUDE_PREFER_LOGIN";
157
+ var PREFER_KEY_ENV = "VO_RUNNER_PREFER_KEY";
158
+ var CLAUDE_PREFER_KEY_ENV = "VO_RUNNER_CLAUDE_PREFER_KEY";
159
+ var CLAUDE_CREDENTIAL_SOURCE = Object.freeze({
160
+ /** PREFER_LOGIN set (and not overridden): any API key is ignored. */
161
+ PREFER_LOGIN: "prefer_login",
162
+ /** An explicit ANTHROPIC_API_KEY in the environment — the manual override. */
163
+ ENV_KEY: "env_key",
164
+ /** No key anywhere; the spawn falls through to the login session. */
165
+ NO_KEY: "no_key",
166
+ /** A stored key, used because the operator explicitly opted out of tier 1. */
167
+ KEYCHAIN_PREFER_KEY: "keychain_prefer_key",
168
+ /** A stored key exists but a proven live subscription outranks it. */
169
+ SUBSCRIPTION_WINS: "subscription_wins",
170
+ /** A stored key, used because no live subscription was proven. */
171
+ KEYCHAIN: "keychain"
172
+ });
173
+ function isTruthyFlag(v) {
174
+ const s = String(v ?? "").trim().toLowerCase();
175
+ return s === "1" || s === "true" || s === "yes" || s === "on";
176
+ }
177
+ function wantsLogin(env) {
178
+ return isTruthyFlag(env[CLAUDE_PREFER_LOGIN_ENV]) || isTruthyFlag(env[PREFER_LOGIN_ENV]);
179
+ }
180
+ function wantsKey(env) {
181
+ return isTruthyFlag(env[CLAUDE_PREFER_KEY_ENV]) || isTruthyFlag(env[PREFER_KEY_ENV]);
182
+ }
183
+ function classifyClaudeCredential(baseEnv = {}, { getKey, probeLogin } = {}) {
184
+ const preferKey = wantsKey(baseEnv);
185
+ if (!preferKey && wantsLogin(baseEnv)) {
186
+ return { source: CLAUDE_CREDENTIAL_SOURCE.PREFER_LOGIN, key: null };
187
+ }
188
+ if (baseEnv.ANTHROPIC_API_KEY) {
189
+ return { source: CLAUDE_CREDENTIAL_SOURCE.ENV_KEY, key: null };
190
+ }
191
+ const key = getKey();
192
+ if (!key) {
193
+ return { source: CLAUDE_CREDENTIAL_SOURCE.NO_KEY, key: null };
194
+ }
195
+ if (preferKey) {
196
+ return { source: CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN_PREFER_KEY, key };
197
+ }
198
+ if (probeLogin() === true) {
199
+ return { source: CLAUDE_CREDENTIAL_SOURCE.SUBSCRIPTION_WINS, key: null };
200
+ }
201
+ return { source: CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN, key };
202
+ }
203
+
204
+ // ../../scripts/virtual-office/code-runner/anthropic-key-store.mjs
205
+ var require2 = createRequire(import.meta.url);
206
+ var KEY_SERVICE = "algosuite-vo";
207
+ var KEY_ACCOUNT = "anthropic-api-key";
208
+ var _entryCtor;
209
+ var _loadTried = false;
210
+ function defaultEntryCtor() {
211
+ if (_loadTried) return _entryCtor;
212
+ _loadTried = true;
213
+ try {
214
+ _entryCtor = require2("@napi-rs/keyring").Entry;
215
+ } catch {
216
+ _entryCtor = null;
217
+ }
218
+ return _entryCtor;
219
+ }
220
+ function getAnthropicKey({ EntryCtor = defaultEntryCtor() } = {}) {
221
+ if (!EntryCtor) return null;
222
+ try {
223
+ return new EntryCtor(KEY_SERVICE, KEY_ACCOUNT).getPassword() || null;
224
+ } catch {
225
+ return null;
226
+ }
227
+ }
228
+ function withAnthropicKey(baseEnv = {}, { getKey = getAnthropicKey, probeLogin = probeClaudeLoginState } = {}) {
229
+ const { source, key } = classifyClaudeCredential(baseEnv, { getKey, probeLogin });
230
+ const next = { ...baseEnv };
231
+ if (source === CLAUDE_CREDENTIAL_SOURCE.PREFER_LOGIN) {
232
+ delete next.ANTHROPIC_API_KEY;
233
+ return next;
234
+ }
235
+ if (key !== null) next.ANTHROPIC_API_KEY = key;
236
+ return next;
237
+ }
238
+ function claudeCostBasis(env = process.env) {
239
+ return String(env.ANTHROPIC_API_KEY || "").trim() ? "vendor_billed" : "subscription_api_equivalent";
240
+ }
241
+ var AUTH_SOURCE_DESCRIPTION = Object.freeze({
242
+ [CLAUDE_CREDENTIAL_SOURCE.PREFER_LOGIN]: "claude auth login (VO_RUNNER_PREFER_LOGIN set \u2014 any API key ignored)",
243
+ [CLAUDE_CREDENTIAL_SOURCE.ENV_KEY]: "ANTHROPIC_API_KEY from environment",
244
+ [CLAUDE_CREDENTIAL_SOURCE.NO_KEY]: "claude auth login session (no API key set)",
245
+ [CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN_PREFER_KEY]: "ANTHROPIC_API_KEY from OS keychain (VO_RUNNER_PREFER_KEY set \u2014 subscription ignored)",
246
+ [CLAUDE_CREDENTIAL_SOURCE.SUBSCRIPTION_WINS]: "claude auth login session (subscription beats the stored keychain key)",
247
+ [CLAUDE_CREDENTIAL_SOURCE.KEYCHAIN]: "ANTHROPIC_API_KEY from OS keychain"
248
+ });
249
+ function describeAnthropicAuthSource(baseEnv = {}, { getKey = getAnthropicKey, probeLogin = probeClaudeLoginState } = {}) {
250
+ const { source } = classifyClaudeCredential(baseEnv, { getKey, probeLogin });
251
+ return AUTH_SOURCE_DESCRIPTION[source];
252
+ }
253
+ function probeClaudeLoginState({
254
+ spawn: spawn2 = spawnSync2,
255
+ buildWindowsLaunch = buildWindowsClaudeLaunch,
256
+ platform = process.platform
257
+ } = {}) {
258
+ try {
259
+ const launch = platform === "win32" ? buildWindowsLaunch({ bin: "claude", args: ["auth", "status"] }) : { bin: "claude", args: ["auth", "status"], spawnOptions: { windowsHide: true } };
260
+ const st = spawn2(launch.bin, launch.args, { ...launch.spawnOptions, timeout: 5e3, encoding: "utf8" });
261
+ const parsed = JSON.parse(String(st.stdout || "").trim() || "{}");
262
+ return typeof parsed.loggedIn === "boolean" ? parsed.loggedIn : null;
263
+ } catch {
264
+ return null;
265
+ }
266
+ }
267
+
268
+ // ../../scripts/virtual-office/code-runner/sandbox/sandbox-docker.mjs
269
+ import { spawnSync as spawnSync3 } from "node:child_process";
270
+
271
+ // ../../scripts/virtual-office/code-runner/context7-mcp.mjs
272
+ var CONTEXT7_URL = "https://mcp.context7.com/mcp";
273
+ function context7McpConfig(env = process.env) {
274
+ if (env.VO_ENABLE_CONTEXT7 !== "1") return null;
275
+ const url = env.VO_CONTEXT7_URL && env.VO_CONTEXT7_URL.trim() || CONTEXT7_URL;
276
+ const server = { type: "http", url };
277
+ if (env.CONTEXT7_API_KEY && env.CONTEXT7_API_KEY.trim()) {
278
+ server.headers = { CONTEXT7_API_KEY: env.CONTEXT7_API_KEY.trim() };
279
+ }
280
+ return { mcpServers: { context7: server } };
281
+ }
282
+ function context7McpArgs(env = process.env) {
283
+ const cfg = context7McpConfig(env);
284
+ return cfg ? ["--mcp-config", JSON.stringify(cfg)] : [];
285
+ }
286
+
287
+ // ../../scripts/virtual-office/code-runner/claude-args.mjs
288
+ var DEFAULT_PERMISSION_MODE = "acceptEdits";
289
+ var VO_SESSION_STATE_TOOL = "mcp__vo-mcp__vo_report_session_state";
290
+ var VO_HEADLESS_PNPM_TOOL = "Bash(pnpm *)";
291
+ var VO_HEADLESS_PNPM_FROM_DIR_TOOL = "Bash(pnpm --dir *)";
292
+ var VO_RESEARCH_TOOLS = ["WebFetch", "WebSearch"];
293
+ var VO_WORKFLOW_TOOLS = ["Workflow"];
294
+ var VO_CONSENSUS_TOOLS = ["mcp__vo-mcp__vo_consensus_judgment", "mcp__vo-mcp__vo_verify_answer"];
295
+ var SAFE_PERMISSION_MODES = /* @__PURE__ */ new Set(["acceptEdits", "plan", "default", "dontAsk", "delegate"]);
296
+ function normalizeClaudePermissionMode(value) {
297
+ const normalized = String(value ?? "").trim() || DEFAULT_PERMISSION_MODE;
298
+ if (!SAFE_PERMISSION_MODES.has(normalized)) {
299
+ throw new Error(`unsafe Claude permission mode "${normalized}"`);
300
+ }
301
+ return normalized;
302
+ }
303
+ function buildClaudeArgs({ permissionMode = DEFAULT_PERMISSION_MODE, maxTurns, model, effort, maxBudgetUsd, researchHarness = false, env = process.env } = {}) {
304
+ const effectivePermissionMode = normalizeClaudePermissionMode(permissionMode);
305
+ const noWeb = String(env?.VO_CODE_RUNNER_NO_WEB ?? "").trim() === "1";
306
+ const research = noWeb ? [] : VO_RESEARCH_TOOLS;
307
+ const noWorkflow = noWeb || String(env?.VO_CODE_RUNNER_NO_WORKFLOW ?? "").trim() === "1";
308
+ const workflow = researchHarness === true && !noWorkflow ? VO_WORKFLOW_TOOLS : [];
309
+ const noConsensus = String(env?.VO_CODE_RUNNER_NO_CONSENSUS ?? "").trim() === "1";
310
+ const consensus = noConsensus ? [] : VO_CONSENSUS_TOOLS;
311
+ const baseTools = effectivePermissionMode === DEFAULT_PERMISSION_MODE ? [VO_SESSION_STATE_TOOL, VO_HEADLESS_PNPM_TOOL, VO_HEADLESS_PNPM_FROM_DIR_TOOL] : [VO_SESSION_STATE_TOOL];
312
+ const allowedTools = [...baseTools, ...consensus, ...research, ...workflow].join(",");
313
+ const args = [
314
+ "-p",
315
+ "--output-format",
316
+ "stream-json",
317
+ "--verbose",
318
+ "--permission-mode",
319
+ effectivePermissionMode,
320
+ "--allowedTools",
321
+ allowedTools
322
+ ];
323
+ if (Number.isInteger(maxTurns) && maxTurns > 0) {
324
+ args.push("--max-turns", String(maxTurns));
325
+ }
326
+ if (model) {
327
+ args.push("--model", String(model));
328
+ }
329
+ if (effort) {
330
+ args.push("--effort", String(effort));
331
+ }
332
+ if (typeof maxBudgetUsd === "number" && maxBudgetUsd > 0) {
333
+ args.push("--max-budget-usd", String(maxBudgetUsd));
334
+ }
335
+ args.push(...context7McpArgs(env));
336
+ return args;
337
+ }
338
+
339
+ // ../../scripts/virtual-office/code-runner/terminal-process-cleanup.mjs
340
+ import { spawnSync as spawnSync4 } from "node:child_process";
341
+
342
+ // ../../scripts/virtual-office/code-runner/orphan-agent-reaper.mjs
343
+ import { spawnSync as spawnSync5 } from "node:child_process";
344
+ import { existsSync as existsSync2, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
345
+ import os from "node:os";
346
+ import path2 from "node:path";
347
+
348
+ // ../../scripts/virtual-office/code-runner/agent-token-usage.mjs
349
+ var MAX_TOKEN_COUNT = 1e9;
350
+ var MAX_COST_USD = 1e4;
351
+ var NO_AGENT_SPAWNED_ECONOMICS = Object.freeze({ cost_usd: 0, cost_basis: "no_agent_spawned" });
352
+ function count(value) {
353
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return null;
354
+ return Math.min(MAX_TOKEN_COUNT, Math.round(value));
355
+ }
356
+ function money(value) {
357
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return null;
358
+ return Math.min(MAX_COST_USD, value);
359
+ }
360
+ function extractTokenUsage(evt) {
361
+ const models = extractModelUsage(evt);
362
+ if (models) {
363
+ const out2 = models.reduce((sum, row) => ({
364
+ input_tokens: Math.min(MAX_TOKEN_COUNT, sum.input_tokens + row.input_tokens),
365
+ output_tokens: Math.min(MAX_TOKEN_COUNT, sum.output_tokens + row.output_tokens),
366
+ cache_creation_tokens: Math.min(MAX_TOKEN_COUNT, sum.cache_creation_tokens + row.cache_creation_tokens),
367
+ cache_read_tokens: Math.min(MAX_TOKEN_COUNT, sum.cache_read_tokens + row.cache_read_tokens)
368
+ }), { input_tokens: 0, output_tokens: 0, cache_creation_tokens: 0, cache_read_tokens: 0 });
369
+ return Object.values(out2).some((value) => value > 0) ? out2 : null;
370
+ }
371
+ const u = evt?.usage;
372
+ if (!u || typeof u !== "object") return null;
373
+ const out = {
374
+ input_tokens: count(u.input_tokens) ?? 0,
375
+ output_tokens: count(u.output_tokens) ?? 0,
376
+ cache_creation_tokens: count(u.cache_creation_input_tokens) ?? 0,
377
+ cache_read_tokens: count(u.cache_read_input_tokens) ?? 0
378
+ };
379
+ const total = out.input_tokens + out.output_tokens + out.cache_creation_tokens + out.cache_read_tokens;
380
+ return total > 0 ? out : null;
381
+ }
382
+ var MAX_MODELS = 20;
383
+ function extractModelUsage(evt) {
384
+ const m = evt?.modelUsage;
385
+ if (!m || typeof m !== "object" || Array.isArray(m)) return null;
386
+ const rows = [];
387
+ for (const [model, raw] of Object.entries(m)) {
388
+ if (rows.length >= MAX_MODELS) break;
389
+ if (!model || typeof raw !== "object" || raw === null) continue;
390
+ rows.push({
391
+ model: String(model).slice(0, 120),
392
+ input_tokens: count(raw.inputTokens) ?? 0,
393
+ output_tokens: count(raw.outputTokens) ?? 0,
394
+ cache_read_tokens: count(raw.cacheReadInputTokens) ?? 0,
395
+ cache_creation_tokens: count(raw.cacheCreationInputTokens) ?? 0,
396
+ cost_usd: money(raw.costUSD) ?? 0
397
+ });
398
+ }
399
+ return rows.length > 0 ? rows : null;
400
+ }
401
+
402
+ // ../../scripts/virtual-office/code-runner/claude-result-event.mjs
403
+ var CAPPED_RESULT_SUBTYPES = Object.freeze(["error_max_budget_usd", "error_max_turns"]);
404
+ function buildResultEvent(evt) {
405
+ const isError = Boolean(evt.is_error) || evt.subtype === "error_max_turns" || evt.subtype === "error_during_execution";
406
+ return {
407
+ kind: "result",
408
+ isError,
409
+ costUsd: typeof evt.total_cost_usd === "number" ? evt.total_cost_usd : null,
410
+ summary: typeof evt.result === "string" && evt.result.length > 0 ? evt.result : evt.subtype || (isError ? "error" : "completed"),
411
+ numTurns: typeof evt.num_turns === "number" ? evt.num_turns : null,
412
+ tokenUsage: extractTokenUsage(evt),
413
+ modelUsage: extractModelUsage(evt)
414
+ };
415
+ }
416
+
417
+ // ../../scripts/virtual-office/code-runner/claude-stream-event.mjs
418
+ function extractText(content) {
419
+ if (typeof content === "string") return content.trim();
420
+ if (!Array.isArray(content)) return "";
421
+ return content.filter((block) => block && block.type === "text" && typeof block.text === "string").map((block) => block.text).join("").trim();
422
+ }
423
+ function parseClaudeStreamEvent(line) {
424
+ const trimmed = String(line || "").trim();
425
+ if (!trimmed) return null;
426
+ let event;
427
+ try {
428
+ event = JSON.parse(trimmed);
429
+ } catch {
430
+ return null;
431
+ }
432
+ if (!event || typeof event !== "object") return null;
433
+ if (event.type === "assistant" && event.message?.content) {
434
+ const text = extractText(event.message.content);
435
+ const tokenUsage = extractTokenUsage({ usage: event.message.usage });
436
+ return text || tokenUsage ? { kind: "progress", text, ...tokenUsage ? { tokenUsage } : {} } : null;
437
+ }
438
+ return event.type === "result" ? buildResultEvent(event) : null;
439
+ }
440
+
441
+ // ../../scripts/virtual-office/code-runner/agent-auth-tier.mjs
442
+ var AUTH_TIER_SUBSCRIPTION = "subscription";
443
+ var AUTH_TIER_API_KEY = "api_key";
444
+ var AUTH_TIER_LOCAL = "local";
445
+ var AUTH_TIER_UNKNOWN = "unknown";
446
+ var AUTH_TIERS = Object.freeze([
447
+ AUTH_TIER_SUBSCRIPTION,
448
+ AUTH_TIER_API_KEY,
449
+ AUTH_TIER_LOCAL,
450
+ AUTH_TIER_UNKNOWN
451
+ ]);
452
+ var COST_BASIS_TO_TIER = Object.freeze({
453
+ subscription_api_equivalent: AUTH_TIER_SUBSCRIPTION,
454
+ vendor_billed: AUTH_TIER_API_KEY,
455
+ local_zero: AUTH_TIER_LOCAL
456
+ });
457
+ function authTierFromCostBasis(costBasis) {
458
+ return COST_BASIS_TO_TIER[String(costBasis ?? "")] ?? AUTH_TIER_UNKNOWN;
459
+ }
460
+ function safeAuthTier(compute) {
461
+ try {
462
+ return normalizeAuthTier(compute());
463
+ } catch {
464
+ return AUTH_TIER_UNKNOWN;
465
+ }
466
+ }
467
+ function normalizeAuthTier(value) {
468
+ return AUTH_TIERS.includes(value) ? value : AUTH_TIER_UNKNOWN;
469
+ }
470
+
471
+ // ../../scripts/virtual-office/code-runner/cli-version-floor.mjs
472
+ var MIN_CLAUDE_CLI_VERSION = "2.1.218";
473
+ var SECURITY_RATIONALE = "Claude Code 2.1.211/2.1.213 fixed a PreToolUse-hook bypass on unsandboxed Bash (our destructive-fs/git/cloud tripwires DO NOT FIRE on older CLIs) and worktree-subagents mutating the main checkout. 2.1.218 fixed Windows paths with a lowercase-\\u segment (e.g. ...\\utils\\, ...\\ui\\) being corrupted into CJK in tool inputs, making those files silently inaccessible \u2014 the fleet is Windows and 1,376 tracked files sit under utils/ alone. Update: npm install -g @anthropic-ai/claude-code (or the native installer).";
474
+ function parseCliVersion(output) {
475
+ const match = /\b(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?\b/.exec(String(output ?? ""));
476
+ return match ? `${match[1]}.${match[2]}.${match[3]}` : null;
477
+ }
478
+ function compareSemver(a, b) {
479
+ const pa = a.split(".").map(Number);
480
+ const pb = b.split(".").map(Number);
481
+ for (let i = 0; i < 3; i += 1) {
482
+ if (pa[i] !== pb[i]) return pa[i] < pb[i] ? -1 : 1;
483
+ }
484
+ return 0;
485
+ }
486
+ function checkCliVersionFloor(versionOutput, { floor = MIN_CLAUDE_CLI_VERSION } = {}) {
487
+ const version = parseCliVersion(versionOutput);
488
+ if (!version) {
489
+ const seen = String(versionOutput ?? "").trim().slice(0, 120) || "<empty>";
490
+ return {
491
+ ok: false,
492
+ version: null,
493
+ floor,
494
+ message: `could not parse a semver from \`claude --version\` output ("${seen}") \u2014 cannot prove the CLI meets the ${floor} security floor. ${SECURITY_RATIONALE}`
495
+ };
496
+ }
497
+ if (compareSemver(version, floor) < 0) {
498
+ return {
499
+ ok: false,
500
+ version,
501
+ floor,
502
+ message: `claude CLI ${version} is BELOW the minimum security floor ${floor}. ` + SECURITY_RATIONALE
503
+ };
504
+ }
505
+ return {
506
+ ok: true,
507
+ version,
508
+ floor,
509
+ message: `claude CLI ${version} meets the minimum security floor ${floor}`
510
+ };
511
+ }
512
+ function applyCliVersionFloor({ versionOutput, env = process.env, log = console.error } = {}) {
513
+ const check = checkCliVersionFloor(versionOutput);
514
+ if (check.ok) return { refused: false, check, message: check.message };
515
+ const allowUnsafe = String(env?.VO_CLI_FLOOR_ALLOW_UNSAFE ?? "") === "1";
516
+ const message = `[cli-version-floor] ${allowUnsafe ? "WARNING (unsafe emergency override)" : "REFUSING"}: ` + check.message;
517
+ try {
518
+ log(message);
519
+ } catch {
520
+ }
521
+ return { refused: !allowUnsafe, check, message };
522
+ }
523
+
524
+ // ../../scripts/virtual-office/code-runner/claude-auth-check.mjs
525
+ var FIRST_VERSION_TIMEOUT_MS = 4500;
526
+ var RETRY_VERSION_TIMEOUT_MS = 2e3;
527
+ function errorCode(error) {
528
+ return String(error?.code || "").toUpperCase();
529
+ }
530
+ function isTimeout(probe) {
531
+ return errorCode(probe?.error) === "ETIMEDOUT" || String(probe?.signal || "").toUpperCase() === "SIGTERM";
532
+ }
533
+ function notFound(probe) {
534
+ return errorCode(probe?.error) === "ENOENT";
535
+ }
536
+ function resolveClaudeAuthTier({
537
+ env = process.env,
538
+ loggedIn = null,
539
+ getStoredKey = getAnthropicKey
540
+ } = {}) {
541
+ return safeAuthTier(() => {
542
+ const spawnEnv = withAnthropicKey(env, { getKey: getStoredKey, probeLogin: () => loggedIn });
543
+ const tier = authTierFromCostBasis(claudeCostBasis(spawnEnv));
544
+ if (tier === AUTH_TIER_SUBSCRIPTION && loggedIn !== true) return AUTH_TIER_UNKNOWN;
545
+ return tier;
546
+ });
547
+ }
548
+ async function checkClaudeAuth({
549
+ spawnVersion = spawnClaudeSync,
550
+ probeLogin = probeClaudeLoginState,
551
+ getStoredKey = getAnthropicKey,
552
+ env = process.env
553
+ } = {}) {
554
+ try {
555
+ let probe = spawnVersion(["--version"], {
556
+ timeout: FIRST_VERSION_TIMEOUT_MS,
557
+ encoding: "utf8",
558
+ env
559
+ });
560
+ let retriedAfterTimeout = false;
561
+ if (isTimeout(probe)) {
562
+ retriedAfterTimeout = true;
563
+ probe = spawnVersion(["--version"], {
564
+ timeout: RETRY_VERSION_TIMEOUT_MS,
565
+ encoding: "utf8",
566
+ env
567
+ });
568
+ }
569
+ if (probe.error) {
570
+ if (notFound(probe)) {
571
+ return {
572
+ installed: false,
573
+ authenticated: false,
574
+ message: "claude CLI not found on PATH \u2014 it is a SEPARATE install from the Claude Desktop app and the Claude Code IDE extension. Install: npm install -g @anthropic-ai/claude-code, then sign in: claude auth login."
575
+ };
576
+ }
577
+ return {
578
+ installed: true,
579
+ authenticated: false,
580
+ message: isTimeout(probe) ? "claude CLI executable was found, but its cold-start version probe timed out twice; availability will be retried without misreporting it as uninstalled." : `claude CLI executable was found, but its version probe failed: ${probe.error.message}`
581
+ };
582
+ }
583
+ if (probe.status !== 0) {
584
+ return { installed: true, authenticated: false, message: "claude binary exists but --version failed (auth unclear)" };
585
+ }
586
+ const floorGate = applyCliVersionFloor({ versionOutput: probe.stdout, env });
587
+ if (floorGate.refused) {
588
+ return { installed: true, authenticated: false, message: floorGate.message };
589
+ }
590
+ const loggedIn = retriedAfterTimeout ? null : probeLogin();
591
+ if (loggedIn === false) {
592
+ return {
593
+ installed: true,
594
+ authenticated: false,
595
+ message: "claude CLI is installed but NOT logged in \u2014 its login is SEPARATE from the Claude Desktop app and the Claude Code IDE extension. Run: claude auth login (Claude subscription), then restart the runner."
596
+ };
597
+ }
598
+ return {
599
+ installed: true,
600
+ authenticated: true,
601
+ // Dispatch-time billing signal, carried on the same probe that already
602
+ // paid for the login read. Never sent for a non-authenticated result:
603
+ // there is no tier without a working credential.
604
+ authTier: resolveClaudeAuthTier({ env, loggedIn, getStoredKey }),
605
+ message: loggedIn === true ? "claude CLI installed and logged in (claude auth status)" : "claude binary found (login state unknown \u2014 auth check is best-effort)"
606
+ };
607
+ } catch (error) {
608
+ return {
609
+ installed: false,
610
+ authenticated: false,
611
+ message: `checkAuth probe failed: ${error.message}`
612
+ };
613
+ }
614
+ }
615
+
616
+ // ../../scripts/virtual-office/code-runner/claude-runner.mjs
617
+ function parseStreamEvent(line) {
618
+ return parseClaudeStreamEvent(line);
619
+ }
620
+ var ClaudeRunner = class {
621
+ get enforcesBudgetCap() {
622
+ return true;
623
+ }
624
+ get binary() {
625
+ return "claude";
626
+ }
627
+ buildArgs({ permissionMode, maxTurns, model, effort, maxBudgetUsd, researchHarness } = {}) {
628
+ return buildClaudeArgs({ permissionMode, maxTurns, model, effort, maxBudgetUsd, researchHarness });
629
+ }
630
+ parseEvent(line) {
631
+ return parseStreamEvent(line);
632
+ }
633
+ getSpawnOptions() {
634
+ return { shell: false, windowsHide: true };
635
+ }
636
+ prepareSpawn({ bin, args, spawnOptions, env = process.env } = {}) {
637
+ if (process.platform !== "win32") return { bin, args, spawnOptions: spawnOptions ?? this.getSpawnOptions() };
638
+ return buildWindowsClaudeLaunch({ bin, args, env });
639
+ }
640
+ applyAuthEnv(env = process.env) {
641
+ return withAnthropicKey(env);
642
+ }
643
+ costBasis(env = process.env) {
644
+ return claudeCostBasis(env);
645
+ }
646
+ describeAuth(env = process.env) {
647
+ return describeAnthropicAuthSource(env);
648
+ }
649
+ async checkAuth() {
650
+ return checkClaudeAuth();
651
+ }
652
+ };
653
+ var claudeRunner = new ClaudeRunner();
654
+
655
+ // ../../scripts/virtual-office/code-runner/codex-runner.mjs
656
+ import { spawnSync as spawnSync6 } from "node:child_process";
657
+ import { existsSync as existsSync3 } from "node:fs";
658
+ import { win32 } from "node:path";
659
+
660
+ // ../../scripts/virtual-office/code-runner/agent-key-store.mjs
661
+ import { createRequire as createRequire2 } from "node:module";
662
+ var require3 = createRequire2(import.meta.url);
663
+ var KEY_SERVICE2 = "algosuite-vo";
664
+ var PROVIDER_ENV = {
665
+ anthropic: ["ANTHROPIC_API_KEY"],
666
+ openai: ["OPENAI_API_KEY", "CODEX_API_KEY"],
667
+ cursor: ["CURSOR_API_KEY"],
668
+ meta: ["MODEL_API_KEY"],
669
+ // Generic OpenAI-compatible runner (bring-your-own model + endpoint): its key
670
+ // is a dedicated var so it never collides with a real OpenAI/Codex key.
671
+ "oai-compat": ["VO_CODE_RUNNER_OAI_API_KEY"],
672
+ // Sovereign local inference (Ollama / LM Studio). The key is OPTIONAL — most
673
+ // local servers need none — and exists for locally secured endpoints only.
674
+ local: ["VO_CODE_RUNNER_LOCAL_API_KEY"],
675
+ // AlgoHQ cloud consensus (ADR-002 moat plane) entitlement token. Not a model
676
+ // key: it authorises the runner's `vo_consensus_judgment` / `vo_verify_answer`
677
+ // tools against the moat. On an npm-installed runner the local consensus
678
+ // engine is never present (it is a workspace-only package), so WITHOUT this
679
+ // token every consensus call answers `unimplemented /
680
+ // consensus-engine-package-not-installed` (2026-08-16 finding, task fbd8659b).
681
+ moat: ["VO_ENTITLEMENT_TOKEN"]
682
+ };
683
+ var PROVIDER_ALIAS = {
684
+ claude: "anthropic",
685
+ anthropic: "anthropic",
686
+ codex: "openai",
687
+ openai: "openai",
688
+ cursor: "cursor",
689
+ meta: "meta",
690
+ muse: "meta",
691
+ spark: "meta",
692
+ "muse-spark": "meta",
693
+ oai: "oai-compat",
694
+ "oai-compat": "oai-compat",
695
+ local: "local",
696
+ ollama: "local",
697
+ lmstudio: "local",
698
+ moat: "moat",
699
+ entitlement: "moat",
700
+ consensus: "moat"
701
+ };
702
+ function resolveProvider(name) {
703
+ const key = String(name || "").trim().toLowerCase();
704
+ return PROVIDER_ALIAS[key] || null;
705
+ }
706
+ function accountFor(provider) {
707
+ return `${provider}-api-key`;
708
+ }
709
+ var _entryCtor2;
710
+ var _loadTried2 = false;
711
+ function defaultEntryCtor2() {
712
+ if (_loadTried2) return _entryCtor2;
713
+ _loadTried2 = true;
714
+ try {
715
+ _entryCtor2 = require3("@napi-rs/keyring").Entry;
716
+ } catch {
717
+ _entryCtor2 = null;
718
+ }
719
+ return _entryCtor2;
720
+ }
721
+ function getAgentKey(provider, { EntryCtor = defaultEntryCtor2() } = {}) {
722
+ const p = resolveProvider(provider);
723
+ if (!p || !EntryCtor) return null;
724
+ try {
725
+ return new EntryCtor(KEY_SERVICE2, accountFor(p)).getPassword() || null;
726
+ } catch {
727
+ return null;
728
+ }
729
+ }
730
+ function withAgentKey(provider, baseEnv = {}, { getKey = getAgentKey } = {}) {
731
+ const p = resolveProvider(provider);
732
+ const vars = p && PROVIDER_ENV[p] || [];
733
+ const out = { ...baseEnv };
734
+ if (!p || vars.length === 0) return out;
735
+ if (vars.some((v) => out[v])) return out;
736
+ const key = getKey(p);
737
+ if (!key) return out;
738
+ for (const v of vars) out[v] = key;
739
+ return out;
740
+ }
741
+
742
+ // ../../scripts/virtual-office/code-runner/flat-token-usage.mjs
743
+ var MAX_TOKEN_COUNT2 = 1e9;
744
+ function count2(value) {
745
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return 0;
746
+ return Math.min(MAX_TOKEN_COUNT2, Math.round(value));
747
+ }
748
+ function extractFlatTokenUsage(usage) {
749
+ if (!usage || typeof usage !== "object" || Array.isArray(usage)) return null;
750
+ const rawInput = count2(usage.input_tokens ?? usage.prompt_tokens);
751
+ const cached = count2(
752
+ usage.cached_input_tokens ?? usage.cache_read_input_tokens ?? usage.cache_read_tokens
753
+ );
754
+ const hasInclusiveCache = usage.cached_input_tokens !== void 0;
755
+ const out = {
756
+ input_tokens: hasInclusiveCache ? Math.max(0, rawInput - cached) : rawInput,
757
+ output_tokens: count2(usage.output_tokens ?? usage.completion_tokens),
758
+ cache_creation_tokens: count2(
759
+ usage.cache_creation_input_tokens ?? usage.cache_creation_tokens
760
+ ),
761
+ cache_read_tokens: cached
762
+ };
763
+ return Object.values(out).some((value) => value > 0) ? out : null;
764
+ }
765
+
766
+ // ../../scripts/virtual-office/code-runner/error-message.mjs
767
+ var FAILURE_SCHEMA = "vo.runner_failure.v1";
768
+ var FAILURE_DEFINITIONS = {
769
+ model_unsupported_for_account: {
770
+ source: "terminal_event",
771
+ operator_next_action: "Create a new owner-reviewed task with an account-supported Codex model; keep the preserved draft PR for review rather than resuming this task."
772
+ },
773
+ codex_runtime_launch_logon_session: {
774
+ source: "stderr",
775
+ win32_error: 1312,
776
+ operator_next_action: "Repair the Codex Windows logon/session configuration on the serving host, then rerun the runner."
777
+ }
778
+ };
779
+ function codexFailure(code, fields = {}) {
780
+ const definition = FAILURE_DEFINITIONS[code];
781
+ return {
782
+ schema: FAILURE_SCHEMA,
783
+ agent: "codex",
784
+ source: definition.source,
785
+ code,
786
+ ...fields,
787
+ operator_next_action: definition.operator_next_action
788
+ };
789
+ }
790
+ function classifyCodexTerminalFailure(evt = {}) {
791
+ const error = evt && typeof evt.error === "object" && evt.error !== null ? evt.error : null;
792
+ if (!/^The ['"][^'"]+['"] model is not supported when using Codex with a ChatGPT account\.$/u.test(
793
+ typeof error?.message === "string" ? error.message : ""
794
+ )) return null;
795
+ const status = Number(evt.status ?? error.status);
796
+ return codexFailure("model_unsupported_for_account", Number.isInteger(status) ? { provider_status: status } : {});
797
+ }
798
+ function classifyCodexStderr(stderr = "") {
799
+ if (!/(?:^|\n)\s*windows sandbox(?: failed)?: runner error:.*\bCreateProcessAsUserW failed:\s*1312\b/imu.test(String(stderr))) return null;
800
+ return codexFailure("codex_runtime_launch_logon_session", { win32_error: 1312 });
801
+ }
802
+
803
+ // ../../scripts/virtual-office/code-runner/codex-runner.mjs
804
+ var CODEX_PREFER_LOGIN_ENV = "VO_RUNNER_CODEX_PREFER_LOGIN";
805
+ var LEGACY_PREFER_LOGIN_ENV = "VO_RUNNER_PREFER_LOGIN";
806
+ function isTruthyFlag2(value) {
807
+ return ["1", "true", "yes", "on"].includes(String(value ?? "").trim().toLowerCase());
808
+ }
809
+ function resolveCodexBinary({
810
+ env = process.env,
811
+ platform = process.platform,
812
+ exists = existsSync3
813
+ } = {}) {
814
+ if (platform !== "win32") return "codex";
815
+ const appData = String(env.APPDATA || "").trim();
816
+ const userProfile = String(env.USERPROFILE || "").trim();
817
+ const localAppData = String(env.LOCALAPPDATA || "").trim();
818
+ const candidates = [];
819
+ if (appData) {
820
+ candidates.push(win32.join(
821
+ appData,
822
+ "npm",
823
+ "node_modules",
824
+ "@openai",
825
+ "codex",
826
+ "node_modules",
827
+ "@openai",
828
+ "codex-win32-x64",
829
+ "vendor",
830
+ "x86_64-pc-windows-msvc",
831
+ "bin",
832
+ "codex.exe"
833
+ ));
834
+ }
835
+ if (userProfile) {
836
+ candidates.push(win32.join(userProfile, ".local", "bin", "codex.exe"));
837
+ candidates.push(win32.join(userProfile, ".codex", "bin", "codex.exe"));
838
+ }
839
+ if (localAppData) {
840
+ candidates.push(win32.join(localAppData, "Microsoft", "WindowsApps", "codex.exe"));
841
+ }
842
+ const absolute = candidates.find((candidate) => exists(candidate));
843
+ if (absolute) return absolute;
844
+ return "codex";
845
+ }
846
+ function buildCodexArgs({ model, effort } = {}) {
847
+ const args = ["exec", "--json", "-c", 'approval_policy="never"', "--sandbox", "workspace-write", "--skip-git-repo-check"];
848
+ if (model) {
849
+ args.push("--model", String(model));
850
+ }
851
+ if (effort) {
852
+ args.push("-c", `model_reasoning_effort="${String(effort)}"`);
853
+ }
854
+ args.push("-");
855
+ return args;
856
+ }
857
+ function itemText(item) {
858
+ if (!item) return "";
859
+ if (typeof item.text === "string") return item.text;
860
+ if (typeof item.message === "string") return item.message;
861
+ if (Array.isArray(item.content)) {
862
+ return item.content.map((b) => typeof b === "string" ? b : typeof b?.text === "string" ? b.text : "").join("");
863
+ }
864
+ return "";
865
+ }
866
+ function parseCodexVersion(stdout) {
867
+ if (typeof stdout !== "string") return null;
868
+ const match = stdout.match(/\b(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)\b/u);
869
+ return match ? match[1] : null;
870
+ }
871
+ function parseCodexEvent(line) {
872
+ const trimmed = String(line || "").trim();
873
+ if (!trimmed) return null;
874
+ let evt;
875
+ try {
876
+ evt = JSON.parse(trimmed);
877
+ } catch {
878
+ return null;
879
+ }
880
+ if (!evt || typeof evt !== "object") return null;
881
+ const type = evt.type;
882
+ if (type === "item.completed" && evt.item) {
883
+ const it = evt.item.type;
884
+ if (it === "agent_message" || it === "assistant_message") {
885
+ const text = itemText(evt.item).trim();
886
+ return text ? { kind: "progress", text } : null;
887
+ }
888
+ return null;
889
+ }
890
+ if (type === "turn.completed") {
891
+ return {
892
+ kind: "result",
893
+ isError: false,
894
+ costUsd: null,
895
+ summary: "completed",
896
+ numTurns: null,
897
+ tokenUsage: extractFlatTokenUsage(evt.usage)
898
+ };
899
+ }
900
+ if (type === "turn.failed" || type === "error") {
901
+ const msg = evt.error && (evt.error.message || evt.error) || evt.message || "codex run failed";
902
+ const failure = classifyCodexTerminalFailure(evt);
903
+ return { kind: "result", isError: true, costUsd: null, summary: String(msg), numTurns: null, ...failure ? { failure } : {} };
904
+ }
905
+ return null;
906
+ }
907
+ var CodexRunner = class {
908
+ constructor({ spawn: spawn2 = spawnSync6, resolveBinary = resolveCodexBinary, env = process.env } = {}) {
909
+ this.spawn = spawn2;
910
+ this.resolveBinary = resolveBinary;
911
+ this.env = env;
912
+ }
913
+ get binary() {
914
+ return this.resolveBinary();
915
+ }
916
+ buildArgs(opts = {}) {
917
+ return buildCodexArgs(opts);
918
+ }
919
+ parseEvent(line) {
920
+ return parseCodexEvent(line);
921
+ }
922
+ classifyStderr(stderr) {
923
+ return classifyCodexStderr(stderr);
924
+ }
925
+ /**
926
+ * SECURITY: never `shell: true` — same RCE class as cursor-runner. The old
927
+ * `shell: win32 && !/\.exe$/` fell back to shell mode whenever
928
+ * resolveCodexBinary() could not find one of its hardcoded absolute paths and
929
+ * returned the bare string 'codex'. Node's shell mode joins argv into
930
+ * `cmd /d /s /c` with windowsVerbatimArguments, and buildCodexArgs() puts the
931
+ * control-plane-controlled `model` into argv, so a payload of
932
+ * `{ agent: 'codex', model: 'gpt-5 & <cmd>' }` executed arbitrary code —
933
+ * including on hosts where codex is NOT installed, because cmd runs the first
934
+ * command, it fails, and `&` runs the rest anyway.
935
+ *
936
+ * With shell:false a `.cmd`/`.ps1` shim no longer resolves and the spawn fails
937
+ * closed with ENOENT, matching resolveWindowsClaudeExecutable()'s policy.
938
+ */
939
+ getSpawnOptions() {
940
+ return {
941
+ shell: false,
942
+ windowsHide: true,
943
+ windowsVerbatimArguments: false
944
+ };
945
+ }
946
+ applyAuthEnv(env = process.env) {
947
+ if (isTruthyFlag2(env[CODEX_PREFER_LOGIN_ENV]) || isTruthyFlag2(env[LEGACY_PREFER_LOGIN_ENV])) {
948
+ const out = { ...env };
949
+ delete out.OPENAI_API_KEY;
950
+ delete out.CODEX_API_KEY;
951
+ return out;
952
+ }
953
+ return withAgentKey("openai", env);
954
+ }
955
+ costBasis(env = process.env) {
956
+ return String(env.OPENAI_API_KEY || env.CODEX_API_KEY || "").trim() ? "vendor_billed" : "subscription_api_equivalent";
957
+ }
958
+ authTier(env = this.env) {
959
+ return safeAuthTier(() => authTierFromCostBasis(this.costBasis(this.applyAuthEnv(env))));
960
+ }
961
+ async checkAuth() {
962
+ try {
963
+ const bin = this.binary;
964
+ const version = this.spawn(bin, ["--version"], {
965
+ ...this.getSpawnOptions({ bin }),
966
+ windowsHide: true,
967
+ timeout: 3e3,
968
+ encoding: "utf8"
969
+ });
970
+ if (version.error) {
971
+ return { installed: false, authenticated: false, message: `codex not found on PATH: ${version.error.message}` };
972
+ }
973
+ if (version.status !== 0) {
974
+ return { installed: true, authenticated: false, message: "codex exists but --version failed (auth unclear)" };
975
+ }
976
+ const cliVersion = parseCodexVersion(version.stdout);
977
+ const versionField = cliVersion ? { version: cliVersion } : {};
978
+ const login = this.spawn(bin, ["login", "status"], {
979
+ ...this.getSpawnOptions({ bin }),
980
+ windowsHide: true,
981
+ timeout: 5e3,
982
+ encoding: "utf8"
983
+ });
984
+ const output = `${login.stdout || ""}
985
+ ${login.stderr || ""}`.trim();
986
+ if (login.error || login.status !== 0) {
987
+ const authEnv = this.applyAuthEnv(this.env);
988
+ if (authEnv.OPENAI_API_KEY || authEnv.CODEX_API_KEY) {
989
+ return {
990
+ installed: true,
991
+ authenticated: true,
992
+ ...versionField,
993
+ authTier: this.authTier(),
994
+ message: "codex API key available (no persisted ChatGPT login)"
995
+ };
996
+ }
997
+ return {
998
+ installed: true,
999
+ authenticated: false,
1000
+ ...versionField,
1001
+ message: output || login.error?.message || "codex is installed but not logged in"
1002
+ };
1003
+ }
1004
+ return {
1005
+ installed: true,
1006
+ authenticated: true,
1007
+ ...versionField,
1008
+ authTier: this.authTier(),
1009
+ message: output || "codex login status succeeded"
1010
+ };
1011
+ } catch (err) {
1012
+ return { installed: false, authenticated: false, message: `checkAuth probe failed: ${err.message}` };
1013
+ }
1014
+ }
1015
+ };
1016
+ var codexRunner = new CodexRunner();
1017
+
1018
+ // ../../scripts/virtual-office/code-runner/cursor-runner.mjs
1019
+ import { spawnSync as spawnSync7 } from "node:child_process";
1020
+ function buildCursorArgs({ model, prompt } = {}) {
1021
+ const args = ["-p", "--output-format", "stream-json", "--force"];
1022
+ if (model) {
1023
+ args.push("--model", String(model));
1024
+ }
1025
+ const p = String(prompt ?? "");
1026
+ if (p.length > 0) {
1027
+ args.push(p);
1028
+ }
1029
+ return args;
1030
+ }
1031
+ function messageText(message) {
1032
+ if (!message) return "";
1033
+ const content = message.content;
1034
+ if (typeof content === "string") return content;
1035
+ if (Array.isArray(content)) {
1036
+ return content.map((b) => typeof b === "string" ? b : typeof b?.text === "string" ? b.text : "").join("");
1037
+ }
1038
+ return "";
1039
+ }
1040
+ function parseCursorEvent(line) {
1041
+ const trimmed = String(line || "").trim();
1042
+ if (!trimmed) return null;
1043
+ let evt;
1044
+ try {
1045
+ evt = JSON.parse(trimmed);
1046
+ } catch {
1047
+ return null;
1048
+ }
1049
+ if (!evt || typeof evt !== "object") return null;
1050
+ if (evt.type === "assistant") {
1051
+ const text = messageText(evt.message).trim();
1052
+ return text ? { kind: "progress", text } : null;
1053
+ }
1054
+ if (evt.type === "result") {
1055
+ const isError = Boolean(evt.is_error) || evt.subtype === "error";
1056
+ const tokenUsage = extractFlatTokenUsage(evt.usage);
1057
+ return {
1058
+ kind: "result",
1059
+ isError,
1060
+ costUsd: null,
1061
+ ...tokenUsage ? { tokenUsage } : {},
1062
+ summary: typeof evt.result === "string" && evt.result.length > 0 ? evt.result : evt.subtype || (isError ? "error" : "completed"),
1063
+ numTurns: null
1064
+ };
1065
+ }
1066
+ return null;
1067
+ }
1068
+ var CursorRunner = class {
1069
+ get binary() {
1070
+ return "cursor-agent";
1071
+ }
1072
+ buildArgs(opts = {}) {
1073
+ return buildCursorArgs(opts);
1074
+ }
1075
+ parseEvent(line) {
1076
+ return parseCursorEvent(line);
1077
+ }
1078
+ /**
1079
+ * SECURITY: never `shell: true`. Node's shell mode on Windows joins argv and
1080
+ * hands it to `cmd /d /s /c` with windowsVerbatimArguments, so every cmd
1081
+ * metacharacter (& | > ^) in an argument is interpreted by the shell. This
1082
+ * runner puts two control-plane-controlled strings into argv — `task.model`
1083
+ * and the composed prompt (buildCursorArgs) — so shell mode turned a task
1084
+ * payload into arbitrary host code execution. It fired even without
1085
+ * cursor-agent installed: cmd runs the first command, it fails, and `&` runs
1086
+ * the rest anyway. With shell:false argv goes straight to CreateProcess and
1087
+ * metacharacters are inert.
1088
+ *
1089
+ * Consequence on Windows: a `.cmd`/`.ps1` shim no longer resolves, so the
1090
+ * runner fails closed with ENOENT rather than executing through a shell —
1091
+ * the same policy resolveWindowsClaudeExecutable() enforces for Claude.
1092
+ */
1093
+ getSpawnOptions() {
1094
+ return {
1095
+ shell: false,
1096
+ windowsHide: true,
1097
+ windowsVerbatimArguments: false
1098
+ };
1099
+ }
1100
+ /**
1101
+ * Fill CURSOR_API_KEY from the OS keychain when not already set, so a BYO
1102
+ * friend who ran `vo-mcp set-key --provider cursor` authenticates without an
1103
+ * env var. Explicit env wins; no key stored → a prior `cursor-agent login`.
1104
+ */
1105
+ applyAuthEnv(env = process.env) {
1106
+ return withAgentKey("cursor", env);
1107
+ }
1108
+ costBasis(env = process.env) {
1109
+ return env.CURSOR_API_KEY ? "vendor_billed" : "unknown";
1110
+ }
1111
+ /**
1112
+ * Dispatch-time billing tier. Inherits costBasis()'s deliberate refusal to
1113
+ * guess: a prior interactive `cursor-agent login` has undocumented
1114
+ * subscription semantics, so it reports 'unknown' rather than claiming a
1115
+ * flat-cost seat the runner cannot actually prove. Keychain read only — the
1116
+ * heartbeat never pays for a subprocess to answer this.
1117
+ */
1118
+ authTier(env = process.env) {
1119
+ return safeAuthTier(() => authTierFromCostBasis(this.costBasis(this.applyAuthEnv(env))));
1120
+ }
1121
+ /** Best-effort: is `cursor-agent` on PATH? Never throws. */
1122
+ async checkAuth() {
1123
+ try {
1124
+ const { status, error } = spawnSync7("cursor-agent", ["--version"], {
1125
+ shell: false,
1126
+ windowsHide: true,
1127
+ timeout: 3e3,
1128
+ stdio: "ignore"
1129
+ });
1130
+ if (error) {
1131
+ return { installed: false, authenticated: false, message: `cursor-agent not found on PATH: ${error.message}` };
1132
+ }
1133
+ if (status !== 0) {
1134
+ return { installed: true, authenticated: false, message: "cursor-agent exists but --version failed (auth unclear)" };
1135
+ }
1136
+ if (process.env.VO_CODE_RUNNER_ALLOW_UNMETERED_CURSOR !== "1") {
1137
+ return {
1138
+ installed: true,
1139
+ authenticated: false,
1140
+ message: "cursor-agent disabled: its documented stream-JSON result has no token/cost fields; set VO_CODE_RUNNER_ALLOW_UNMETERED_CURSOR=1 only for an explicit unmeasured experiment"
1141
+ };
1142
+ }
1143
+ return {
1144
+ installed: true,
1145
+ authenticated: true,
1146
+ authTier: this.authTier(),
1147
+ message: "cursor-agent found (EXPERIMENTAL: headless mode may need a TTY; auth check is best-effort)"
1148
+ };
1149
+ } catch (err) {
1150
+ return { installed: false, authenticated: false, message: `checkAuth probe failed: ${err.message}` };
1151
+ }
1152
+ }
1153
+ };
1154
+ var cursorRunner = new CursorRunner();
1155
+
1156
+ // ../../scripts/virtual-office/code-runner/ollama-agent-tools.mjs
1157
+ var MAX_READ_BYTES = 64 * 1024;
1158
+ var MAX_WRITE_BYTES = 512 * 1024;
1159
+ var TOOL_DEFS = [
1160
+ {
1161
+ type: "function",
1162
+ function: {
1163
+ name: "read_file",
1164
+ description: "Read a UTF-8 text file inside the working directory. Returns up to 64 KiB.",
1165
+ parameters: {
1166
+ type: "object",
1167
+ properties: {
1168
+ path: { type: "string", description: "File path relative to the working directory." }
1169
+ },
1170
+ required: ["path"]
1171
+ }
1172
+ }
1173
+ },
1174
+ {
1175
+ type: "function",
1176
+ function: {
1177
+ name: "list_files",
1178
+ description: "List entries in a directory inside the working directory (files and subdirs).",
1179
+ parameters: {
1180
+ type: "object",
1181
+ properties: {
1182
+ path: { type: "string", description: 'Directory path relative to the working directory. Default ".".' }
1183
+ }
1184
+ }
1185
+ }
1186
+ },
1187
+ {
1188
+ type: "function",
1189
+ function: {
1190
+ name: "write_file",
1191
+ description: "Create or overwrite a UTF-8 text file inside the working directory. Parent dirs are created.",
1192
+ parameters: {
1193
+ type: "object",
1194
+ properties: {
1195
+ path: { type: "string", description: "File path relative to the working directory." },
1196
+ content: { type: "string", description: "Full new file contents." }
1197
+ },
1198
+ required: ["path", "content"]
1199
+ }
1200
+ }
1201
+ }
1202
+ ];
1203
+ var TOOL_NAMES = TOOL_DEFS.map((t) => t.function.name);
1204
+ var READ_ONLY_TOOL_NAMES = Object.freeze(["read_file", "list_files"]);
1205
+ var READ_ONLY_TOOL_DEFS = Object.freeze(
1206
+ TOOL_DEFS.filter((tool) => READ_ONLY_TOOL_NAMES.includes(tool.function.name))
1207
+ );
1208
+
1209
+ // ../../scripts/virtual-office/code-runner/ollama-agent-core.mjs
1210
+ var MAX_TURNS_DEFAULT = 20;
1211
+ var NUM_CTX_DEFAULT = 16384;
1212
+ function parseOllamaAgentEvent(line) {
1213
+ const trimmed = String(line || "").trim();
1214
+ if (!trimmed) return null;
1215
+ let evt;
1216
+ try {
1217
+ evt = JSON.parse(trimmed);
1218
+ } catch {
1219
+ return null;
1220
+ }
1221
+ if (!evt || typeof evt !== "object") return null;
1222
+ if (evt.type === "progress") {
1223
+ const text = String(evt.text || "").trim();
1224
+ return text ? { kind: "progress", text } : null;
1225
+ }
1226
+ if (evt.type === "tool") {
1227
+ const via = evt.recovered ? " (recovered from text)" : "";
1228
+ const label = `${evt.ok === false ? "tool failed" : "tool"}: ${evt.name}${evt.path ? ` ${evt.path}` : ""}${via}`;
1229
+ return { kind: "progress", text: label };
1230
+ }
1231
+ if (evt.type === "result") {
1232
+ const usage = evt.usage || null;
1233
+ const tokenUsage = usage ? { inputTokens: usage.inputTokens ?? null, outputTokens: usage.outputTokens ?? null, totalTokens: usage.totalTokens ?? null } : void 0;
1234
+ return {
1235
+ kind: "result",
1236
+ isError: Boolean(evt.isError),
1237
+ costUsd: 0,
1238
+ // sovereign local inference is free — no meter.
1239
+ summary: String(evt.summary || (evt.isError ? "local run failed" : "completed")),
1240
+ numTurns: Number.isInteger(evt.numTurns) ? evt.numTurns : null,
1241
+ ...tokenUsage ? { tokenUsage } : {},
1242
+ // Pass the sovereign receipt through to the daemon. Omitted entirely when
1243
+ // absent so the event shape is unchanged for every other transport.
1244
+ ...evt.receipt ? { receipt: evt.receipt } : {}
1245
+ };
1246
+ }
1247
+ return null;
1248
+ }
1249
+
1250
+ // ../../scripts/virtual-office/code-runner/ollama-native-transport.mjs
1251
+ import { fileURLToPath } from "node:url";
1252
+ import { dirname, join } from "node:path";
1253
+ var DEFAULT_LOCAL_TRANSPORT = "codex";
1254
+ var LOCAL_NATIVE_PROFILES = Object.freeze(["coding", "verification"]);
1255
+ var DEFAULT_LOCAL_NATIVE_PROFILE = "coding";
1256
+ function resolveLocalNativeProfile(env = process.env) {
1257
+ const profile = String(env.VO_CODE_RUNNER_LOCAL_PROFILE || "").trim().toLowerCase() || DEFAULT_LOCAL_NATIVE_PROFILE;
1258
+ if (!LOCAL_NATIVE_PROFILES.includes(profile)) {
1259
+ throw new Error(`local-model runner (native): unknown profile "${profile}" (coding|verification).`);
1260
+ }
1261
+ return profile;
1262
+ }
1263
+ function resolveLocalTransport(env = process.env) {
1264
+ return String(env.VO_CODE_RUNNER_LOCAL_TRANSPORT || "").trim().toLowerCase() === "native" ? "native" : DEFAULT_LOCAL_TRANSPORT;
1265
+ }
1266
+ function ollamaAgentScriptPath() {
1267
+ return join(dirname(fileURLToPath(import.meta.url)), "ollama-agent.mjs");
1268
+ }
1269
+ function posIntOr(raw, fallback) {
1270
+ const n = Number(String(raw ?? "").trim());
1271
+ return Number.isInteger(n) && n > 0 ? n : fallback;
1272
+ }
1273
+ function buildOllamaAgentArgs({ model, numCtx, maxTurns, profile = DEFAULT_LOCAL_NATIVE_PROFILE } = {}) {
1274
+ return [
1275
+ ollamaAgentScriptPath(),
1276
+ "--model",
1277
+ String(model),
1278
+ "--profile",
1279
+ String(profile),
1280
+ "--num-ctx",
1281
+ String(posIntOr(numCtx, NUM_CTX_DEFAULT)),
1282
+ "--max-turns",
1283
+ String(posIntOr(maxTurns, MAX_TURNS_DEFAULT))
1284
+ ];
1285
+ }
1286
+ function buildLocalNativeArgs(opts = {}, env = process.env) {
1287
+ const provider = resolveLocalProvider(env);
1288
+ if (provider !== "ollama") {
1289
+ throw new Error(
1290
+ `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.`
1291
+ );
1292
+ }
1293
+ const model = resolveLocalModel(env);
1294
+ if (!model) {
1295
+ throw new Error(
1296
+ "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)."
1297
+ );
1298
+ }
1299
+ if (!isValidLocalModel(model)) {
1300
+ throw new Error(`local-model runner (native): "${model}" is not a valid local model id.`);
1301
+ }
1302
+ const baseUrl = resolveLocalBaseUrl(env);
1303
+ if (baseUrl && !isLoopbackBaseUrl(baseUrl)) {
1304
+ throw new Error(
1305
+ "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."
1306
+ );
1307
+ }
1308
+ return buildOllamaAgentArgs({
1309
+ model,
1310
+ profile: resolveLocalNativeProfile(env),
1311
+ numCtx: env.VO_CODE_RUNNER_LOCAL_NUM_CTX,
1312
+ maxTurns: env.VO_CODE_RUNNER_LOCAL_MAX_TURNS
1313
+ });
1314
+ }
1315
+
1316
+ // ../../scripts/virtual-office/code-runner/local-model-runner.mjs
1317
+ var LOCAL_API_KEY_ENV = "VO_CODE_RUNNER_LOCAL_API_KEY";
1318
+ var FORBIDDEN_LOCAL_CHILD_CREDENTIALS = /* @__PURE__ */ new Set([
1319
+ "ANTHROPIC_API_KEY",
1320
+ "AWS_ACCESS_KEY_ID",
1321
+ "AWS_SECRET_ACCESS_KEY",
1322
+ "AWS_SESSION_TOKEN",
1323
+ "FIREBASE_TOKEN",
1324
+ "GH_TOKEN",
1325
+ "GITHUB_TOKEN",
1326
+ "GOOGLE_API_KEY",
1327
+ "GOOGLE_APPLICATION_CREDENTIALS",
1328
+ "OPENAI_API_KEY"
1329
+ ]);
1330
+ var LOCAL_PROVIDERS = ["ollama", "lmstudio"];
1331
+ var DEFAULT_LOCAL_PROVIDER = "ollama";
1332
+ var LOCAL_PROBE_URLS = {
1333
+ ollama: "http://127.0.0.1:11434/api/version",
1334
+ lmstudio: "http://127.0.0.1:1234/v1/models"
1335
+ };
1336
+ function resolveLocalProvider(env = process.env) {
1337
+ return String(env.VO_CODE_RUNNER_LOCAL_PROVIDER || "").trim().toLowerCase() || DEFAULT_LOCAL_PROVIDER;
1338
+ }
1339
+ var remoteDesiredLocalModel = "";
1340
+ function resolveLocalModel(env = process.env) {
1341
+ return String(env.VO_CODE_RUNNER_LOCAL_MODEL || "").trim() || remoteDesiredLocalModel;
1342
+ }
1343
+ function resolveLocalBaseUrl(env = process.env) {
1344
+ return String(env.VO_CODE_RUNNER_LOCAL_BASE_URL || "").trim();
1345
+ }
1346
+ var LOCAL_MODEL_RE = new RegExp("^[A-Za-z0-9][A-Za-z0-9._/:-]{0,127}$");
1347
+ function isValidLocalModel(model) {
1348
+ return LOCAL_MODEL_RE.test(String(model || ""));
1349
+ }
1350
+ function isLoopbackBaseUrl(url) {
1351
+ const raw = String(url || "").trim();
1352
+ if (!/^https?:\/\/[^\s"'`\\]+$/.test(raw)) return false;
1353
+ let parsed;
1354
+ try {
1355
+ parsed = new URL(raw);
1356
+ } catch {
1357
+ return false;
1358
+ }
1359
+ const host = parsed.hostname.toLowerCase();
1360
+ return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
1361
+ }
1362
+ function buildLocalArgs(opts = {}, env = process.env) {
1363
+ const provider = resolveLocalProvider(env);
1364
+ if (!LOCAL_PROVIDERS.includes(provider)) {
1365
+ throw new Error(
1366
+ `local-model runner: unknown VO_CODE_RUNNER_LOCAL_PROVIDER "${provider}" (supported: ${LOCAL_PROVIDERS.join(", ")}).`
1367
+ );
1368
+ }
1369
+ const model = resolveLocalModel(env);
1370
+ if (!model) {
1371
+ throw new Error(
1372
+ "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)."
1373
+ );
1374
+ }
1375
+ if (!isValidLocalModel(model)) {
1376
+ throw new Error(`local-model runner: "${model}" is not a valid local model id.`);
1377
+ }
1378
+ const baseUrl = resolveLocalBaseUrl(env);
1379
+ if (baseUrl && !isLoopbackBaseUrl(baseUrl)) {
1380
+ throw new Error(
1381
+ "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."
1382
+ );
1383
+ }
1384
+ const args = [
1385
+ "exec",
1386
+ "--json",
1387
+ "-c",
1388
+ 'approval_policy="never"',
1389
+ "--sandbox",
1390
+ "workspace-write",
1391
+ "--skip-git-repo-check",
1392
+ "--oss",
1393
+ "--local-provider",
1394
+ provider,
1395
+ "--model",
1396
+ model
1397
+ ];
1398
+ if (opts.effort) {
1399
+ args.push("-c", `model_reasoning_effort="${String(opts.effort)}"`);
1400
+ }
1401
+ args.push("-");
1402
+ return args;
1403
+ }
1404
+ function applyLocalAuthEnv(baseEnv = process.env, configEnv = process.env) {
1405
+ const out = withAgentKey("local", baseEnv);
1406
+ for (const key of Object.keys(out)) {
1407
+ if (FORBIDDEN_LOCAL_CHILD_CREDENTIALS.has(key.toUpperCase())) delete out[key];
1408
+ }
1409
+ const baseUrl = resolveLocalBaseUrl(configEnv);
1410
+ if (baseUrl && isLoopbackBaseUrl(baseUrl) && resolveLocalProvider(configEnv) === "ollama" && !String(out.OLLAMA_HOST || "").trim()) {
1411
+ out.OLLAMA_HOST = baseUrl.replace(/\/+$/, "");
1412
+ }
1413
+ return out;
1414
+ }
1415
+ var LocalModelRunner = class {
1416
+ constructor({
1417
+ spawn: spawn2 = null,
1418
+ resolveBinary = resolveCodexBinary,
1419
+ env = process.env,
1420
+ fetchImpl = globalThis.fetch
1421
+ } = {}) {
1422
+ this.spawn = spawn2;
1423
+ this.resolveBinary = resolveBinary;
1424
+ this.env = env;
1425
+ this.fetchImpl = fetchImpl;
1426
+ }
1427
+ /**
1428
+ * Transport binary. codex transport → the codex CLI. native transport →
1429
+ * this daemon's own node (process.execPath), which runs ollama-agent.mjs; no
1430
+ * external CLI is involved on the native path.
1431
+ */
1432
+ get binary() {
1433
+ return resolveLocalTransport(this.env) === "native" ? process.execPath : this.resolveBinary();
1434
+ }
1435
+ buildArgs(opts = {}) {
1436
+ return resolveLocalTransport(this.env) === "native" ? buildLocalNativeArgs(opts, this.env) : buildLocalArgs(opts, this.env);
1437
+ }
1438
+ /**
1439
+ * native transport → parse the executor's own JSONL contract. codex transport
1440
+ * → codex JSONL maps identically, except a sovereign local model has no vendor
1441
+ * bill: codex omits total_cost_usd for OSS runs, so turn that known fact into a
1442
+ * measured zero at the producer. (The native parser already stamps costUsd:0.)
1443
+ */
1444
+ parseEvent(line) {
1445
+ if (resolveLocalTransport(this.env) === "native") return parseOllamaAgentEvent(line);
1446
+ const event = parseCodexEvent(line);
1447
+ return event?.kind === "result" ? { ...event, costUsd: 0 } : event;
1448
+ }
1449
+ // SECURITY: never shell — see no-shell-spawn.test.mjs. The model id is
1450
+ // control-plane-influenced and validated, but shell:false is the hard floor.
1451
+ getSpawnOptions() {
1452
+ return {
1453
+ shell: false,
1454
+ windowsHide: true,
1455
+ windowsVerbatimArguments: false
1456
+ };
1457
+ }
1458
+ applyAuthEnv(env = process.env) {
1459
+ return applyLocalAuthEnv(env, this.env);
1460
+ }
1461
+ costBasis() {
1462
+ return "local_zero";
1463
+ }
1464
+ /** Dispatch-time billing tier: local inference is never billed by a vendor. */
1465
+ authTier() {
1466
+ return safeAuthTier(() => authTierFromCostBasis(this.costBasis()));
1467
+ }
1468
+ describeAuth(env = process.env) {
1469
+ const authEnv = this.applyAuthEnv(env);
1470
+ const provider = resolveLocalProvider(this.env);
1471
+ const model = resolveLocalModel(this.env) || "<unset>";
1472
+ const hasKey = Boolean(String(authEnv[LOCAL_API_KEY_ENV] || "").trim());
1473
+ return `local provider=${provider} model=${model} key=${hasKey ? "set" : "none (optional)"}`;
1474
+ }
1475
+ /**
1476
+ * Best-effort: codex transport present AND the local inference endpoint
1477
+ * answers. Never throws, never spends tokens; the endpoint probe is bounded
1478
+ * to 1.5s so a stopped Ollama can't hang availability checks.
1479
+ */
1480
+ async checkAuth() {
1481
+ const provider = resolveLocalProvider(this.env);
1482
+ if (!LOCAL_PROVIDERS.includes(provider)) {
1483
+ return {
1484
+ installed: false,
1485
+ authenticated: false,
1486
+ message: `unknown local provider "${provider}" (supported: ${LOCAL_PROVIDERS.join(", ")})`
1487
+ };
1488
+ }
1489
+ const model = resolveLocalModel(this.env);
1490
+ const override = resolveLocalBaseUrl(this.env);
1491
+ if (override && !isLoopbackBaseUrl(override)) {
1492
+ return {
1493
+ installed: true,
1494
+ authenticated: false,
1495
+ message: "VO_CODE_RUNNER_LOCAL_BASE_URL is not loopback \u2014 refused (fail-closed)"
1496
+ };
1497
+ }
1498
+ const probeUrl = provider === "ollama" && override ? `${override.replace(/\/+$/, "")}/api/version` : LOCAL_PROBE_URLS[provider];
1499
+ let endpointUp = false;
1500
+ let probeNote = "";
1501
+ try {
1502
+ const res = await this.fetchImpl(probeUrl, { signal: AbortSignal.timeout(1500) });
1503
+ endpointUp = Boolean(res?.ok);
1504
+ if (!endpointUp) probeNote = `endpoint ${probeUrl} answered HTTP ${res?.status}`;
1505
+ } catch {
1506
+ probeNote = `no local inference server answering at ${probeUrl}`;
1507
+ }
1508
+ if (!endpointUp) {
1509
+ return {
1510
+ installed: true,
1511
+ authenticated: false,
1512
+ message: `${probeNote} \u2014 start ${provider === "ollama" ? "Ollama" : "LM Studio"} first`
1513
+ };
1514
+ }
1515
+ if (!model) {
1516
+ return {
1517
+ installed: true,
1518
+ authenticated: false,
1519
+ message: `${provider} is running but VO_CODE_RUNNER_LOCAL_MODEL is not set`
1520
+ };
1521
+ }
1522
+ return {
1523
+ installed: true,
1524
+ authenticated: true,
1525
+ authTier: this.authTier(),
1526
+ message: `${provider} reachable; model "${model}" configured (local-only, no cloud spend)`
1527
+ };
1528
+ }
1529
+ };
1530
+ var localModelRunner = new LocalModelRunner();
1531
+
1532
+ // ../../scripts/virtual-office/code-runner/meta-runner.mjs
1533
+ var META_API_KEY_ENV = "MODEL_API_KEY";
1534
+ var META_API_KEY_ALIAS = "META_API";
1535
+ function applyMetaAuthEnv(baseEnv = process.env) {
1536
+ const out = withAgentKey("meta", baseEnv);
1537
+ if (!String(out[META_API_KEY_ENV] || "").trim() && String(out[META_API_KEY_ALIAS] || "").trim()) {
1538
+ out[META_API_KEY_ENV] = out[META_API_KEY_ALIAS];
1539
+ }
1540
+ return out;
1541
+ }
1542
+ function buildMetaArgs(opts = {}) {
1543
+ void opts;
1544
+ throw new Error(
1545
+ "Muse Spark full-repository coding is disabled by the AlgoSuite Model Firewall policy. Use Muse only as a restricted reviewer through a sanitized task capsule."
1546
+ );
1547
+ }
1548
+ var MetaRunner = class {
1549
+ get binary() {
1550
+ return resolveCodexBinary();
1551
+ }
1552
+ buildArgs(opts = {}) {
1553
+ return buildMetaArgs(opts);
1554
+ }
1555
+ parseEvent(line) {
1556
+ return parseCodexEvent(line);
1557
+ }
1558
+ // SECURITY: never shell — see no-shell-spawn.test.mjs. Inert today (buildArgs
1559
+ // throws) but this goes hot the moment the transport is enabled.
1560
+ getSpawnOptions() {
1561
+ return {
1562
+ shell: false,
1563
+ windowsHide: true,
1564
+ windowsVerbatimArguments: false
1565
+ };
1566
+ }
1567
+ applyAuthEnv(env = process.env) {
1568
+ return applyMetaAuthEnv(env);
1569
+ }
1570
+ describeAuth(env = process.env) {
1571
+ const authEnv = applyMetaAuthEnv(env);
1572
+ const hasKey = Boolean(String(authEnv[META_API_KEY_ENV] || "").trim());
1573
+ return `meta muse-spark key=${hasKey ? "set" : "MISSING"} transport=codex`;
1574
+ }
1575
+ async checkAuth() {
1576
+ return {
1577
+ installed: false,
1578
+ authenticated: false,
1579
+ message: "Muse Spark coding is disabled; sanitized Model Firewall review only"
1580
+ };
1581
+ }
1582
+ };
1583
+ var metaRunner = new MetaRunner();
1584
+
1585
+ // ../../scripts/virtual-office/code-runner/openai-compatible-runner.mjs
1586
+ var OAI_API_KEY_ENV = "VO_CODE_RUNNER_OAI_API_KEY";
1587
+ function resolveOaiBaseUrl(env = process.env) {
1588
+ return String(env.VO_CODE_RUNNER_OAI_BASE_URL || "").trim();
1589
+ }
1590
+ var OpenAICompatibleRunner = class {
1591
+ /** Codex is the transport binary. */
1592
+ get binary() {
1593
+ return resolveCodexBinary();
1594
+ }
1595
+ buildArgs(opts = {}) {
1596
+ void opts;
1597
+ throw new Error(
1598
+ "OpenAI-compatible full-repository coding is disabled. The `oai` runner lane is RETIRED (PR #8742) and executes nothing. Set VO_CODE_RUNNER_AGENT to claude, codex, cursor, local, or meta. Use an explicit sanitized task capsule through the AlgoSuite Model Firewall."
1599
+ );
1600
+ }
1601
+ /** Codex JSONL events map identically → reuse the proven parser. */
1602
+ parseEvent(line) {
1603
+ return parseCodexEvent(line);
1604
+ }
1605
+ // SECURITY: never shell — see no-shell-spawn.test.mjs. Inert today (buildArgs
1606
+ // throws) but this goes hot the moment the transport is enabled.
1607
+ getSpawnOptions() {
1608
+ return {
1609
+ shell: false,
1610
+ windowsHide: true,
1611
+ windowsVerbatimArguments: false
1612
+ };
1613
+ }
1614
+ /** Fill the BYO key env var from the OS keychain when not already set. */
1615
+ applyAuthEnv(env = process.env) {
1616
+ return withAgentKey("oai-compat", env);
1617
+ }
1618
+ describeAuth(env = process.env) {
1619
+ const hasKey = Boolean(String(env[OAI_API_KEY_ENV] || "").trim());
1620
+ const baseUrl = resolveOaiBaseUrl(env);
1621
+ return `oai-compat RETIRED endpoint=${baseUrl || "<unset>"} key=${hasKey ? "set" : "MISSING"}`;
1622
+ }
1623
+ /** Always unavailable: the lane is retired, so nothing can authenticate it. */
1624
+ async checkAuth() {
1625
+ return {
1626
+ installed: false,
1627
+ authenticated: false,
1628
+ message: "the `oai` lane is RETIRED (PR #8742); OpenAI-compatible coding is disabled \u2014 sanitized Model Firewall task capsules only"
1629
+ };
1630
+ }
1631
+ };
1632
+ var openaiCompatibleRunner = new OpenAICompatibleRunner();
1633
+
1634
+ // ../../scripts/virtual-office/code-runner/agent-runner-interface.mjs
1635
+ function validateAgentRunner(runner) {
1636
+ if (!runner || typeof runner !== "object") {
1637
+ throw new TypeError("AgentRunner must be an object");
1638
+ }
1639
+ if (typeof runner.binary !== "string" || runner.binary.length === 0) {
1640
+ throw new TypeError("AgentRunner.binary must be a non-empty string");
1641
+ }
1642
+ if (typeof runner.buildArgs !== "function") {
1643
+ throw new TypeError("AgentRunner.buildArgs must be a function");
1644
+ }
1645
+ if (typeof runner.parseEvent !== "function") {
1646
+ throw new TypeError("AgentRunner.parseEvent must be a function");
1647
+ }
1648
+ if (typeof runner.getSpawnOptions !== "function") {
1649
+ throw new TypeError("AgentRunner.getSpawnOptions must be a function");
1650
+ }
1651
+ if (typeof runner.checkAuth !== "function") {
1652
+ throw new TypeError("AgentRunner.checkAuth must be a function");
1653
+ }
1654
+ }
1655
+
1656
+ // ../../scripts/virtual-office/code-runner/resolve-runner.mjs
1657
+ var DEFAULT_AGENT = "claude";
1658
+ var RUNNERS = {
1659
+ claude: claudeRunner,
1660
+ codex: codexRunner,
1661
+ cursor: cursorRunner,
1662
+ // `local` = sovereign local inference (Ollama / LM Studio; Mistral/Llama-class
1663
+ // models) — free tier of the pricing pivot; nothing leaves the user's machine.
1664
+ local: localModelRunner,
1665
+ meta: metaRunner,
1666
+ oai: openaiCompatibleRunner
1667
+ };
1668
+ function listAgents() {
1669
+ return Object.keys(RUNNERS);
1670
+ }
1671
+ function inferAgentFromBin(bin) {
1672
+ const raw = String(bin || "").trim().toLowerCase();
1673
+ if (!raw) return null;
1674
+ const base = raw.replace(/\\/g, "/").split("/").pop() || raw;
1675
+ if (base.includes("codex")) return "codex";
1676
+ if (base.includes("cursor-agent") || base === "cursor" || base.startsWith("cursor.")) return "cursor";
1677
+ if (base.includes("claude")) return "claude";
1678
+ return null;
1679
+ }
1680
+ function inferAgentFromEnvBin(env) {
1681
+ for (const bin of [
1682
+ env.VO_CODE_RUNNER_BIN,
1683
+ env.VO_CODE_RUNNER_CLAUDE_BIN
1684
+ ]) {
1685
+ const agent2 = inferAgentFromBin(bin);
1686
+ if (agent2) return { agent: agent2, bin };
1687
+ }
1688
+ return null;
1689
+ }
1690
+ function resolveRunner(env = process.env, { warn = () => {
1691
+ } } = {}) {
1692
+ const explicitAgent = String(env.VO_CODE_RUNNER_AGENT || env.VO_AGENT || "").trim();
1693
+ const inferred = explicitAgent ? null : inferAgentFromEnvBin(env);
1694
+ const raw = String(explicitAgent || inferred?.agent || DEFAULT_AGENT).trim().toLowerCase();
1695
+ let agent2 = raw;
1696
+ let fellBack = false;
1697
+ let runner = RUNNERS[agent2];
1698
+ if (inferred && runner) {
1699
+ try {
1700
+ warn(`VO_CODE_RUNNER_AGENT not set; inferred "${agent2}" from runner binary "${inferred.bin}"`);
1701
+ } catch {
1702
+ }
1703
+ }
1704
+ if (!runner) {
1705
+ try {
1706
+ warn(`unknown VO_CODE_RUNNER_AGENT "${raw}"; falling back to "${DEFAULT_AGENT}" (known: ${listAgents().join(", ")})`);
1707
+ } catch {
1708
+ }
1709
+ agent2 = DEFAULT_AGENT;
1710
+ runner = RUNNERS[DEFAULT_AGENT];
1711
+ fellBack = true;
1712
+ }
1713
+ validateAgentRunner(runner);
1714
+ const runnerBin = env.VO_CODE_RUNNER_BIN || (inferred && inferred.agent === agent2 ? inferred.bin : "") || (agent2 === "claude" ? env.VO_CODE_RUNNER_CLAUDE_BIN : "") || runner.binary;
1715
+ return { agent: agent2, runner, runnerBin, fellBack };
1716
+ }
1717
+
1718
+ // ../../scripts/virtual-office/code-runner/agent-auth-probe-cli.mjs
1719
+ var agent = String(process.argv[2] || "");
1720
+ try {
1721
+ const result = await resolveRunner({ ...process.env, VO_CODE_RUNNER_AGENT: agent }).runner.checkAuth();
1722
+ process.stdout.write(`${JSON.stringify(result)}
1723
+ `);
1724
+ } catch (error) {
1725
+ process.stdout.write(`${JSON.stringify({
1726
+ installed: false,
1727
+ authenticated: false,
1728
+ message: String(error?.message || error)
1729
+ })}
1730
+ `);
1731
+ }