@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.
- package/README.md +49 -21
- package/dist/hono/agent.d.ts.map +1 -1
- package/dist/hono/agent.js +3 -1
- package/dist/hono/agent.js.map +1 -1
- package/dist/hono/agent.test.js +2 -0
- package/dist/hono/agent.test.js.map +1 -1
- package/dist/hono/bridge-job.d.ts +23 -0
- package/dist/hono/bridge-job.d.ts.map +1 -1
- package/dist/hono/bridge-job.js +160 -25
- package/dist/hono/bridge-job.js.map +1 -1
- package/dist/hono/bridge-job.test.js +233 -13
- package/dist/hono/bridge-job.test.js.map +1 -1
- package/dist/hono/model-capabilities.d.ts +1 -0
- package/dist/hono/model-capabilities.d.ts.map +1 -1
- package/dist/hono/model-capabilities.js +4 -0
- package/dist/hono/model-capabilities.js.map +1 -1
- package/dist/hono/openrouter-session.d.ts +19 -13
- package/dist/hono/openrouter-session.d.ts.map +1 -1
- package/dist/hono/openrouter-session.js +19 -13
- package/dist/hono/openrouter-session.js.map +1 -1
- package/dist/hono/relay.d.ts +8 -0
- package/dist/hono/relay.d.ts.map +1 -1
- package/dist/hono/relay.js +2 -0
- package/dist/hono/relay.js.map +1 -1
- package/dist/hono/relay.test.js +30 -0
- package/dist/hono/relay.test.js.map +1 -1
- package/dist/hono/runtime-files.d.ts +1 -0
- package/dist/hono/runtime-files.d.ts.map +1 -1
- package/dist/hono/runtime-files.js +3 -0
- package/dist/hono/runtime-files.js.map +1 -1
- package/dist/hono/served-model.d.ts +8 -0
- package/dist/hono/served-model.d.ts.map +1 -1
- package/dist/hono/served-model.js +41 -0
- package/dist/hono/served-model.js.map +1 -1
- package/dist/hono/served-model.test.js +19 -1
- package/dist/hono/served-model.test.js.map +1 -1
- package/dist/hono/session-continuity.integration.test.js +2 -0
- package/dist/hono/session-continuity.integration.test.js.map +1 -1
- package/dist/hono/tool-result-ledger.d.ts +32 -0
- package/dist/hono/tool-result-ledger.d.ts.map +1 -0
- package/dist/hono/tool-result-ledger.js +67 -0
- package/dist/hono/tool-result-ledger.js.map +1 -0
- package/dist/hono/tool-result-ledger.test.d.ts +2 -0
- package/dist/hono/tool-result-ledger.test.d.ts.map +1 -0
- package/dist/hono/tool-result-ledger.test.js +61 -0
- package/dist/hono/tool-result-ledger.test.js.map +1 -0
- package/dist/hono/turn-route.d.ts +71 -0
- package/dist/hono/turn-route.d.ts.map +1 -0
- package/dist/hono/turn-route.js +112 -0
- package/dist/hono/turn-route.js.map +1 -0
- package/dist/hono/turn-route.test.d.ts +2 -0
- package/dist/hono/turn-route.test.d.ts.map +1 -0
- package/dist/hono/turn-route.test.js +134 -0
- package/dist/hono/turn-route.test.js.map +1 -0
- package/dist/hono/workspace-instructions.d.ts +43 -0
- package/dist/hono/workspace-instructions.d.ts.map +1 -0
- package/dist/hono/workspace-instructions.js +68 -0
- package/dist/hono/workspace-instructions.js.map +1 -0
- package/dist/hono/workspace-instructions.test.d.ts +2 -0
- package/dist/hono/workspace-instructions.test.d.ts.map +1 -0
- package/dist/hono/workspace-instructions.test.js +68 -0
- package/dist/hono/workspace-instructions.test.js.map +1 -0
- package/dist/review/digest.d.ts +13 -0
- package/dist/review/digest.d.ts.map +1 -1
- package/dist/review/digest.js +1 -1
- package/dist/review/digest.js.map +1 -1
- package/package.json +1 -1
package/dist/hono/bridge-job.js
CHANGED
|
@@ -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 {
|
|
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", "
|
|
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
|
-
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
//
|
|
1840
|
-
//
|
|
1841
|
-
//
|
|
1842
|
-
//
|
|
1843
|
-
|
|
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
|
-
//
|
|
1911
|
-
[MODEL_CAPABILITIES_ENV]:
|
|
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);
|