@koda-sl/baker-bridge 0.165.0 → 0.171.0-dev.289ff1b9d

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 (67) hide show
  1. package/README.md +49 -21
  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 +23 -0
  8. package/dist/hono/bridge-job.d.ts.map +1 -1
  9. package/dist/hono/bridge-job.js +160 -25
  10. package/dist/hono/bridge-job.js.map +1 -1
  11. package/dist/hono/bridge-job.test.js +233 -13
  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 +19 -13
  18. package/dist/hono/openrouter-session.d.ts.map +1 -1
  19. package/dist/hono/openrouter-session.js +19 -13
  20. package/dist/hono/openrouter-session.js.map +1 -1
  21. package/dist/hono/relay.d.ts +8 -0
  22. package/dist/hono/relay.d.ts.map +1 -1
  23. package/dist/hono/relay.js +2 -0
  24. package/dist/hono/relay.js.map +1 -1
  25. package/dist/hono/relay.test.js +30 -0
  26. package/dist/hono/relay.test.js.map +1 -1
  27. package/dist/hono/runtime-files.d.ts +1 -0
  28. package/dist/hono/runtime-files.d.ts.map +1 -1
  29. package/dist/hono/runtime-files.js +3 -0
  30. package/dist/hono/runtime-files.js.map +1 -1
  31. package/dist/hono/served-model.d.ts +8 -0
  32. package/dist/hono/served-model.d.ts.map +1 -1
  33. package/dist/hono/served-model.js +41 -0
  34. package/dist/hono/served-model.js.map +1 -1
  35. package/dist/hono/served-model.test.js +19 -1
  36. package/dist/hono/served-model.test.js.map +1 -1
  37. package/dist/hono/session-continuity.integration.test.js +2 -0
  38. package/dist/hono/session-continuity.integration.test.js.map +1 -1
  39. package/dist/hono/tool-result-ledger.d.ts +32 -0
  40. package/dist/hono/tool-result-ledger.d.ts.map +1 -0
  41. package/dist/hono/tool-result-ledger.js +67 -0
  42. package/dist/hono/tool-result-ledger.js.map +1 -0
  43. package/dist/hono/tool-result-ledger.test.d.ts +2 -0
  44. package/dist/hono/tool-result-ledger.test.d.ts.map +1 -0
  45. package/dist/hono/tool-result-ledger.test.js +61 -0
  46. package/dist/hono/tool-result-ledger.test.js.map +1 -0
  47. package/dist/hono/turn-route.d.ts +71 -0
  48. package/dist/hono/turn-route.d.ts.map +1 -0
  49. package/dist/hono/turn-route.js +112 -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/dist/hono/workspace-instructions.d.ts +43 -0
  56. package/dist/hono/workspace-instructions.d.ts.map +1 -0
  57. package/dist/hono/workspace-instructions.js +68 -0
  58. package/dist/hono/workspace-instructions.js.map +1 -0
  59. package/dist/hono/workspace-instructions.test.d.ts +2 -0
  60. package/dist/hono/workspace-instructions.test.d.ts.map +1 -0
  61. package/dist/hono/workspace-instructions.test.js +68 -0
  62. package/dist/hono/workspace-instructions.test.js.map +1 -0
  63. package/dist/review/digest.d.ts +13 -0
  64. package/dist/review/digest.d.ts.map +1 -1
  65. package/dist/review/digest.js +1 -1
  66. package/dist/review/digest.js.map +1 -1
  67. package/package.json +1 -1
