@koda-sl/baker-bridge 0.168.0-dev.d78500250 → 0.172.0-dev.b570aa3f2

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.
Files changed (63) hide show
  1. package/README.md +35 -19
  2. package/dist/hono/agent.d.ts.map +1 -1
  3. package/dist/hono/agent.js +3 -1
  4. package/dist/hono/agent.js.map +1 -1
  5. package/dist/hono/agent.test.js +2 -0
  6. package/dist/hono/agent.test.js.map +1 -1
  7. package/dist/hono/bridge-job.d.ts +19 -11
  8. package/dist/hono/bridge-job.d.ts.map +1 -1
  9. package/dist/hono/bridge-job.js +125 -50
  10. package/dist/hono/bridge-job.js.map +1 -1
  11. package/dist/hono/bridge-job.test.js +139 -7
  12. package/dist/hono/bridge-job.test.js.map +1 -1
  13. package/dist/hono/model-capabilities.d.ts +1 -0
  14. package/dist/hono/model-capabilities.d.ts.map +1 -1
  15. package/dist/hono/model-capabilities.js +4 -0
  16. package/dist/hono/model-capabilities.js.map +1 -1
  17. package/dist/hono/openrouter-session.d.ts +3 -0
  18. package/dist/hono/openrouter-session.d.ts.map +1 -1
  19. package/dist/hono/openrouter-session.js +3 -0
  20. package/dist/hono/openrouter-session.js.map +1 -1
  21. package/dist/hono/relay.d.ts +4 -4
  22. package/dist/hono/relay.d.ts.map +1 -1
  23. package/dist/hono/relay.js +1 -1
  24. package/dist/hono/relay.js.map +1 -1
  25. package/dist/hono/relay.test.js +10 -4
  26. package/dist/hono/relay.test.js.map +1 -1
  27. package/dist/hono/served-model.d.ts +8 -0
  28. package/dist/hono/served-model.d.ts.map +1 -1
  29. package/dist/hono/served-model.js +41 -0
  30. package/dist/hono/served-model.js.map +1 -1
  31. package/dist/hono/served-model.test.js +19 -1
  32. package/dist/hono/served-model.test.js.map +1 -1
  33. package/dist/hono/session-continuity.integration.test.js +2 -0
  34. package/dist/hono/session-continuity.integration.test.js.map +1 -1
  35. package/dist/hono/subagent-route.d.ts +64 -0
  36. package/dist/hono/subagent-route.d.ts.map +1 -0
  37. package/dist/hono/subagent-route.js +147 -0
  38. package/dist/hono/subagent-route.js.map +1 -0
  39. package/dist/hono/subagent-route.test.d.ts +2 -0
  40. package/dist/hono/subagent-route.test.d.ts.map +1 -0
  41. package/dist/hono/subagent-route.test.js +68 -0
  42. package/dist/hono/subagent-route.test.js.map +1 -0
  43. package/dist/hono/tool-result-ledger.d.ts +2 -0
  44. package/dist/hono/tool-result-ledger.d.ts.map +1 -1
  45. package/dist/hono/tool-result-ledger.js +12 -8
  46. package/dist/hono/tool-result-ledger.js.map +1 -1
  47. package/dist/hono/turn-route.d.ts +84 -0
  48. package/dist/hono/turn-route.d.ts.map +1 -0
  49. package/dist/hono/turn-route.js +121 -0
  50. package/dist/hono/turn-route.js.map +1 -0
  51. package/dist/hono/turn-route.test.d.ts +2 -0
  52. package/dist/hono/turn-route.test.d.ts.map +1 -0
  53. package/dist/hono/turn-route.test.js +134 -0
  54. package/dist/hono/turn-route.test.js.map +1 -0
  55. package/package.json +2 -1
  56. package/dist/hono/effort-route.d.ts +0 -43
  57. package/dist/hono/effort-route.d.ts.map +0 -1
  58. package/dist/hono/effort-route.js +0 -36
  59. package/dist/hono/effort-route.js.map +0 -1
  60. package/dist/hono/effort-route.test.d.ts +0 -2
  61. package/dist/hono/effort-route.test.d.ts.map +0 -1
  62. package/dist/hono/effort-route.test.js +0 -54
  63. package/dist/hono/effort-route.test.js.map +0 -1
