baro-ai 0.51.4 → 0.51.5

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/dist/cli.mjs CHANGED
@@ -40350,6 +40350,150 @@ function buildDag(stories, options = {}) {
40350
40350
  return levels;
40351
40351
  }
40352
40352
 
40353
+ // ../baro-orchestrator/src/routing.ts
40354
+ var BACKENDS = ["claude", "openai", "codex", "opencode", "pi"];
40355
+ function isBackend(s2) {
40356
+ return BACKENDS.includes(s2);
40357
+ }
40358
+ var CLAUDE_TIER_RE = /^(opus|sonnet|haiku)\b/i;
40359
+ function isClaudeTierName(s2) {
40360
+ return CLAUDE_TIER_RE.test(s2.trim());
40361
+ }
40362
+ function splitBackendModel(raw) {
40363
+ const trimmed = raw.trim();
40364
+ if (!trimmed) return {};
40365
+ const idx = trimmed.indexOf(":");
40366
+ if (idx > 0) {
40367
+ const prefix = trimmed.slice(0, idx).toLowerCase();
40368
+ if (isBackend(prefix)) {
40369
+ const model = trimmed.slice(idx + 1).trim();
40370
+ return { backend: prefix, model: model.length ? model : void 0 };
40371
+ }
40372
+ }
40373
+ if (isBackend(trimmed.toLowerCase())) {
40374
+ return { backend: trimmed.toLowerCase() };
40375
+ }
40376
+ return { model: trimmed };
40377
+ }
40378
+ function looksLikeUrl(s2) {
40379
+ return /^https?:\/\//i.test(s2.trim());
40380
+ }
40381
+ function splitModelEndpoint(model) {
40382
+ const at = model.indexOf("@");
40383
+ if (at < 0) return { model };
40384
+ const ref = model.slice(at + 1).trim();
40385
+ return { model: model.slice(0, at).trim(), endpointRef: ref || void 0 };
40386
+ }
40387
+ function resolveEndpoint(ref, opts) {
40388
+ if (looksLikeUrl(ref)) {
40389
+ return { baseUrl: ref, apiKey: opts.defaultApiKey };
40390
+ }
40391
+ const ep = opts.endpoints?.[ref.toLowerCase()] ?? opts.endpoints?.[ref];
40392
+ if (!ep) {
40393
+ throw new Error(
40394
+ `unknown OpenAI endpoint "${ref}" \u2014 define it with --openai-endpoint ${ref}=<url> or use an inline https:// URL`
40395
+ );
40396
+ }
40397
+ return { baseUrl: ep.baseUrl, apiKey: ep.apiKey ?? opts.defaultApiKey };
40398
+ }
40399
+ function resolveStoryRoute(rawModel, opts) {
40400
+ const raw = (opts.override ?? rawModel ?? "").trim();
40401
+ if (!raw) return defaultRoute(opts.fallbackBackend, opts.openaiDefaultModel);
40402
+ const direct = splitBackendModel(raw);
40403
+ if (direct.backend === void 0 && opts.tierMap) {
40404
+ const mapped = opts.tierMap[raw.toLowerCase()] ?? opts.tierMap[raw];
40405
+ if (mapped) {
40406
+ return resolveStoryRoute(mapped, {
40407
+ ...opts,
40408
+ override: void 0,
40409
+ tierMap: void 0
40410
+ });
40411
+ }
40412
+ }
40413
+ if (direct.backend) {
40414
+ if (direct.model) return buildRoute(direct.backend, direct.model, opts);
40415
+ return defaultRoute(direct.backend, opts.openaiDefaultModel);
40416
+ }
40417
+ const backend = opts.fallbackBackend;
40418
+ if (backend === "claude") {
40419
+ return { backend, model: direct.model };
40420
+ }
40421
+ if (direct.model && !isClaudeTierName(direct.model)) {
40422
+ return buildRoute(backend, direct.model, opts);
40423
+ }
40424
+ return defaultRoute(backend, opts.openaiDefaultModel);
40425
+ }
40426
+ function buildRoute(backend, model, opts) {
40427
+ if (backend !== "openai") return { backend, model };
40428
+ const { model: bareModel, endpointRef } = splitModelEndpoint(model);
40429
+ if (!endpointRef) return { backend, model: bareModel };
40430
+ const ep = resolveEndpoint(endpointRef, opts);
40431
+ return { backend, model: bareModel, baseUrl: ep.baseUrl, apiKey: ep.apiKey };
40432
+ }
40433
+ function defaultRoute(backend, openaiDefaultModel) {
40434
+ if (backend === "openai") return { backend, model: openaiDefaultModel };
40435
+ return { backend };
40436
+ }
40437
+ function parseTierMap(spec) {
40438
+ const map = {};
40439
+ for (const part of spec.split(",")) {
40440
+ const seg = part.trim();
40441
+ if (!seg) continue;
40442
+ const eq = seg.indexOf("=");
40443
+ if (eq <= 0) {
40444
+ throw new Error(
40445
+ `bad --tier-map entry "${seg}" (expected tier=backend:model)`
40446
+ );
40447
+ }
40448
+ const tier = seg.slice(0, eq).trim().toLowerCase();
40449
+ const route = seg.slice(eq + 1).trim();
40450
+ if (!tier || !route) {
40451
+ throw new Error(
40452
+ `bad --tier-map entry "${seg}" (expected tier=backend:model)`
40453
+ );
40454
+ }
40455
+ const { backend } = splitBackendModel(route);
40456
+ if (!backend) {
40457
+ throw new Error(
40458
+ `--tier-map route "${route}" for tier "${tier}" must name a backend (claude: | openai: | codex: | opencode: | pi:)`
40459
+ );
40460
+ }
40461
+ map[tier] = route;
40462
+ }
40463
+ return map;
40464
+ }
40465
+ function parseEndpoints(specs, keyFor) {
40466
+ const map = {};
40467
+ for (const raw of specs) {
40468
+ const spec = raw.trim();
40469
+ if (!spec) continue;
40470
+ const eq = spec.indexOf("=");
40471
+ if (eq <= 0) {
40472
+ throw new Error(
40473
+ `bad --openai-endpoint "${spec}" (expected name=url)`
40474
+ );
40475
+ }
40476
+ const name = spec.slice(0, eq).trim().toLowerCase();
40477
+ const url = spec.slice(eq + 1).trim();
40478
+ if (!name || !url) {
40479
+ throw new Error(
40480
+ `bad --openai-endpoint "${spec}" (expected name=url)`
40481
+ );
40482
+ }
40483
+ if (!looksLikeUrl(url)) {
40484
+ throw new Error(
40485
+ `--openai-endpoint "${name}" url "${url}" must start with http:// or https://`
40486
+ );
40487
+ }
40488
+ map[name] = { baseUrl: url, apiKey: keyFor?.(name) };
40489
+ }
40490
+ return map;
40491
+ }
40492
+ function formatRoute(route) {
40493
+ const base = route.model ? `${route.backend}:${route.model}` : route.backend;
40494
+ return route.baseUrl ? `${base}@${route.baseUrl}` : base;
40495
+ }
40496
+
40353
40497
  // ../baro-orchestrator/src/participants/auditor.ts
