@polderlabs/bizar 10.23.14 → 10.23.15

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
@@ -129,6 +129,7 @@ function showHelp() {
129
129
  workflow <subcommand> Session-bound autopilot workflow state
130
130
  hook <name> Run a portable Claude Code hook
131
131
  worktree-merge <branch> Merge a feature branch with archive tag (no work lost)
132
+ worker <subcommand> Run an exact-model Claude process worker in a worktree
132
133
  models Configure the global model picker and Models.dev metadata
133
134
  evidence <subcommand> Inspect model-routing evidence
134
135
  improve <subcommand> Propose and verify bounded self-edits
@@ -613,6 +614,14 @@ async function main() {
613
614
  return;
614
615
  }
615
616
 
617
+ case 'worker': {
618
+ const mod = await importCommand('worker');
619
+ if (!mod) { process.exit(EXIT_ERROR); return; }
620
+ const found = await mod.run(cmd, cmdArgs, isHelpRequest);
621
+ if (found === false) process.exit(EXIT_USAGE);
622
+ break;
623
+ }
624
+
616
625
  case 'explain-run': {
617
626
  const mod = await importCommand('explain-run');
618
627
  if (!mod) {
@@ -991,6 +991,7 @@ export function applyModelOverrides({ settingsJsonPath, pickedIds, liveIds = [],
991
991
  // must be keys; configured gateway aliases are values. This also suppresses
992
992
  // print-mode `[claude-code:unrecognized_model]` diagnostics for Agent SDK calls.
993
993
  settings.modelOverrides = buildClaudeModelOverrides(synced);
994
+ const nativeAgentAliases = applyNativeAgentAliasTargets(settings, synced);
994
995
  if (requiresGatewayModelDiscovery(synced)) {
995
996
  settings.env = {
996
997
  ...(settings.env || {}),
@@ -1020,6 +1021,7 @@ export function applyModelOverrides({ settingsJsonPath, pickedIds, liveIds = [],
1020
1021
  syncedIds: synced,
1021
1022
  skippedStale: skipped,
1022
1023
  skippedDisabled,
1024
+ nativeAgentAliases,
1023
1025
  settingsPath: path,
1024
1026
  };
1025
1027
  }
@@ -1042,9 +1044,8 @@ export function configuredEnabledModels(router) {
1042
1044
  }
1043
1045
 
1044
1046
  export const CLAUDE_MODEL_OVERRIDE_KEYS = Object.freeze([
1045
- 'claude-fable-5',
1046
- 'claude-opus-5',
1047
1047
  'claude-sonnet-5',
1048
+ 'claude-opus-5',
1048
1049
  'claude-haiku-4-5-20251001',
1049
1050
  'claude-opus-4-8',
1050
1051
  'claude-opus-4-7',
@@ -1060,13 +1061,61 @@ export const CLAUDE_MODEL_OVERRIDE_KEYS = Object.freeze([
1060
1061
  'claude-3-5-sonnet-20241022',
1061
1062
  ]);
1062
1063
 
1064
+ // Claude Code's native Agent tool accepts these transport labels in the
1065
+ // installed runtime. They are labels only: Bizar binds them to the operator's
1066
+ // gateway IDs below; they never select an Anthropic provider by themselves.
1067
+ export const NATIVE_AGENT_ALIASES = Object.freeze(['sonnet', 'opus', 'haiku', 'fable']);
1068
+
1069
+ const NATIVE_AGENT_OVERRIDE_KEYS = Object.freeze({
1070
+ sonnet: 'claude-sonnet-5',
1071
+ opus: 'claude-opus-5',
1072
+ haiku: 'claude-haiku-4-5-20251001',
1073
+ fable: 'claude-fable-5',
1074
+ });
1075
+
1076
+ export function buildNativeAgentAliasTargets(modelIds) {
1077
+ const unique = [...new Set((Array.isArray(modelIds) ? modelIds : [])
1078
+ .filter((id) => typeof id === 'string' && id.trim())
1079
+ .map((id) => id.trim()))];
1080
+ if (unique.length === 0) return {};
1081
+ return Object.fromEntries(NATIVE_AGENT_ALIASES.map((alias, index) => [
1082
+ alias,
1083
+ unique[index] || unique[0],
1084
+ ]));
1085
+ }
1086
+
1087
+ export function applyNativeAgentAliasTargets(settings, modelIds) {
1088
+ const targets = buildNativeAgentAliasTargets(modelIds);
1089
+ const env = { ...(settings.env || {}) };
1090
+ const envKeys = {
1091
+ sonnet: 'ANTHROPIC_DEFAULT_SONNET_MODEL',
1092
+ opus: 'ANTHROPIC_DEFAULT_OPUS_MODEL',
1093
+ haiku: 'ANTHROPIC_DEFAULT_HAIKU_MODEL',
1094
+ fable: 'ANTHROPIC_DEFAULT_FABLE_MODEL',
1095
+ };
1096
+ for (const alias of NATIVE_AGENT_ALIASES) {
1097
+ const envKey = envKeys[alias];
1098
+ if (targets[alias]) env[envKey] = targets[alias];
1099
+ else delete env[envKey];
1100
+ }
1101
+ settings.env = env;
1102
+ return targets;
1103
+ }
1104
+
1063
1105
  export function buildClaudeModelOverrides(modelIds) {
1064
1106
  const unique = [...new Set((Array.isArray(modelIds) ? modelIds : [])
1065
1107
  .filter((id) => typeof id === 'string' && id.trim())
1066
1108
  .map((id) => id.trim()))];
1067
1109
  if (unique.length === 0) return {};
1068
- return Object.fromEntries(CLAUDE_MODEL_OVERRIDE_KEYS
1069
- .map((key, index) => [key, unique[index % unique.length]]));
1110
+ const targets = buildNativeAgentAliasTargets(unique);
1111
+ const nativeEntries = NATIVE_AGENT_ALIASES.map((alias) => [
1112
+ NATIVE_AGENT_OVERRIDE_KEYS[alias],
1113
+ targets[alias],
1114
+ ]);
1115
+ const remaining = CLAUDE_MODEL_OVERRIDE_KEYS
1116
+ .filter((key) => !Object.values(NATIVE_AGENT_OVERRIDE_KEYS).includes(key))
1117
+ .map((key, index) => [key, unique[index % unique.length]]);
1118
+ return Object.fromEntries([...nativeEntries, ...remaining]);
1070
1119
  }
1071
1120
 
1072
1121
  /** Custom gateway IDs must be discoverable to Claude's SDK/subagent path. */
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Exact-model process workers.
3
+ *
4
+ * This command is the separately launched alternative: it creates an isolated
5
+ * worktree and starts a top-level Claude process with the literal gateway ID
6
+ * selected through `bizar models`. No global settings are rewritten per
7
+ * worker, so parallel workers cannot race each other's model selection.
8
+ */
9
+ import { spawn, spawnSync } from 'node:child_process';
10
+ import { createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
11
+ import { basename, dirname, join } from 'node:path';
12
+ import { randomUUID } from 'node:crypto';
13
+ import { resolveBizarHome, resolveClaudeConfigDir, resolveGlobalModelRouter } from '../config-paths.mjs';
14
+
15
+ function runGit(args, cwd) {
16
+ const result = spawnSync('git', args, { cwd, encoding: 'utf8' });
17
+ if (result.status !== 0) throw new Error((result.stderr || result.stdout || `git ${args.join(' ')} failed`).trim());
18
+ return result.stdout.trim();
19
+ }
20
+
21
+ function readRouter() {
22
+ const path = resolveGlobalModelRouter();
23
+ try { return JSON.parse(readFileSync(path, 'utf8')); } catch { throw new Error('Global Bizar model router is missing or invalid; run `bizar models`.'); }
24
+ }
25
+
26
+ export function enabledModels(router) {
27
+ const disabled = new Set((router.disabledProviders || []).filter((id) => typeof id === 'string').map((id) => id.trim().toLowerCase()));
28
+ return (router.userSelected?.models || []).filter((id) => typeof id === 'string' && id.trim() && ![...disabled].some((prefix) => id.toLowerCase().startsWith(prefix)));
29
+ }
30
+
31
+ function workerRoot() { return join(resolveBizarHome(), 'workers'); }
32
+ function statePath(id) { return join(workerRoot(), `${id}.json`); }
33
+ function writeState(state) {
34
+ mkdirSync(workerRoot(), { recursive: true, mode: 0o700 });
35
+ writeFileSync(statePath(state.id), JSON.stringify(state, null, 2) + '\n', { mode: 0o600 });
36
+ }
37
+
38
+ export function buildWorkerPlan({ repoRoot = process.cwd(), model, task, agent = 'todd' } = {}) {
39
+ if (!model || typeof model !== 'string') throw new Error('worker start requires --model <selected gateway model id>.');
40
+ if (!task || typeof task !== 'string') throw new Error('worker start requires --task <bounded task>.');
41
+ const router = readRouter();
42
+ if (!enabledModels(router).includes(model)) throw new Error(`Model ${model} is not an enabled global bizar models selection.`);
43
+ const root = runGit(['rev-parse', '--show-toplevel'], repoRoot);
44
+ const id = `worker-${randomUUID().slice(0, 8)}`;
45
+ const branch = `wt/${id}`;
46
+ const worktree = join(dirname(root), `${basename(root)}-${id}`);
47
+ const logPath = join(workerRoot(), `${id}.log`);
48
+ return { id, root, branch, worktree, model, task: task.trim(), agent, logPath };
49
+ }
50
+
51
+ export function workerClaudeArgs(plan) {
52
+ return [
53
+ '--print', '--name', `bizar-${plan.id}`, '--model', plan.model,
54
+ '--permission-mode', 'acceptEdits', '--agent', plan.agent,
55
+ `${plan.task}\n\nYou are an exact-model Bizar process worker in ${plan.worktree}. Work only in this worktree. Do not spawn subagents. Implement and test the bounded task, commit one logical change locally, never push, then report the commit and verification.`,
56
+ ];
57
+ }
58
+
59
+ function parseStart(args) {
60
+ const value = (name) => {
61
+ const eq = args.find((arg) => arg.startsWith(`${name}=`));
62
+ if (eq) return eq.slice(name.length + 1);
63
+ const index = args.indexOf(name);
64
+ return index >= 0 ? args[index + 1] : null;
65
+ };
66
+ return { model: value('--model'), task: value('--task'), agent: value('--agent') || 'todd', background: args.includes('--background') };
67
+ }
68
+
69
+ function listWorkers() {
70
+ if (!existsSync(workerRoot())) return [];
71
+ return readdirSync(workerRoot()).filter((name) => name.endsWith('.json')).flatMap((name) => {
72
+ try { return [JSON.parse(readFileSync(join(workerRoot(), name), 'utf8'))]; } catch { return []; }
73
+ }).sort((a, b) => String(b.startedAt || '').localeCompare(String(a.startedAt || '')));
74
+ }
75
+
76
+ function usage() {
77
+ console.log(`
78
+ bizar worker start --model <selected-id> --task <task> [--agent todd] [--background]
79
+ bizar worker list [--json]
80
+
81
+ Starts an isolated top-level Claude Code process in a wt/ worktree with the
82
+ exact selected gateway model. Use this when a separately launched worktree
83
+ process is useful. Merge completed branches with bizar worktree-merge <branch>.
84
+ `);
85
+ }
86
+
87
+ export async function run(name, args, isHelpRequest) {
88
+ if (name !== 'worker') return false;
89
+ if (isHelpRequest || args.length === 0) { usage(); return true; }
90
+ const [subcommand] = args;
91
+ if (subcommand === 'list') {
92
+ const rows = listWorkers();
93
+ if (args.includes('--json')) process.stdout.write(JSON.stringify(rows, null, 2) + '\n');
94
+ else for (const row of rows) console.log(`${row.id}\t${row.status}\t${row.model}\t${row.branch}`);
95
+ return true;
96
+ }
97
+ if (subcommand !== 'start') { usage(); return false; }
98
+ const parsed = parseStart(args.slice(1));
99
+ const plan = buildWorkerPlan({ repoRoot: process.cwd(), ...parsed });
100
+ mkdirSync(workerRoot(), { recursive: true, mode: 0o700 });
101
+ runGit(['worktree', 'add', '-b', plan.branch, plan.worktree, 'HEAD'], plan.root);
102
+ const state = { ...plan, status: 'running', startedAt: new Date().toISOString(), pid: null };
103
+ const child = spawn(process.env.CLAUDE_BIN || 'claude', workerClaudeArgs(plan), {
104
+ cwd: plan.worktree,
105
+ env: { ...process.env, CLAUDE_CONFIG_DIR: resolveClaudeConfigDir() },
106
+ detached: parsed.background,
107
+ stdio: parsed.background ? ['ignore', 'pipe', 'pipe'] : 'inherit',
108
+ });
109
+ child.once('error', (error) => {
110
+ writeState({ ...state, status: 'failed', exitedAt: new Date().toISOString(), error: error.message });
111
+ console.error(`Unable to start exact-model worker: ${error.message}`);
112
+ });
113
+ state.pid = child.pid || null;
114
+ writeState(state);
115
+ child.once('exit', (code) => writeState({ ...state, status: code === 0 ? 'completed' : 'failed', exitedAt: new Date().toISOString(), exitCode: code ?? 1 }));
116
+ if (parsed.background) {
117
+ const log = createWriteStream(plan.logPath, { flags: 'a', mode: 0o600 });
118
+ child.stdout?.pipe(log); child.stderr?.pipe(log); child.unref();
119
+ console.log(JSON.stringify({ id: plan.id, status: 'running', model: plan.model, branch: plan.branch, worktree: plan.worktree }));
120
+ return true;
121
+ }
122
+ const code = await new Promise((done) => child.once('exit', (value) => done(value ?? 1)));
123
+ process.exitCode = code;
124
+ return true;
125
+ }
@@ -17,7 +17,7 @@ default for meaningful work.
17
17
  | Shape | Signals | Execution |
18
18
  |---|---|---|
19
19
  | Tiny direct | one obvious copy, typo, comment, whitespace, or single style-token edit; one target; no behavior or test change | inspect, make the micro-edit, run the smallest proving check yourself |
20
- | Single isolated worker | one bounded implementation after scope is clear | dispatch one worktree-isolated Agent with an explicit Bizar model; integrate and verify |
20
+ | Single isolated worker | one bounded implementation after scope is clear | dispatch one worktree-isolated native Agent with its explicit Bizar model; use an exact-model process worker only when a separate Claude process is useful; integrate and verify |
21
21
  | Native workflow | repeatable diagnosis, research, review, or an implementation needing visible phase barriers | invoke the matching Bizar workflow with explicit Bizar routing |
22
22
  | Agent team | three or more sustained, independent roles need bounded cross-talk or coordinated handoff | use the native Agent-team capability; writers use worktrees and explicit Bizar models |
23
23
  | Parallel agents | two disjoint writable scopes with no cross-talk needed | dispatch concurrently with explicit models and worktree isolation |
@@ -34,15 +34,20 @@ version-sensitive claims. Inspect installed skills before hard or specialized
34
34
  work; if stuck with no match, search skills.sh and review the candidate before
35
35
  proposing installation.
36
36
 
37
- Before every Workflow, Agent, or Agent-team call, read the global Bizar model router and construct a
38
- small `args.routing` object whose `default`, `medium`, and `high` values are
39
- explicit enabled configured model IDs (user picks win; otherwise use enabled
40
- tier candidates). Include the user's task in the same args object under the
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`.
37
+ Before every Workflow, Agent, or Agent-team call, read the global Bizar model
38
+ router and construct a small `args.routing` object whose `default`, `medium`,
39
+ and `high` values are explicit enabled configured gateway IDs. Include the
40
+ user's task in the same args object under the workflow's documented task field.
41
+ Pass the chosen full gateway ID directly in every native Agent `model` field;
42
+ current Claude Code supports full model IDs there. Include that same ID in
43
+ `additionalContext.bizarConfiguredModel` for audit telemetry. Never use
44
+ `inherit` or an unconfigured provider default. `bizar models` additionally
45
+ maintains `sonnet`, `opus`, `haiku`, and `fable` aliases as compatibility
46
+ shortcuts, but aliases must not limit dispatch to four selected models. The
47
+ exact-model `bizar worker` command remains available where a separate top-level
48
+ Claude process is useful, not as a fallback for normal native dispatch. If no
49
+ configured model exists, stop and ask the operator to run `bizar models`; never
50
+ omit model selection or cycle providers.
46
51
 
47
52
  Invoke the selected workflow by `name` first. If Claude reports that the Bizar
48
53
  name is unavailable, resolve the active Claude config directory and retry once
@@ -55,14 +60,16 @@ implementation around a broken workflow installation.
55
60
 
56
61
  ## Models
57
62
 
58
- For every Agent call, select the cheapest sufficient enabled configured model
63
+ For every dispatch, select the cheapest sufficient enabled configured model
59
64
  from the global Bizar router. User-selected models take precedence over tier
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
62
- enabled configured candidate exists, stop with the configuration error. Never
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.
65
+ candidates; `disabledProviders` excludes both. Native aliases are transport
66
+ labels bound by `bizar models`, not Anthropic selections. Use the full selected
67
+ model ID directly for native Agents and teams; every enabled selection is
68
+ eligible. Use `bizar worker` only when a separately launched process worktree
69
+ is useful. If no enabled configured candidate exists, stop with the
70
+ configuration error. Never let Claude choose an unconfigured default, inherit
71
+ the session model, use an unmapped alias,
72
+ or retry by cycling models, providers, or tiers.
66
73
 
67
74
  ## Worktree Discipline and integration
68
75
 
@@ -169,15 +169,26 @@ const NATIVE_AGENT_TRANSPORT_KEYS = Object.freeze({
169
169
  sonnet: 'claude-sonnet-5',
170
170
  opus: 'claude-opus-5',
171
171
  haiku: 'claude-haiku-4-5-20251001',
172
+ fable: 'claude-fable-5',
173
+ });
174
+
175
+ const NATIVE_AGENT_TRANSPORT_ENV_KEYS = Object.freeze({
176
+ sonnet: 'ANTHROPIC_DEFAULT_SONNET_MODEL',
177
+ opus: 'ANTHROPIC_DEFAULT_OPUS_MODEL',
178
+ haiku: 'ANTHROPIC_DEFAULT_HAIKU_MODEL',
179
+ fable: 'ANTHROPIC_DEFAULT_FABLE_MODEL',
172
180
  });
173
181
 
174
182
  function readTransportTarget(alias, options = {}) {
175
183
  const key = NATIVE_AGENT_TRANSPORT_KEYS[alias];
176
184
  if (!key) return '';
177
- const overrides = options.modelOverrides || (() => {
185
+ const settings = options.settings || (options.modelOverrides ? null : (() => {
178
186
  const settingsPath = options.settingsPath || join(resolveClaudeConfigDir(), 'settings.json');
179
- try { return JSON.parse(readFileSync(settingsPath, 'utf8'))?.modelOverrides; } catch { return null; }
180
- })();
187
+ try { return JSON.parse(readFileSync(settingsPath, 'utf8')); } catch { return null; }
188
+ })());
189
+ const envTarget = settings?.env?.[NATIVE_AGENT_TRANSPORT_ENV_KEYS[alias]];
190
+ if (typeof envTarget === 'string' && envTarget.trim()) return envTarget.trim();
191
+ const overrides = options.modelOverrides || settings?.modelOverrides;
181
192
  return typeof overrides?.[key] === 'string' ? overrides[key].trim() : '';
182
193
  }
183
194
 
@@ -234,6 +245,13 @@ export async function guardAgentModel(input, options = {}) {
234
245
  return {};
235
246
  }
236
247
 
248
+ // Claude Code accepts full model IDs for native Agent calls. A model picked
249
+ // through `bizar models` is therefore valid directly; do not collapse every
250
+ // user selection into the four family aliases.
251
+ if (userPicks.has(requested) && !hasFailoverContract) {
252
+ return {};
253
+ }
254
+
237
255
  // F-185 contract: when the orchestrator passes both `routingDecisionId`
238
256
  // and `fallback`, validate the fallback against the userSelected pool
239
257
  // but skip the live-discovery re-probe. The fallback's eligibility was
@@ -116,6 +116,28 @@ function requiresGatewayModelDiscovery(modelIds) {
116
116
  return modelIds.some((id) => !/^(?:claude(?:-|$)|anthropic(?:[./-]|$))/i.test(id));
117
117
  }
118
118
 
119
+ // Bind Claude Code's four family aliases to the operator's selected gateway
120
+ // IDs for compatibility. They never imply an Anthropic provider selection and
121
+ // do not constrain full-ID native Agent dispatch.
122
+ const NATIVE_AGENT_ALIASES = ['sonnet', 'opus', 'haiku', 'fable'];
123
+ const NATIVE_AGENT_OVERRIDE_KEYS = {
124
+ sonnet: 'claude-sonnet-5',
125
+ opus: 'claude-opus-5',
126
+ haiku: 'claude-haiku-4-5-20251001',
127
+ fable: 'claude-fable-5',
128
+ };
129
+ const NATIVE_AGENT_ENV_KEYS = {
130
+ sonnet: 'ANTHROPIC_DEFAULT_SONNET_MODEL',
131
+ opus: 'ANTHROPIC_DEFAULT_OPUS_MODEL',
132
+ haiku: 'ANTHROPIC_DEFAULT_HAIKU_MODEL',
133
+ fable: 'ANTHROPIC_DEFAULT_FABLE_MODEL',
134
+ };
135
+
136
+ function nativeAgentAliasTargets(modelIds) {
137
+ if (modelIds.length === 0) return {};
138
+ return Object.fromEntries(NATIVE_AGENT_ALIASES.map((alias, index) => [alias, modelIds[index] || modelIds[0]]));
139
+ }
140
+
119
141
  function readSettingsPath() {
120
142
  return join(resolveClaudeConfigDir(), 'settings.json');
121
143
  }
@@ -217,18 +239,24 @@ function syncOnce() {
217
239
  });
218
240
 
219
241
  const overrideKeys = [
220
- 'claude-fable-5', 'claude-opus-5', 'claude-sonnet-5',
221
- 'claude-haiku-4-5-20251001', 'claude-opus-4-8', 'claude-opus-4-7',
242
+ 'claude-sonnet-5', 'claude-opus-5', 'claude-haiku-4-5-20251001', 'claude-fable-5',
243
+ 'claude-opus-4-8', 'claude-opus-4-7',
222
244
  'claude-opus-4-6', 'claude-sonnet-4-6', 'claude-opus-4-5-20251101',
223
245
  'claude-sonnet-4-5-20250929', 'claude-opus-4-1-20250805',
224
246
  'claude-opus-4-20250514', 'claude-sonnet-4-20250514',
225
247
  'claude-3-7-sonnet-20250219', 'claude-3-5-haiku-20241022',
226
248
  'claude-3-5-sonnet-20241022',
227
249
  ];
250
+ const aliasTargets = nativeAgentAliasTargets(liveIds);
228
251
  settings.modelPicker = { options };
229
252
  settings.modelOverrides = Object.fromEntries(
230
- overrideKeys.map((key, index) => [key, liveIds[index % liveIds.length]]),
253
+ overrideKeys.map((key, index) => {
254
+ const alias = NATIVE_AGENT_ALIASES.find((candidate) => NATIVE_AGENT_OVERRIDE_KEYS[candidate] === key);
255
+ return [key, alias ? aliasTargets[alias] : liveIds[index % liveIds.length]];
256
+ }),
231
257
  );
258
+ settings.env = { ...(settings.env || {}) };
259
+ for (const alias of NATIVE_AGENT_ALIASES) settings.env[NATIVE_AGENT_ENV_KEYS[alias]] = aliasTargets[alias];
232
260
  if (requiresGatewayModelDiscovery(liveIds)) {
233
261
  settings.env = {
234
262
  ...(settings.env || {}),
@@ -22,12 +22,14 @@ const WORKFLOW_DEFAULT_MODEL = typeof WORKFLOW_INPUT.model === 'string'
22
22
  : ''
23
23
  const routeModel = (risk) => {
24
24
  const candidate = WORKFLOW_ROUTING[risk] || WORKFLOW_ROUTING.default || WORKFLOW_DEFAULT_MODEL
25
- return typeof candidate === 'string' ? candidate.trim() : ''
25
+ if (typeof candidate !== 'string') return ''
26
+ const selected = candidate.trim()
27
+ return selected
26
28
  }
27
29
  if (!routeModel('medium') || !routeModel('high')) {
28
30
  return {
29
31
  status: 'blocked',
30
- reason: 'No explicit configured Bizar model routing was supplied. Read the global Bizar model router and retry with args.routing; provider defaults are prohibited.',
32
+ reason: 'No explicit enabled Bizar model was supplied for workflow routing. Read the global model router and retry with args.routing; provider defaults are prohibited.',
31
33
  }
32
34
  }
33
35
  let WORKFLOW_DISPATCH_SEQUENCE = 0
@@ -21,12 +21,14 @@ const WORKFLOW_DEFAULT_MODEL = typeof WORKFLOW_INPUT.model === 'string'
21
21
  : ''
22
22
  const routeModel = (risk) => {
23
23
  const candidate = WORKFLOW_ROUTING[risk] || WORKFLOW_ROUTING.default || WORKFLOW_DEFAULT_MODEL
24
- return typeof candidate === 'string' ? candidate.trim() : ''
24
+ if (typeof candidate !== 'string') return ''
25
+ const selected = candidate.trim()
26
+ return selected
25
27
  }
26
28
  if (!routeModel('medium') || !routeModel('high')) {
27
29
  return {
28
30
  status: 'blocked',
29
- reason: 'No explicit configured Bizar model routing was supplied. Read the global Bizar model router and retry with args.routing; provider defaults are prohibited.',
31
+ reason: 'No explicit enabled Bizar model was supplied for workflow routing. Read the global model router and retry with args.routing; provider defaults are prohibited.',
30
32
  }
31
33
  }
32
34
  let WORKFLOW_DISPATCH_SEQUENCE = 0
@@ -22,12 +22,14 @@ const WORKFLOW_DEFAULT_MODEL = typeof WORKFLOW_INPUT.model === 'string'
22
22
  : ''
23
23
  const routeModel = (risk) => {
24
24
  const candidate = WORKFLOW_ROUTING[risk] || WORKFLOW_ROUTING.default || WORKFLOW_DEFAULT_MODEL
25
- return typeof candidate === 'string' ? candidate.trim() : ''
25
+ if (typeof candidate !== 'string') return ''
26
+ const selected = candidate.trim()
27
+ return selected
26
28
  }
27
29
  if (!routeModel('medium') || !routeModel('high')) {
28
30
  return {
29
31
  status: 'blocked',
30
- reason: 'No explicit configured Bizar model routing was supplied. Read the global Bizar model router and retry with args.routing; provider defaults are prohibited.',
32
+ reason: 'No explicit enabled Bizar model was supplied for workflow routing. Read the global model router and retry with args.routing; provider defaults are prohibited.',
31
33
  }
32
34
  }
33
35
  let WORKFLOW_DISPATCH_SEQUENCE = 0
@@ -796,11 +796,12 @@ export function classifyDispatchOutcome(result, error, startMs) {
796
796
  }
797
797
 
798
798
  /**
799
- * Build the augmented agent-call payload. The native Agent model field cannot
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
+ * Build the augmented native-Agent payload. Claude Code supports an explicit
800
+ * full model ID in an Agent invocation; use the selected gateway ID directly
801
+ * so every configured selection is usable by native agents and teams. The
802
+ * transport aliases remain a compatibility option for callers that need them.
802
803
  */
803
- export function augmentPayload(opts, decision, agentName) {
804
+ export function augmentPayload(opts, decision, agentName, context = {}) {
804
805
  return {
805
806
  ...opts,
806
807
  model: decision.modelId,
@@ -858,13 +859,14 @@ export async function dispatchAgent(agentFn, agentName, prompt, opts = {}, conte
858
859
  if (!agentName || typeof agentName !== 'string') {
859
860
  throw new TypeError('dispatchAgent requires a non-empty agentName');
860
861
  }
861
- const decision = computeDecision(agentName, prompt, opts, context);
862
+ const ctx = context ?? loadDispatchContext();
863
+ const decision = computeDecision(agentName, prompt, opts, ctx);
862
864
  if (!decision.modelId) {
863
865
  throw new ModelRoutingError(
864
866
  '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.',
865
867
  );
866
868
  }
867
- const augmented = augmentPayload(opts, decision, agentName);
869
+ const augmented = augmentPayload(opts, decision, agentName, ctx);
868
870
 
869
871
  // F-191 / IMP-018 audit trail — persist the decision before invoking
870
872
  // the agent. Best-effort: telemetry failures MUST NOT abort the
@@ -20,12 +20,14 @@ const WORKFLOW_DEFAULT_MODEL = typeof WORKFLOW_INPUT.model === 'string'
20
20
  : ''
21
21
  const routeModel = (risk) => {
22
22
  const candidate = WORKFLOW_ROUTING[risk] || WORKFLOW_ROUTING.default || WORKFLOW_DEFAULT_MODEL
23
- return typeof candidate === 'string' ? candidate.trim() : ''
23
+ if (typeof candidate !== 'string') return ''
24
+ const selected = candidate.trim()
25
+ return selected
24
26
  }
25
27
  if (!routeModel('medium') || !routeModel('high')) {
26
28
  return {
27
29
  status: 'blocked',
28
- reason: 'No explicit configured Bizar model routing was supplied. Read the global Bizar model router and retry with args.routing; provider defaults are prohibited.',
30
+ reason: 'No explicit enabled Bizar model was supplied for workflow routing. Read the global model router and retry with args.routing; provider defaults are prohibited.',
29
31
  }
30
32
  }
31
33
  let WORKFLOW_DISPATCH_SEQUENCE = 0
@@ -19,12 +19,14 @@ const WORKFLOW_DEFAULT_MODEL = typeof WORKFLOW_INPUT.model === 'string'
19
19
  : ''
20
20
  const routeModel = (risk) => {
21
21
  const candidate = WORKFLOW_ROUTING[risk] || WORKFLOW_ROUTING.default || WORKFLOW_DEFAULT_MODEL
22
- return typeof candidate === 'string' ? candidate.trim() : ''
22
+ if (typeof candidate !== 'string') return ''
23
+ const selected = candidate.trim()
24
+ return selected
23
25
  }
24
26
  if (!routeModel('medium') || !routeModel('high')) {
25
27
  return {
26
28
  status: 'blocked',
27
- reason: 'No explicit configured Bizar model routing was supplied. Read the global Bizar model router and retry with args.routing; provider defaults are prohibited.',
29
+ reason: 'No explicit enabled Bizar model was supplied for workflow routing. Read the global model router and retry with args.routing; provider defaults are prohibited.',
28
30
  }
29
31
  }
30
32
  let WORKFLOW_DISPATCH_SEQUENCE = 0
@@ -21,12 +21,14 @@ const WORKFLOW_DEFAULT_MODEL = typeof WORKFLOW_INPUT.model === 'string'
21
21
  : ''
22
22
  const routeModel = (risk) => {
23
23
  const candidate = WORKFLOW_ROUTING[risk] || WORKFLOW_ROUTING.default || WORKFLOW_DEFAULT_MODEL
24
- return typeof candidate === 'string' ? candidate.trim() : ''
24
+ if (typeof candidate !== 'string') return ''
25
+ const selected = candidate.trim()
26
+ return selected
25
27
  }
26
28
  if (!routeModel('medium') || !routeModel('high')) {
27
29
  return {
28
30
  status: 'blocked',
29
- reason: 'No explicit configured Bizar model routing was supplied. Read the global Bizar model router and retry with args.routing; provider defaults are prohibited.',
31
+ reason: 'No explicit enabled Bizar model was supplied for workflow routing. Read the global model router and retry with args.routing; provider defaults are prohibited.',
30
32
  }
31
33
  }
32
34
  let WORKFLOW_DISPATCH_SEQUENCE = 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "10.23.14",
3
+ "version": "10.23.15",
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": {