@polderlabs/bizar 10.23.11 → 10.23.13

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.
@@ -1015,7 +1015,14 @@ export function applyModelOverrides({ settingsJsonPath, pickedIds, liveIds = [],
1015
1015
  if (Object.keys(settings.env).length === 0) delete settings.env;
1016
1016
  }
1017
1017
  writeAtomic(path, JSON.stringify(settings, null, 2) + '\n');
1018
- return { wrote: true, syncedIds: synced, skippedStale: skipped, skippedDisabled, settingsPath: path };
1018
+ return {
1019
+ wrote: true,
1020
+ syncedIds: synced,
1021
+ skippedStale: skipped,
1022
+ skippedDisabled,
1023
+ settingsPath: path,
1024
+ agentAliases: resolveNativeAgentAliases(settings.modelOverrides),
1025
+ };
1019
1026
  }
1020
1027
 
1021
1028
  export function configuredFallbackModels(router) {
@@ -1054,16 +1061,35 @@ export const CLAUDE_MODEL_OVERRIDE_KEYS = Object.freeze([
1054
1061
  'claude-3-5-sonnet-20241022',
1055
1062
  ]);
1056
1063
 
1064
+ // These are Claude Code transport aliases, not model choices. Gateway model
1065
+ // IDs remain entirely operator-selected in the global Bizar router. Do not
1066
+ // use `fable`: its Claude-branded label is misleading for opted-out users.
1067
+ export const NATIVE_AGENT_TRANSPORT_KEYS = Object.freeze({
1068
+ sonnet: 'claude-sonnet-5',
1069
+ opus: 'claude-opus-5',
1070
+ haiku: 'claude-haiku-4-5-20251001',
1071
+ });
1072
+
1073
+ export function resolveNativeAgentAliases(modelOverrides) {
1074
+ const overrides = modelOverrides && typeof modelOverrides === 'object' ? modelOverrides : {};
1075
+ return Object.fromEntries(Object.entries(NATIVE_AGENT_TRANSPORT_KEYS)
1076
+ .filter(([, key]) => typeof overrides[key] === 'string' && overrides[key].trim())
1077
+ .map(([alias, key]) => [overrides[key].trim(), alias]));
1078
+ }
1079
+
1057
1080
  export function buildClaudeModelOverrides(modelIds) {
1058
1081
  const unique = [...new Set((Array.isArray(modelIds) ? modelIds : [])
1059
1082
  .filter((id) => typeof id === 'string' && id.trim())
1060
1083
  .map((id) => id.trim()))];
1061
1084
  if (unique.length === 0) return {};
1062
- // Cover every built-in alias: the workflow runtime may normalize an Agent
1063
- // request through one of these names, and no alias may escape to an
1064
- // unconfigured Anthropic default.
1065
- return Object.fromEntries(CLAUDE_MODEL_OVERRIDE_KEYS
1066
- .map((key, index) => [key, unique[index % unique.length]]));
1085
+ // Reserve accepted native Agent aliases first so custom gateway IDs have a
1086
+ // valid transport without putting the raw ID into Agent.model.
1087
+ const transportIndex = new Map(Object.values(NATIVE_AGENT_TRANSPORT_KEYS)
1088
+ .map((key, index) => [key, index]));
1089
+ return Object.fromEntries(CLAUDE_MODEL_OVERRIDE_KEYS.map((key, index) => [
1090
+ key,
1091
+ unique[(transportIndex.get(key) ?? index) % unique.length],
1092
+ ]));
1067
1093
  }
1068
1094
 
1069
1095
  /** Custom gateway IDs must be discoverable to Claude's SDK/subagent path. */
@@ -20,7 +20,10 @@ export function resolveGlobalModelRouter(options = {}) {
20
20
  ? env.BIZAR_MODEL_ROUTER_CONFIG.trim()
21
21
  : '';
22
22
  if (override) return isAbsolute(override) ? override : resolve(cwd, override);
23
- return join(resolveBizarHome({ env, cwd }), 'config', 'claude', 'model-router.json');
23
+ // Claude Code reads hooks, agents, skills, and model-router from ~/.claude/.
24
+ // Use ~/.claude/model-router.json as the single canonical location so the
25
+ // hook and the CLI always agree on the same file.
26
+ return join(resolveClaudeConfigDir({ env, cwd }), 'model-router.json');
24
27
  }
25
28
 
26
29
  export function resolveGlobalLearningDir(options = {}) {
package/cli/provision.mjs CHANGED
@@ -490,6 +490,8 @@ export function isBizarManagedModelRouter(value) {
490
490
  }
491
491
 
492
492
  export async function syncModelRouter({ dryRun = false, force = false } = {}) {
493
+ // Write to ~/.claude/model-router.json (resolveGlobalModelRouter now points there).
494
+ // This is the single canonical location — both the CLI and Claude Code hooks read it.
493
495
  const dest = resolveGlobalModelRouter();
494
496
  if (existsSync(dest)) {
495
497
  return { ok: true, message: `${dest} is operator-managed — preserved`, preserved: true };
@@ -38,9 +38,11 @@ Before every Workflow, Agent, or Agent-team call, read the global Bizar model ro
38
38
  small `args.routing` object whose `default`, `medium`, and `high` values are
39
39
  explicit enabled configured model IDs (user picks win; otherwise use enabled
40
40
  tier candidates). Include the user's task in the same args object under the
41
- workflow's documented task field. Never pass only a string and never use
42
- `inherit`, `sonnet`, `opus`, or another provider default. If no configured ID
43
- exists, stop and ask the operator to run `bizar models`.
41
+ workflow's documented task field. Native Agent accepts only its transport
42
+ aliases (`sonnet`, `opus`, or `haiku`), never a raw gateway ID. Use the alias
43
+ that `bizar models` synchronized for the selected ID, and include the selected
44
+ ID in `additionalContext.bizarConfiguredModel` for guard verification. If no
45
+ configured ID exists, stop and ask the operator to run `bizar models`.
44
46
 
45
47
  Invoke the selected workflow by `name` first. If Claude reports that the Bizar
46
48
  name is unavailable, resolve the active Claude config directory and retry once
@@ -55,11 +57,12 @@ implementation around a broken workflow installation.
55
57
 
56
58
  For every Agent call, select the cheapest sufficient enabled configured model
57
59
  from the global Bizar router. User-selected models take precedence over tier
58
- candidates; `disabledProviders` excludes both. Always pass `model`. If no
59
- enabled configured candidate exists, stop with the configuration error. Never
60
- let Claude choose an unconfigured default and never retry by cycling models,
61
- aliases, providers, or tiers. A single configured transport failover is allowed
62
- only when the router explicitly supplies it.
60
+ candidates; `disabledProviders` excludes both. Pass only the synchronized
61
+ native transport alias, never a raw gateway ID; the guard verifies its mapping
62
+ to the selected custom ID. If no enabled configured candidate exists, stop
63
+ with the configuration error. Never let Claude choose an unconfigured default
64
+ or retry by cycling aliases, providers, or tiers. A single configured transport
65
+ failover is allowed only when the router explicitly supplies it.
63
66
 
64
67
  ## Worktree Discipline and integration
65
68
 
@@ -165,6 +165,22 @@ function readConfiguredParentModel(options = {}) {
165
165
  }
166
166
  }
167
167
 
168
+ const NATIVE_AGENT_TRANSPORT_KEYS = Object.freeze({
169
+ sonnet: 'claude-sonnet-5',
170
+ opus: 'claude-opus-5',
171
+ haiku: 'claude-haiku-4-5-20251001',
172
+ });
173
+
174
+ function readTransportTarget(alias, options = {}) {
175
+ const key = NATIVE_AGENT_TRANSPORT_KEYS[alias];
176
+ if (!key) return '';
177
+ const overrides = options.modelOverrides || (() => {
178
+ const settingsPath = options.settingsPath || join(resolveClaudeConfigDir(), 'settings.json');
179
+ try { return JSON.parse(readFileSync(settingsPath, 'utf8'))?.modelOverrides; } catch { return null; }
180
+ })();
181
+ return typeof overrides?.[key] === 'string' ? overrides[key].trim() : '';
182
+ }
183
+
168
184
  export async function guardAgentModel(input, options = {}) {
169
185
  if (!input || typeof input !== 'object') return {};
170
186
  if (input.hook_event_name !== 'PreToolUse' || input.tool_name !== 'Agent') return {};
@@ -190,13 +206,15 @@ export async function guardAgentModel(input, options = {}) {
190
206
  const allowed = configuredModels(registry);
191
207
  const userPicks = userSelectedModels(registry);
192
208
 
209
+ const configuredContext = typeof toolInput.additionalContext?.bizarConfiguredModel === 'string'
210
+ ? toolInput.additionalContext.bizarConfiguredModel.trim()
211
+ : '';
212
+
193
213
  // The native Agent tool rejects arbitrary gateway IDs in its `model` enum.
194
214
  // Inheritance is safe only when the auditable Bizar selection equals the
195
215
  // actual global Claude parent model and is a selected, enabled user pick.
196
216
  if (!requested || requested === 'inherit') {
197
- const configured = typeof toolInput.additionalContext?.bizarConfiguredModel === 'string'
198
- ? toolInput.additionalContext.bizarConfiguredModel.trim()
199
- : '';
217
+ const configured = configuredContext;
200
218
  const parent = readConfiguredParentModel(options);
201
219
  if (!configured || configured !== parent || !userPicks.has(configured)) {
202
220
  return deny('Bizar Agent dispatch blocked: native Agent inheritance requires the global Claude parent model to exactly match an enabled Bizar user selection. Run `bizar models` and restart Claude Code.');
@@ -204,6 +222,14 @@ export async function guardAgentModel(input, options = {}) {
204
222
  return {};
205
223
  }
206
224
 
225
+ const transportTarget = readTransportTarget(requested, options);
226
+ if (transportTarget) {
227
+ if (configuredContext !== transportTarget || !userPicks.has(transportTarget)) {
228
+ return deny(`Bizar Agent dispatch blocked: native alias ${requested} does not map to the audited enabled Bizar selection. Re-run \`bizar models\` and restart Claude Code.`);
229
+ }
230
+ return {};
231
+ }
232
+
207
233
  // F-185 contract: when the orchestrator passes both `routingDecisionId`
208
234
  // and `fallback`, validate the fallback against the userSelected pool
209
235
  // but skip the live-discovery re-probe. The fallback's eligibility was
@@ -3,7 +3,7 @@
3
3
  * sessionstart-model-sync.mjs — Claude Code SessionStart hook.
4
4
  *
5
5
  * Re-applies the operator's `userSelected.models` block (from
6
- * `~/.config/bizar/config/claude/model-router.json`) into the three
6
+ * `~/.claude/model-router.json`) into the three
7
7
  * Claude Code settings keys that the operator owns:
8
8
  *
9
9
  * - modelPicker → { options: [{ model, label }] } in user pick order
@@ -23,7 +23,7 @@
23
23
  * This hook ONLY touches `modelPicker`, `modelOverrides`, and `model`.
24
24
  * Env, mcpServers, permissions, hooks, and every other operator key is
25
25
  * left untouched. The source of truth for picks
26
- * (`~/.config/bizar/config/claude/model-router.json`) is also untouched.
26
+ * (`~/.claude/model-router.json`) is also untouched.
27
27
  *
28
28
  * Failure policy:
29
29
  * This is an advisory hook. Any failure (missing router, malformed JSON,
@@ -60,7 +60,7 @@ const ROUTE_POLICY = [
60
60
  'Adaptive Bizar routing policy:',
61
61
  '- If this is the primary session, you ARE @mike. First do only bounded read-only orientation. Then ask one concise clarification question describing the inferred outcome, the material choice/risk, and your proposed coordination mode. Wait for the answer before edits, branches, tests, or editor dispatch. If the user explicitly waives questions, continue autonomously.',
62
62
  '- After clarification, choose the lightest coordination mode: a direct tiny edit, one isolated Agent for a bounded change, a native Bizar Workflow for repeatable phased work, parallel Agents for disjoint scopes, or an Agent team only when 3+ sustained roles need cross-talk. Do not force a workflow or team when it adds no value.',
63
- '- Every Agent or team member receives an explicit enabled configured Bizar model; use no provider alias, inheritance, or default. Every editing worker uses call-level `isolation: "worktree"`. For genuinely disjoint writable scopes, dispatch concurrently; otherwise use one owner.',
63
+ '- Every Agent or team member receives an enabled configured Bizar selection. Native Agent calls use only the synchronized sonnet/opus/haiku transport alias plus additionalContext.bizarConfiguredModel; the guard proves that alias maps to the selected custom gateway ID. Never pass raw gateway IDs or fall back to a provider default. Every editing worker uses call-level `isolation: "worktree"`. For genuinely disjoint writable scopes, dispatch concurrently; otherwise use one owner.',
64
64
  '- Consume terminal agent results, merge queued worktrees with bizar worktree-merge, and run integration checks in the primary session. A subagent may not recursively dispatch itself.',
65
65
  '- Do NOT execute any tool you do not have. If a tool you need is missing from your tools list, dispatch to a subagent that has it — do not pretend you have it.',
66
66
  '- If you are already running as a Bizar custom agent, follow your assigned role and do not recursively dispatch yourself.',
@@ -377,16 +377,34 @@ function defaultConfigPaths({ cwd = process.cwd(), env = process.env } = {}) {
377
377
  const configuredRouter = typeof env.BIZAR_MODEL_ROUTER_CONFIG === 'string'
378
378
  ? env.BIZAR_MODEL_ROUTER_CONFIG.trim()
379
379
  : '';
380
+ const claudeDir = typeof env.CLAUDE_CONFIG_DIR === 'string' && env.CLAUDE_CONFIG_DIR.trim()
381
+ ? (isAbsolute(env.CLAUDE_CONFIG_DIR.trim()) ? env.CLAUDE_CONFIG_DIR.trim() : resolve(cwd, env.CLAUDE_CONFIG_DIR.trim()))
382
+ : join(userHome, '.claude');
380
383
  return {
381
384
  modelRouter: configuredRouter
382
385
  ? (isAbsolute(configuredRouter) ? configuredRouter : resolve(cwd, configuredRouter))
383
- : join(home, 'config', 'claude', 'model-router.json'),
386
+ : join(claudeDir, 'model-router.json'),
384
387
  health: join(home, 'health.json'),
385
388
  budget: join(home, 'budget.json'),
386
389
  userSelected: join(home, 'userSelected.json'),
390
+ claudeSettings: join(claudeDir, 'settings.json'),
387
391
  };
388
392
  }
389
393
 
394
+ const NATIVE_AGENT_TRANSPORT_KEYS = Object.freeze({
395
+ sonnet: 'claude-sonnet-5',
396
+ opus: 'claude-opus-5',
397
+ haiku: 'claude-haiku-4-5-20251001',
398
+ });
399
+
400
+ function loadAgentTransportAliases(settingsPath) {
401
+ const overrides = readJson(settingsPath)?.modelOverrides;
402
+ if (!overrides || typeof overrides !== 'object') return {};
403
+ return Object.fromEntries(Object.entries(NATIVE_AGENT_TRANSPORT_KEYS)
404
+ .filter(([, key]) => typeof overrides[key] === 'string' && overrides[key].trim())
405
+ .map(([alias, key]) => [overrides[key].trim(), alias]));
406
+ }
407
+
390
408
  function readJson(path) {
391
409
  try {
392
410
  if (!existsSync(path)) return undefined;
@@ -461,7 +479,8 @@ export function loadDispatchContext({ cwd = process.cwd(), env = process.env } =
461
479
  const healthRaw = readJson(paths.health) ?? {};
462
480
  const health = (healthRaw && typeof healthRaw === 'object' && healthRaw.models) || {};
463
481
  const activeSessionModel = env.BIZAR_ACTIVE_SESSION_MODEL ?? loadActiveSessionModel();
464
- return { selectedProfiles, staticProfiles, activeSessionModel, budget, health, evidenceDir: resolveEvidenceDir() };
482
+ const agentAliases = loadAgentTransportAliases(paths.claudeSettings);
483
+ return { selectedProfiles, staticProfiles, activeSessionModel, budget, health, agentAliases, evidenceDir: resolveEvidenceDir() };
465
484
  }
466
485
 
467
486
  function resolveEvidenceDir({ cwd = process.cwd(), env = process.env } = {}) {
@@ -797,9 +816,10 @@ export function classifyDispatchOutcome(result, error, startMs) {
797
816
  * carry arbitrary gateway IDs, so the selected Bizar ID is audit context and
798
817
  * the guard permits inheritance only from an identical configured parent.
799
818
  */
800
- export function augmentPayload(opts, decision, agentName) {
819
+ export function augmentPayload(opts, decision, agentName, transportAlias) {
801
820
  return {
802
821
  ...opts,
822
+ model: transportAlias,
803
823
  additionalContext: {
804
824
  ...(opts.additionalContext && typeof opts.additionalContext === 'object' ? opts.additionalContext : {}),
805
825
  bizarConfiguredModel: decision.modelId ?? null,
@@ -860,7 +880,14 @@ export async function dispatchAgent(agentFn, agentName, prompt, opts = {}, conte
860
880
  'No enabled configured model is available for this Agent dispatch. Configure a model tier or user selection with `bizar models`; refusing to inherit an unconfigured provider default.',
861
881
  );
862
882
  }
863
- const augmented = augmentPayload(opts, decision, agentName);
883
+ const ctx = context ?? loadDispatchContext();
884
+ const transportAlias = ctx.agentAliases?.[decision.modelId];
885
+ if (!transportAlias) {
886
+ throw new ModelRoutingError(
887
+ `No native Agent transport alias maps to ${decision.modelId}. Re-run \`bizar models\` to synchronize global Claude settings, then restart Claude Code.`,
888
+ );
889
+ }
890
+ const augmented = augmentPayload(opts, decision, agentName, transportAlias);
864
891
 
865
892
  // F-191 / IMP-018 audit trail — persist the decision before invoking
866
893
  // the agent. Best-effort: telemetry failures MUST NOT abort the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "10.23.11",
3
+ "version": "10.23.13",
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": {
@@ -46,11 +46,15 @@ function modelIds(value) {
46
46
  : [];
47
47
  }
48
48
  function defaultConfigPath(_cwd = process.cwd()) {
49
- // Anchor on BIZAR_HOME the router file is operator-controlled state
50
- // that must survive cwd changes and `bizar install --force` clean runs.
51
- // The `_cwd` parameter is retained for the explicit `configPath` branch
52
- // (relative `BIZAR_MODEL_ROUTER_CONFIG` overrides still resolve against
53
- // it for tests that pre-stage the file in a tmp dir).
49
+ // Claude Code reads hooks, agents, skills, and model-router from ~/.claude/.
50
+ // Use ~/.claude/model-router.json as the single canonical location so the
51
+ // hook and SDK always agree. Backward-compat fallback to BIZAR_HOME for
52
+ // existing installations that still have the file there.
53
+ const claudeConfigDir = join(osHomedir(), ".claude");
54
+ const claudePath = join(claudeConfigDir, "model-router.json");
55
+ if (existsSync(claudePath))
56
+ return claudePath;
57
+ // Fallback: BIZAR_HOME (for pre-10.23.12 installs).
54
58
  return join(bizarHome(), "config", "claude", "model-router.json");
55
59
  }
56
60
  export function loadModelRegistry(src = {}) {
@@ -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.23.11";
4
+ export declare const SDK_VERSION: "10.23.13";
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.23.11";
4
+ export const SDK_VERSION = "10.23.13";
5
5
  //# sourceMappingURL=version.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar-sdk",
3
- "version": "10.23.11",
3
+ "version": "10.23.13",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",