@polderlabs/bizar 10.23.17 → 10.23.20

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.
@@ -675,7 +675,7 @@ export function defaultTierHint(modelId) {
675
675
  // Premium first — strongest model family.
676
676
  if (/(qwen3\.8|gpt-5|opus|o3-pro|o4-mini|sonnet-4)/.test(id)) return 'premium';
677
677
  // High — newer mid-tier (haiku-4 is stronger than haiku-3-5).
678
- if (/(haiku-4|sonnet-3-7|mini-high|m3-high|grok-3)/.test(id)) return 'high';
678
+ if (/(haiku-4|sonnet-3-7|mini-high|m3-high|grok-3|glm[-/]?5\.3[-/]?flash)/.test(id)) return 'high';
679
679
  // Default — mainline sonnet / gpt-4 / m3.
680
680
  if (/(sonnet|gpt-4|m3(-|$)|(^|[^a-z])default($|[^a-z]))/.test(id)) return 'default';
681
681
  // Budget — small / fast variants. Match after the default family so that
@@ -1072,8 +1072,19 @@ export const NATIVE_AGENT_ALIASES = Object.freeze(['sonnet', 'opus', 'haiku', 'f
1072
1072
 
1073
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
1074
 
1075
- export function modelAgentName(modelId) {
1076
- return `bizar-model-${[...String(modelId || '').toLowerCase()].map((ch) => MODEL_AGENT_WORDS[ch] || 'unknown').join('-')}`;
1075
+ // These are the stable Bizar role frontmatter names. Model definitions are
1076
+ // projected per role so Claude Code's task UI retains a meaningful Bizar agent
1077
+ // identity instead of displaying an opaque model-only worker.
1078
+ export const BIZAR_AGENT_ROLES = Object.freeze(['mike', 'paul', 'karen', 'linda', 'ria', 'greg', 'steve', 'oscar', 'todd', 'susan', 'pam', 'brenda', 'janet', 'kevin', 'brad', 'carl']);
1079
+
1080
+ export function modelAgentName(modelId, role = 'worker') {
1081
+ const safeRole = BIZAR_AGENT_ROLES.includes(role) ? role : 'worker';
1082
+ return `${safeRole}-bizar-${[...String(modelId || '').toLowerCase()].map((ch) => MODEL_AGENT_WORDS[ch] || 'unknown').join('-')}`;
1083
+ }
1084
+
1085
+ export function isGeneratedModelAgentName(name, modelIds) {
1086
+ return typeof name === 'string' && (Array.isArray(modelIds) ? modelIds : []).some((id) =>
1087
+ BIZAR_AGENT_ROLES.some((role) => name === modelAgentName(id, role)));
1077
1088
  }
1078
1089
 
1079
1090
  export function syncGeneratedModelAgents(modelIds, opts = {}) {
@@ -1082,8 +1093,12 @@ export function syncGeneratedModelAgents(modelIds, opts = {}) {
1082
1093
  if (existsSync(agentsDir)) rmSync(agentsDir, { recursive: true, force: true });
1083
1094
  if (!ids.length) return { agentsDir, names: [] };
1084
1095
  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 }));
1096
+ const names = [];
1097
+ ids.forEach((id) => BIZAR_AGENT_ROLES.forEach((role) => {
1098
+ const name = modelAgentName(id, role);
1099
+ names.push(name);
1100
+ writeFileSync(join(agentsDir, `${name}.md`), `---\nname: ${name}\ndescription: Bizar ${role} role on a configured gateway model.\nmodel: ${id}\n---\n\nYou are the Bizar ${role} role. Follow the assigned task, preserve your role boundary, and report concise evidence to the coordinator.\n`, { mode: 0o600 });
1101
+ }));
1087
1102
  return { agentsDir, names };
1088
1103
  }
1089
1104
 
@@ -2348,10 +2363,10 @@ export async function run(name, args, isHelpRequest, deps = {}) {
2348
2363
  if (wantAgentTypes) {
2349
2364
  const router = loadRouter(routerPath);
2350
2365
  const models = configuredEnabledModels(router);
2351
- const agentTypes = Object.fromEntries(models.map((id) => [id, modelAgentName(id)]));
2366
+ const agentTypes = Object.fromEntries(models.map((id) => [id, Object.fromEntries(BIZAR_AGENT_ROLES.map((role) => [role, modelAgentName(id, role)]))]));
2352
2367
  const payload = { routerPath, models, agentTypes, agentsDir: join(resolveClaudeConfigDir(), 'agents', 'bizar-models') };
2353
2368
  if (wantJson) process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
2354
- else for (const id of models) process.stdout.write(`${id}\t${agentTypes[id]}\n`);
2369
+ else for (const id of models) process.stdout.write(`${id}\t${JSON.stringify(agentTypes[id])}\n`);
2355
2370
  return true;
2356
2371
  }
2357
2372
 
@@ -368,10 +368,7 @@ Background (async, returns immediately):
368
368
 
369
369
  ### Coordinating multiple background agents
370
370
 
371
- When fanning out several research or exploration tasks, issue multiple `Agent` calls in the **same message** with `run_in_background: true`. Each runs in parallel. Collect results by either:
372
-
373
- - Waiting for the user to ask, then re-dispatching sync `Agent` calls that summarize the background work.
374
- - Reading the background agent's final summary when it completes.
371
+ When fanning out several research or exploration tasks, issue multiple `Agent` calls in the **same message** with `run_in_background: true`. Each runs in parallel. Collect the original final summary/result when the task completes. Do not re-dispatch a completed background agent to summarize its work: that creates a new, context-poor task and its acknowledgement is not a replacement for the original deliverable.
375
372
 
376
373
  ---
377
374
 
@@ -63,8 +63,9 @@ from the global Bizar router. User-selected models take precedence over tier
63
63
  candidates; `disabledProviders` excludes both. Native aliases are compatibility
64
64
  transport labels bound by `bizar models`, not Anthropic selections. Native
65
65
  Agent/Task calls accept only those aliases, so do not pass a full gateway ID in
66
- `model`. Run `bizar models --agent-types --json`, look up the selected ID, pass
67
- its generated name as `subagent_type`, and omit `model`; the generated global
66
+ `model`. Run `bizar models --agent-types --json`, look up the selected ID and
67
+ Bizar role, pass its role-specific generated name (for example `greg-*` or
68
+ `todd-*`) as `subagent_type`, and omit `model`; the generated global
68
69
  definition's `model:` frontmatter selects the full gateway ID. This is the
69
70
  default for individual subagents, workflows, and agent-team teammates. For
70
71
  teams, do not name a competing model in the spawn prompt. Use `bizar worker`
@@ -93,8 +94,10 @@ Worktree branches use `wt/<agent_type>-<short-task-id>`.
93
94
  - Inspect an idle task after its second idle notification. Stop and reassign a
94
95
  task that has no progress/evidence; do not model-cycle it.
95
96
  - A `TaskCompleted`, `SubagentStop`, or `<task-notification>` is terminal. Read
96
- its `<result>`, mark the worker done/failed, merge queued work, and continue
97
- the active objective. Never route a completion notification as a new prompt.
97
+ its original `<result>` once, mark the worker done/failed, merge queued work,
98
+ and continue the active objective. Never route a completion notification as
99
+ a new prompt, send it back to the worker, or replace its deliverable with a
100
+ later acknowledgement/status reply.
98
101
 
99
102
  ## Learning and completion
100
103
 
@@ -49,7 +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
+ import { isGeneratedModelAgentName } from '../../../cli/commands/models.mjs';
53
53
 
54
54
  function deny(reason) {
55
55
  return {
@@ -226,7 +226,7 @@ export async function guardAgentModel(input, options = {}) {
226
226
  // Inheritance is safe only when the auditable Bizar selection equals the
227
227
  // actual global Claude parent model and is a selected, enabled user pick.
228
228
  if (!requested || requested === 'inherit') {
229
- const generated = typeof toolInput.subagent_type === 'string' && [...userPicks].some((id) => toolInput.subagent_type === modelAgentName(id));
229
+ const generated = isGeneratedModelAgentName(toolInput.subagent_type, [...userPicks]);
230
230
  if (generated && !requested) return {};
231
231
  const configured = configuredContext;
232
232
  const parent = readConfiguredParentModel(options);
@@ -252,7 +252,7 @@ export async function guardAgentModel(input, options = {}) {
252
252
  // a gateway accepts arbitrary IDs for the main conversation. Full IDs belong
253
253
  // in Bizar's generated subagent frontmatter, not in this enum-shaped field.
254
254
  if (userPicks.has(requested)) {
255
- return deny(`Bizar Agent dispatch blocked: native Agent model accepts aliases only. Use generated subagent_type ${modelAgentName(requested)} and omit model; re-run \`bizar models\` if it is missing.`);
255
+ return deny('Bizar Agent dispatch blocked: native Agent model accepts aliases only. Use the role-specific generated subagent_type from `bizar models --agent-types --json` and omit model; re-run `bizar models` if it is missing.');
256
256
  }
257
257
 
258
258
  // F-185 contract: when the orchestrator passes both `routingDecisionId`
@@ -188,7 +188,7 @@ function startupBriefing(cwd, featureBrief, recentCommits, projectLine, progress
188
188
  if (progressLast) lines.push(`- Progress: ${progressLast}.`);
189
189
  lines.push('- You are @mike. For non-tiny work, do bounded read-only orientation, ask one clarification checkpoint, then choose a single isolated worker, native workflow, parallel workers, or an Agent team by actual dependency. Use worktrees for editors and explicit Bizar models for every Agent.');
190
190
  lines.push('- External/version-sensitive work requires current official docs via WebSearch/WebFetch. Use relevant installed skills; apply i-have-adhd to user output. WIP=1.');
191
- lines.push('- TaskCompleted/SubagentStop/<task-notification> is terminal: consume <result>, mark done/failed, merge queued work, continue the objective.');
191
+ lines.push('- TaskCompleted/SubagentStop/<task-notification> is terminal: consume its original <result> once, mark done/failed, merge queued work, and continue the objective. Never turn a terminal notification into a worker follow-up or replace that result with a later status reply.');
192
192
  // Default-first-stop hint when nothing is active yet.
193
193
  if (featureBrief && featureBrief.active.length === 0) {
194
194
  lines.push(
@@ -56,7 +56,11 @@ export function handleTeamLifecycle(input, options = {}) {
56
56
  task.updatedAt = now;
57
57
  if (event === 'TaskCompleted' || event === 'SubagentStop') {
58
58
  task.state = /fail|error/i.test(record.status) ? 'failed' : 'completed';
59
- context = `Agent task ${taskId} is terminal (${task.state}). Consume its result, reconcile/merge its worktree, and continue the active objective.`;
59
+ // Claude Code already delivers the terminal result to the parent. Do
60
+ // not add another instruction here: lifecycle hook output can be
61
+ // surfaced in an agent context and turn a completed task into a bogus
62
+ // follow-up conversation ("Ready", "No action needed", etc.).
63
+ // This hook owns durable liveness telemetry only.
60
64
  } else if (event === 'TeammateIdle') {
61
65
  task.idleCount = Number(task.idleCount || 0) + 1;
62
66
  if (task.idleCount >= 2) context = `Agent task ${taskId} has been idle ${task.idleCount} times. Inspect its evidence now; stop and reassign if it made no progress. Do not model-cycle.`;
@@ -60,8 +60,8 @@ 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
- '- For every Agent, workflow worker, or agent-team teammate, choose one exact ID from the enabled `bizar models` user selection, then obtain its generated definition name from `bizar models --agent-types --json`. Pass that name as `subagent_type` and OMIT the native `model` field. The definition frontmatter owns the exact gateway ID. Never put a raw gateway ID or a Claude family alias in the native model field; aliases are compatibility-only. For teams, do not name a competing model in the spawn prompt. If the map or definition is missing, run `bizar models`; do not retry by cycling providers or tiers. Every editing worker uses call-level `isolation: "worktree"`. For genuinely disjoint writable scopes, dispatch concurrently; otherwise use one owner.',
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.',
63
+ '- For every Agent, workflow worker, or agent-team teammate, choose one exact ID from the enabled `bizar models` user selection, then obtain its role-specific generated definition name from `bizar models --agent-types --json` (for example greg or todd). Pass that name as `subagent_type` and OMIT the native `model` field. The definition frontmatter owns the exact gateway ID. Never put a raw gateway ID or a Claude family alias in the native model field; aliases are compatibility-only. For teams, do not name a competing model in the spawn prompt. If the map or definition is missing, run `bizar models`; do not retry by cycling providers or tiers. Every editing worker uses call-level `isolation: "worktree"`. For genuinely disjoint writable scopes, dispatch concurrently; otherwise use one owner.',
64
+ '- Consume each terminal agent result exactly once from its original `<result>`/final summary, merge queued worktrees with bizar worktree-merge, and run integration checks in the primary session. Never send a terminal notification, idle ping, or completed task back to that agent as a follow-up and never re-dispatch a completed background agent merely to summarize its result. 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.',
67
67
  ].join('\n');
@@ -83,7 +83,7 @@ process.stdin.on('end', async () => {
83
83
  process.stdout.write(JSON.stringify({
84
84
  hookSpecificOutput: {
85
85
  hookEventName: 'UserPromptSubmit',
86
- additionalContext: 'Bizar terminal task update: consume the structured status and <result> now. Mark the agent terminal, merge any queued worktree, continue the active objective, and do not route this notification as a new request.',
86
+ additionalContext: 'Bizar terminal task update: consume this original structured <result> exactly once. Mark the agent terminal and merge any queued worktree. Do not send this notification back to the agent, do not request a follow-up acknowledgement, and do not replace its result with a later idle/status reply.',
87
87
  },
88
88
  }) + '\n');
89
89
  return;
@@ -26,7 +26,7 @@ const routeModel = (risk) => {
26
26
  const selected = candidate.trim()
27
27
  return selected
28
28
  }
29
- const routeAgentType = (risk) => WORKFLOW_ROUTING.agentTypes?.[routeModel(risk)] || ''
29
+ const routeAgentType = (risk, role = 'todd') => WORKFLOW_ROUTING.agentTypes?.[routeModel(risk)]?.[({ 'research-analyst': 'greg', planner: 'paul', implementer: 'todd', 'qa-reviewer': 'linda', reviewer: 'linda' }[role] || role)] || ''
30
30
  if (!routeModel('medium') || !routeModel('high') || !routeAgentType('medium') || !routeAgentType('high')) {
31
31
  return {
32
32
  status: 'blocked',
@@ -38,7 +38,7 @@ const dispatchAgent = (agentFn, agentName, prompt, opts = {}) => {
38
38
  const sequence = ++WORKFLOW_DISPATCH_SEQUENCE
39
39
  const prefix = `[Bizar dispatch ${sequence}: ${agentName}; role=${opts.role || 'worker'}; phase=${opts.phase || 'work'}; label=${opts.label || agentName}]`
40
40
  const agentOptions = {
41
- subagent_type: routeAgentType(opts.risk || 'medium'),
41
+ subagent_type: routeAgentType(opts.risk || 'medium', opts.role),
42
42
  effort: opts.risk === 'high' ? 'high' : 'medium',
43
43
  }
44
44
  if (opts.schema) agentOptions.schema = opts.schema
@@ -25,7 +25,7 @@ const routeModel = (risk) => {
25
25
  const selected = candidate.trim()
26
26
  return selected
27
27
  }
28
- const routeAgentType = (risk) => WORKFLOW_ROUTING.agentTypes?.[routeModel(risk)] || ''
28
+ const routeAgentType = (risk, role = 'todd') => WORKFLOW_ROUTING.agentTypes?.[routeModel(risk)]?.[({ 'research-analyst': 'greg', planner: 'paul', implementer: 'todd', 'qa-reviewer': 'linda', reviewer: 'linda' }[role] || role)] || ''
29
29
  if (!routeModel('medium') || !routeModel('high') || !routeAgentType('medium') || !routeAgentType('high')) {
30
30
  return {
31
31
  status: 'blocked',
@@ -37,7 +37,7 @@ const dispatchAgent = (agentFn, agentName, prompt, opts = {}) => {
37
37
  const sequence = ++WORKFLOW_DISPATCH_SEQUENCE
38
38
  const prefix = `[Bizar dispatch ${sequence}: ${agentName}; role=${opts.role || 'worker'}; phase=${opts.phase || 'work'}; label=${opts.label || agentName}]`
39
39
  const agentOptions = {
40
- subagent_type: routeAgentType(opts.risk || 'medium'),
40
+ subagent_type: routeAgentType(opts.risk || 'medium', opts.role),
41
41
  effort: opts.risk === 'high' ? 'high' : 'medium',
42
42
  }
43
43
  if (opts.schema) agentOptions.schema = opts.schema
@@ -26,7 +26,7 @@ const routeModel = (risk) => {
26
26
  const selected = candidate.trim()
27
27
  return selected
28
28
  }
29
- const routeAgentType = (risk) => WORKFLOW_ROUTING.agentTypes?.[routeModel(risk)] || ''
29
+ const routeAgentType = (risk, role = 'todd') => WORKFLOW_ROUTING.agentTypes?.[routeModel(risk)]?.[({ 'research-analyst': 'greg', planner: 'paul', implementer: 'todd', 'qa-reviewer': 'linda', reviewer: 'linda' }[role] || role)] || ''
30
30
  if (!routeModel('medium') || !routeModel('high') || !routeAgentType('medium') || !routeAgentType('high')) {
31
31
  return {
32
32
  status: 'blocked',
@@ -38,7 +38,7 @@ const dispatchAgent = (agentFn, agentName, prompt, opts = {}) => {
38
38
  const sequence = ++WORKFLOW_DISPATCH_SEQUENCE
39
39
  const prefix = `[Bizar dispatch ${sequence}: ${agentName}; role=${opts.role || 'worker'}; phase=${opts.phase || 'work'}; label=${opts.label || agentName}]`
40
40
  const agentOptions = {
41
- subagent_type: routeAgentType(opts.risk || 'medium'),
41
+ subagent_type: routeAgentType(opts.risk || 'medium', opts.role),
42
42
  effort: opts.risk === 'high' ? 'high' : 'medium',
43
43
  }
44
44
  if (opts.schema) agentOptions.schema = opts.schema
@@ -34,7 +34,14 @@ import { homedir } from 'node:os';
34
34
  import { dirname, isAbsolute, join, resolve } from 'node:path';
35
35
 
36
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('-')}`; }
37
+ const ROLE_TO_BIZAR_AGENT = Object.freeze({
38
+ 'research-analyst': 'greg', planner: 'paul', implementer: 'todd',
39
+ 'qa-reviewer': 'linda', reviewer: 'linda', 'debug-specialist': 'carl',
40
+ 'principal-engineer': 'karen', 'ui-designer': 'ria', 'it-lead': 'steve',
41
+ 'knowledge-manager': 'oscar', 'support-tech': 'kevin', 'exec-assistant': 'pam',
42
+ 'office-coordinator': 'brenda', 'office-greeter': 'janet', 'brand-designer': 'brad',
43
+ });
44
+ function modelAgentName(modelId, role = 'todd') { return `${ROLE_TO_BIZAR_AGENT[role] || (Object.values(ROLE_TO_BIZAR_AGENT).includes(role) ? role : 'todd')}-bizar-${[...String(modelId || '').toLowerCase()].map((ch) => MODEL_AGENT_WORDS[ch] || 'unknown').join('-')}`; }
38
45
 
39
46
  const { O_APPEND, O_CREAT, O_WRONLY } = fsConstants;
40
47
 
@@ -88,7 +95,7 @@ function defaultTierHintForId(modelId) {
88
95
  const id = String(modelId || '').toLowerCase();
89
96
  if (!id) return 'default';
90
97
  if (/(qwen3\.8|gpt-5|opus|o3-pro|o4-mini|sonnet-4)/.test(id)) return 'premium';
91
- if (/(haiku-4|sonnet-3-7|mini-high|m3-high|grok-3)/.test(id)) return 'high';
98
+ if (/(haiku-4|sonnet-3-7|mini-high|m3-high|grok-3|glm[-/]?5\.3[-/]?flash)/.test(id)) return 'high';
92
99
  if (/(sonnet|gpt-4|m3(-|$)|(^|[^a-z])default($|[^a-z]))/.test(id)) return 'default';
93
100
  if (/(nano|mini[-/]|flash|lite|tiny|haiku($|[-_]\d))/.test(id)) return 'budget';
94
101
  return 'mid';
@@ -804,7 +811,7 @@ export function classifyDispatchOutcome(result, error, startMs) {
804
811
  export function augmentPayload(opts, decision, agentName, context = {}) {
805
812
  return {
806
813
  ...opts,
807
- subagent_type: modelAgentName(decision.modelId),
814
+ subagent_type: modelAgentName(decision.modelId, opts.role),
808
815
  additionalContext: {
809
816
  ...(opts.additionalContext && typeof opts.additionalContext === 'object' ? opts.additionalContext : {}),
810
817
  bizarConfiguredModel: decision.modelId ?? null,
@@ -24,7 +24,7 @@ const routeModel = (risk) => {
24
24
  const selected = candidate.trim()
25
25
  return selected
26
26
  }
27
- const routeAgentType = (risk) => WORKFLOW_ROUTING.agentTypes?.[routeModel(risk)] || ''
27
+ const routeAgentType = (risk, role = 'todd') => WORKFLOW_ROUTING.agentTypes?.[routeModel(risk)]?.[({ 'research-analyst': 'greg', planner: 'paul', implementer: 'todd', 'qa-reviewer': 'linda', reviewer: 'linda' }[role] || role)] || ''
28
28
  if (!routeModel('medium') || !routeModel('high') || !routeAgentType('medium') || !routeAgentType('high')) {
29
29
  return {
30
30
  status: 'blocked',
@@ -36,7 +36,7 @@ const dispatchAgent = (agentFn, agentName, prompt, opts = {}) => {
36
36
  const sequence = ++WORKFLOW_DISPATCH_SEQUENCE
37
37
  const prefix = `[Bizar dispatch ${sequence}: ${agentName}; role=${opts.role || 'worker'}; phase=${opts.phase || 'work'}; label=${opts.label || agentName}]`
38
38
  const agentOptions = {
39
- subagent_type: routeAgentType(opts.risk || 'medium'),
39
+ subagent_type: routeAgentType(opts.risk || 'medium', opts.role),
40
40
  effort: opts.risk === 'high' ? 'high' : 'medium',
41
41
  }
42
42
  if (opts.schema) agentOptions.schema = opts.schema
@@ -23,7 +23,7 @@ const routeModel = (risk) => {
23
23
  const selected = candidate.trim()
24
24
  return selected
25
25
  }
26
- const routeAgentType = (risk) => WORKFLOW_ROUTING.agentTypes?.[routeModel(risk)] || ''
26
+ const routeAgentType = (risk, role = 'todd') => WORKFLOW_ROUTING.agentTypes?.[routeModel(risk)]?.[({ 'research-analyst': 'greg', planner: 'paul', implementer: 'todd', 'qa-reviewer': 'linda', reviewer: 'linda' }[role] || role)] || ''
27
27
  if (!routeModel('medium') || !routeModel('high') || !routeAgentType('medium') || !routeAgentType('high')) {
28
28
  return {
29
29
  status: 'blocked',
@@ -35,7 +35,7 @@ const dispatchAgent = (agentFn, agentName, prompt, opts = {}) => {
35
35
  const sequence = ++WORKFLOW_DISPATCH_SEQUENCE
36
36
  const prefix = `[Bizar dispatch ${sequence}: ${agentName}; role=${opts.role || 'worker'}; phase=${opts.phase || 'work'}; label=${opts.label || agentName}]`
37
37
  const agentOptions = {
38
- subagent_type: routeAgentType(opts.risk || 'medium'),
38
+ subagent_type: routeAgentType(opts.risk || 'medium', opts.role),
39
39
  effort: opts.risk === 'high' ? 'high' : 'medium',
40
40
  }
41
41
  if (opts.schema) agentOptions.schema = opts.schema
@@ -25,7 +25,7 @@ const routeModel = (risk) => {
25
25
  const selected = candidate.trim()
26
26
  return selected
27
27
  }
28
- const routeAgentType = (risk) => WORKFLOW_ROUTING.agentTypes?.[routeModel(risk)] || ''
28
+ const routeAgentType = (risk, role = 'todd') => WORKFLOW_ROUTING.agentTypes?.[routeModel(risk)]?.[({ 'research-analyst': 'greg', planner: 'paul', implementer: 'todd', 'qa-reviewer': 'linda', reviewer: 'linda' }[role] || role)] || ''
29
29
  if (!routeModel('medium') || !routeModel('high') || !routeAgentType('medium') || !routeAgentType('high')) {
30
30
  return {
31
31
  status: 'blocked',
@@ -37,7 +37,7 @@ const dispatchAgent = (agentFn, agentName, prompt, opts = {}) => {
37
37
  const sequence = ++WORKFLOW_DISPATCH_SEQUENCE
38
38
  const prefix = `[Bizar dispatch ${sequence}: ${agentName}; role=${opts.role || 'worker'}; phase=${opts.phase || 'work'}; label=${opts.label || agentName}]`
39
39
  const agentOptions = {
40
- subagent_type: routeAgentType(opts.risk || 'medium'),
40
+ subagent_type: routeAgentType(opts.risk || 'medium', opts.role),
41
41
  effort: opts.risk === 'high' ? 'high' : 'medium',
42
42
  }
43
43
  if (opts.schema) agentOptions.schema = opts.schema
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "10.23.17",
3
+ "version": "10.23.20",
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": {
@@ -213,7 +213,7 @@ export function defaultTierHintForId(modelId) {
213
213
  return "default";
214
214
  if (/(qwen3\.8|gpt-5|opus|o3-pro|o4-mini|sonnet-4)/.test(id))
215
215
  return "premium";
216
- if (/(haiku-4|sonnet-3-7|mini-high|m3-high|grok-3)/.test(id))
216
+ if (/(haiku-4|sonnet-3-7|mini-high|m3-high|grok-3|glm[-/]?5\.3[-/]?flash)/.test(id))
217
217
  return "high";
218
218
  if (/(sonnet|gpt-4|m3(-|$)|(^|[^a-z])default($|[^a-z]))/.test(id))
219
219
  return "default";
@@ -31,7 +31,7 @@ export function defaultTierHintForId(modelId) {
31
31
  const id = String(modelId || "").toLowerCase();
32
32
  if (!id) return "default";
33
33
  if (/(qwen3\.8|gpt-5|opus|o3-pro|o4-mini|sonnet-4)/.test(id)) return "premium";
34
- if (/(haiku-4|sonnet-3-7|mini-high|m3-high|grok-3)/.test(id)) return "high";
34
+ if (/(haiku-4|sonnet-3-7|mini-high|m3-high|grok-3|glm[-/]?5\.3[-/]?flash)/.test(id)) return "high";
35
35
  if (/(sonnet|gpt-4|m3(-|$)|(^|[^a-z])default($|[^a-z]))/.test(id)) return "default";
36
36
  if (/(nano|mini[-/]|flash|lite|tiny|haiku($|[-_]\d))/.test(id)) return "budget";
37
37
  return "mid";
@@ -312,4 +312,4 @@ export function classifyError(message) {
312
312
  if (/(invalid.*model|unknown.*model|model not found|no such model|not a valid model)/.test(m)) return "invalid-model";
313
313
  if (/(quality|incomplete|truncated|garbage|low quality|incoherent)/.test(m)) return "model-quality";
314
314
  return "invalid-model";
315
- }
315
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * SDK version constant. Keep synchronized with the workspace package versions.
3
3
  */
4
- export declare const SDK_VERSION: "10.23.13";
4
+ export declare const SDK_VERSION: "10.23.20";
5
5
  //# sourceMappingURL=version.d.ts.map
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * SDK version constant. Keep synchronized with the workspace package versions.
3
3
  */
4
- export const SDK_VERSION = "10.23.13";
4
+ export const SDK_VERSION = "10.23.20";
5
5
  //# sourceMappingURL=version.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar-sdk",
3
- "version": "10.23.13",
3
+ "version": "10.23.20",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",