@algosuite/vo-mcp 0.2.0-beta.26 → 0.2.0-beta.28

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.
@@ -177,27 +177,48 @@ function getAnthropicKey({ EntryCtor = defaultEntryCtor() } = {}) {
177
177
  }
178
178
  var PREFER_LOGIN_ENV = "VO_RUNNER_PREFER_LOGIN";
179
179
  var CLAUDE_PREFER_LOGIN_ENV = "VO_RUNNER_CLAUDE_PREFER_LOGIN";
180
+ var PREFER_KEY_ENV = "VO_RUNNER_PREFER_KEY";
181
+ var CLAUDE_PREFER_KEY_ENV = "VO_RUNNER_CLAUDE_PREFER_KEY";
180
182
  function isTruthyFlag(v) {
181
183
  const s = String(v ?? "").trim().toLowerCase();
182
184
  return s === "1" || s === "true" || s === "yes" || s === "on";
183
185
  }
184
- function withAnthropicKey(baseEnv = {}, { getKey = getAnthropicKey } = {}) {
185
- if (isTruthyFlag(baseEnv[CLAUDE_PREFER_LOGIN_ENV]) || isTruthyFlag(baseEnv[PREFER_LOGIN_ENV])) {
186
+ function wantsLogin(env) {
187
+ return isTruthyFlag(env[CLAUDE_PREFER_LOGIN_ENV]) || isTruthyFlag(env[PREFER_LOGIN_ENV]);
188
+ }
189
+ function wantsKey(env) {
190
+ return isTruthyFlag(env[CLAUDE_PREFER_KEY_ENV]) || isTruthyFlag(env[PREFER_KEY_ENV]);
191
+ }
192
+ function withAnthropicKey(baseEnv = {}, { getKey = getAnthropicKey, probeLogin = probeClaudeLoginState } = {}) {
193
+ const preferKey = wantsKey(baseEnv);
194
+ if (!preferKey && wantsLogin(baseEnv)) {
186
195
  const next = { ...baseEnv };
187
196
  delete next.ANTHROPIC_API_KEY;
188
197
  return next;
189
198
  }
190
199
  if (baseEnv.ANTHROPIC_API_KEY) return { ...baseEnv };
191
200
  const key = getKey();
192
- return key ? { ...baseEnv, ANTHROPIC_API_KEY: key } : { ...baseEnv };
201
+ if (!key) return { ...baseEnv };
202
+ if (!preferKey && probeLogin() === true) return { ...baseEnv };
203
+ return { ...baseEnv, ANTHROPIC_API_KEY: key };
204
+ }
205
+ function claudeCostBasis(env = process.env) {
206
+ return String(env.ANTHROPIC_API_KEY || "").trim() ? "vendor_billed" : "subscription_api_equivalent";
193
207
  }
194
- function describeAnthropicAuthSource(baseEnv = {}, { getKey = getAnthropicKey } = {}) {
195
- if (isTruthyFlag(baseEnv[CLAUDE_PREFER_LOGIN_ENV]) || isTruthyFlag(baseEnv[PREFER_LOGIN_ENV])) {
208
+ function describeAnthropicAuthSource(baseEnv = {}, { getKey = getAnthropicKey, probeLogin = probeClaudeLoginState } = {}) {
209
+ const preferKey = wantsKey(baseEnv);
210
+ if (!preferKey && wantsLogin(baseEnv)) {
196
211
  return "claude auth login (VO_RUNNER_PREFER_LOGIN set \u2014 any API key ignored)";
197
212
  }
198
213
  if (baseEnv.ANTHROPIC_API_KEY) return "ANTHROPIC_API_KEY from environment";
199
- if (getKey()) return "ANTHROPIC_API_KEY from OS keychain";
200
- return "claude auth login session (no API key set)";
214
+ if (!getKey()) return "claude auth login session (no API key set)";
215
+ if (preferKey) {
216
+ return "ANTHROPIC_API_KEY from OS keychain (VO_RUNNER_PREFER_KEY set \u2014 subscription ignored)";
217
+ }
218
+ if (probeLogin() === true) {
219
+ return "claude auth login session (subscription beats the stored keychain key)";
220
+ }
221
+ return "ANTHROPIC_API_KEY from OS keychain";
201
222
  }
202
223
  function probeClaudeLoginState({
203
224
  spawn: spawn2 = spawnSync2,
@@ -375,6 +396,36 @@ function parseClaudeStreamEvent(line) {
375
396
  return event.type === "result" ? buildResultEvent(event) : null;
376
397
  }
377
398
 
399
+ // ../../scripts/virtual-office/code-runner/agent-auth-tier.mjs
400
+ var AUTH_TIER_SUBSCRIPTION = "subscription";
401
+ var AUTH_TIER_API_KEY = "api_key";
402
+ var AUTH_TIER_LOCAL = "local";
403
+ var AUTH_TIER_UNKNOWN = "unknown";
404
+ var AUTH_TIERS = Object.freeze([
405
+ AUTH_TIER_SUBSCRIPTION,
406
+ AUTH_TIER_API_KEY,
407
+ AUTH_TIER_LOCAL,
408
+ AUTH_TIER_UNKNOWN
409
+ ]);
410
+ var COST_BASIS_TO_TIER = Object.freeze({
411
+ subscription_api_equivalent: AUTH_TIER_SUBSCRIPTION,
412
+ vendor_billed: AUTH_TIER_API_KEY,
413
+ local_zero: AUTH_TIER_LOCAL
414
+ });
415
+ function authTierFromCostBasis(costBasis) {
416
+ return COST_BASIS_TO_TIER[String(costBasis ?? "")] ?? AUTH_TIER_UNKNOWN;
417
+ }
418
+ function safeAuthTier(compute) {
419
+ try {
420
+ return normalizeAuthTier(compute());
421
+ } catch {
422
+ return AUTH_TIER_UNKNOWN;
423
+ }
424
+ }
425
+ function normalizeAuthTier(value) {
426
+ return AUTH_TIERS.includes(value) ? value : AUTH_TIER_UNKNOWN;
427
+ }
428
+
378
429
  // ../../scripts/virtual-office/code-runner/cli-version-floor.mjs
379
430
  var MIN_CLAUDE_CLI_VERSION = "2.1.218";
380
431
  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).";
@@ -440,9 +491,22 @@ function isTimeout(probe) {
440
491
  function notFound(probe) {
441
492
  return errorCode(probe?.error) === "ENOENT";
442
493
  }
494
+ function resolveClaudeAuthTier({
495
+ env = process.env,
496
+ loggedIn = null,
497
+ getStoredKey = getAnthropicKey
498
+ } = {}) {
499
+ return safeAuthTier(() => {
500
+ const spawnEnv = withAnthropicKey(env, { getKey: getStoredKey, probeLogin: () => loggedIn });
501
+ const tier = authTierFromCostBasis(claudeCostBasis(spawnEnv));
502
+ if (tier === AUTH_TIER_SUBSCRIPTION && loggedIn !== true) return AUTH_TIER_UNKNOWN;
503
+ return tier;
504
+ });
505
+ }
443
506
  async function checkClaudeAuth({
444
507
  spawnVersion = spawnClaudeSync,
445
508
  probeLogin = probeClaudeLoginState,
509
+ getStoredKey = getAnthropicKey,
446
510
  env = process.env
447
511
  } = {}) {
448
512
  try {
@@ -492,6 +556,10 @@ async function checkClaudeAuth({
492
556
  return {
493
557
  installed: true,
494
558
  authenticated: true,
559
+ // Dispatch-time billing signal, carried on the same probe that already
560
+ // paid for the login read. Never sent for a non-authenticated result:
561
+ // there is no tier without a working credential.
562
+ authTier: resolveClaudeAuthTier({ env, loggedIn, getStoredKey }),
495
563
  message: loggedIn === true ? "claude CLI installed and logged in (claude auth status)" : "claude binary found (login state unknown \u2014 auth check is best-effort)"
496
564
  };
497
565
  } catch (error) {
@@ -536,7 +604,7 @@ var ClaudeRunner = class {
536
604
  return withAnthropicKey(env);
537
605
  }
538
606
  costBasis(env = process.env) {
539
- return String(env.ANTHROPIC_API_KEY || "").trim() ? "vendor_billed" : "subscription_api_equivalent";
607
+ return claudeCostBasis(env);
540
608
  }
541
609
  /** Describe which Anthropic auth source the spawn will use (for runner logs). */
542
610
  describeAuth(env = process.env) {
@@ -811,6 +879,19 @@ var CodexRunner = class {
811
879
  costBasis(env = process.env) {
812
880
  return String(env.OPENAI_API_KEY || env.CODEX_API_KEY || "").trim() ? "vendor_billed" : "subscription_api_equivalent";
813
881
  }
882
+ /**
883
+ * Dispatch-time billing tier for the NEXT codex spawn, from the SAME facts
884
+ * checkAuth() already gathered — no extra subprocess. applyAuthEnv() is a
885
+ * keychain read in this process (@napi-rs/keyring), not a spawn.
886
+ *
887
+ * Codex is the agent where this signal was already computed and then thrown
888
+ * away: checkAuth() distinguished "API key available (no persisted ChatGPT
889
+ * login)" from a real login, but only inside a `message` string that the
890
+ * heartbeat schema strips before storage. This gives that fact a typed home.
891
+ */
892
+ authTier(env = this.env) {
893
+ return safeAuthTier(() => authTierFromCostBasis(this.costBasis(this.applyAuthEnv(env))));
894
+ }
814
895
  /** Best-effort binary + persisted-login probe. Never throws or spends tokens. */
815
896
  async checkAuth() {
816
897
  try {
@@ -844,6 +925,7 @@ ${login.stderr || ""}`.trim();
844
925
  installed: true,
845
926
  authenticated: true,
846
927
  ...versionField,
928
+ authTier: this.authTier(),
847
929
  message: "codex API key available (no persisted ChatGPT login)"
848
930
  };
849
931
  }
@@ -858,6 +940,7 @@ ${login.stderr || ""}`.trim();
858
940
  installed: true,
859
941
  authenticated: true,
860
942
  ...versionField,
943
+ authTier: this.authTier(),
861
944
  message: output || "codex login status succeeded"
862
945
  };
863
946
  } catch (err) {
@@ -960,6 +1043,16 @@ var CursorRunner = class {
960
1043
  costBasis(env = process.env) {
961
1044
  return env.CURSOR_API_KEY ? "vendor_billed" : "unknown";
962
1045
  }
1046
+ /**
1047
+ * Dispatch-time billing tier. Inherits costBasis()'s deliberate refusal to
1048
+ * guess: a prior interactive `cursor-agent login` has undocumented
1049
+ * subscription semantics, so it reports 'unknown' rather than claiming a
1050
+ * flat-cost seat the runner cannot actually prove. Keychain read only — the
1051
+ * heartbeat never pays for a subprocess to answer this.
1052
+ */
1053
+ authTier(env = process.env) {
1054
+ return safeAuthTier(() => authTierFromCostBasis(this.costBasis(this.applyAuthEnv(env))));
1055
+ }
963
1056
  /** Best-effort: is `cursor-agent` on PATH? Never throws. */
964
1057
  async checkAuth() {
965
1058
  try {
@@ -985,6 +1078,7 @@ var CursorRunner = class {
985
1078
  return {
986
1079
  installed: true,
987
1080
  authenticated: true,
1081
+ authTier: this.authTier(),
988
1082
  message: "cursor-agent found (EXPERIMENTAL: headless mode may need a TTY; auth check is best-effort)"
989
1083
  };
990
1084
  } catch (err) {
@@ -1121,6 +1215,10 @@ var LocalModelRunner = class {
1121
1215
  costBasis() {
1122
1216
  return "local_zero";
1123
1217
  }
1218
+ /** Dispatch-time billing tier: local inference is never billed by a vendor. */
1219
+ authTier() {
1220
+ return safeAuthTier(() => authTierFromCostBasis(this.costBasis()));
1221
+ }
1124
1222
  describeAuth(env = process.env) {
1125
1223
  const authEnv = this.applyAuthEnv(env);
1126
1224
  const provider = resolveLocalProvider(this.env);
@@ -1178,6 +1276,7 @@ var LocalModelRunner = class {
1178
1276
  return {
1179
1277
  installed: true,
1180
1278
  authenticated: true,
1279
+ authTier: this.authTier(),
1181
1280
  message: `${provider} reachable; model "${model}" configured (local-only, no cloud spend)`
1182
1281
  };
1183
1282
  }
@@ -1250,7 +1349,7 @@ var OpenAICompatibleRunner = class {
1250
1349
  buildArgs(opts = {}) {
1251
1350
  void opts;
1252
1351
  throw new Error(
1253
- "OpenAI-compatible full-repository coding is disabled. Use an explicit sanitized task capsule through the AlgoSuite Model Firewall."
1352
+ "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."
1254
1353
  );
1255
1354
  }
1256
1355
  /** Codex JSONL events map identically → reuse the proven parser. */
@@ -1273,14 +1372,14 @@ var OpenAICompatibleRunner = class {
1273
1372
  describeAuth(env = process.env) {
1274
1373
  const hasKey = Boolean(String(env[OAI_API_KEY_ENV] || "").trim());
1275
1374
  const baseUrl = resolveOaiBaseUrl(env);
1276
- return `oai-compat endpoint=${baseUrl || "<unset>"} key=${hasKey ? "set" : "MISSING"}`;
1375
+ return `oai-compat RETIRED endpoint=${baseUrl || "<unset>"} key=${hasKey ? "set" : "MISSING"}`;
1277
1376
  }
1278
- /** Best-effort: base URL chosen AND the codex transport is installed. */
1377
+ /** Always unavailable: the lane is retired, so nothing can authenticate it. */
1279
1378
  async checkAuth() {
1280
1379
  return {
1281
1380
  installed: false,
1282
1381
  authenticated: false,
1283
- message: "OpenAI-compatible coding is disabled; sanitized Model Firewall task capsules only"
1382
+ message: "the `oai` lane is RETIRED (PR #8742); OpenAI-compatible coding is disabled \u2014 sanitized Model Firewall task capsules only"
1284
1383
  };
1285
1384
  }
1286
1385
  };