@@ -262,8 +262,9 @@ function createExecutor(options) {
262
262
  ...(options.catchUpWithMain ? { catchUpWithMain: options.catchUpWithMain } : {}),
263
263
  ...(options.keepCliInStep ? { keepCliInStep: options.keepCliInStep } : {}),
264
264
  ...(options.holdsNoWork ? { holdsNoWork: options.holdsNoWork } : {}),
265
- // Never the network: a turn nobody judges runs at the default effort.
266
- effortJudge: options.effortJudge ?? (async () => null),
265
+ // Never the network: a turn nobody judges runs on the default model and effort.
266
+ routeJudge: options.routeJudge ?? (async () => null),
267
+ ...(options.routeModels ? { routeModels: true } : {}),
267
268
  });
268
269
  }
269
270
  function memoryInFlightTurns(initial) {
@@ -1345,6 +1346,17 @@ describe("BridgeJobExecutor", () => {
1345
1346
  await executor.execute({ threadId: "thread_bedrock", turnId: "turn_bedrock", prompt: "Build a landing", history: history([]) }, activeControls());
1346
1347
  expect(queryInputs[0]?.options.env).toMatchObject({ CLAUDE_CODE_MODEL_CAPABILITIES: "per_turn_effort" });
1347
1348
  });
1349
+ // The CLI names each session with a structured-output request OpenRouter's Bedrock
1350
+ // cannot serve, so it went to Azure on OpenRouter credits; nothing reads the name.
1351
+ it("never sends the CLI's session-title request", async () => {
1352
+ const queryInputs = [];
1353
+ const executor = createExecutor({
1354
+ queryInputs,
1355
+ queryResults: [scriptedMessages(resultMessage(0, "session_untitled"))],
1356
+ });
1357
+ await executor.execute({ threadId: "thread_untitled", turnId: "turn_untitled", prompt: "Build a landing", history: history([]) }, activeControls());
1358
+ expect(queryInputs[0]?.options.env).toMatchObject({ CLAUDE_CODE_DISABLE_TERMINAL_TITLE: "1" });
1359
+ });
1348
1360
  it("silences the CLI task reminders and ANSI colour in the agent subprocess", async () => {
1349
1361
  const queryInputs = [];
1350
1362
  const executor = createExecutor({
@@ -1483,6 +1495,78 @@ describe("BridgeJobExecutor", () => {
1483
1495
  expect(options.env?.CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD).toBe("1");
1484
1496
  expect(await readFile(join(instructionsDirMock.value, "CLAUDE.md"), "utf8")).toBe(`@${join(workspace, "COMPANY.md")}\n`);
1485
1497
  });
1498
+ // Excluding CLAUDE.md from the memory files took it from every subagent that
1499
+ // loaded it there too; measured through the real CLI, `general-purpose` and
1500
+ // `research` lost it while still reading COMPANY.md.
1501
+ it("gives the shared instructions back to the subagents that loaded CLAUDE.md, and only to them", async () => {
1502
+ const workspace = await mkdtemp(join(tmpdir(), "baker-job-subagent-instructions-"));
1503
+ await writeFile(join(workspace, "CLAUDE.md"), "# Project Instructions\n\n## Client Context\n\nOne client per repo. Everything known about them:\n\n@COMPANY.md\n\n## Rules\n\nStage, don't ask.\n");
1504
+ await mkdir(join(workspace, ".claude", "agents"), { recursive: true });
1505
+ await writeFile(join(workspace, ".claude", "agents", "claim-checker.md"), "---\nname: claim-checker\nmodel: claude-sonnet-5-5\nomitClaudeMd: true\n---\nCheck claims.\n");
1506
+ projectRootMock.value = workspace;
1507
+ const queryInputs = [];
1508
+ const executor = createExecutor({
1509
+ queryInputs,
1510
+ queryResults: [scriptedMessages(resultMessage(0, "session_subagent_instructions"))],
1511
+ });
1512
+ await executor.execute({ threadId: "thread_sub_instr", turnId: "turn_sub_instr", prompt: "Build a landing", history: history([]) }, activeControls());
1513
+ const start = queryInputs[0]?.options.hooks?.SubagentStart?.[0]?.hooks[0];
1514
+ const contextFor = async (agentType) => (await start?.({
1515
+ hook_event_name: "SubagentStart",
1516
+ agent_type: agentType,
1517
+ agent_id: "a",
1518
+ session_id: "s",
1519
+ transcript_path: "",
1520
+ cwd: "",
1521
+ }, undefined, { signal: new AbortController().signal }))?.hookSpecificOutput?.additionalContext;
1522
+ expect(await contextFor("general-purpose")).toContain("Stage, don't ask.");
1523
+ expect(await contextFor("general-purpose")).not.toContain("@COMPANY.md");
1524
+ expect(await contextFor("claim-checker")).toBeUndefined();
1525
+ expect(await contextFor("fork")).toBeUndefined();
1526
+ });
1527
+ it("moves a built-in subagent that only reads to Sonnet, and records it with the turn", async () => {
1528
+ const queryInputs = [];
1529
+ let spawned;
1530
+ const executor = createExecutor({
1531
+ queryInputs,
1532
+ model: "claude-opus-5-5",
1533
+ routeModels: true,
1534
+ // The turn: finished work, so Opus. The spawn: only reading.
1535
+ routeJudge: (questions) => Promise.resolve(String(questions[0]?.instructions.message).includes("Check which step")
1536
+ ? [0.2, 0.8, 0.9, 0.1]
1537
+ : [0.1, 0.9, 0.1, 0.9]),
1538
+ queryResults: [
1539
+ {
1540
+ async *[Symbol.asyncIterator]() {
1541
+ const preToolUse = queryInputs[0]?.options.hooks?.PreToolUse?.find((m) => m.matcher === "Agent")?.hooks[0];
1542
+ const result = (await preToolUse?.({
1543
+ hook_event_name: "PreToolUse",
1544
+ tool_name: "Agent",
1545
+ tool_input: {
1546
+ subagent_type: "general-purpose",
1547
+ description: "Walk the flow",
1548
+ prompt: "Check which step fires the conversion.",
1549
+ },
1550
+ tool_use_id: "toolu_1",
1551
+ session_id: "s",
1552
+ transcript_path: "",
1553
+ cwd: "",
1554
+ }, "toolu_1", { signal: new AbortController().signal }));
1555
+ spawned = result.hookSpecificOutput?.updatedInput;
1556
+ yield resultMessage(0, "session_subagent_route");
1557
+ },
1558
+ },
1559
+ ],
1560
+ });
1561
+ const outcome = await executor.execute({ threadId: "thread_sub_route", turnId: "turn_sub_route", prompt: "Rebuild the landing", history: history([]) }, activeControls());
1562
+ expect(spawned).toMatchObject({ subagent_type: "general-purpose", model: "sonnet", effort: "high" });
1563
+ expect(outcome).toMatchObject({
1564
+ turnRoute: {
1565
+ model: "claude-opus-5-5",
1566
+ subagents: [{ agentType: "general-purpose", model: "sonnet", effort: "high", reason: "read_only" }],
1567
+ },
1568
+ });
1569
+ });
1486
1570
  it("lets the CLI load a CLAUDE.md it cannot split, exactly as before", async () => {
1487
1571
  const workspace = await mkdtemp(join(tmpdir(), "baker-job-instructions-old-"));
1488
1572
  await writeFile(join(workspace, "CLAUDE.md"), "# Project Instructions\n\nNo company import.\n");
@@ -1700,9 +1784,11 @@ describe("BridgeJobExecutor", () => {
1700
1784
  const asked = [];
1701
1785
  const executor = createExecutor({
1702
1786
  queryInputs,
1703
- effortJudge: async (questions) => {
1787
+ model: "claude-opus-5-5",
1788
+ // small, complex, read-only, deliverable
1789
+ routeJudge: (questions) => {
1704
1790
  asked.push(...questions);
1705
- return [0.92, 0.05];
1791
+ return Promise.resolve([0.92, 0.05, 0.6, 0.1]);
1706
1792
  },
1707
1793
  queryResults: [scriptedMessages(resultMessage(0, "session_effort_low"))],
1708
1794
  });
@@ -1721,17 +1807,63 @@ describe("BridgeJobExecutor", () => {
1721
1807
  });
1722
1808
  expect(outcome).toMatchObject({
1723
1809
  kind: "completed",
1724
- effortDecision: { effort: "low", reason: "small", small: 0.92, complex: 0.05 },
1810
+ turnRoute: { model: "claude-opus-5-5", modelReason: "fixed", effort: "low", reason: "small" },
1811
+ });
1812
+ });
1813
+ it("starts a chat that only reads on Sonnet, thinking harder, when the Runtime routes models", async () => {
1814
+ const queryInputs = [];
1815
+ const executor = createExecutor({
1816
+ queryInputs,
1817
+ model: "claude-opus-5-5",
1818
+ routeModels: true,
1819
+ routeJudge: () => Promise.resolve([0.2, 0.9, 0.85, 0.1]),
1820
+ queryResults: [scriptedMessages(resultMessage(0, "session_reading"))],
1725
1821
  });
1822
+ const outcome = await executor.execute({ threadId: "thread_reading", turnId: "turn_reading", prompt: "¿Cómo van los anuncios?", history: history([]) }, activeControls());
1823
+ expect(queryInputs[0]?.options).toMatchObject({ model: "claude-sonnet-5-5", effort: "high" });
1824
+ expect(outcome).toMatchObject({
1825
+ model: "claude-sonnet-5-5",
1826
+ turnRoute: { model: "claude-sonnet-5-5", modelReason: "read_only", effort: "high" },
1827
+ });
1828
+ });
1829
+ it("moves a chat that ran on Sonnet to Opus once a turn asks for finished work", async () => {
1830
+ const configDir = await mkdtemp(join(tmpdir(), "bridge-turn-route-"));
1831
+ try {
1832
+ await mkdir(join(configDir, "projects", "-home-user-repo"), { recursive: true });
1833
+ await writeFile(join(configDir, "projects", "-home-user-repo", "session_sonnet.jsonl"), `${JSON.stringify({ type: "assistant", requestedModel: "claude-sonnet-5-5", message: { id: "m", model: "claude-sonnet-5-5", content: [] } })}\n`);
1834
+ vi.stubEnv("CLAUDE_CONFIG_DIR", configDir);
1835
+ const queryInputs = [];
1836
+ const executor = createExecutor({
1837
+ queryInputs,
1838
+ model: "claude-opus-5-5",
1839
+ routeModels: true,
1840
+ loadedBoundary: cleanBoundary("session_sonnet"),
1841
+ routeJudge: () => Promise.resolve([0.1, 0.95, 0.2, 0.9]),
1842
+ queryResults: [scriptedMessages(resultMessage(0, "session_sonnet"))],
1843
+ });
1844
+ await executor.execute({
1845
+ threadId: "thread_upgrade",
1846
+ turnId: "turn_upgrade",
1847
+ prompt: "Perfecto, créame una landing con esto",
1848
+ history: history([
1849
+ { role: "user", text: "¿Cómo van los anuncios?", createdAt: 1 },
1850
+ { role: "assistant", text: "Van bien: el CPL bajó un 12%.", createdAt: 2 },
1851
+ ]),
1852
+ }, activeControls());
1853
+ expect(queryInputs[0]?.options).toMatchObject({ model: "claude-opus-5-5", effort: "medium" });
1854
+ }
1855
+ finally {
1856
+ await rm(configDir, { recursive: true, force: true });
1857
+ }
1726
1858
  });
1727
1859
  it("keeps a plan-mode turn at medium without asking Jev", async () => {
1728
1860
  const queryInputs = [];
1729
1861
  let asked = false;
1730
1862
  const executor = createExecutor({
1731
1863
  queryInputs,
1732
- effortJudge: async () => {
1864
+ routeJudge: () => {
1733
1865
  asked = true;
1734
- return [0.99, 0];
1866
+ return Promise.resolve([0.99, 0, 1, 0]);
1735
1867
  },
1736
1868
  queryResults: [scriptedMessages(resultMessage(0, "session_effort_plan"))],
1737
1869
  });