@polderlabs/bizar 10.23.11 → 10.23.14

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,13 @@ 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
+ };
1019
1025
  }
1020
1026
 
1021
1027
  export function configuredFallbackModels(router) {
@@ -1059,9 +1065,6 @@ export function buildClaudeModelOverrides(modelIds) {
1059
1065
  .filter((id) => typeof id === 'string' && id.trim())
1060
1066
  .map((id) => id.trim()))];
1061
1067
  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
1068
  return Object.fromEntries(CLAUDE_MODEL_OVERRIDE_KEYS
1066
1069
  .map((key, index) => [key, unique[index % unique.length]]));
1067
1070
  }
@@ -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. Pass the exact enabled raw gateway ID chosen
42
+ from `bizar models` as `model`; never substitute a Claude family alias. You may
43
+ include that selection in `additionalContext.bizarConfiguredModel` for audit
44
+ telemetry. If no configured model exists, stop and ask the operator to run
45
+ `bizar models`; never retry by omitting `model` or using `inherit`.
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
60
+ candidates; `disabledProviders` excludes both. Pass that raw selected custom
61
+ ID directly; the guard verifies it is in the enabled selected pool. If no
59
62
  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.
63
+ let Claude choose an unconfigured default, inherit the session model, or retry
64
+ by cycling models, providers, or tiers. A single configured transport failover
65
+ 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,18 @@ export async function guardAgentModel(input, options = {}) {
204
222
  return {};
205
223
  }
206
224
 
225
+ const transportTarget = readTransportTarget(requested, options);
226
+ if (transportTarget) {
227
+ // `modelOverrides` is the native Agent transport contract: Claude Code
228
+ // invokes this alias with the mapped gateway ID. The optional context is
229
+ // useful for telemetry, but must not make a valid configured alias fail
230
+ // when Mike's prompt carries a different (also selected) planning pick.
231
+ if (!userPicks.has(transportTarget)) {
232
+ return deny(`Bizar Agent dispatch blocked: native alias ${requested} does not map to an enabled Bizar selection. Re-run \`bizar models\` and restart Claude Code.`);
233
+ }
234
+ return {};
235
+ }
236
+
207
237
  // F-185 contract: when the orchestrator passes both `routingDecisionId`
208
238
  // and `fallback`, validate the fallback against the userSelected pool
209
239
  // 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 one exact raw model ID from the enabled `bizar models` user selection. Pass that ID directly as `model`; never substitute a Claude family alias, provider default, or inherited session model. The guard allows only IDs in the global user-selected pool. If a selected custom model is denied, inspect `~/.claude/model-router.json`; do not retry with another model. 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,10 +377,13 @@ 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'),
@@ -794,12 +797,13 @@ export function classifyDispatchOutcome(result, error, startMs) {
794
797
 
795
798
  /**
796
799
  * Build the augmented agent-call payload. The native Agent model field cannot
797
- * carry arbitrary gateway IDs, so the selected Bizar ID is audit context and
798
- * the guard permits inheritance only from an identical configured parent.
800
+ * carry selected gateway IDs directly. The guard validates that the raw ID is
801
+ * an enabled Bizar selection before the native Agent tool sees it.
799
802
  */
800
803
  export function augmentPayload(opts, decision, agentName) {
801
804
  return {
802
805
  ...opts,
806
+ model: decision.modelId,
803
807
  additionalContext: {
804
808
  ...(opts.additionalContext && typeof opts.additionalContext === 'object' ? opts.additionalContext : {}),
805
809
  bizarConfiguredModel: decision.modelId ?? null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "10.23.11",
3
+ "version": "10.23.14",
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",