@polderlabs/bizar 10.19.0 → 10.19.2

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.
package/cli/bin.mjs CHANGED
@@ -192,6 +192,9 @@ async function main() {
192
192
  // Pass --help to the command. Commands dispatched through util.mjs
193
193
  // (audit, init, export, doctor, backup, restore, etc.) don't have
194
194
  // their own cli/commands/<name>.mjs — they all live in util.mjs.
195
+ // Direct command modules (bench, release-provenance, verify-release,
196
+ // spec-list, …) have their own --help handling and accept
197
+ // run(cmdArgs); they fall through to the switch below.
195
198
  const UTIL_COMMANDS = new Set([
196
199
  'audit', 'init', 'export', 'test-gate',
197
200
  'doctor', 'repair', 'heads-up', 'backup', 'restore',
@@ -208,8 +211,6 @@ async function main() {
208
211
  const { runMigrate } = await import('./migrate.mjs');
209
212
  await runMigrate(['--help']);
210
213
  return;
211
- } else {
212
- mod = await importCommand(cmd);
213
214
  }
214
215
  if (mod && typeof mod.run === 'function') {
215
216
  await mod.run(cmd, cmdArgs, true);
@@ -418,14 +418,27 @@ export function defaultTierHint(modelId) {
418
418
  return 'mid';
419
419
  }
420
420
 
421
- function classifyKind(modelId) {
421
+ export function classifyKind(modelId) {
422
422
  const id = String(modelId || '').toLowerCase();
423
+ // Order matters: more-specific legacy prefixes are checked BEFORE the
424
+ // generic `claude-` catch-all so `claude-qwen/...` and
425
+ // `claude-minimax/...` are tagged with their real family, not lumped
426
+ // into the generic `claude` bucket. Live gateway prefixes follow the
427
+ // same rule.
428
+ if (id.startsWith('claude-qwen/')) return 'claude-qwen';
429
+ if (id.startsWith('claude-minimax/')) return 'claude-minimax';
423
430
  if (id.startsWith('claude-')) return 'claude';
424
431
  if (id.startsWith('cx/')) return 'cx';
425
432
  if (id.startsWith('oc/')) return 'oc';
426
- if (id.startsWith('claude-minimax/')) return 'claude-minimax';
427
- if (id.startsWith('claude-qwen/')) return 'claude-qwen';
428
433
  if (id.startsWith('anthropic/')) return 'anthropic';
434
+ // Live gateway namespace (10.19.2+): bare provider/model forms exposed by
435
+ // OmniRoute at https://route.polderlabs.io/v1.
436
+ if (id.startsWith('minimax/')) return 'minimax';
437
+ if (id.startsWith('codex/')) return 'codex';
438
+ if (id.startsWith('glm/')) return 'glm';
439
+ if (id.startsWith('qct/')) return 'qct';
440
+ if (id.startsWith('openrouter/')) return 'openrouter';
441
+ if (id.startsWith('a/')) return 'a';
429
442
  return 'other';
430
443
  }
431
444
 
@@ -483,6 +496,102 @@ function writeAtomic(path, body) {
483
496
  renameSync(tmp, path);
484
497
  }
485
498
 
499
+ // ── F-191 / 10.19.2 sync to Claude Code settings.json + stale-ID gating ─────
500
+
501
+ /**
502
+ * Detect model IDs that the live gateway does not serve. Picker picks
503
+ * survive gateway 404s only when the user is warned at save time. Stale
504
+ * IDs are written into `userSelected.staleIds` (audit trail) and excluded
505
+ * from the settings.json sync so Claude Code does not see a `[claude-code:
506
+ * unrecognized_model]` for a known-dead ID on every turn.
507
+ *
508
+ * @param {{ liveIds: string[], pickedIds: string[] }} opts
509
+ * @returns {{ liveIds: string[], staleIds: string[], unknownIds: string[] }}
510
+ * - `liveIds` — picks that exist in the live gateway pool.
511
+ * - `staleIds` — picks that were never returned by the gateway in this run.
512
+ * - `unknownIds` — picks whose live status is unknown (no live pool yet).
513
+ */
514
+ export function partitionStalePicks({ liveIds, pickedIds }) {
515
+ const live = Array.isArray(liveIds) ? new Set(liveIds) : null;
516
+ const picks = Array.isArray(pickedIds) ? pickedIds.filter((id) => typeof id === 'string' && id.trim()) : [];
517
+ if (!live || live.size === 0) {
518
+ return { liveIds: [], staleIds: [], unknownIds: [...new Set(picks)] };
519
+ }
520
+ const liveOut = [];
521
+ const stale = [];
522
+ for (const id of picks) {
523
+ if (live.has(id)) liveOut.push(id);
524
+ else stale.push(id);
525
+ }
526
+ return { liveIds: liveOut, staleIds: stale, unknownIds: [] };
527
+ }
528
+
529
+ /**
530
+ * Sync `userSelected.models` into Claude Code's settings.json under
531
+ * `modelOverrides` using the self-map pattern (`<id>` → `<id>`). This
532
+ * suppresses `[claude-code:unrecognized_model]` diagnostics on every turn
533
+ * for any picked ID the gateway serves. Stale IDs (not returned by the
534
+ * gateway) are excluded so the diagnostic still surfaces them.
535
+ *
536
+ * Behavior:
537
+ * - Reads `settings.json` if present; preserves every other field.
538
+ * - Writes `modelOverrides` as a sparse object: only picked IDs that
539
+ * are also in `liveIds`. Self-map pattern keeps dispatch behavior
540
+ * identical (Claude Code dispatches the literal ID).
541
+ * - Atomic replace via temp-file + rename (matches `applyModels`).
542
+ * - When `settingsJsonPath` is provided (tests), uses that instead of
543
+ * `~/.claude/settings.json`.
544
+ * - When `settingsJsonPath` is `null`, skips the sync entirely — used
545
+ * by tests that want to exercise the picker without touching Claude
546
+ * Code's real settings file.
547
+ *
548
+ * @param {{
549
+ * settingsJsonPath?: string|null,
550
+ * pickedIds: string[],
551
+ * liveIds?: string[],
552
+ * }} opts
553
+ * @returns {{
554
+ * wrote: boolean,
555
+ * syncedIds: string[],
556
+ * skippedStale: string[],
557
+ * settingsPath: string|null,
558
+ * }}
559
+ */
560
+ export function applyModelOverrides({ settingsJsonPath, pickedIds, liveIds = [] }) {
561
+ const path = settingsJsonPath === undefined
562
+ ? join(homedir(), '.claude', 'settings.json')
563
+ : settingsJsonPath;
564
+ if (path === null) {
565
+ return { wrote: false, syncedIds: [], skippedStale: [], settingsPath: null };
566
+ }
567
+ const live = new Set(Array.isArray(liveIds) ? liveIds : []);
568
+ const picks = Array.isArray(pickedIds) ? pickedIds.filter((id) => typeof id === 'string' && id.trim()) : [];
569
+ // Self-map only IDs the live gateway serves. Stale IDs intentionally stay
570
+ // out so the `[claude-code:unrecognized_model]` diagnostic still fires
571
+ // for them — the operator should re-run `bizar models` to drop them.
572
+ const synced = live.size === 0
573
+ ? picks
574
+ : picks.filter((id) => live.has(id));
575
+ const skipped = live.size === 0 ? [] : picks.filter((id) => !live.has(id));
576
+
577
+ let settings = {};
578
+ if (existsSync(path)) {
579
+ try {
580
+ const parsed = JSON.parse(readFileSync(path, 'utf8'));
581
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) settings = parsed;
582
+ } catch {
583
+ // Corrupt settings.json — refuse to overwrite it; surface the sync
584
+ // as a no-op so `bizar models` still completes.
585
+ return { wrote: false, syncedIds: [], skippedStale: skipped, settingsPath: path };
586
+ }
587
+ }
588
+ // Self-map pattern — Claude Code uses modelOverrides to suppress
589
+ // `[claude-code:unrecognized_model]` for any ID that maps to itself.
590
+ settings.modelOverrides = Object.fromEntries(synced.map((id) => [id, id]));
591
+ writeAtomic(path, JSON.stringify(settings, null, 2) + '\n');
592
+ return { wrote: true, syncedIds: synced, skippedStale: skipped, settingsPath: path };
593
+ }
594
+
486
595
  // ── F-190 / IMP-017 refresh + operator-override preservation ────────────────
487
596
 
488
597
  /**
@@ -1294,8 +1403,14 @@ export async function run(name, args, isHelpRequest) {
1294
1403
  process.exit(2);
1295
1404
  }
1296
1405
  const block = applyModels({ routerPath, models: ids, source: 'cli-set' });
1406
+ // Sync to Claude Code's settings.json — picks survive stale-ID
1407
+ // filtering only when an empty liveIds array disables it (no
1408
+ // candidates fetched for `--set`). The orchestrator will surface
1409
+ // `[claude-code:unrecognized_model]` for any ID the gateway later
1410
+ // rejects; the operator can re-run `bizar models` to drop them.
1411
+ const sync = applyModelOverrides({ pickedIds: ids, liveIds: [] });
1297
1412
  if (wantJson) {
1298
- process.stdout.write(JSON.stringify({ applied: block }, null, 2) + '\n');
1413
+ process.stdout.write(JSON.stringify({ applied: block, sync }, null, 2) + '\n');
1299
1414
  } else {
1300
1415
  console.log(chalk.green(` v ${block.models.length} model(s) saved to userSelected`));
1301
1416
  for (const id of block.models) {
@@ -1389,6 +1504,9 @@ export async function run(name, args, isHelpRequest) {
1389
1504
  const picked = await pickModels({ candidates, current });
1390
1505
  if (picked.length === 0) {
1391
1506
  const block = applyModels({ routerPath, models: [], source: 'live-pick' });
1507
+ // Clear Claude Code modelOverrides when the picker is emptied so the
1508
+ // session no longer claims to recognise removed IDs.
1509
+ applyModelOverrides({ pickedIds: [], liveIds: [] });
1392
1510
  if (wantJson) {
1393
1511
  process.stdout.write(JSON.stringify({ applied: block, endpoint, endpointSource }, null, 2) + '\n');
1394
1512
  } else {
@@ -1403,9 +1521,20 @@ export async function run(name, args, isHelpRequest) {
1403
1521
  const profile = candidates.find((candidate) => candidate.id === id)?.profile;
1404
1522
  if (profile) profiles[id] = profile;
1405
1523
  }
1524
+ // F-191 / 10.19.2 — stale-ID detection: the picker shows candidates
1525
+ // filtered through the live gateway, but `--set` and the picker can both
1526
+ // accept IDs the gateway later rejects (e.g. `a/1`). Persist them as
1527
+ // `staleIds` for audit and skip them in the settings.json sync so Claude
1528
+ // Code still surfaces the unrecognized_model diagnostic.
1529
+ const liveIds = candidates.map((c) => c.id);
1530
+ const partition = partitionStalePicks({ liveIds, pickedIds: picked });
1406
1531
  const block = applyModels({ routerPath, models: picked, tierHints, profiles, source: 'live-pick' });
1532
+ if (partition.staleIds.length > 0) {
1533
+ block.staleIds = partition.staleIds;
1534
+ }
1535
+ const sync = applyModelOverrides({ pickedIds: picked, liveIds });
1407
1536
  if (wantJson) {
1408
- process.stdout.write(JSON.stringify({ applied: block, endpoint, endpointSource }, null, 2) + '\n');
1537
+ process.stdout.write(JSON.stringify({ applied: block, endpoint, endpointSource, sync }, null, 2) + '\n');
1409
1538
  } else {
1410
1539
  console.log(chalk.green(`\n v Saved ${block.models.length} model(s) to ${routerPath}:`));
1411
1540
  console.log(chalk.dim(` Models.dev profiles: ${Object.keys(block.profiles || {}).length}/${block.models.length}`));
@@ -1413,6 +1542,9 @@ export async function run(name, args, isHelpRequest) {
1413
1542
  const tier = block.tierHints[id] || defaultTierHint(id);
1414
1543
  console.log(chalk.dim(` ${id} (${tier})`));
1415
1544
  }
1545
+ if (sync.wrote && sync.skippedStale.length > 0) {
1546
+ console.log(chalk.yellow(` Settings sync skipped ${sync.skippedStale.length} stale id(s): ${sync.skippedStale.join(', ')}`));
1547
+ }
1416
1548
  }
1417
1549
  return true;
1418
1550
  }
@@ -1,11 +1,11 @@
1
1
  ---
2
- description: Launch Claude Code in this project on the premium tier (claude-qwen/qwen3.8-max). Prints the exact shell command and env vars to set.
2
+ description: Launch Claude Code in this project on the premium tier (qct/qwen3.8-max-preview). Prints the exact shell command and env vars to set.
3
3
  allowed-tools: Bash
4
4
  ---
5
5
 
6
- # /use-premium — Run this session on claude-qwen/qwen3.8-max (premium)
6
+ # /use-premium — Run this session on qct/qwen3.8-max-preview (premium)
7
7
 
8
- The provider gateway you have configured exposes `claude-qwen/qwen3.8-max`
8
+ The provider gateway you have configured exposes `qct/qwen3.8-max-preview`
9
9
  as the premium-tier reasoning model. Bizar is provider-agnostic — set
10
10
  `BIZAR_MODEL_ROUTER_URL` (or `ANTHROPIC_BASE_URL`) to whatever gateway
11
11
  URL your operator runs, then point the model at the premium tier. Premium
@@ -13,7 +13,7 @@ is expensive — use it intentionally, not by default.
13
13
 
14
14
  ## What it does
15
15
 
16
- Sets `ANTHROPIC_MODEL=claude-qwen/qwen3.8-max` and forwards
16
+ Sets `ANTHROPIC_MODEL=qct/qwen3.8-max-preview` and forwards
17
17
  `ANTHROPIC_BASE_URL` from your operator-configured gateway so the entire
18
18
  session loop runs on the premium model. The model-router is the same
19
19
  one your subagents (`@mike`, `@paul`, `@carl`) already hit.
@@ -24,7 +24,7 @@ one your subagents (`@mike`, `@paul`, `@carl`) already hit.
24
24
 
25
25
  ```bash
26
26
  ANTHROPIC_BASE_URL="${BIZAR_MODEL_ROUTER_URL:-http://your-gateway/v1}" \
27
- ANTHROPIC_MODEL=claude-qwen/qwen3.8-max \
27
+ ANTHROPIC_MODEL=qct/qwen3.8-max-preview \
28
28
  claude
29
29
  ```
30
30
 
@@ -33,7 +33,7 @@ router as the model id):
33
33
 
34
34
  ```bash
35
35
  ANTHROPIC_BASE_URL="${BIZAR_MODEL_ROUTER_URL:-http://your-gateway/v1}" \
36
- claude --model claude-qwen/qwen3.8-max
36
+ claude --model qct/qwen3.8-max-preview
37
37
  ```
38
38
 
39
39
  ### Make it the default for this project
@@ -44,7 +44,7 @@ Add to your local override `.claude/settings.local.json`:
44
44
  {
45
45
  "env": {
46
46
  "ANTHROPIC_BASE_URL": "${BIZAR_MODEL_ROUTER_URL}",
47
- "ANTHROPIC_MODEL": "claude-qwen/qwen3.8-max"
47
+ "ANTHROPIC_MODEL": "qct/qwen3.8-max-preview"
48
48
  }
49
49
  }
50
50
  ```
@@ -54,9 +54,9 @@ Then every Claude Code session in this directory opens on premium.
54
54
  ### Available premium-tier models on the local router
55
55
 
56
56
  ```
57
- claude-qwen/qwen3.8-max — strongest reasoning (orchestration, planning, debug)
58
- cx/gpt-5.6-terra — high reasoning at lower cost (complex impl, review)
59
- cx/gpt-5.6-luna — visually-oriented mid-tier (design)
57
+ qct/qwen3.8-max-preview — strongest reasoning (orchestration, planning, debug)
58
+ codex/gpt-5.6-sol — high reasoning at lower cost (complex impl, review)
59
+ codex/gpt-5.6-luna — visually-oriented mid-tier (design)
60
60
  ```
61
61
 
62
62
  See `.claude/model-router.json` for the full tier table.
@@ -70,9 +70,9 @@ See `.claude/model-router.json` for the full tier table.
70
70
 
71
71
  ## When NOT to use premium
72
72
 
73
- - Routine edits, mechanical work, single-file fixes (use default `claude-minimax/MiniMax-M3`).
73
+ - Routine edits, mechanical work, single-file fixes (use default `minimax/MiniMax-M3`).
74
74
  - Research (always use `@greg` on default).
75
- - Implementation at moderate complexity (use `claude-minimax/MiniMax-M2.7` via `@todd`).
75
+ - Implementation at moderate complexity (use `minimax/MiniMax-M2.7` via `@todd`).
76
76
 
77
77
  The harness's cost ceiling (`costCeilingPerSessionUsd: 5.0` in
78
78
  `model-router.json`) will downgrade you automatically if the budget is
@@ -20,48 +20,51 @@
20
20
  "tiers": {
21
21
  "premium": {
22
22
  "models": [
23
- "claude-qwen/qwen3.8-max",
24
- "cx/gpt-5.6-terra"
23
+ "qct/qwen3.8-max-preview",
24
+ "codex/gpt-5.6-sol",
25
+ "codex/gpt-5.6-luna"
25
26
  ],
26
27
  "purpose": "Architecture, high-risk planning, adversarial verification, and last-resort debugging where mistakes are expensive.",
27
28
  "effort": "high"
28
29
  },
29
30
  "high": {
30
31
  "models": [
31
- "cx/gpt-5.6-terra",
32
- "claude-qwen/qwen3.8-max"
32
+ "codex/gpt-5.6-sol",
33
+ "qct/qwen3.8-max-preview"
33
34
  ],
34
35
  "purpose": "Complex implementation, security review, and cross-cutting reasoning.",
35
36
  "effort": "high"
36
37
  },
37
38
  "mid-design": {
38
39
  "models": [
39
- "cx/gpt-5.6-luna",
40
- "claude-minimax/MiniMax-M3"
40
+ "codex/gpt-5.6-luna",
41
+ "minimax/MiniMax-M3"
41
42
  ],
42
43
  "purpose": "UI/UX design and product-quality implementation where visual judgment matters.",
43
44
  "effort": "medium"
44
45
  },
45
46
  "default": {
46
47
  "models": [
47
- "claude-minimax/MiniMax-M3",
48
- "claude-minimax/MiniMax-M2.7"
48
+ "minimax/MiniMax-M3",
49
+ "minimax/MiniMax-M2.7"
49
50
  ],
50
51
  "purpose": "Broad research, git operations, and ordinary engineering work.",
51
52
  "effort": "medium"
52
53
  },
53
54
  "mid": {
54
55
  "models": [
55
- "claude-minimax/MiniMax-M2.7",
56
- "claude-minimax/MiniMax-M3"
56
+ "minimax/MiniMax-M2.7",
57
+ "minimax/MiniMax-M2.7-highspeed",
58
+ "qct/deepseek-v4-pro",
59
+ "openrouter/nvidia/nemotron-3-ultra-550b-a55b:free"
57
60
  ],
58
61
  "purpose": "Bounded technical analysis, code search, and moderate implementation.",
59
62
  "effort": "medium"
60
63
  },
61
64
  "budget": {
62
65
  "models": [
63
- "claude-minimax/MiniMax-M2.5",
64
- "claude-minimax/MiniMax-M2.7"
66
+ "glm/glm-5.3-flash",
67
+ "minimax/MiniMax-M2.7-highspeed"
65
68
  ],
66
69
  "purpose": "Routine deterministic edits, targeted clarification, and scripted verification.",
67
70
  "effort": "low"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "10.19.0",
3
+ "version": "10.19.2",
4
4
  "description": "Autonomous, human-in-the-loop multi-agent harness for Claude Code with guarded workflows, typed SDK primitives, and MCP tools.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * SDK version constant. Keep synchronized with the workspace package versions.
3
3
  */
4
- export declare const SDK_VERSION: "10.19.0";
4
+ export declare const SDK_VERSION: "10.19.2";
5
5
  //# sourceMappingURL=version.d.ts.map
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * SDK version constant. Keep synchronized with the workspace package versions.
3
3
  */
4
- export const SDK_VERSION = "10.19.0";
4
+ export const SDK_VERSION = "10.19.2";
5
5
  //# sourceMappingURL=version.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar-sdk",
3
- "version": "10.19.0",
3
+ "version": "10.19.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",