@polderlabs/bizar 10.23.14 → 10.23.16
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 +9 -0
- package/cli/commands/models.mjs +75 -5
- package/cli/commands/worker.mjs +125 -0
- package/config/claude/agents/office-manager.md +22 -17
- package/config/claude/hooks/agent-model-guard.mjs +25 -4
- package/config/claude/hooks/sessionstart-model-sync.mjs +31 -3
- package/config/workflows/bizar-debug.js +7 -4
- package/config/workflows/bizar-implement.js +7 -4
- package/config/workflows/bizar-research.js +7 -4
- package/config/workflows/lib/dispatch.js +9 -7
- package/config/workflows/ultracode-research.js +7 -4
- package/config/workflows/ultracode-review.js +7 -4
- package/config/workflows/ultracode.js +7 -4
- package/package.json +1 -1
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) {
|
package/cli/commands/models.mjs
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* reject user-selected IDs.
|
|
19
19
|
*/
|
|
20
20
|
import chalk from 'chalk';
|
|
21
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
21
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
22
22
|
import { dirname, join } from 'node:path';
|
|
23
23
|
import readline from 'node:readline';
|
|
24
24
|
|
|
@@ -991,6 +991,10 @@ 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);
|
|
995
|
+
const generatedAgents = syncGeneratedModelAgents(synced, {
|
|
996
|
+
agentsDir: settingsJsonPath === undefined ? undefined : join(dirname(path), 'agents', 'bizar-models'),
|
|
997
|
+
});
|
|
994
998
|
if (requiresGatewayModelDiscovery(synced)) {
|
|
995
999
|
settings.env = {
|
|
996
1000
|
...(settings.env || {}),
|
|
@@ -1020,6 +1024,8 @@ export function applyModelOverrides({ settingsJsonPath, pickedIds, liveIds = [],
|
|
|
1020
1024
|
syncedIds: synced,
|
|
1021
1025
|
skippedStale: skipped,
|
|
1022
1026
|
skippedDisabled,
|
|
1027
|
+
nativeAgentAliases,
|
|
1028
|
+
generatedAgents,
|
|
1023
1029
|
settingsPath: path,
|
|
1024
1030
|
};
|
|
1025
1031
|
}
|
|
@@ -1042,9 +1048,8 @@ export function configuredEnabledModels(router) {
|
|
|
1042
1048
|
}
|
|
1043
1049
|
|
|
1044
1050
|
export const CLAUDE_MODEL_OVERRIDE_KEYS = Object.freeze([
|
|
1045
|
-
'claude-fable-5',
|
|
1046
|
-
'claude-opus-5',
|
|
1047
1051
|
'claude-sonnet-5',
|
|
1052
|
+
'claude-opus-5',
|
|
1048
1053
|
'claude-haiku-4-5-20251001',
|
|
1049
1054
|
'claude-opus-4-8',
|
|
1050
1055
|
'claude-opus-4-7',
|
|
@@ -1060,13 +1065,78 @@ export const CLAUDE_MODEL_OVERRIDE_KEYS = Object.freeze([
|
|
|
1060
1065
|
'claude-3-5-sonnet-20241022',
|
|
1061
1066
|
]);
|
|
1062
1067
|
|
|
1068
|
+
// Claude Code's native Agent tool accepts these transport labels in the
|
|
1069
|
+
// installed runtime. They are labels only: Bizar binds them to the operator's
|
|
1070
|
+
// gateway IDs below; they never select an Anthropic provider by themselves.
|
|
1071
|
+
export const NATIVE_AGENT_ALIASES = Object.freeze(['sonnet', 'opus', 'haiku', 'fable']);
|
|
1072
|
+
|
|
1073
|
+
const MODEL_AGENT_WORDS = Object.freeze({ a:'alpha', b:'bravo', c:'charlie', d:'delta', e:'echo', f:'foxtrot', g:'golf', h:'hotel', i:'india', j:'juliet', k:'kilo', l:'lima', m:'mike', n:'november', o:'oscar', p:'papa', q:'quebec', r:'romeo', s:'sierra', t:'tango', u:'uniform', v:'victor', w:'whiskey', x:'xray', y:'yankee', z:'zulu', 0:'zero', 1:'one', 2:'two', 3:'three', 4:'four', 5:'five', 6:'six', 7:'seven', 8:'eight', 9:'nine', '/':'slash', '.':'dot', '-':'dash', '_':'under' });
|
|
1074
|
+
|
|
1075
|
+
export function modelAgentName(modelId) {
|
|
1076
|
+
return `bizar-model-${[...String(modelId || '').toLowerCase()].map((ch) => MODEL_AGENT_WORDS[ch] || 'unknown').join('-')}`;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
export function syncGeneratedModelAgents(modelIds, opts = {}) {
|
|
1080
|
+
const agentsDir = opts.agentsDir || join(resolveClaudeConfigDir(), 'agents', 'bizar-models');
|
|
1081
|
+
const ids = [...new Set((Array.isArray(modelIds) ? modelIds : []).filter((id) => typeof id === 'string' && id.trim()).map((id) => id.trim()))];
|
|
1082
|
+
if (existsSync(agentsDir)) rmSync(agentsDir, { recursive: true, force: true });
|
|
1083
|
+
if (!ids.length) return { agentsDir, names: [] };
|
|
1084
|
+
mkdirSync(agentsDir, { recursive: true, mode: 0o700 });
|
|
1085
|
+
const names = ids.map((id) => modelAgentName(id));
|
|
1086
|
+
ids.forEach((id, index) => writeFileSync(join(agentsDir, `${names[index]}.md`), `---\nname: ${names[index]}\ndescription: Bizar configured gateway model worker.\nmodel: ${id}\n---\n\nFollow the assigned Bizar role and task. Report concise evidence to the coordinator.\n`, { mode: 0o600 }));
|
|
1087
|
+
return { agentsDir, names };
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
const NATIVE_AGENT_OVERRIDE_KEYS = Object.freeze({
|
|
1091
|
+
sonnet: 'claude-sonnet-5',
|
|
1092
|
+
opus: 'claude-opus-5',
|
|
1093
|
+
haiku: 'claude-haiku-4-5-20251001',
|
|
1094
|
+
fable: 'claude-fable-5',
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
export function buildNativeAgentAliasTargets(modelIds) {
|
|
1098
|
+
const unique = [...new Set((Array.isArray(modelIds) ? modelIds : [])
|
|
1099
|
+
.filter((id) => typeof id === 'string' && id.trim())
|
|
1100
|
+
.map((id) => id.trim()))];
|
|
1101
|
+
if (unique.length === 0) return {};
|
|
1102
|
+
return Object.fromEntries(NATIVE_AGENT_ALIASES.map((alias, index) => [
|
|
1103
|
+
alias,
|
|
1104
|
+
unique[index] || unique[0],
|
|
1105
|
+
]));
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
export function applyNativeAgentAliasTargets(settings, modelIds) {
|
|
1109
|
+
const targets = buildNativeAgentAliasTargets(modelIds);
|
|
1110
|
+
const env = { ...(settings.env || {}) };
|
|
1111
|
+
const envKeys = {
|
|
1112
|
+
sonnet: 'ANTHROPIC_DEFAULT_SONNET_MODEL',
|
|
1113
|
+
opus: 'ANTHROPIC_DEFAULT_OPUS_MODEL',
|
|
1114
|
+
haiku: 'ANTHROPIC_DEFAULT_HAIKU_MODEL',
|
|
1115
|
+
fable: 'ANTHROPIC_DEFAULT_FABLE_MODEL',
|
|
1116
|
+
};
|
|
1117
|
+
for (const alias of NATIVE_AGENT_ALIASES) {
|
|
1118
|
+
const envKey = envKeys[alias];
|
|
1119
|
+
if (targets[alias]) env[envKey] = targets[alias];
|
|
1120
|
+
else delete env[envKey];
|
|
1121
|
+
}
|
|
1122
|
+
settings.env = env;
|
|
1123
|
+
return targets;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1063
1126
|
export function buildClaudeModelOverrides(modelIds) {
|
|
1064
1127
|
const unique = [...new Set((Array.isArray(modelIds) ? modelIds : [])
|
|
1065
1128
|
.filter((id) => typeof id === 'string' && id.trim())
|
|
1066
1129
|
.map((id) => id.trim()))];
|
|
1067
1130
|
if (unique.length === 0) return {};
|
|
1068
|
-
|
|
1069
|
-
|
|
1131
|
+
const targets = buildNativeAgentAliasTargets(unique);
|
|
1132
|
+
const nativeEntries = NATIVE_AGENT_ALIASES.map((alias) => [
|
|
1133
|
+
NATIVE_AGENT_OVERRIDE_KEYS[alias],
|
|
1134
|
+
targets[alias],
|
|
1135
|
+
]);
|
|
1136
|
+
const remaining = CLAUDE_MODEL_OVERRIDE_KEYS
|
|
1137
|
+
.filter((key) => !Object.values(NATIVE_AGENT_OVERRIDE_KEYS).includes(key))
|
|
1138
|
+
.map((key, index) => [key, unique[index % unique.length]]);
|
|
1139
|
+
return Object.fromEntries([...nativeEntries, ...remaining]);
|
|
1070
1140
|
}
|
|
1071
1141
|
|
|
1072
1142
|
/** 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
|
|
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,18 @@ 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
|
|
38
|
-
small `args.routing` object whose `default`, `medium`,
|
|
39
|
-
explicit enabled configured
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
+
Use the generated `bizar-models` user agent matching the chosen full gateway
|
|
42
|
+
ID as `subagent_type`, and omit the native Agent `model` parameter entirely.
|
|
43
|
+
That definition's frontmatter owns the full-ID selection for ordinary agents,
|
|
44
|
+
workflows, and teams. Include the raw ID in `additionalContext.bizarConfiguredModel`
|
|
45
|
+
for audit telemetry. Never use `inherit` or an unconfigured provider default.
|
|
46
|
+
`sonnet`, `opus`, `haiku`, and `fable` are compatibility aliases only. If no
|
|
47
|
+
generated configured definition exists, stop and ask the operator to run
|
|
48
|
+
`bizar models`; never omit the generated agent type or cycle providers.
|
|
46
49
|
|
|
47
50
|
Invoke the selected workflow by `name` first. If Claude reports that the Bizar
|
|
48
51
|
name is unavailable, resolve the active Claude config directory and retry once
|
|
@@ -55,14 +58,16 @@ implementation around a broken workflow installation.
|
|
|
55
58
|
|
|
56
59
|
## Models
|
|
57
60
|
|
|
58
|
-
For every
|
|
61
|
+
For every dispatch, select the cheapest sufficient enabled configured model
|
|
59
62
|
from the global Bizar router. User-selected models take precedence over tier
|
|
60
|
-
candidates; `disabledProviders` excludes both.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
candidates; `disabledProviders` excludes both. Native aliases are transport
|
|
64
|
+
labels bound by `bizar models`, not Anthropic selections. Use the full selected
|
|
65
|
+
model ID directly for native Agents and teams; every enabled selection is
|
|
66
|
+
eligible. Use `bizar worker` only when a separately launched process worktree
|
|
67
|
+
is useful. If no enabled configured candidate exists, stop with the
|
|
68
|
+
configuration error. Never let Claude choose an unconfigured default, inherit
|
|
69
|
+
the session model, use an unmapped alias,
|
|
70
|
+
or retry by cycling models, providers, or tiers.
|
|
66
71
|
|
|
67
72
|
## Worktree Discipline and integration
|
|
68
73
|
|
|
@@ -49,6 +49,7 @@ import { pathToFileURL } from 'node:url';
|
|
|
49
49
|
|
|
50
50
|
import { loadModelRouter } from '../../../config/agents/model-assignment.mjs';
|
|
51
51
|
import { resolveClaudeConfigDir } from '../../../cli/config-paths.mjs';
|
|
52
|
+
import { modelAgentName } from '../../../cli/commands/models.mjs';
|
|
52
53
|
|
|
53
54
|
function deny(reason) {
|
|
54
55
|
return {
|
|
@@ -169,21 +170,32 @@ const NATIVE_AGENT_TRANSPORT_KEYS = Object.freeze({
|
|
|
169
170
|
sonnet: 'claude-sonnet-5',
|
|
170
171
|
opus: 'claude-opus-5',
|
|
171
172
|
haiku: 'claude-haiku-4-5-20251001',
|
|
173
|
+
fable: 'claude-fable-5',
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const NATIVE_AGENT_TRANSPORT_ENV_KEYS = Object.freeze({
|
|
177
|
+
sonnet: 'ANTHROPIC_DEFAULT_SONNET_MODEL',
|
|
178
|
+
opus: 'ANTHROPIC_DEFAULT_OPUS_MODEL',
|
|
179
|
+
haiku: 'ANTHROPIC_DEFAULT_HAIKU_MODEL',
|
|
180
|
+
fable: 'ANTHROPIC_DEFAULT_FABLE_MODEL',
|
|
172
181
|
});
|
|
173
182
|
|
|
174
183
|
function readTransportTarget(alias, options = {}) {
|
|
175
184
|
const key = NATIVE_AGENT_TRANSPORT_KEYS[alias];
|
|
176
185
|
if (!key) return '';
|
|
177
|
-
const
|
|
186
|
+
const settings = options.settings || (options.modelOverrides ? null : (() => {
|
|
178
187
|
const settingsPath = options.settingsPath || join(resolveClaudeConfigDir(), 'settings.json');
|
|
179
|
-
try { return JSON.parse(readFileSync(settingsPath, 'utf8'))
|
|
180
|
-
})();
|
|
188
|
+
try { return JSON.parse(readFileSync(settingsPath, 'utf8')); } catch { return null; }
|
|
189
|
+
})());
|
|
190
|
+
const envTarget = settings?.env?.[NATIVE_AGENT_TRANSPORT_ENV_KEYS[alias]];
|
|
191
|
+
if (typeof envTarget === 'string' && envTarget.trim()) return envTarget.trim();
|
|
192
|
+
const overrides = options.modelOverrides || settings?.modelOverrides;
|
|
181
193
|
return typeof overrides?.[key] === 'string' ? overrides[key].trim() : '';
|
|
182
194
|
}
|
|
183
195
|
|
|
184
196
|
export async function guardAgentModel(input, options = {}) {
|
|
185
197
|
if (!input || typeof input !== 'object') return {};
|
|
186
|
-
if (input.hook_event_name !== 'PreToolUse' || input.tool_name
|
|
198
|
+
if (input.hook_event_name !== 'PreToolUse' || !['Agent', 'Task'].includes(input.tool_name)) return {};
|
|
187
199
|
const toolInput = input.tool_input && typeof input.tool_input === 'object' ? input.tool_input : {};
|
|
188
200
|
const requested = typeof toolInput.model === 'string' ? toolInput.model.trim() : '';
|
|
189
201
|
const failoverBlock = readFailoverBlock(toolInput);
|
|
@@ -214,6 +226,8 @@ export async function guardAgentModel(input, options = {}) {
|
|
|
214
226
|
// Inheritance is safe only when the auditable Bizar selection equals the
|
|
215
227
|
// actual global Claude parent model and is a selected, enabled user pick.
|
|
216
228
|
if (!requested || requested === 'inherit') {
|
|
229
|
+
const generated = typeof toolInput.subagent_type === 'string' && [...userPicks].some((id) => toolInput.subagent_type === modelAgentName(id));
|
|
230
|
+
if (generated && !requested) return {};
|
|
217
231
|
const configured = configuredContext;
|
|
218
232
|
const parent = readConfiguredParentModel(options);
|
|
219
233
|
if (!configured || configured !== parent || !userPicks.has(configured)) {
|
|
@@ -234,6 +248,13 @@ export async function guardAgentModel(input, options = {}) {
|
|
|
234
248
|
return {};
|
|
235
249
|
}
|
|
236
250
|
|
|
251
|
+
// Claude Code accepts full model IDs for native Agent calls. A model picked
|
|
252
|
+
// through `bizar models` is therefore valid directly; do not collapse every
|
|
253
|
+
// user selection into the four family aliases.
|
|
254
|
+
if (userPicks.has(requested) && !hasFailoverContract) {
|
|
255
|
+
return {};
|
|
256
|
+
}
|
|
257
|
+
|
|
237
258
|
// F-185 contract: when the orchestrator passes both `routingDecisionId`
|
|
238
259
|
// and `fallback`, validate the fallback against the userSelected pool
|
|
239
260
|
// 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-
|
|
221
|
-
'claude-
|
|
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) =>
|
|
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,15 @@ 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
|
-
|
|
25
|
+
if (typeof candidate !== 'string') return ''
|
|
26
|
+
const selected = candidate.trim()
|
|
27
|
+
return selected
|
|
26
28
|
}
|
|
27
|
-
|
|
29
|
+
const routeAgentType = (risk) => WORKFLOW_ROUTING.agentTypes?.[routeModel(risk)] || ''
|
|
30
|
+
if (!routeModel('medium') || !routeModel('high') || !routeAgentType('medium') || !routeAgentType('high')) {
|
|
28
31
|
return {
|
|
29
32
|
status: 'blocked',
|
|
30
|
-
reason: 'No
|
|
33
|
+
reason: 'No generated Bizar model-agent mapping was supplied for workflow routing. Run bizar models and retry; provider defaults are prohibited.',
|
|
31
34
|
}
|
|
32
35
|
}
|
|
33
36
|
let WORKFLOW_DISPATCH_SEQUENCE = 0
|
|
@@ -35,7 +38,7 @@ const dispatchAgent = (agentFn, agentName, prompt, opts = {}) => {
|
|
|
35
38
|
const sequence = ++WORKFLOW_DISPATCH_SEQUENCE
|
|
36
39
|
const prefix = `[Bizar dispatch ${sequence}: ${agentName}; role=${opts.role || 'worker'}; phase=${opts.phase || 'work'}; label=${opts.label || agentName}]`
|
|
37
40
|
const agentOptions = {
|
|
38
|
-
|
|
41
|
+
subagent_type: routeAgentType(opts.risk || 'medium'),
|
|
39
42
|
effort: opts.risk === 'high' ? 'high' : 'medium',
|
|
40
43
|
}
|
|
41
44
|
if (opts.schema) agentOptions.schema = opts.schema
|
|
@@ -21,12 +21,15 @@ 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
|
-
|
|
24
|
+
if (typeof candidate !== 'string') return ''
|
|
25
|
+
const selected = candidate.trim()
|
|
26
|
+
return selected
|
|
25
27
|
}
|
|
26
|
-
|
|
28
|
+
const routeAgentType = (risk) => WORKFLOW_ROUTING.agentTypes?.[routeModel(risk)] || ''
|
|
29
|
+
if (!routeModel('medium') || !routeModel('high') || !routeAgentType('medium') || !routeAgentType('high')) {
|
|
27
30
|
return {
|
|
28
31
|
status: 'blocked',
|
|
29
|
-
reason: 'No
|
|
32
|
+
reason: 'No generated Bizar model-agent mapping was supplied for workflow routing. Run bizar models and retry; provider defaults are prohibited.',
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
35
|
let WORKFLOW_DISPATCH_SEQUENCE = 0
|
|
@@ -34,7 +37,7 @@ const dispatchAgent = (agentFn, agentName, prompt, opts = {}) => {
|
|
|
34
37
|
const sequence = ++WORKFLOW_DISPATCH_SEQUENCE
|
|
35
38
|
const prefix = `[Bizar dispatch ${sequence}: ${agentName}; role=${opts.role || 'worker'}; phase=${opts.phase || 'work'}; label=${opts.label || agentName}]`
|
|
36
39
|
const agentOptions = {
|
|
37
|
-
|
|
40
|
+
subagent_type: routeAgentType(opts.risk || 'medium'),
|
|
38
41
|
effort: opts.risk === 'high' ? 'high' : 'medium',
|
|
39
42
|
}
|
|
40
43
|
if (opts.schema) agentOptions.schema = opts.schema
|
|
@@ -22,12 +22,15 @@ 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
|
-
|
|
25
|
+
if (typeof candidate !== 'string') return ''
|
|
26
|
+
const selected = candidate.trim()
|
|
27
|
+
return selected
|
|
26
28
|
}
|
|
27
|
-
|
|
29
|
+
const routeAgentType = (risk) => WORKFLOW_ROUTING.agentTypes?.[routeModel(risk)] || ''
|
|
30
|
+
if (!routeModel('medium') || !routeModel('high') || !routeAgentType('medium') || !routeAgentType('high')) {
|
|
28
31
|
return {
|
|
29
32
|
status: 'blocked',
|
|
30
|
-
reason: 'No
|
|
33
|
+
reason: 'No generated Bizar model-agent mapping was supplied for workflow routing. Run bizar models and retry; provider defaults are prohibited.',
|
|
31
34
|
}
|
|
32
35
|
}
|
|
33
36
|
let WORKFLOW_DISPATCH_SEQUENCE = 0
|
|
@@ -35,7 +38,7 @@ const dispatchAgent = (agentFn, agentName, prompt, opts = {}) => {
|
|
|
35
38
|
const sequence = ++WORKFLOW_DISPATCH_SEQUENCE
|
|
36
39
|
const prefix = `[Bizar dispatch ${sequence}: ${agentName}; role=${opts.role || 'worker'}; phase=${opts.phase || 'work'}; label=${opts.label || agentName}]`
|
|
37
40
|
const agentOptions = {
|
|
38
|
-
|
|
41
|
+
subagent_type: routeAgentType(opts.risk || 'medium'),
|
|
39
42
|
effort: opts.risk === 'high' ? 'high' : 'medium',
|
|
40
43
|
}
|
|
41
44
|
if (opts.schema) agentOptions.schema = opts.schema
|
|
@@ -33,6 +33,9 @@ import { existsSync, readFileSync, appendFileSync, mkdirSync, writeFileSync, ren
|
|
|
33
33
|
import { homedir } from 'node:os';
|
|
34
34
|
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
35
35
|
|
|
36
|
+
const MODEL_AGENT_WORDS = { a:'alpha', b:'bravo', c:'charlie', d:'delta', e:'echo', f:'foxtrot', g:'golf', h:'hotel', i:'india', j:'juliet', k:'kilo', l:'lima', m:'mike', n:'november', o:'oscar', p:'papa', q:'quebec', r:'romeo', s:'sierra', t:'tango', u:'uniform', v:'victor', w:'whiskey', x:'xray', y:'yankee', z:'zulu', 0:'zero', 1:'one', 2:'two', 3:'three', 4:'four', 5:'five', 6:'six', 7:'seven', 8:'eight', 9:'nine', '/':'slash', '.':'dot', '-':'dash', '_':'under' };
|
|
37
|
+
function modelAgentName(modelId) { return `bizar-model-${[...String(modelId || '').toLowerCase()].map((ch) => MODEL_AGENT_WORDS[ch] || 'unknown').join('-')}`; }
|
|
38
|
+
|
|
36
39
|
const { O_APPEND, O_CREAT, O_WRONLY } = fsConstants;
|
|
37
40
|
|
|
38
41
|
/* ────────────────────────────────────────────────────────────────────────── */
|
|
@@ -796,14 +799,12 @@ export function classifyDispatchOutcome(result, error, startMs) {
|
|
|
796
799
|
}
|
|
797
800
|
|
|
798
801
|
/**
|
|
799
|
-
* Build
|
|
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.
|
|
802
|
+
* Build an Agent payload that selects a generated full-ID model definition.
|
|
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
|
+
subagent_type: modelAgentName(decision.modelId),
|
|
807
808
|
additionalContext: {
|
|
808
809
|
...(opts.additionalContext && typeof opts.additionalContext === 'object' ? opts.additionalContext : {}),
|
|
809
810
|
bizarConfiguredModel: decision.modelId ?? null,
|
|
@@ -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
|
|
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,15 @@ 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
|
-
|
|
23
|
+
if (typeof candidate !== 'string') return ''
|
|
24
|
+
const selected = candidate.trim()
|
|
25
|
+
return selected
|
|
24
26
|
}
|
|
25
|
-
|
|
27
|
+
const routeAgentType = (risk) => WORKFLOW_ROUTING.agentTypes?.[routeModel(risk)] || ''
|
|
28
|
+
if (!routeModel('medium') || !routeModel('high') || !routeAgentType('medium') || !routeAgentType('high')) {
|
|
26
29
|
return {
|
|
27
30
|
status: 'blocked',
|
|
28
|
-
reason: 'No
|
|
31
|
+
reason: 'No generated Bizar model-agent mapping was supplied for workflow routing. Run bizar models and retry; provider defaults are prohibited.',
|
|
29
32
|
}
|
|
30
33
|
}
|
|
31
34
|
let WORKFLOW_DISPATCH_SEQUENCE = 0
|
|
@@ -33,7 +36,7 @@ const dispatchAgent = (agentFn, agentName, prompt, opts = {}) => {
|
|
|
33
36
|
const sequence = ++WORKFLOW_DISPATCH_SEQUENCE
|
|
34
37
|
const prefix = `[Bizar dispatch ${sequence}: ${agentName}; role=${opts.role || 'worker'}; phase=${opts.phase || 'work'}; label=${opts.label || agentName}]`
|
|
35
38
|
const agentOptions = {
|
|
36
|
-
|
|
39
|
+
subagent_type: routeAgentType(opts.risk || 'medium'),
|
|
37
40
|
effort: opts.risk === 'high' ? 'high' : 'medium',
|
|
38
41
|
}
|
|
39
42
|
if (opts.schema) agentOptions.schema = opts.schema
|
|
@@ -19,12 +19,15 @@ 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
|
-
|
|
22
|
+
if (typeof candidate !== 'string') return ''
|
|
23
|
+
const selected = candidate.trim()
|
|
24
|
+
return selected
|
|
23
25
|
}
|
|
24
|
-
|
|
26
|
+
const routeAgentType = (risk) => WORKFLOW_ROUTING.agentTypes?.[routeModel(risk)] || ''
|
|
27
|
+
if (!routeModel('medium') || !routeModel('high') || !routeAgentType('medium') || !routeAgentType('high')) {
|
|
25
28
|
return {
|
|
26
29
|
status: 'blocked',
|
|
27
|
-
reason: 'No
|
|
30
|
+
reason: 'No generated Bizar model-agent mapping was supplied for workflow routing. Run bizar models and retry; provider defaults are prohibited.',
|
|
28
31
|
}
|
|
29
32
|
}
|
|
30
33
|
let WORKFLOW_DISPATCH_SEQUENCE = 0
|
|
@@ -32,7 +35,7 @@ const dispatchAgent = (agentFn, agentName, prompt, opts = {}) => {
|
|
|
32
35
|
const sequence = ++WORKFLOW_DISPATCH_SEQUENCE
|
|
33
36
|
const prefix = `[Bizar dispatch ${sequence}: ${agentName}; role=${opts.role || 'worker'}; phase=${opts.phase || 'work'}; label=${opts.label || agentName}]`
|
|
34
37
|
const agentOptions = {
|
|
35
|
-
|
|
38
|
+
subagent_type: routeAgentType(opts.risk || 'medium'),
|
|
36
39
|
effort: opts.risk === 'high' ? 'high' : 'medium',
|
|
37
40
|
}
|
|
38
41
|
if (opts.schema) agentOptions.schema = opts.schema
|
|
@@ -21,12 +21,15 @@ 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
|
-
|
|
24
|
+
if (typeof candidate !== 'string') return ''
|
|
25
|
+
const selected = candidate.trim()
|
|
26
|
+
return selected
|
|
25
27
|
}
|
|
26
|
-
|
|
28
|
+
const routeAgentType = (risk) => WORKFLOW_ROUTING.agentTypes?.[routeModel(risk)] || ''
|
|
29
|
+
if (!routeModel('medium') || !routeModel('high') || !routeAgentType('medium') || !routeAgentType('high')) {
|
|
27
30
|
return {
|
|
28
31
|
status: 'blocked',
|
|
29
|
-
reason: 'No
|
|
32
|
+
reason: 'No generated Bizar model-agent mapping was supplied for workflow routing. Run bizar models and retry; provider defaults are prohibited.',
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
35
|
let WORKFLOW_DISPATCH_SEQUENCE = 0
|
|
@@ -34,7 +37,7 @@ const dispatchAgent = (agentFn, agentName, prompt, opts = {}) => {
|
|
|
34
37
|
const sequence = ++WORKFLOW_DISPATCH_SEQUENCE
|
|
35
38
|
const prefix = `[Bizar dispatch ${sequence}: ${agentName}; role=${opts.role || 'worker'}; phase=${opts.phase || 'work'}; label=${opts.label || agentName}]`
|
|
36
39
|
const agentOptions = {
|
|
37
|
-
|
|
40
|
+
subagent_type: routeAgentType(opts.risk || 'medium'),
|
|
38
41
|
effort: opts.risk === 'high' ? 'high' : 'medium',
|
|
39
42
|
}
|
|
40
43
|
if (opts.schema) agentOptions.schema = opts.schema
|
package/package.json
CHANGED