@@ -23,11 +23,11 @@ import { composioScopesToInvalidate, failedCustomServers, reportMcpInvalid, serv
23
23
  import { agentModelOverride, autoCompactWindowForModel, betasForModel, contextWindowForModel, } from "./model-capabilities.js";
24
24
  import { agentShellPrefixEnv, killFirstWrapper } from "./oom-priority.js";
25
25
  import { anthropicCustomHeaders, runtimeOpenRouterTags } from "./openrouter-attribution.js";
26
- import { BEDROCK_SERVABLE_CAPABILITIES, buildAgentExtraBody, EXTRA_BODY_ENV, MODEL_CAPABILITIES_ENV, } from "./openrouter-session.js";
26
+ import { buildAgentExtraBody, EXTRA_BODY_ENV, MODEL_CAPABILITIES_ENV, PER_TURN_EFFORT_CAPABILITIES, } from "./openrouter-session.js";
27
27
  import { credentialReminder, currentReferenceReminder, kickoffBriefReminder } from "./prompt-context.js";
28
28
  import { publishLanguageReminder, publishRunReminder, publishStandingReminder } from "./publish-handoff.js";
29
29
  import { bridgeRuntimeFiles } from "./runtime-files.js";
30
- import { alignServedModels } from "./served-model.js";
30
+ import { alignServedModels, readConversationModel } from "./served-model.js";
31
31
  import { applyCurrentSenderAttribution, buildCancelBoundary, buildCleanBoundary, buildRehydratedPrompt, compactionRetryAttempt, emptyInProgressSession, interruptedResumeAttempt, isCliResumeRefusal, isContextOverflowError, isResumeSessionError, observeInProgressSession, prepareSessionAttempt, stallContinuationAttempt, } from "./session-continuity.js";
32
32
  import { readSessionSkills } from "./session-skills.js";
33
33
  import { ownQueryUsage, readUsageBaseline } from "./session-usage-baseline.js";
@@ -35,8 +35,11 @@ import { skillLanguageHooks } from "./skill-language.js";
35
35
  import { BridgeSlashCommands, leadingSkillCommand, verbatimSlashCommand } from "./slash-commands.js";
36
36
  import { createBakerUiServer, normalizeTagInputToolInput, sanitizeTagChanges, TAG_INPUT_TOOL_NAME, unknownPrefillMessage, } from "./tag-input.js";
37
37
  import { blockingToolDelegated, toolRunAsCommand } from "./tool-in-shell.js";
38
+ import { ToolResultLedger } from "./tool-result-ledger.js";
38
39
  import { recordTurnEvent } from "./turn-events.js";
39
40
  import { addQueryModelUsage, queryModelUsage, turnModelUsageList } from "./turn-model-usage.js";
41
+ import { askTurnSignals, needsSignals, routeTurn, } from "./turn-route.js";
42
+ import { prepareSharedInstructions } from "./workspace-instructions.js";
40
43
  import { settleWorkspaceAfterTasks } from "./workspace-settle.js";
41
44
  export function parsePlanDecision(value) {
42
45
  if (!value || typeof value !== "object") {
@@ -217,6 +220,13 @@ const BASE_DISALLOWED_TOOLS = [
217
220
  // not asked to fund a research fleet. Ordinary fan-out is unaffected:
218
221
  // `Task` stays, so the scaffold's subagents still run.
219
222
  "Workflow",
223
+ // Developer tools: design-system sync, code-review findings, Jupyter edits.
224
+ // None was called once in 358 production chats (Oct 2026), and each
225
+ // definition rides every request whenever the CLI does not defer it —
226
+ // DesignSync's alone is ~9k characters.
227
+ "DesignSync",
228
+ "ReportFindings",
229
+ "NotebookEdit",
220
230
  // NOT listed: `SendMessage`. It is how the model follows up with a
221
231
  // subagent it already ran, and the CLI tells it to on every Agent result.
222
232
  // The continued subagent runs in the background, but it announces itself
@@ -245,7 +255,7 @@ function disallowedToolsFor(job) {
245
255
  function modelForJob(job, sessionModel) {
246
256
  return job.review?.model ?? sessionModel;
247
257
  }
248
- export const REVIEW_DISALLOWED_TOOLS = ["Edit", "Write", "MultiEdit", "NotebookEdit", "ExitPlanMode", "Skill"];
258
+ export const REVIEW_DISALLOWED_TOOLS = ["Edit", "Write", "MultiEdit", "ExitPlanMode", "Skill"];
249
259
  /**
250
260
  * The findings contract, enforced by the SDK rather than parsed back out of
251
261
  * prose. `structured_output` on the result message is what reaches Convex, and
@@ -432,6 +442,19 @@ function createBridgeJobState() {
432
442
  detachedSession: false,
433
443
  modelUsageTotals: new Map(),
434
444
  queryModelUsage: new Map(),
445
+ toolResults: new ToolResultLedger(),
446
+ };
447
+ }
448
+ /** What a finished turn reports about its spend: per-model usage and per-tool result sizes. */
449
+ function turnUsage(state) {
450
+ const toolResults = state.toolResults.summary();
451
+ return {
452
+ modelUsage: turnModelUsageList(state.modelUsageTotals),
453
+ ...(toolResults.length > 0 ? { toolResults } : {}),
454
+ // Left off when nothing was decided: Jev never answered and the model is the Runtime's.
455
+ ...(state.turnRoute && !(state.turnRoute.reason === "unjudged" && state.turnRoute.modelReason === "fixed")
456
+ ? { turnRoute: state.turnRoute }
457
+ : {}),
435
458
  };
436
459
  }
437
460
  function errorMessage(error) {
@@ -508,6 +531,66 @@ function hookSettingsForJob(job) {
508
531
  }
509
532
  return {};
510
533
  }
534
+ /**
535
+ * Which instruction files a job loads as memory. A Session review loads none:
536
+ * its brief has it read the rulebook as the Session had it (`git show
537
+ * <sha>:CLAUDE.md`), since Publish may already have moved the working tree past
538
+ * it, and the auto-loaded copy is ~24k tokens of the marketer's instructions
539
+ * written cold and re-read on every review request. The client's agent loads
540
+ * the workspace's `CLAUDE.md` itself only when it could not be split into the
541
+ * shared system prompt (`prepareSharedInstructions`).
542
+ */
543
+ /** A Session review reads no CLAUDE.md, so it has nothing to share. */
544
+ async function sharedInstructionsFor(job) {
545
+ if (job.review) {
546
+ return null;
547
+ }
548
+ return await prepareSharedInstructions({
549
+ projectRoot: bridgeRuntimeFiles.projectRoot(),
550
+ contextDir: bridgeRuntimeFiles.instructionsDir(),
551
+ });
552
+ }
553
+ function sharedInstructionOptions(sharedInstructions) {
554
+ if (!sharedInstructions) {
555
+ return {};
556
+ }
557
+ return {
558
+ systemPrompt: {
559
+ type: "preset",
560
+ preset: "claude_code",
561
+ append: sharedInstructions.shared,
562
+ // Recorded once per conversation, so a scaffold sync mid-chat cannot
563
+ // move the cached prefix under it; it lands at the next compaction.
564
+ snapshot: true,
565
+ },
566
+ additionalDirectories: [bridgeRuntimeFiles.instructionsDir()],
567
+ };
568
+ }
569
+ function bridgeJudge(feature) {
570
+ return jevJudge(process.env.OPENROUTER_API_KEY, runtimeOpenRouterTags(process.env, { service: "bridge", feature }));
571
+ }
572
+ function judgesOf(dependencies) {
573
+ return {
574
+ judge: dependencies.judge ?? bridgeJudge("bridge.toolCallGuard"),
575
+ routeJudge: dependencies.routeJudge ?? bridgeJudge("bridge.turnRoute"),
576
+ };
577
+ }
578
+ function sharedInstructionEnv(sharedInstructions) {
579
+ return sharedInstructions ? { CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD: "1" } : {};
580
+ }
581
+ function turnEffortOf(state) {
582
+ return state.turnRoute?.effort ?? "medium";
583
+ }
584
+ /** The agent's last message in the chat, empty on its first turn. */
585
+ function previousReplyOf(job) {
586
+ return (job.history?.entries ?? []).filter((entry) => entry.role === "assistant").at(-1)?.text ?? "";
587
+ }
588
+ function instructionSettingsForJob(job, sharedInstructions) {
589
+ if (job.review) {
590
+ return { claudeMdExcludes: ["**/CLAUDE.md"] };
591
+ }
592
+ return sharedInstructions ? { claudeMdExcludes: [join(bridgeRuntimeFiles.projectRoot(), "CLAUDE.md")] } : {};
593
+ }
511
594
  // Carry a Runtime-wide model override down to the fan-out workers.
512
595
  //
513
596
  // The shared subagents pin themselves to Sonnet 5.5 in their own frontmatter (see
@@ -1169,6 +1252,8 @@ export class BridgeJobExecutor {
1169
1252
  holdsNoWork;
1170
1253
  keepCliInStep;
1171
1254
  judge;
1255
+ routeJudge;
1256
+ routeModels;
1172
1257
  constructor(dependencies) {
1173
1258
  this.sdk = dependencies.sdk;
1174
1259
  this.relay = dependencies.relay;
@@ -1194,9 +1279,8 @@ export class BridgeJobExecutor {
1194
1279
  this.keepCliInStep = dependencies.keepCliInStep;
1195
1280
  this.readFlowTree =
1196
1281
  dependencies.readFlowTree ?? ((flowSlug) => readRawFlowTree(bridgeRuntimeFiles.flowsDir(), flowSlug));
1197
- this.judge =
1198
- dependencies.judge ??
1199
- jevJudge(process.env.OPENROUTER_API_KEY, runtimeOpenRouterTags(process.env, { service: "bridge", feature: "bridge.toolCallGuard" }));
1282
+ ({ judge: this.judge, routeJudge: this.routeJudge } = judgesOf(dependencies));
1283
+ this.routeModels = dependencies.routeModels === true;
1200
1284
  }
1201
1285
  async execute(job, controls) {
1202
1286
  try {
@@ -1208,6 +1292,45 @@ export class BridgeJobExecutor {
1208
1292
  await this.inFlightTurns.clear(job.threadId, job.turnId).catch(() => undefined);
1209
1293
  }
1210
1294
  }
1295
+ /**
1296
+ * What Jev reads in this turn, asked while the boundary loads (~0.3 s). Null
1297
+ * for a Session review, which keeps its own model and effort, and for a turn
1298
+ * that decides without it. Never throws: an unanswered judge is no signal.
1299
+ */
1300
+ async askSignals(job, request) {
1301
+ if (job.review || !needsSignals(this.turnFacts(job, request))) {
1302
+ return null;
1303
+ }
1304
+ return await askTurnSignals({
1305
+ message: job.prompt,
1306
+ previousReply: previousReplyOf(job),
1307
+ judge: this.routeJudge,
1308
+ }).catch(() => null);
1309
+ }
1310
+ /** This turn's model and effort (see `turn-route.ts`); undefined for a Session review. */
1311
+ async routeFor(job, request, attempt, signals) {
1312
+ if (job.review) {
1313
+ return undefined;
1314
+ }
1315
+ const resumed = attempt.resumeOptions.resume;
1316
+ const sessionModel = resumed ? await readConversationModel(resumed, claudeConfigDir()) : undefined;
1317
+ return routeTurn({
1318
+ ...this.turnFacts(job, request),
1319
+ signals: await signals,
1320
+ defaultModel: this.model,
1321
+ routeModels: this.routeModels,
1322
+ ...(sessionModel ? { sessionModel } : {}),
1323
+ startedChat: previousReplyOf(job) === "",
1324
+ });
1325
+ }
1326
+ turnFacts(job, request) {
1327
+ return {
1328
+ planMode: job.planMode === true,
1329
+ leadingCommand: request.leadingCommand,
1330
+ unattended: job.unattended === true,
1331
+ hasAttachments: (job.attachments?.length ?? 0) > 0,
1332
+ };
1333
+ }
1211
1334
  async executeTurn(job, controls) {
1212
1335
  const state = createBridgeJobState();
1213
1336
  let result = { costUsd: 0, attemptedRehydration: false };
@@ -1219,6 +1342,8 @@ export class BridgeJobExecutor {
1219
1342
  if (!request?.prompt || !controls.isActive()) {
1220
1343
  return { kind: "cancelled", costUsd: 0, model: state.model };
1221
1344
  }
1345
+ // Asked while the boundary loads, so the answer (~0.3 s) costs the turn no time.
1346
+ const signals = this.askSignals(job, request);
1222
1347
  const loaded = await this.loadTrustedBoundary(job, controls);
1223
1348
  if (loaded?.kind !== "clean") {
1224
1349
  request = await this.withContinuedAttachments(job, request);
@@ -1237,6 +1362,7 @@ export class BridgeJobExecutor {
1237
1362
  state.detachedSession = attempt.detachedSession === true;
1238
1363
  result.attemptedRehydration = attempt.attemptedRehydration;
1239
1364
  this.recordAttemptContextSource(job, attempt);
1365
+ state.turnRoute = await this.routeFor(job, request, attempt, signals);
1240
1366
  result = await this.runAttempt(job, controls, state, attempt);
1241
1367
  return await this.finishSuccessfulAttempt(job, controls, state, result.costUsd);
1242
1368
  }
@@ -1245,6 +1371,8 @@ export class BridgeJobExecutor {
1245
1371
  if (repaired && controls.isActive()) {
1246
1372
  const repairState = createBridgeJobState();
1247
1373
  repairState.detachedSession = repaired.detachedSession === true;
1374
+ // The same turn, so the same model and level: a change would re-write the cache.
1375
+ repairState.turnRoute = state.turnRoute;
1248
1376
  let repairResult = { costUsd: 0, attemptedRehydration: repaired.attemptedRehydration };
1249
1377
  try {
1250
1378
  repairResult = await this.runAttempt(job, controls, repairState, repaired);
@@ -1394,7 +1522,7 @@ export class BridgeJobExecutor {
1394
1522
  return `${block}\n\n${job.prompt}`;
1395
1523
  }
1396
1524
  async runAttempt(job, controls, state, attempt) {
1397
- state.model = this.model;
1525
+ state.model = state.turnRoute?.model ?? this.model;
1398
1526
  // A stalled stream closes the wedged query and resumes the CLI session in
1399
1527
  // place — the turn survives a hung upstream request instead of dying at the
1400
1528
  // 30-minute session watchdog with all mid-turn work stranded.
@@ -1402,7 +1530,7 @@ export class BridgeJobExecutor {
1402
1530
  let current = attempt;
1403
1531
  for (let stallRetries = 0;;) {
1404
1532
  try {
1405
- costUsd += await this.runQueryOnce(job, controls, state, current, this.model);
1533
+ costUsd += await this.runQueryOnce(job, controls, state, current, state.model);
1406
1534
  break;
1407
1535
  }
1408
1536
  catch (error) {
@@ -1447,22 +1575,22 @@ export class BridgeJobExecutor {
1447
1575
  return { costUsd, attemptedRehydration: attempt.attemptedRehydration };
1448
1576
  }
1449
1577
  async finishSuccessfulAttempt(job, controls, state, costUsd) {
1450
- const modelUsage = turnModelUsageList(state.modelUsageTotals);
1578
+ const usage = turnUsage(state);
1451
1579
  if (!controls.isActive()) {
1452
1580
  await this.clearIfDirty(job, controls, state, "abort");
1453
- return { kind: "cancelled", costUsd, model: state.model, modelUsage };
1581
+ return { kind: "cancelled", costUsd, model: state.model, ...usage };
1454
1582
  }
1455
1583
  // No boundary is handed back, so the refused exchange never becomes part of
1456
1584
  // the conversation the next message resumes.
1457
1585
  if (state.refusedWithoutFallback) {
1458
- return { kind: "errored", costUsd, model: state.model, modelUsage, errors: [MODEL_REFUSAL_MESSAGE] };
1586
+ return { kind: "errored", costUsd, model: state.model, ...usage, errors: [MODEL_REFUSAL_MESSAGE] };
1459
1587
  }
1460
1588
  if (!state.postedAssistantEvent && state.turnText.length > 0) {
1461
1589
  await this.postSyntheticAssistantEvent(job, state);
1462
1590
  }
1463
1591
  if (!controls.isActive()) {
1464
1592
  await this.clearIfDirty(job, controls, state, "abort");
1465
- return { kind: "cancelled", costUsd, model: state.model, modelUsage };
1593
+ return { kind: "cancelled", costUsd, model: state.model, ...usage };
1466
1594
  }
1467
1595
  // A detached turn (publish) ran in a session of its own. Handing that session
1468
1596
  // back as the thread's boundary would make the next chat message resume a
@@ -1479,7 +1607,7 @@ export class BridgeJobExecutor {
1479
1607
  kind: "completed",
1480
1608
  costUsd,
1481
1609
  model: state.model,
1482
- modelUsage,
1610
+ ...usage,
1483
1611
  ...(workspaceUnchanged ? { workspaceUnchanged: true } : {}),
1484
1612
  cleanBoundary,
1485
1613
  ...(state.structuredOutput === undefined ? {} : { structuredOutput: state.structuredOutput }),
@@ -1487,7 +1615,7 @@ export class BridgeJobExecutor {
1487
1615
  }
1488
1616
  errorOutcome(job, controls, state, result, error) {
1489
1617
  const model = state.model;
1490
- const modelUsage = turnModelUsageList(state.modelUsageTotals);
1618
+ const usage = turnUsage(state);
1491
1619
  if (result.attemptedRehydration) {
1492
1620
  void this.relay.contextSource({ threadId: job.threadId, turnId: job.turnId }, "rehydration_failed", {
1493
1621
  historyEntryCount: job.history?.entries.length ?? 0,
@@ -1497,7 +1625,7 @@ export class BridgeJobExecutor {
1497
1625
  });
1498
1626
  }
1499
1627
  if (!controls.isActive()) {
1500
- return { kind: "cancelled", costUsd: result.costUsd, model, modelUsage };
1628
+ return { kind: "cancelled", costUsd: result.costUsd, model, ...usage };
1501
1629
  }
1502
1630
  const shown = userFacingTurnError(error);
1503
1631
  if (shown !== errorMessage(error)) {
@@ -1507,7 +1635,7 @@ export class BridgeJobExecutor {
1507
1635
  kind: "errored",
1508
1636
  costUsd: result.costUsd,
1509
1637
  model,
1510
- modelUsage,
1638
+ ...usage,
1511
1639
  errors: [shown],
1512
1640
  };
1513
1641
  }
@@ -1782,6 +1910,7 @@ export class BridgeJobExecutor {
1782
1910
  await this.alignResumedTranscript(attempt);
1783
1911
  const mcpServers = await this.resolveMcpServers(job);
1784
1912
  const skills = await readSessionSkills(bridgeRuntimeFiles.projectRoot());
1913
+ const sharedInstructions = await sharedInstructionsFor(job);
1785
1914
  const autoCompactWindow = autoCompactWindowForModel(model);
1786
1915
  const betas = betasForModel(model);
1787
1916
  // No `outputStyle`: the built-in `Concise` style was pinned here against Opus 5's
@@ -1817,6 +1946,7 @@ export class BridgeJobExecutor {
1817
1946
  }
1818
1947
  : {}),
1819
1948
  ...hookSettingsForJob(job),
1949
+ ...instructionSettingsForJob(job, sharedInstructions),
1820
1950
  };
1821
1951
  const agentTags = runtimeOpenRouterTags(process.env, {
1822
1952
  service: "agent",
@@ -1836,15 +1966,17 @@ export class BridgeJobExecutor {
1836
1966
  // more per level than Opus 5, so compare `low` on real Sessions before
1837
1967
  // treating this as tuned.
1838
1968
  //
1839
- // MUST stay a single constant. The resolved effort is rendered into the
1840
- // prompt, so changing it between requests invalidates every cache
1841
- // breakpoint above it — on a session carrying hundreds of thousands of
1842
- // tokens that re-write costs far more than any per-turn tuning saves.
1843
- // Vary it per message only once the route accepts per-message effort
1844
- // (see `BEDROCK_SERVABLE_CAPABILITIES`); prose is no substitute.
1845
- effort: "medium",
1969
+ // A changed top-level effort alone re-writes the cached conversation from
1970
+ // its first tool exchange onward. It may differ between turns only because
1971
+ // the CLI also restates it per message (`PER_TURN_EFFORT_CAPABILITIES`),
1972
+ // which keeps the cache; prose is no substitute.
1973
+ effort: turnEffortOf(state),
1846
1974
  cwd: process.cwd(),
1847
1975
  settingSources: ["project"],
1976
+ // The scaffold's CLAUDE.md, minus its `@COMPANY.md` import, as the system
1977
+ // prompt, so every chat reads one cached copy instead of writing its own;
1978
+ // the import loads from `instructionsDir` (see workspace-instructions.ts).
1979
+ ...sharedInstructionOptions(sharedInstructions),
1848
1980
  // `baker_ui` hosts in-process interactive dashboard tools (secure tag
1849
1981
  // form). Always present; HTTP (Composio) servers merge alongside it.
1850
1982
  mcpServers: { ...(mcpServers ?? {}), baker_ui: createBakerUiServer({ credentials: credentialsEnabled() }) },
@@ -1896,6 +2028,8 @@ export class BridgeJobExecutor {
1896
2028
  // they are never TTY-gated, so every help read the agent does inside
1897
2029
  // the sandbox pays ~5-10% of its tokens on colour codes it can't see.
1898
2030
  NO_COLOR: "1",
2031
+ // Loads the `CLAUDE.md` in `instructionsDir`, which imports COMPANY.md.
2032
+ ...sharedInstructionEnv(sharedInstructions),
1899
2033
  // Pin the conversation to one OpenRouter provider instance so the
1900
2034
  // instance-scoped prompt cache is actually re-read across the turn's
1901
2035
  // many tool-call requests. Merged into every model request body by the
@@ -1907,8 +2041,8 @@ export class BridgeJobExecutor {
1907
2041
  ANTHROPIC_CUSTOM_HEADERS: [process.env.ANTHROPIC_CUSTOM_HEADERS, anthropicCustomHeaders(agentTags.headers)]
1908
2042
  .filter(Boolean)
1909
2043
  .join("\n"),
1910
- // Without it no Opus 5.5 request can reach Bedrock; see openrouter-session.ts.
1911
- [MODEL_CAPABILITIES_ENV]: BEDROCK_SERVABLE_CAPABILITIES,
2044
+ // Lets a turn change effort without losing the prompt cache; see openrouter-session.ts.
2045
+ [MODEL_CAPABILITIES_ENV]: PER_TURN_EFFORT_CAPABILITIES,
1912
2046
  // The CLI names each session with a second, structured-output request on the
1913
2047
  // turn's model. OpenRouter's Bedrock cannot serve that, so it was billed to
1914
2048
  // OpenRouter credits on Azure or Vertex, and nothing in Baker reads the name.
@@ -1990,6 +2124,7 @@ export class BridgeJobExecutor {
1990
2124
  for await (const msg of query) {
1991
2125
  stallDetector.observeMessage(msg);
1992
2126
  turnEnd.observeMessage(msg);
2127
+ state.toolResults.observe(msg);
1993
2128
  controls.setWorkInFlight?.(stallDetector.hasPendingToolUses() || turnEnd.hasInFlightWork());
1994
2129
  try {
1995
2130
  costUsd = await this.consumeSdkMessage(job, controls, state, msg, costUsd);