40354
40498
  import { appendFileSync, mkdirSync } from "fs";
40355
40499
  import { dirname } from "path";
@@ -42914,9 +43058,16 @@ var ProgressForwarder = class extends BaseObserver {
42914
43058
  };
42915
43059
 
42916
43060
  // ../baro-orchestrator/src/participants/forwarders/story-lifecycle.ts
43061
+ var CREATE_TOOLS = /* @__PURE__ */ new Set(["Write", "write_file"]);
43062
+ var EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "MultiEdit", "NotebookEdit", "edit_file"]);
42917
43063
  var StoryLifecycleForwarder = class extends BaseObserver {
42918
43064
  startedStories = /* @__PURE__ */ new Set();
42919
43065
  retryCounts = /* @__PURE__ */ new Map();
43066
+ // storyId → (path → first-touch kind). Distinct paths per story, so a
43067
+ // file touched many times counts once, classified by its first write.
43068
+ // Reset on each retry so story_complete reflects only the winning
43069
+ // attempt's touches, not files a failed attempt wrote and abandoned.
43070
+ filesByStory = /* @__PURE__ */ new Map();
42920
43071
  async onExternalEvent(_source, event) {
42921
43072
  if (AgentState.is(event)) {
42922
43073
  this.handleAgentState(event.data);
@@ -42927,6 +43078,27 @@ var StoryLifecycleForwarder = class extends BaseObserver {
42927
43078
  return;
42928
43079
  }
42929
43080
  }
43081
+ // Same signal the Sentry uses: a write/edit tool call from a story
43082
+ // agent. We attribute the touched path to that agent's story and
43083
+ // remember its first-touch kind so story_complete can report real
43084
+ // per-story file counts instead of a hardcoded 0.
43085
+ async onExternalFunctionCall(source, item) {
43086
+ const isCreate = CREATE_TOOLS.has(item.name);
43087
+ const isEdit = EDIT_TOOLS.has(item.name);
43088
+ if (!isCreate && !isEdit) return;
43089
+ const agentId = source.agentId;
43090
+ if (typeof agentId !== "string") return;
43091
+ const path6 = extractPath(item);
43092
+ if (!path6) return;
43093
+ let paths = this.filesByStory.get(agentId);
43094
+ if (!paths) {
43095
+ paths = /* @__PURE__ */ new Map();
43096
+ this.filesByStory.set(agentId, paths);
43097
+ }
43098
+ if (!paths.has(path6)) {
43099
+ paths.set(path6, isCreate ? "created" : "modified");
43100
+ }
43101
+ }
42930
43102
  handleAgentState(item) {
42931
43103
  if (item.phase === "running" && !this.startedStories.has(item.agentId)) {
42932
43104
  this.startedStories.add(item.agentId);
@@ -42935,19 +43107,31 @@ var StoryLifecycleForwarder = class extends BaseObserver {
42935
43107
  if (item.phase === "waiting" && item.detail?.includes("retrying")) {
42936
43108
  const count = (this.retryCounts.get(item.agentId) ?? 0) + 1;
42937
43109
  this.retryCounts.set(item.agentId, count);
43110
+ this.filesByStory.delete(item.agentId);
42938
43111
  emit({ type: "story_retry", id: item.agentId, attempt: count });
42939
43112
  }
42940
43113
  }
42941
43114
  handleStoryResult(item) {
42942
43115
  if (item.success) {
43116
+ const paths = this.filesByStory.get(item.storyId);
43117
+ let created = 0;
43118
+ let modified = 0;
43119
+ if (paths) {
43120
+ for (const kind of paths.values()) {
43121
+ if (kind === "created") created++;
43122
+ else modified++;
43123
+ }
43124
+ }
43125
+ this.filesByStory.delete(item.storyId);
42943
43126
  emit({
42944
43127
  type: "story_complete",
42945
43128
  id: item.storyId,
42946
43129
  duration_secs: item.durationSecs,
42947
- files_created: 0,
42948
- files_modified: 0
43130
+ files_created: created,
43131
+ files_modified: modified
42949
43132
  });
42950
43133
  } else {
43134
+ this.filesByStory.delete(item.storyId);
42951
43135
  emit({
42952
43136
  type: "story_error",
42953
43137
  id: item.storyId,
@@ -42958,6 +43142,19 @@ var StoryLifecycleForwarder = class extends BaseObserver {
42958
43142
  }
42959
43143
  }
42960
43144
  };
43145
+ function extractPath(item) {
43146
+ let args;
43147
+ try {
43148
+ args = JSON.parse(item.args);
43149
+ } catch {
43150
+ return null;
43151
+ }
43152
+ for (const key of ["file_path", "path", "notebook_path"]) {
43153
+ const v = args[key];
43154
+ if (typeof v === "string") return v;
43155
+ }
43156
+ return null;
43157
+ }
42961
43158
 
42962
43159
  // ../baro-orchestrator/src/participants/forwarders/token-usage.ts
42963
43160
  var TokenUsageForwarder = class extends BaseObserver {
@@ -43594,7 +43791,7 @@ var Sentry = class extends BaseObserver {
43594
43791
  if (!WRITE_TOOLS.has(item.name)) return;
43595
43792
  const agentId = source.agentId;
43596
43793
  if (typeof agentId !== "string") return;
43597
- const path6 = extractPath(item);
43794
+ const path6 = extractPath2(item);
43598
43795
  if (!path6) return;
43599
43796
  const touch = {
43600
43797
  agentId,
@@ -43629,7 +43826,7 @@ var Sentry = class extends BaseObserver {
43629
43826
  }
43630
43827
  }
43631
43828
  };
43632
- function extractPath(item) {
43829
+ function extractPath2(item) {
43633
43830
  let args;
43634
43831
  try {
43635
43832
  args = JSON.parse(item.args);
@@ -47014,150 +47211,6 @@ function raceWithTimeout4(p, ms, label) {
47014
47211
  ]);
47015
47212
  }
47016
47213
 
47017
- // ../baro-orchestrator/src/routing.ts
47018
- var BACKENDS = ["claude", "openai", "codex", "opencode", "pi"];
47019
- function isBackend(s2) {
47020
- return BACKENDS.includes(s2);
47021
- }
47022
- var CLAUDE_TIER_RE = /^(opus|sonnet|haiku)\b/i;
47023
- function isClaudeTierName(s2) {
47024
- return CLAUDE_TIER_RE.test(s2.trim());
47025
- }
47026
- function splitBackendModel(raw) {
47027
- const trimmed = raw.trim();
47028
- if (!trimmed) return {};
47029
- const idx = trimmed.indexOf(":");
47030
- if (idx > 0) {
47031
- const prefix = trimmed.slice(0, idx).toLowerCase();
47032
- if (isBackend(prefix)) {
47033
- const model = trimmed.slice(idx + 1).trim();
47034
- return { backend: prefix, model: model.length ? model : void 0 };
47035
- }
47036
- }
47037
- if (isBackend(trimmed.toLowerCase())) {
47038
- return { backend: trimmed.toLowerCase() };
47039
- }
47040
- return { model: trimmed };
47041
- }
47042
- function looksLikeUrl(s2) {
47043
- return /^https?:\/\//i.test(s2.trim());
47044
- }
47045
- function splitModelEndpoint(model) {
47046
- const at = model.indexOf("@");
47047
- if (at < 0) return { model };
47048
- const ref = model.slice(at + 1).trim();
47049
- return { model: model.slice(0, at).trim(), endpointRef: ref || void 0 };
47050
- }
47051
- function resolveEndpoint(ref, opts) {
47052
- if (looksLikeUrl(ref)) {
47053
- return { baseUrl: ref, apiKey: opts.defaultApiKey };
47054
- }
47055
- const ep = opts.endpoints?.[ref.toLowerCase()] ?? opts.endpoints?.[ref];
47056
- if (!ep) {
47057
- throw new Error(
47058
- `unknown OpenAI endpoint "${ref}" \u2014 define it with --openai-endpoint ${ref}=<url> or use an inline https:// URL`
47059
- );
47060
- }
47061
- return { baseUrl: ep.baseUrl, apiKey: ep.apiKey ?? opts.defaultApiKey };
47062
- }
47063
- function resolveStoryRoute(rawModel, opts) {
47064
- const raw = (opts.override ?? rawModel ?? "").trim();
47065
- if (!raw) return defaultRoute(opts.fallbackBackend, opts.openaiDefaultModel);
47066
- const direct = splitBackendModel(raw);
47067
- if (direct.backend === void 0 && opts.tierMap) {
47068
- const mapped = opts.tierMap[raw.toLowerCase()] ?? opts.tierMap[raw];
47069
- if (mapped) {
47070
- return resolveStoryRoute(mapped, {
47071
- ...opts,
47072
- override: void 0,
47073
- tierMap: void 0
47074
- });
47075
- }
47076
- }
47077
- if (direct.backend) {
47078
- if (direct.model) return buildRoute(direct.backend, direct.model, opts);
47079
- return defaultRoute(direct.backend, opts.openaiDefaultModel);
47080
- }
47081
- const backend = opts.fallbackBackend;
47082
- if (backend === "claude") {
47083
- return { backend, model: direct.model };
47084
- }
47085
- if (direct.model && !isClaudeTierName(direct.model)) {
47086
- return buildRoute(backend, direct.model, opts);
47087
- }
47088
- return defaultRoute(backend, opts.openaiDefaultModel);
47089
- }
47090
- function buildRoute(backend, model, opts) {
47091
- if (backend !== "openai") return { backend, model };
47092
- const { model: bareModel, endpointRef } = splitModelEndpoint(model);
47093
- if (!endpointRef) return { backend, model: bareModel };
47094
- const ep = resolveEndpoint(endpointRef, opts);
47095
- return { backend, model: bareModel, baseUrl: ep.baseUrl, apiKey: ep.apiKey };
47096
- }
47097
- function defaultRoute(backend, openaiDefaultModel) {
47098
- if (backend === "openai") return { backend, model: openaiDefaultModel };
47099
- return { backend };
47100
- }
47101
- function parseTierMap(spec) {
47102
- const map = {};
47103
- for (const part of spec.split(",")) {
47104
- const seg = part.trim();
47105
- if (!seg) continue;
47106
- const eq = seg.indexOf("=");
47107
- if (eq <= 0) {
47108
- throw new Error(
47109
- `bad --tier-map entry "${seg}" (expected tier=backend:model)`
47110
- );
47111
- }
47112
- const tier = seg.slice(0, eq).trim().toLowerCase();
47113
- const route = seg.slice(eq + 1).trim();
47114
- if (!tier || !route) {
47115
- throw new Error(
47116
- `bad --tier-map entry "${seg}" (expected tier=backend:model)`
47117
- );
47118
- }
47119
- const { backend } = splitBackendModel(route);
47120
- if (!backend) {
47121
- throw new Error(
47122
- `--tier-map route "${route}" for tier "${tier}" must name a backend (claude: | openai: | codex: | opencode: | pi:)`
47123
- );
47124
- }
47125
- map[tier] = route;
47126
- }
47127
- return map;
47128
- }
47129
- function parseEndpoints(specs, keyFor) {
47130
- const map = {};
47131
- for (const raw of specs) {
47132
- const spec = raw.trim();
47133
- if (!spec) continue;
47134
- const eq = spec.indexOf("=");
47135
- if (eq <= 0) {
47136
- throw new Error(
47137
- `bad --openai-endpoint "${spec}" (expected name=url)`
47138
- );
47139
- }
47140
- const name = spec.slice(0, eq).trim().toLowerCase();
47141
- const url = spec.slice(eq + 1).trim();
47142
- if (!name || !url) {
47143
- throw new Error(
47144
- `bad --openai-endpoint "${spec}" (expected name=url)`
47145
- );
47146
- }
47147
- if (!looksLikeUrl(url)) {
47148
- throw new Error(
47149
- `--openai-endpoint "${name}" url "${url}" must start with http:// or https://`
47150
- );
47151
- }
47152
- map[name] = { baseUrl: url, apiKey: keyFor?.(name) };
47153
- }
47154
- return map;
47155
- }
47156
- function formatRoute(route) {
47157
- const base = route.model ? `${route.backend}:${route.model}` : route.backend;
47158
- return route.baseUrl ? `${base}@${route.baseUrl}` : base;
47159
- }
47160
-
47161
47214
  // ../baro-orchestrator/src/participants/story-factory.ts
47162
47215
  var StoryFactory = class extends BaseObserver {
47163
47216
  constructor(opts) {
@@ -47347,7 +47400,8 @@ var Surgeon = class extends BaseObserver {
47347
47400
  maxReplans: opts.maxReplans ?? 10,
47348
47401
  claudeBin: opts.claudeBin ?? "claude",
47349
47402
  timeoutMs: opts.timeoutMs ?? 9e4,
47350
- snapshot: opts.snapshot
47403
+ snapshot: opts.snapshot,
47404
+ resolveRoute: opts.resolveRoute
47351
47405
  };
47352
47406
  }
47353
47407
  /** Resolves once every in-flight LLM evaluation has completed. */
@@ -47386,7 +47440,7 @@ var Surgeon = class extends BaseObserver {
47386
47440
  */
47387
47441
  async evaluateWithLlm(failure) {
47388
47442
  const snap = this.opts.snapshot();
47389
- const prompt = buildSurgeonPrompt(snap, failure);
47443
+ const prompt = buildSurgeonPrompt(snap, failure, this.opts.resolveRoute);
47390
47444
  try {
47391
47445
  const { stdout } = await execFileAsync3(
47392
47446
  this.opts.claudeBin,
@@ -47436,11 +47490,12 @@ var Surgeon = class extends BaseObserver {
47436
47490
  }
47437
47491
  }
47438
47492
  };
47439
- function buildSurgeonPrompt(snap, failure) {
47493
+ function buildSurgeonPrompt(snap, failure, resolveRoute) {
47440
47494
  const storyLines = snap.stories.map(
47441
47495
  (s2) => ` - ${s2.id} ${s2.passes ? "[passed]" : "[pending]"} ${s2.model ? `<tier:${s2.model}> ` : ""}"${s2.title}" deps=${JSON.stringify(s2.dependsOn)}`
47442
47496
  ).join("\n");
47443
47497
  const failureStory = snap.stories.find((s2) => s2.id === failure.storyId);
47498
+ const ranOn = resolveRoute ? resolveRoute(failureStory?.model) : null;
47444
47499
  return [
47445
47500
  `# Project: ${snap.project}`,
47446
47501
  `Description: ${snap.description}`,
@@ -47453,6 +47508,9 @@ function buildSurgeonPrompt(snap, failure) {
47453
47508
  `Title: ${failureStory?.title ?? "(unknown)"}`,
47454
47509
  `Description: ${failureStory?.description ?? "(unknown)"}`,
47455
47510
  `Tier that just failed: ${failureStory?.model ?? "(default)"}`,
47511
+ ...ranOn ? [
47512
+ `Model that actually ran: ${ranOn} (an override replaced the planner tier above; refer to THIS model in your reason, not the tier)`
47513
+ ] : [],
47456
47514
  `Attempts: ${failure.attempts}`,
47457
47515
  `Error: ${failure.error ?? "(no reason captured)"}`,
47458
47516
  "",
@@ -47503,7 +47561,8 @@ var SurgeonCodex = class extends BaseObserver {
47503
47561
  maxReplans: opts.maxReplans ?? 10,
47504
47562
  codexBin: opts.codexBin ?? "codex",
47505
47563
  timeoutMs: opts.timeoutMs ?? 3e5,
47506
- snapshot: opts.snapshot
47564
+ snapshot: opts.snapshot,
47565
+ resolveRoute: opts.resolveRoute
47507
47566
  };
47508
47567
  }
47509
47568
  async idle() {
@@ -47527,7 +47586,7 @@ var SurgeonCodex = class extends BaseObserver {
47527
47586
  }
47528
47587
  async evaluateWithLlm(failure) {
47529
47588
  const snap = this.opts.snapshot();
47530
- const userPrompt = buildSurgeonPrompt(snap, failure);
47589
+ const userPrompt = buildSurgeonPrompt(snap, failure, this.opts.resolveRoute);
47531
47590
  const prompt = `${SURGEON_SYSTEM_PROMPT}
47532
47591
 
47533
47592
  ${userPrompt}`;
@@ -47599,7 +47658,8 @@ var SurgeonOpenAI = class extends BaseObserver {
47599
47658
  this.opts = {
47600
47659
  maxReplans: opts.maxReplans ?? 10,
47601
47660
  model: opts.model ?? "gpt-5.5",
47602
- snapshot: opts.snapshot
47661
+ snapshot: opts.snapshot,
47662
+ resolveRoute: opts.resolveRoute
47603
47663
  };
47604
47664
  this.model = pickModel3(this.opts.model);
47605
47665
  }
@@ -47631,7 +47691,7 @@ var SurgeonOpenAI = class extends BaseObserver {
47631
47691
  */
47632
47692
  async evaluate(failure) {
47633
47693
  const snap = this.opts.snapshot();
47634
- const userPrompt = buildSurgeonPrompt(snap, failure);
47694
+ const userPrompt = buildSurgeonPrompt(snap, failure, this.opts.resolveRoute);
47635
47695
  const context = ModelContext.create("surgeon").addContextItem(SystemMessageItem.create(SURGEON_SYSTEM_PROMPT)).addContextItem(UserMessageItem.create(userPrompt));
47636
47696
  try {
47637
47697
  const round = await runInferenceRound(context, this.model);
@@ -47688,7 +47748,8 @@ var SurgeonOpenCode = class extends BaseObserver {
47688
47748
  maxReplans: opts.maxReplans ?? 10,
47689
47749
  opencodeBin: opts.opencodeBin ?? "opencode",
47690
47750
  timeoutMs: opts.timeoutMs ?? 3e5,
47691
- snapshot: opts.snapshot
47751
+ snapshot: opts.snapshot,
47752
+ resolveRoute: opts.resolveRoute
47692
47753
  };
47693
47754
  }
47694
47755
  async idle() {
@@ -47712,7 +47773,7 @@ var SurgeonOpenCode = class extends BaseObserver {
47712
47773
  }
47713
47774
  async evaluateWithLlm(failure) {
47714
47775
  const snap = this.opts.snapshot();
47715
- const userPrompt = buildSurgeonPrompt(snap, failure);
47776
+ const userPrompt = buildSurgeonPrompt(snap, failure, this.opts.resolveRoute);
47716
47777
  const prompt = `${SURGEON_SYSTEM_PROMPT}
47717
47778
 
47718
47779
  ${userPrompt}`;
@@ -47767,7 +47828,8 @@ var SurgeonPi = class extends BaseObserver {
47767
47828
  maxReplans: opts.maxReplans ?? 10,
47768
47829
  piBin: opts.piBin ?? "pi",
47769
47830
  timeoutMs: opts.timeoutMs ?? 3e5,
47770
- snapshot: opts.snapshot
47831
+ snapshot: opts.snapshot,
47832
+ resolveRoute: opts.resolveRoute
47771
47833
  };
47772
47834
  }
47773
47835
  async idle() {
@@ -47791,7 +47853,7 @@ var SurgeonPi = class extends BaseObserver {
47791
47853
  }
47792
47854
  async evaluateWithLlm(failure) {
47793
47855
  const snap = this.opts.snapshot();
47794
- const userPrompt = buildSurgeonPrompt(snap, failure);
47856
+ const userPrompt = buildSurgeonPrompt(snap, failure, this.opts.resolveRoute);
47795
47857
  const prompt = `${SURGEON_SYSTEM_PROMPT}
47796
47858
 
47797
47859
  ${userPrompt}`;
@@ -47968,32 +48030,53 @@ async function orchestrate(config) {
47968
48030
  }))
47969
48031
  };
47970
48032
  };
48033
+ const storyRouting = {
48034
+ fallbackBackend: storyLlm,
48035
+ openaiDefaultModel: config.storyModel ?? "gpt-5.5",
48036
+ override: config.storyModel,
48037
+ tierMap: config.tierMap,
48038
+ endpoints: config.openaiEndpoints,
48039
+ defaultApiKey: process.env.OPENAI_API_KEY
48040
+ };
48041
+ const routingOverridden = storyLlm !== "claude" || !!config.storyModel || !!config.tierMap;
48042
+ const resolveRoute = routingOverridden ? (model) => {
48043
+ try {
48044
+ return formatRoute(resolveStoryRoute(model, storyRouting));
48045
+ } catch {
48046
+ return null;
48047
+ }
48048
+ } : void 0;
47971
48049
  if (surgeonLlm === "openai") {
47972
48050
  surgeon = new SurgeonOpenAI({
47973
48051
  snapshot,
48052
+ resolveRoute,
47974
48053
  model: config.surgeonModel ?? "gpt-5.5"
47975
48054
  });
47976
48055
  } else if (surgeonLlm === "codex") {
47977
48056
  surgeon = new SurgeonCodex({
47978
48057
  snapshot,
48058
+ resolveRoute,
47979
48059
  useLlm: config.surgeonUseLlm ?? true,
47980
48060
  model: config.surgeonModel
47981
48061
  });
47982
48062
  } else if (surgeonLlm === "opencode") {
47983
48063
  surgeon = new SurgeonOpenCode({
47984
48064
  snapshot,
48065
+ resolveRoute,
47985
48066
  useLlm: config.surgeonUseLlm ?? true,
47986
48067
  model: config.surgeonModel
47987
48068
  });
47988
48069
  } else if (surgeonLlm === "pi") {
47989
48070
  surgeon = new SurgeonPi({
47990
48071
  snapshot,
48072
+ resolveRoute,
47991
48073
  useLlm: config.surgeonUseLlm ?? true,
47992
48074
  model: config.surgeonModel
47993
48075
  });
47994
48076
  } else {
47995
48077
  surgeon = new Surgeon({
47996
48078
  snapshot,
48079
+ resolveRoute,
47997
48080
  useLlm: config.surgeonUseLlm ?? false,
47998
48081
  model: config.surgeonModel ?? "opus"
47999
48082
  });