@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.
- package/cli/commands/models.mjs +7 -4
- package/cli/config-paths.mjs +4 -1
- package/cli/provision.mjs +2 -0
- package/config/claude/agents/office-manager.md +10 -7
- package/config/claude/hooks/agent-model-guard.mjs +33 -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 +7 -3
- 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,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 {
|
|
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
|
}
|
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. 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.
|
|
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
|
|
61
|
-
|
|
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 =
|
|
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
|
-
* `~/.
|
|
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
|
|
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(
|
|
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
|
|
798
|
-
*
|
|
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
|
@@ -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 = {}) {
|