@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.
- package/cli/commands/models.mjs +32 -6
- package/cli/config-paths.mjs +4 -1
- package/cli/provision.mjs +2 -0
- package/config/claude/agents/office-manager.md +11 -8
- package/config/claude/hooks/agent-model-guard.mjs +29 -3
- package/config/claude/hooks/sessionstart-model-sync.mjs +2 -2
- package/config/claude/hooks/worker-suggest.mjs +1 -1
- package/config/workflows/lib/dispatch.js +31 -4
- package/package.json +1 -1
- package/packages/sdk/dist/router/agent-model-registry.js +9 -5
- package/packages/sdk/dist/version.d.ts +1 -1
- package/packages/sdk/dist/version.js +1 -1
- package/packages/sdk/package.json +1 -1
package/cli/commands/models.mjs
CHANGED
|
@@ -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 {
|
|
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
|
-
//
|
|
1063
|
-
//
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
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. */
|
package/cli/config-paths.mjs
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
42
|
-
|
|
43
|
-
|
|
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.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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 =
|
|
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
|
-
* `~/.
|
|
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
|
-
* (`~/.
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
@@ -46,11 +46,15 @@ function modelIds(value) {
|
|
|
46
46
|
: [];
|
|
47
47
|
}
|
|
48
48
|
function defaultConfigPath(_cwd = process.cwd()) {
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
|
|
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 = {}) {
|